Merge hashes containing same key & value pair - ruby-on-rails

arr1 = [
{entity_type: "Mac", entity_ids: [3], cascade_id: 2, location_id: 1},
{entity_type: "Mac", entity_ids: [2], cascade_id: 2, location_id: 1},
{entity_type: "Mac", entity_ids: [9], cascade_id: 4, location_id: 1},
{entity_type: "Mac", entity_ids: [10], cascade_id: 4, location_id: 1}
]
This is the part of data, that I get after some of my logical iterations.
My desired output here for this example is
[{entity_type: "Mac", entity_ids: [3,2], cascade_id: 2, location_id: 1},
{entity_type: "Mac", entity_ids: [9,10], cascade_id: 4, location_id: 1}]
I want to know how to merge hashes if it's one or two key-value pair are same, merging other key's values to an array.
-> This is one more instance
arr2 = [
{entity_type: "Sub", entity_ids: [7], mac_id: 5, cascade_id: 1, location_id: 1},
{entity_type: "Sub", entity_ids: [10], mac_id: 5, cascade_id: 1, location_id: 1},
{entity_type: "Sub", entity_ids: [4], mac_id: 2, cascade_id: 1, location_id: 1},
{entity_type: "Sub", entity_ids: [11], mac_id: 7, cascade_id: 2, location_id: 2}
]
desired output for this instance is
[{entity_type: "Sub", entity_ids: [7, 10], mac_id: 5, cascade_id: 1, location_id: 1},
{entity_type: "Sub", entity_ids: [4], mac_id: 2, cascade_id: 1, location_id: 1},
{entity_type: "Sub", entity_ids: [11], mac_id: 7, cascade_id: 2, location_id: 2}]

You can compute the desired result as follows.
def doit(arr)
arr.each_with_object({}) do |g,h|
h.update(g.reject { |k,_| k==:entity_ids }=>g) do |_,o,n|
o.merge(entity_ids: o[:entity_ids] + n[:entity_ids])
end
end.values
end
doit(arr1)
#=> [{:entity_type=>"Mac", :entity_ids=>[3, 2], :cascade_id=>2, :location_id=>1},
# {:entity_type=>"Mac", :entity_ids=>[9, 10], :cascade_id=>4, :location_id=>1}]
doit(arr2)
#=> [{:entity_type=>"Sub", :entity_ids=>[7, 10], :mac_id=>5, :cascade_id=>1,
# :location_id=>1},
# {:entity_type=>"Sub", :entity_ids=>[4], :mac_id=>2, :cascade_id=>1,
# :location_id=>1},
# {:entity_type=>"Sub", :entity_ids=>[11], :mac_id=>7, :cascade_id=>2,
# :location_id=>2}]
This uses the form of Hash#update (aka merge!) that employs a block to determine the values of keys that are present in both hashes being merged. See the doc for an explanation of the block variables k, o and n.
If doit's argument is arr1, the steps are as follows.
arr = arr1
e = arr.each_with_object({})
#=> #<Enumerator: [{:entity_type=>"Mac", :entity_ids=>[3], :cascade_id=>2,
# :location_id=>1},
# {:entity_type=>"Mac", :entity_ids=>[2], :cascade_id=>2,
# :location_id=>1},
# {:entity_type=>"Mac", :entity_ids=>[9], :cascade_id=>4,
# :location_id=>1},
# {:entity_type=>"Mac", :entity_ids=>[10], :cascade_id=>4,
# :location_id=>1}
# ]:each_with_object({})>
The first element of the enumerator is passed to the block and values are assigned to the block variables.
g, h = e.next
#=> [{:entity_type=>"Mac", :entity_ids=>[3], :cascade_id=>2, :location_id=>1}, {}]
g #=> {:entity_type=>"Mac", :entity_ids=>[3], :cascade_id=>2, :location_id=>1}
h #=> {}
Compute the (only) key for the hash to be merged with h.
a = g.reject { |k,_| k==:entity_ids }
#=> {:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}
Perform the update operation.
h.update(a=>g)
#=> {{:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}=>
# {:entity_type=>"Mac", :entity_ids=>[3], :cascade_id=>2, :location_id=>1}}
This is the new value of h. As h (which was empty) did not have the key
{:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}
the block was not used to determine the value of this key in the merged hash.
Now generate the next value of the enumerator e, pass it to the block, assign values to the block variables and perform the block calculation.
g, h = e.next
#=> [{:entity_type=>"Mac", :entity_ids=>[2], :cascade_id=>2, :location_id=>1},
# {{:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}=>
# {:entity_type=>"Mac", :entity_ids=>[3], :cascade_id=>2, :location_id=>1}}]
g #=> {:entity_type=>"Mac", :entity_ids=>[2], :cascade_id=>2, :location_id=>1}
h #=> {{:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}=>
# {:entity_type=>"Mac", :entity_ids=>[3, 2], :cascade_id=>2, :location_id=>1}}
a = g.reject { |k,_| k==:entity_ids }
#=> {:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}
h.update(a=>g) do |_,o,n|
puts "_=#{_}, o=#{o}, n=#{n}"
o.merge(entity_ids: o[:entity_ids] + n[:entity_ids])
end
#=> {{:entity_type=>"Mac", :cascade_id=>2, :location_id=>1}=>
# {:entity_type=>"Mac", :entity_ids=>[3, 2], :cascade_id=>2, :location_id=>1}}
This is the new value of h. As both g and h have the key a the block is consulted to obtain the value of that key in the merged hash (new h). The values of that block variables are printed.
_={:entity_type=>"Mac", :cascade_id=>2, :location_id=>1},
o={:entity_type=>"Mac", :entity_ids=>[3], :cascade_id=>2, :location_id=>1},
n={:entity_type=>"Mac", :entity_ids=>[2], :cascade_id=>2, :location_id=>1}
h[:entity_ids] is therefore replaced with
o[:entity_ids] + o[:entity_ids]
#=> [3] + [2] => [3, 2]
The calculations for the two remaining elements of e are similar, at which time
h #=> {{ :entity_type=>"Mac", :cascade_id=>2, :location_id=>1 }=>
# { :entity_type=>"Mac", :entity_ids=>[3, 2], :cascade_id=>2, :location_id=>1 },
# { :entity_type=>"Mac", :cascade_id=>4, :location_id=>1 }=>
# { :entity_type=>"Mac", :entity_ids=>[9, 10], :cascade_id=>4, :location_id=>1 }}
The final step is to return the values of this hash.
h.values
#=> <as shown above>
Note that some of the block variables are underscores (_). Though they are valid local variables, they are commonly used to indicate that they are not used in the block calculation. An alternative convention is to have the unused block variable begin with an underscore, such as _key.

This will work:
def combine(collection)
return [] if collection.empty?
grouping_key = collection.first.keys - [:entity_ids]
grouped_collection = collection.group_by do |element|
grouping_key.map { |key| [key, element[key]] }.to_h
end
grouped_collection.map do |key, elements|
key.merge(entity_ids: elements.map { |e| e[:entity_ids] }.flatten.uniq)
end
end
Here's what's going on:
First we determine a "grouping key" for the collection by sampling the keys of the first element and removing :entity_ids. All other keys combined make up the grouping key on which the combination depends.
The Enumerable#group_by method iterates over a collection and groups it by the grouping key we just constructed.
We then iterate over the grouped collection and merge in a new entity_ids attribute made up of the combined entity ids from each group.

There are two separate challanges in your problem.
merging the hashes.
merging only if other values are not matching.
Problem 1:
To get any custom behaviour while merging you can pass a block to merge method. In your case you want to combine arrays for entity ids. This blocks takes key and left and right values. In your scenerio you want to combine arrays if key == :entity_ids.
one_entity.merge(other_entity){ |key, left, right|
key== :entity_ids ? left + right : left
}
Problem 2:
To merge entities based on whether they have different attributes or same, i am using group_by. This will give me a hash combining entities that can be merged into array that i can map over and merge.
actual.group_by {|x| [x[:entity_type], x[:mac_id], x[:location_id]]}
Combining the two will give me the whole solution which works. You can add more attributes in group_by block if you want.
actual.group_by {|x| [x[:entity_type], x[:mac_id], x[:location_id]]}
.map{|_, entities| entities.reduce({}) { |result, entity|
result.merge(entity){|key, left, right|
key== :entity_ids ? left + right : left
}
}
}

Related

How to convert array of 3 element arrays in to a hash where key is first 2 elements

My problem is that I need to do efficient lookups of if a 2 element array and their corresponding value is nil. So if I have the following arrays:
arr1 = [
[1, 2, 100],
[3, 4, nil],
[5, 6, 101]
]
I want something like
h = {
[1, 2] => 100,
[3, 4] => nil,
[5, 6] => 101
}
So I can do something like:
error = []
arr2 = [
[1,2],
[3,4],
[7,8]
]
arr2.each do |val|
if h.include?(val)
if h[val] == nil
error << "Value is nil"
else
# Do something
end
else
error << "Key doesn't exist"
end
end
Given that overwriting or ignoring duplicates is acceptable per your comment.
You can use Enumerable#each_with_object to iterate the Array and create a Hash like so
arr1 = [
[1, 2, 100],
[3, 4, nil],
[5, 6, 101],
[1, 2, nil],
]
arr1.each_with_object({}) do |(*first_two,last),obj|
obj[first_two] = last
end
#=> {[1, 2]=>nil, [3, 4]=>nil, [5, 6]=>101}
You can ignore duplicates in a similar fashion
arr1.each_with_object({}) do |(*first_two,last),obj|
obj[first_two] = last unless obj.key?(first_two)
end
#=> {[1, 2]=>100, [3, 4]=>nil, [5, 6]=>101}
Explanation:
each_with_object({}) will pass each element of of arr1 to the block along with an object (a Hash in this case)
(*first_two,last),obj - *first_two will collect everything up to last and obj is our Hash
obj[first_two] = last simple Hash key assignment
each_with_object returns the object (obj Hash in this case)
Update as recommended by #Stefan in ruby >= 2.7 you could also use
arr1.to_h {|*first_two,last| [first_two, last] }
This version will overwrite keys
arr1 = [
[1, 2, 100],
[3, 4, nil],
[5, 6, 101]
]
result = {}
arr1.each { |i| result[i.first(2)] = i.last }
=> {[1, 2]=>100, [3, 4]=>nil, [5, 6]=>101}
You can destructure every subarray during mapping and then convert result to hash with Array#to_h method
arr1 = [
[1, 2, 100],
[3, 4, nil],
[5, 6, 101],
[1, 2, nil],
]
arr1.map { |*first_two, last| [first_two, last] }.to_h
# => {[1, 2]=>nil, [3, 4]=>nil, [5, 6]=>101}
Duplicates will be overwritten
In case if you need for two last values as key:
arr.map { |b| { b.shift => b }.invert }

Combine two arrays in Ruby?

Suppose we have two arrays:
foo = [1, 2] # can be mapped in [{id: 1}, {id: 2}]
bar = [{id: 2}, {id: 4}]
As a result I need to get following array:
res = [ [1, nil], [2, {id: 2}], [nil, {id: 4}] ]
# Is also ok [ [{id: 1}, nil], [{id: 2}, {id: 2}], [nil, {id: 4}] ]
I there any standard Ruby method to get such combinations? At first I'm looking for Ruby way, elegant, short solution.
In other words, I need to get an array diff map, which shows absent and newly added elements.
The elements:
elements = (foo + bar.map{ |h| h.values.first }).uniq
#=> [1, 2, 4]
and the combinations:
elements.map { |i| [foo.include?(i) ? i : nil, bar.include?({id: i}) ? {id: i} : nil] }
#=> [[1, nil], [2, {:id=>2}], [nil, {:id=>4}]]
Or as Sebastian suggested, you can also use #find instead of #includes?:
elements.map { |i| [foo.find { |e| e == i }, bar.find { |e| e == { id: i } }] }
I would transform the two arrays to lookup hashes. Then collect all the ids to iterate. Map the ids into the foo and bar values, then zip them together.
foo_lookup = foo.to_h { |id| [id, {id: id}] } # change `{id: id}` into `id` for the other output
bar_lookup = bar.to_h { |item| [item[:id], item] }
ids = foo_lookup.keys | bar_lookup.keys
res = ids.map(&foo_lookup).zip(ids.map(&bar_lookup))
#=> [[{:id=>1}, nil], [{:id=>2}, {:id=>2}], [nil, {:id=>4}]]
Just another option.
foo = [1, 2]
bar = [{id: 2}, {id: 4}]
(foo + bar).group_by { |e| e.is_a?(Hash) ? e[:id] : e }.values.map { |e| e.size == 1 ? e << nil : e }
#=> [[1, nil], [2, {:id=>2}], [{:id=>4}, nil]]
The first part returns
(foo + bar).group_by { |e| e.is_a?(Hash) ? e[:id] : e }.values
#=> [[1], [2, {:id=>2}], [{:id=>4}]]
If you need to sort the sub arrays, just map using Array#unshift or Array#push (prepend or append nil) depending in the element already there.

How to count consecutive numbers in an array?

If I have an array:
array = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2]
I want to be able to identify consecutive matching numbers that have a length of greater than 3. And map the starting index of the consecutive numbers. An example output for the above array would be:
consecutive_numbers = [
{starting_index: 1, value: 2, length: 4},
{starting_index: 10, value: 3, length: 4},
{starting_index: 14, value: 2, length: 7}
]
The values can be the same, but the consecutive series' must be mutually exclusive. See that there are 2 hashes with a value of 2, but their starting indexes are different.
My attempt so far... looks like this:
array.each_cons(3).with_index.select{|(a,b,c), i|
[a,b,c].uniq.length == 1
}
but that will returns:
[[[2, 2, 2], 1], [[2, 2, 2], 2], [[1, 1, 1], 7], [[3, 3, 3], 10], [[3, 3, 3], 11], [[2, 2, 2], 14], [[2, 2, 2], 15], [[2, 2, 2], 16], [[2, 2, 2], 17], [[2, 2, 2], 18]]
But that returns overlapping results.
array.each_with_index.
chunk(&:first).
select { |_,a| a.size > 3 }.
map { |n,a| { starting_index: a.first.last, value: n, length: a.size } }
#=> [{:starting_index=> 1, :value=>2, :length=>4},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
The steps are as follows.
e = array.each_with_index.chunk(&:first)
#=> #<Enumerator: #<Enumerator::Generator:0x00005b1944253c18>:each>
We can convert this enumerator to an array to view the elements it will generate and pass to its block.
e.to_a
#=> [[1, [[1, 0]]],
# [2, [[2, 1], [2, 2], [2, 3], [2, 4]]],
# [5, [[5, 5], [5, 6]]],
# [1, [[1, 7], [1, 8], [1, 9]]],
# [3, [[3, 10], [3, 11], [3, 12], [3, 13]]],
# [2, [[2, 14], [2, 15], [2, 16], [2, 17], [2, 18], [2, 19], [2, 20]]]]
Continuing,
c = e.select { |_,a| a.size > 3 }
#=> [[2, [[2, 1], [2, 2], [2, 3], [2, 4]]],
# [3, [[3, 10], [3, 11], [3, 12], [3, 13]]],
# [2, [[2, 14], [2, 15], [2, 16], [2, 17], [2, 18], [2, 19], [2, 20]]]]
c.map { |n,a| { starting_index: a.first.last, value: n, length: a.size } }
#=> [{:starting_index=> 1, :value=>2, :length=>4},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
This is another way.
array.each_with_index.with_object([]) do |(n,i),arr|
if arr.any? && arr.last[:value] == n
arr.last[:length] += 1
else
arr << { starting_index: i, value: n, length: 1 }
end
end.select { |h| h[:length] > 3 }
#=> [{:starting_index=> 1, :value=>2, :length=>4},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
You can chunk_while each pair of elements are equal:
p array.chunk_while { |a, b| a == b }.to_a
# [[1], [2, 2, 2, 2], [5, 5], [1, 1, 1], [3, 3, 3, 3], [2, 2, 2, 2, 2, 2, 2]]
You select the arrays with 3 or more elements.
After that, with then, you can yield self, so you have access to the array of arrays, which you can use to get the starting_index:
[1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2].chunk_while(&:==).then do |this|
this.each_with_object([]).with_index do |(e, memo), index|
memo << { starting_index: this.to_a[0...index].flatten.size, value: e.first, length: e.size }
end
end.select { |e| e[:length] > 3 }
# [{:starting_index=>1, :value=>2, :length=>4},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
For the starting_index, you get the elements to the current index (non inclusive), flatten them, and get the total of elements.
The value, as each array in the array has the same elements, can be anything, the length, is the length of the current array in the "main" array.
This is another option..
Zipping the array (Enumerable#zip) with its indexes by Endles Ranges
Calling Enumerable#slice_when (another flavour of chunk_while)
Mapping (Enumerable#map) to the required Hash
Finally rejecting (Enumerable#reject) hashes if length is greater than 3
array
.zip(0..)
.slice_when { |a, b| a.first != b.first }
.map { |a| { starting_index: a.first.last, value: a.first.first, length: a.size } }
.reject { |h| h[:length] < 3 }
#=> [{:starting_index=>1, :value=>2, :length=>4}, {:starting_index=>7, :value=>1, :length=>3}, {:starting_index=>10, :value=>3, :length=>4}, {:starting_index=>14, :value=>2, :length=>7}]
Well, the most obvious (and probably the fastest) way is iterate over an array and count everything manually:
array = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2]
array_length_pred = array.length.pred
consecutive_numbers = []
starting_index = 0
value = array.first
length = 1
array.each_with_index do |v, i|
if v != value || i == array_length_pred
length += 1 if i == array_length_pred && value == v
if length >= 3
consecutive_numbers << {
starting_index: starting_index,
value: value,
length: length
}
end
starting_index = i
value = v
length = 1
next
end
length += 1
end
p consecutive_numbers
# [{:starting_index=>1, :value=>2, :length=>4},
# {:starting_index=>7, :value=>1, :length=>3},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
You could work with strings instead.
Here, I coerce the array into a string:
input_sequence = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2].join
I use a regex to group consecutive characters:
groups = input_sequence.gsub(/(.)\1*/).to_a
#=> ["1", "2222", "55", "111", "3333", "2222222"]
Now I can search for the groups as substrings within the input string:
groups.map do |group|
{
starting_index: input_sequence.index(group),
value: group[0].to_i,
length: group.length
}
end.reject { |group| group[:length] <= 3 }
#=> [{:starting_index=>1, :value=>2, :length=>4},
{:starting_index=>7, :value=>1, :length=>3},
{:starting_index=>10, :value=>3, :length=>4},
{:starting_index=>14, :value=>2, :length=>7}]
There's room for improvement here -- I'm creating lots of intermediate objects for one -- but I thought I would offer a different approach.

Ruby sum from nested hash

How can I return the total scores, strokes and rounds from the following array?
players = [{"Angel Cabrera"=>{"score"=>2, "strokes"=>146, "rounds"=>3}},
{"Jason Day"=>{"score"=>1, "strokes"=>145, "rounds"=>3}},
{"Bryson DeChambeau"=>{"score"=>0, "strokes"=>144, "rounds"=>3}},
{"Sergio Garcia"=>{"score"=>0, "strokes"=>144, "rounds"=>3}},
{"Ian Poulter"=>{"score"=>5, "strokes"=>162, "rounds"=>3}},
{"Vijay Singh"=>nil},
{"Jordan Spieth"=>{"score"=>-4, "strokes"=>140, "rounds"=>3}}]
I can get the strokes by doing the following but I know that isn't the best way to do it.
players.each do |x|
x.values()[0]["strokes"]
end
How can I return the sum of the strokes given the array above?
Here are three ways of doing that.
Use the form of Hash#update that employs a block to determine the values of keys that are present in both hashes being merged
players.map { |g| g.first.last }.
compact.
each_with_object({}) { |g,h| h.update(g) { |_,o,v| o+v } }
#=> {"score"=>4, "strokes"=>881, "rounds"=>18}
The steps:
a = players.map { |g| g.first.last }
#=> [{"score"=> 2, "strokes"=>146, "rounds"=>3},
# {"score"=> 1, "strokes"=>145, "rounds"=>3},
# {"score"=> 0, "strokes"=>144, "rounds"=>3},
# {"score"=> 0, "strokes"=>144, "rounds"=>3},
# {"score"=> 5, "strokes"=>162, "rounds"=>3},
# nil,
# {"score"=>-4, "strokes"=>140, "rounds"=>3}]
b = a.compact
#=> [{"score"=> 2, "strokes"=>146, "rounds"=>3},
# {"score"=> 1, "strokes"=>145, "rounds"=>3},
# {"score"=> 0, "strokes"=>144, "rounds"=>3},
# {"score"=> 0, "strokes"=>144, "rounds"=>3},
# {"score"=> 5, "strokes"=>162, "rounds"=>3},
# {"score"=>-4, "strokes"=>140, "rounds"=>3}]
b.each_with_object({}) { |g,h| h.update(g) { |_,o,v| o+v } }
#=> {"score"=>4, "strokes"=>881, "rounds"=>18}
Here, Hash#update (aka merge!) uses the block { |_,o,v| o+v }) to determine the values of keys that are present in both hashes. The first block variable (which is not used, and therefore can be represented by the local variable _) is the key, the second (o, for "old") is the value of the key in h and the third (n, for "new") is the value of the key in g.
Use a counting hash
players.map { |g| g.first.last }.
compact.
each_with_object(Hash.new(0)) { |g,h| g.keys.each { |k| h[k] += g[k] } }
Hash.new(0) creates an empty hash with a default value of zero, represented by the block variable g. This means that if a hash h does not have a key k, h[k] returns the default value (but does not alter the hash). h[k] += g[k] above expands to:
h[k] = h[k] + g[k]
If h does not have a key k, h[k] on the right side is therefore replaced by 0.
Sum values and then convert to a hash
If you are using Ruby v1.9+ and the keys are guaranteed to have the same order in each hash, a third way it could be done is as follows:
["scores", "strokes", "rounds"].zip(
players.map { |g| g.first.last }.
compact.
map(&:values).
transpose.
map { |arr| arr.reduce(:+) }
).to_h
#=> {"scores"=>4, "strokes"=>881, "rounds"=>18}
The steps (starting from b above) are:
c = b.map(&:values)
#=> [[ 2, 146, 3],
# [ 1, 145, 3],
# [ 0, 144, 3],
# [ 0, 144, 3],
# [ 5, 162, 3],
# [-4, 140, 3]]
d = c.transpose
#=> [[ 2, 1, 0, 0, 5, -4],
# [146, 145, 144, 144, 162, 140],
# [ 3, 3, 3, 3, 3, 3]]
totals = d.map { |arr| arr.reduce(:+) }
#=> [4, 881, 18]
e = ["scores", "strokes", "rounds"].zip(totals)
#=> [["scores", 4], ["strokes", 881], ["rounds", 18]]
e.to_h
#=> {"scores"=>4, "strokes"=>881, "rounds"=>18}
Use this code:
#total= 0
players.each do |x|
a= x.values[0]
if a.class == Hash
#total += a["strokes"]
end
end
puts #total

How does Ruby's map method work in this case?

I got the mistake when I want to add doubled values to an array:
arr = [1,2,3]
def my_mistake(arr)
result = Array.new
arr.map { |element| result << element * 2 }
end
#=> [[2, 4, 6], [2, 4, 6], [2, 4, 6]]
def solution(arr)
arr.map { |element| element * 2 }
end
#=> [2,4,6]
However, come back to my mistake and the definition of map method in Ruby.
Invokes the given block once for each element of self. Creates a new array containing the values returned by the block.
I think my_mistake method has to return [[2], [2, 4], [2, 4, 6]] but it doesn't.
Everyone can explain this case for me ?
The resulting array will contain three occurrences of the same reference to the same array, as result is the result of the expression result << element * 2. So the result of the map is (kind of) [result, result, result]. These all point to the same content, which is the content of result at the end of the process ([2, 4, 6]).
What you expected would be achieved if you clone the array at each point, so that every resulting element would point to a different array, and each addition would not affect the previously computed arrays:
arr.map { |element| (result << element * 2).clone }
=> [[2], [2, 4], [2, 4, 6]]
.map returns the last evaluated expression, so no need for the result << part there. Here's something that worked for me:
def my_mistake(arr)
result = [] # '= []' is same like '= Array.new', look-up "literal constructors in Ruby"
new_arr = [] # same like new_arr = Array.new
until arr.empty?
new_arr << arr.shift # we add each element of arr, one by one, starting from the beginning
output << new_arr.map { |e| e * 2} # we calculate *2 for each element
end
return result
end
p my_mistake(arr) #=> [[2], [2, 4], [2, 4, 6]]
If you're nto sure how this works, try putting "p output" after the 6th line:
def my_mistake(arr)
output = []
new_arr = []
until arr.empty?
new_arr << arr.shift
output << new_arr.map { |e| e * 2}
p output
end
return output
end
my_mistake(arr)
The program will print:
[[2]]
[[2], [2, 4]]
[[2], [2, 4], [2, 4, 6]]

Resources