Why Array.new(3, []) works differently than [[], [], []] in Ruby? [duplicate] - ruby

This question already has answers here:
Why does array.each behavior depend on Array.new syntax?
(3 answers)
Closed 5 years ago.
Can anyone explain this to me:
irb(main):001:0> a = Array.new(3, [])
=> [[], [], []]
irb(main):001:0> b = [[], [], []]
=> [[], [], []]
irb(main):003:0> a.each_with_index{ |r, idx| r << 'a' }
=> [["a", "a", "a"], ["a", "a", "a"], ["a", "a", "a"]]
irb(main):004:0> b.each_with_index{ |r, idx| r << 'a' }
=> [["a"], ["a"], ["a"]]

When using the .new method:
Since all the Array elements store the same hash, changes to one of them will affect them all.
If multiple copies are what you want, you should use the block version which uses the result of that block each time an element of the array needs to be initialized:
2.3.0 :001 > a = Array.new(3) { [] }
=> [[], [], []]
2.3.0 :002 > a.each_with_index{ |r, idx| r << 'a' }
=> [["a"], ["a"], ["a"]]
Read the examples here - https://ruby-doc.org/core-2.2.0/Array.html#method-c-new

Related

Creating a Ruby Hash Map in a Functional Way

I have an array I want to turn into a hash map keyed by the item and with an array of indices as the value. For example
arr = ["a", "b", "c", "a"]
would become
hsh = {"a": [0,3], "b": [1], "c": [2]}
I would like to do this in a functional way (rather than a big old for loop), but am a little stuck
lst = arr.collect.with_index { |item, i| [item, i] }
produces
[["a", 0], ["b", 1], ["c", 2], ["a", 3]]
I then tried Hash[lst], but I don't get the array in the value and lose index 0
{"a"=>3, "b"=>1, "c"=>2}
How can I get my desired output in a functional way? I feel like it's something like
Hash[arr.collect.with_index { |item, i| [item, item[i] << i || [i] }]
But that doesn't yield anything.
Note: Trying to not do it this way
hsh = {}
arr.each.with_index do |item, index|
if hsh.has_key?(item)
hsh[item] << index
else
hsh[item] = [index]
end
end
hsh
Input
arr = ["a", "b", "c", "a"]
Code
p arr.map
.with_index
.group_by(&:first)
.transform_values { |arr| arr.map(&:last) }
Output
{"a"=>[0, 3], "b"=>[1], "c"=>[2]}
I would like to do this in a functional way (rather than a big old for loop), but am a little stuck
lst = arr.collect.with_index { |item, i| [item, i] }
produces
[["a", 0], ["b", 1], ["c", 2], ["a", 3]]
This is very close. The first thing I would do is change the inner arrays to hashes:
arr.collect.with_index { |item, i| { item => i }}
#=> [{ "a" => 0 }, { "b" => 1 }, { "c" => 2 }, { "a" => 3 }]
This is one step closer. Now, actually we want the indices in arrays:
arr.collect.with_index { |item, i| { item => [i] }}
#=> [{ "a" => [0] }, { "b" => [1] }, { "c" => [2] }, { "a" => [3] }]
This is even closer. Now, all we need to do is to merge those hashes into one single hash. There is a method for that, which is called Hash#merge. It takes an optional block for deconflicting duplicate keys, and all we need to do is concatenate the arrays:
arr.collect.with_index { |item, i| { item => [i] }}.inject({}) {|acc, h| acc.merge(h) {|_, a, b| a + b } }
#=> { "a" => [0, 3], "b" => [1], "c" => [2] }
And we're done!
How can I get my desired output in a functional way? I feel like it's something like
Hash[arr.collect.with_index { |item, i| [item, item[i] << i || [i] }]
But that doesn't yield anything.
Well, it has a SyntaxError, so obviously if it cannot even be parsed, then it cannot run, and if it doesn't even run, then it cannot possibly yield anything.
However, not that even if it worked, it would still violate your constraint that it should be done "in a functional way", because Array#<< mutates its receiver and is thus not functional.
arr.map.with_index.each_with_object({}){ |(a, i), h| h[a] ? h[a] << i : (h[a] = [i]) }
#=> {"a"=>[0, 3], "b"=>[1], "c"=>[2]}
arr.map.with_index => gives enumeration of each element with it's index
each_with_object => lets you reduce the enumeration on a provided object(represented by h in above)

Array Unexpected Multi Assignment

I have the following array:
#master = Array.new(4, Array.new(2, Array.new()))
=> [[[], []], [[], []], [[], []], [[], []]]
I'm attempting to assign the very first most value with:
#master[0][0] = "x"
=> "x"
But this is doing a multi assignment
#master
=> [["x", []], ["x", []], ["x", []], ["x", []]]
How do I assign only the first value? I'm hoping to get the following Array:
#master
=> [["x", []], [[], []], [[], []], [[], []]]
In that way you use the same reference for every sub array. Try this way
#master = Array.new(4) { Array.new(2) { Array.new } }
You are creating one array an assigning it to every element of the first array; try running this code:
#master.each { |e| puts e.object_id }
Output (your ids will be different):
70192803217260
70192803217260
70192803217260
70192803217260
As you can see, is the exact same object, so try using #master = Array.new(4) { Array.new(2) { Array.new() } } instead, which will create a new array for each item in the first array.

Nested array in Ruby, updates all the array values instead of one

I tried to create a nested array, but when I update one of them, all other arrays seems to be updated, What am I doing wrong?
arr = Array.new(5,Array.new())
# => [[], [], [], [], []]
arr[0]
# => []
arr[0].push(1)
# => [1]
arr
# => [[1], [1], [1], [1], [1]]
You could use the block syntax to initialize the array:
arr = Array.new(5) { Array.new }
=> [[], [], [], [], []]
arr[0].push(1)
arr
=> [[1], [], [], [], []]
map produces the array:
arr = 5.times.map { [] }
arr.first << 42
#⇒ [42]
arr
#⇒ [[42], [], [], [], []]

array[array.size..-1] doesn't return nil

I noticed a strange behavior when Range are used as Array subscript. (At least it's strange for me.)
a = [1,2,3]
=> [1, 2, 3]
a[3]
=> nil
a[3..-1]
=> []
a[4]
=> nil
a[4..-1]
=> nil
I thought a[3..-1] returns nil, but somehow it returns []. a[-3..-4] also returns [].
Could anyone explain why it returns [], when I use marginal values of range?
Because when range.begin == array.length, it always returns []. This is noted as a "special case" in the Ruby documentation:
a = [ "a", "b", "c", "d", "e" ]
# special cases
a[5] #=> nil
a[6, 1] #=> nil
a[5, 1] #=> []
a[5..10] #=> []

Is there a more efficient way to turn an array into a hash?

I think that my method is a little clumsy, and that there is likely to be a one-liner that I'm missing. Ideas?
def _to_hash
hsh = {}
self.each_slice(2){|v| hsh[v[0]] = v[1]}
hsh
end
1.9.3-p0 :003 > ["a", 1, "b", 2]._to_hash
{
"a" => 1,
"b" => 2
}
#phiggy's method is correct, but also remember that you can use a splat operator:
a = ["a", 1, "b", 2]
Hash[*a] #=> {"a"=>1, "b"=>2}
You want Hash's .[] operator:
> Hash["a", 1, "b", 2]
=> {"a"=>1, "b"=>2}

Resources