Why would changing a copied element in an array affect the original element? - arrays

I tried to add a modified element in an array:
mm = Array.new
mm.push({'a' => 1})
mm.unshift(mm[0])
mm[0]['b'] = 2
mm #=> [{'a' => 1, 'b' => 2}, {'a' => 1, 'b' => 2}]
What I expected was:
mm #=> [{'a' => 1, 'b' => 2}, {'a' => 1}]
Can anyone tell me where I am wrong?

You expect the variables to be referenced by value. In ruby, that’s not true. Everything is referenced by reference. To simplify,
▶ h = { a: 1 }
#⇒ { :a => 1 }
▶ h_another_ref = h
#⇒ { :a => 1 }
▶ h_another_ref[:b] = 42
▶ h
#⇒ { :a => 1, :b => 42 }
Here, both h and h_another_ref refer to the same object.
To achieve the desired behaviour, you might actually clone the object (Object#dup or Object#clone):
▶ h = { a: 1 }
#⇒ { :a => 1 }
# ⇓⇓⇓⇓
▶ h_another_inst = h.dup
#⇒ { :a => 1 }
▶ h_another_inst[:b] = 42
▶ h
#⇒ { :a => 1 }
▶ h_another_inst
#⇒ { :a => 1, :b => 42 }

You modified mm[0], which is the same hash instance as mm[1]. It is wrong to expect that mm[0] is modified without mm[1] being modified.

Related

How to convert a Hash with values as arrays to a Hash with keys from array and values as array of keys?

I have a hash of the following form:
{ 1 => [], 2 => ["A", "B"], 3 => ["C"], 4 => ["B", "C"], 5 => ["D"] }
what's the best way to transform this in Ruby to:
{ "A" => [2], "B" => [2, 4], "C" => [3, 4], "D" => [5], "default" => [1] }
The best way I know.
hash = { 1 => [], 2 => ['A','B'], 3 => ['C'], 4 => ['B','C'], 5 => ['D'] }
new_hash = hash.inject({}) do |result, (key, values)|
values.each do |value|
result[value] ||= []
result[value].push(key)
end
result[:default] = [key] if values.empty?
result
end
puts new_hash
A bit of more functional approach:
array
.flat_map {|k,v| v.product([k])}
.group_by(&:first)
.transform_values {|v| v.map(&:last) }
input = { 1 => [], 2 => ['A', 'B'], 3 => ['C'], 4 => ['B', 'C'], 5 => ['D'], 6 => [] }
output =
input.each_with_object(Hash.new([])) do |(key, values), hash|
values.each { |value| hash[value] += [key] }
hash['default'] += [key] if values.empty?
end
output
# => {"default"=>[1, 6], "A"=>[2], "B"=>[2, 4], "C"=>[3, 4], "D"=>[5]}

Ruby Array of hashes to a Hash

I have an array that looks something like this
array = [{ a: 123, b: 'foo', c: 'bar' }, { a: 456, b: 'baz', c: 'qux' }]
I would like to convert this to a hash whose keys are the values of :a in the hashes in array, and whose values consist of hashes with :b and :c.
{ 123 => { b: 'foo', c: 'bar' }, 456 => { b: 'baz', c: 'qux' } }
Is this doable using ruby?
array.each_with_object({}){|e, h| e = e.dup; h[e.delete(:a)] = e}
# => {123=>{:b=>"foo", :c=>"bar"}, 456=>{:b=>"baz", :c=>"qux"}}
If you don't care about side effects:
array.each_with_object({}){|e, h| h[e.delete(:a)] = e}
# => {123=>{:b=>"foo", :c=>"bar"}, 456=>{:b=>"baz", :c=>"qux"}}
I'm not sure what problem you're trying to solve. This is what I come out with:
def group_by_key(array, key)
array.map { |x| [x.delete(key), x] }.to_h
end
It works good on your example:
array = [{ a: 123, b: 'foo', c: 'bar' }, { a: 456, b: 'baz', c: 'qux' }]
group_by_key(array, :a)
#=> {123=>{:b=>"foo", :c=>"bar"}, 456=>{:b=>"baz", :c=>"qux"}}
Assuming that all elements of array (hashes) have a key :a and that array cannot be mutated, use Hash#reject:
array.each_with_object({}) { |g,h| h[g[:a]] = g.reject { |k,_| k == :a } }
#=> {123=>{:b=>"foo", :c=>"bar"}, 456=>{:b=>"baz", :c=>"qux"}}
Using Hash#select method.
array.map { |v| [v.fetch(:a), v.select { |k,_| k != :a }] }.to_h
Exact :a, :b & :c
array.map { |v| [v.fetch(:a), v.select { |k,_| [:b, :c].include? k }] }.to_h
Output
{123=>{:b=>"foo", :c=>"bar"}, 456=>{:b=>"baz", :c=>"qux"}}

Does Perl 6 have a built-in tool to make a deep copy of a nested data structure?

Does Perl 6 have a built-in tool to make a deep copy of a nested data structure?
Added example:
my %hash_A = (
a => {
aa => [ 1, 2, 3, 4, 5 ],
bb => { aaa => 1, bbb => 2 },
},
);
my %hash_B = %hash_A;
#my %hash_B = %hash_A.clone; # same result
%hash_B<a><aa>[2] = 735;
say %hash_A<a><aa>[2]; # says "735" but would like get "3"
my %A = (
a => {
aa => [ 1, 2, 3, 4, 5 ],
bb => { aaa => 1, bbb => 2 },
},
);
my %B = %A.deepmap(-> $c is copy {$c}); # make sure we get a new container instead of cloning the value
dd %A;
dd %B;
%B<a><aa>[2] = 735;
dd %A;
dd %B;
Use .clone and .deepmap to request a copy/deep-copy of a data-structure. But don't bet on it. Any object can define its own .clone method and do whatever it wants with it. If you must mutate and therefore must clone, make sure you test your program with large datasets. Bad algorithms can render a program virtually useless in production use.
The dirty way:
#!/usr/local/bin/perl6
use v6;
use MONKEY-SEE-NO-EVAL;
my %hash_A = (
a => {
aa => [ 1, 2, 3, 4, 5 ],
bb => { aaa => 1, bbb => 2 },
},
);
my %hash_B;
EVAL '%hash_B = (' ~ %hash_A.perl ~ ' )';
%hash_B<a><aa>[2] = 735;
say %hash_A;
say %hash_B;
which gives you:
$ perl6 test.p6
{a => {aa => [1 2 3 4 5], bb => {aaa => 1, bbb => 2}}}
{a => {aa => [1 2 735 4 5], bb => {aaa => 1, bbb => 2}}}
If you eval input from external source, always remember to check it first. Anyway, using EVAL is dangerous and should be avoided.

Hash whose values are array size

I need a method that will take a hash and return a hash whose keys are from the old hash and values are the size of the arrays in the old hash. I.e.,
{ 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
# return
{ 1 => 3, 2 => 3, 7 => 2 }
Is there any way to implement this?
One way:
h = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
h.merge(h) { |*_,a| a.size }
#=> { 1 => 3, 2 => 3, 7 => 2 }
You can use map to build a new array of [key, value] pairs and then convert it back to a hash using to_h:
input = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
input.map { |key, value| [key, value.length] }.to_h
# => {1=>3, 2=>3, 7=>2}
This is quite straightforward: you can use inject to process all the items one by one and compose the result.
input = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
input.inject({}) do |result, (key, value)|
result.merge(key => value.size)
end
# => {1=>3, 2=>3, 7=>2}
Even without inject, just use .each to loop all the items and construct the result using a temporary support Hash.
Just for the sake of completeness, a solution using each_with_object:
input = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
input.each_with_object({}) { |(k, vs), h| h[k] = vs.size }
#=> {1=>3, 2=>3, 7=>2}
One way is to insert the expected keys and values into the new Hash:
h = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
h2 = {}
h.each {|k, v| h2[k] = v.length}
h2
# => {1=>3, 2=>3, 7=>2}
h.keys.zip(h.values.map &:size).to_h
Or you can try this,
h = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
Hash[h.map {|key, value| [key, value.length]}]
# => {1=>3, 2=>3, 7=>2}

Delete from Array and return deleted elements in Ruby

How can I delete some elements from an array and select them?
For example:
class Foo
def initialize
#a = [1,2,3,4,5,6,7,8,9]
end
def get_a
return #a
end
end
foo = Foo.new
b = foo.get_a.sth{ |e| e < 4 }
p b # => [1,2,3]
p foo.get_a # => [4,5,6,7,8,9,10]
What I can use instead of foo.get_a.sth?
If you don't need to retain the object id of a:
a = [1,2,3,4,5,6,7,8,9,10]
b, a = a.partition{|e| e < 4}
b # => [1, 2, 3]
a # => [4, 5, 6, 7, 8, 9, 10]
If you do need to retain the object id of a, then use a temporal array c:
a = [1,2,3,4,5,6,7,8,9,10]
b, c = a.partition{|e| e < 4}
a.replace(c)
Rails 6 now has this:
a = [1, 2, 3]
#=> [1, 2, 3]
a.extract! { |n| n.even? }
#=> [2]
a
#=> [1, 3]
If you were only deleting one item, this doesn't require duplicating the array, etc:
array = [{ id: 1 }, { id: 2 }, {id: 3 }]
array.delete_at(array.find_index { |element| element[:id] == 1 })
#=> {:id=>1}
a = [1, 2, 3, 4]
a.dup - (a.delete_if(&:even?))
#=> [2, 4]
a
#=> [1, 3]
a = [1, 2, 3, 4]
b = a.dup - (a.delete_if { |e| e < 4 })
a
#=> [4]
b
#=> [1, 2, 3]
Edit: It sounds like you are just after #select...
a = [1, 2, 3, 4]
a.select { |e| e < 3 }
#=> [1, 2]
I still don't believe ruby doesn't have something for this in its default libraries. There should be #drop method equivalent for this. Surely there's a gem available that would add the functionality to the array class. But who needs gems when you can just break out your own scripts:
#in initializer somewhere
class Array
def exclude(obj)
x = self
x.delete(obj)
x
end
end
I may submit a pull request to the ruby github project for this method. This superman-patch works very well so far.

Resources