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

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.

Related

How to know if no more values are present in the array as you iterate the array in a loop

So, I have a locations table in database which contains the seller_id. Many locations can have the same seller_id while locations are unique.
In the function, I get an input array which is the list of locations and each location has another array which is list of variants. Example
$data = [
23 => [40 => 1, 25 => 2],
6 => [22 => 3, 24 => 4],
28 => [22 => 3, 24 => 4],
18 => [22 => 3, 24 => 4],
]
So here, 23,6,28 and 18 are the locations and 40,25,22,24 are the variants.
The problem here is, I need an array of seller_id's and each seller_id will have the array of their respective variants. And I need to do it in an optimised way.
I figure it's something of this sort,
$locationIDs = collect($inventoryData)->keys();
$locationBySellers = Location::whereIn('id', $locationIDs)->pluck('seller_id','id');
foreach ($locationBySellers as $location => $seller) {
$variants = array_keys($data[$location]);
echo "Seller: ".$seller.", Variants: ".$variants."\n";
//How to know if no more sellers are present with value $seller
}
Can you help me with this. Any advice would be appreciated
Suppose $data is what you can pull from database. The following code will get the seller_id and their respective variants.
$data = [
23 => [40 => 1, 25 => 2],
6 => [22 => 3, 24 => 4],
28 => [22 => 3, 24 => 4],
18 => [22 => 3, 24 => 4],
];
foreach ($data as $location => $seller) {
$variants = array_keys($data[$location]);
foreach($seller as $variant=>$id) {
if(!isset($sellers[$id])) $sellers[$id]=[];
if(!in_array($variant,$sellers[$id])) array_push($sellers[$id],$variant);
}
// echo "Seller: ".$seller.", Variants: ".$variants."\n";
//How to know if no more sellers are present with value $seller
}
var_dump($sellers);
And it will output array(4) { [1]=> array(1) { [0]=> int(40) } [2]=> array(1) { [0]=> int(25) } [3]=> array(1) { [0]=> int(22) } [4]=> array(1) { [0]=> int(24) } }.

Efficient way to update values to array of hashes in ruby?

I have an array of hashes like below:
items = [ {"id" => 1, "cost" => '2.00'},
{"id" => 2, "cost" => '6.00'},
{"id" => 1, "cost" => '2.00'},
{"id" => 1, "cost" => '2.00'},
{"id" => 1, "cost" => '2.00'} ]
I would like to update the cost to '8.00' where the id = 1. I have tried with the each method like below which does work but I would like to know if there is another more efficient way of updating the values?
items.each { |h| h["cost"] = "8.00" if h["id"] == 1 }
You could just use the same object:
item_1 = {'id' => 1, 'cost' => '2.00'}
item_2 = {'id' => 2, 'cost' => '6.00'}
items = [item_1, item_2, item_1, item_1, item_1]
#=> [{"id"=>1, "cost"=>"2.00"}, {"id"=>2, "cost"=>"6.00"},
# {"id"=>1, "cost"=>"2.00"}, {"id"=>1, "cost"=>"2.00"},
# {"id"=>1, "cost"=>"2.00"}]
This makes updates trivial:
item_1['cost'] = '8.00'
items
#=> [{"id"=>1, "cost"=>"8.00"}, {"id"=>2, "cost"=>"6.00"},
# {"id"=>1, "cost"=>"8.00"}, {"id"=>1, "cost"=>"8.00"},
# {"id"=>1, "cost"=>"8.00"}]
You might consider changing your data structure from:
items = [{"id" => 1, "cost" => '2.00'}, {"id" => 2, "cost" => '6.00'},
{"id" => 1, "cost" => '2.00'}, {"id" => 1, "cost" => '2.00'},
{"id" => 1, "cost" => '2.00'}]
To a hash like this:
items = { 1 => '2.00', 2 => '6.00' }
To updating the record with id = 1 to 8.00 call:
items[1] = '8.00'
Or if you need to know the number of items, you might want to conside a structure like this:
items = { 1 => ['2.00', 4], 2 => ['6.00', 1] }
Than update like this:
items[1][0] = '8.00'
You can achieve this by using each on array
items.each{|v| v["cost"] = "8.00" if v["id"] == 1 }
Cheers!

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}

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

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.

How can I loop through a Perl array of arrays of hashes?

I would like to print an Array of Arrays of Hashes, so I looked at perldsc, and ended up with
for my $j (0 .. $#aoaoh) {
for my $aref (#aoaoh) {
print '"' . join('","', #$aref[$j]), "\"\n";
}
}
but it doesn't work.
Does anyone know how to do this?
It works as far as you've gone. Adding some test data to your program gives us:
#!/usr/bin/perl
use strict;
use warnings;
my #aoaoh = (
[
{ a => 1, b => 2 },
{ c => 3, d => 4 },
],
[
{ a => 101, b => 102 },
{ c => 103, d => 104 },
],
);
for my $j (0 .. $#aoaoh) {
for my $aref (#aoaoh) {
print '"' . join('","', #$aref[$j]), "\"\n";
}
}
And running that gives:
$ ./aoaoh
"HASH(0x9c45818)"
"HASH(0x9c70c48)"
"HASH(0x9c60418)"
"HASH(0x9c70c08)"
So you've successfully navigated the two levels of arrays and you're just left with the hash references to dereference. Something like this perhaps:
#!/usr/bin/perl
use strict;
use warnings;
my #aoaoh = (
[
{ a => 1, b => 2 },
{ c => 3, d => 4 },
],
[
{ a => 101, b => 102 },
{ c => 103, d => 104 },
],
);
for my $j (0 .. $#aoaoh) {
for my $aref (#aoaoh) {
# print '"' . join('","', #$aref[$j]), "\"\n";
for (keys %{$aref->[$j]}) {
print "$_ -> $aref->[$j]{$_}\n";
}
}
}
Which gives:
$ ./aoaoh
a -> 1
b -> 2
a -> 101
b -> 102
c -> 3
d -> 4
c -> 103
d -> 104
Personally, I'd write it like this as I think it's easier to deal with elements than indexes.
#!/usr/bin/perl
use strict;
use warnings;
my #aoaoh = (
[
{ a => 1, b => 2 },
{ c => 3, d => 4 },
],
[
{ a => 101, b => 102 },
{ c => 103, d => 104 },
],
);
for my $aref (#aoaoh) {
for my $href (#$aref) {
for (keys %{$href}) {
print "$_ -> $href->{$_}\n";
}
}
}
foreach my $aoh (#aoaoh) {
foreach my $hashref ( #{$aoh} ) {
foreach my $key ( keys %{$hashref} ) {
print $key . " => " . $hashref->{$key}, "\n";
}
print "#" x 40, "\n";
}
}
UPDATE: Sorry, it must be array (not array ref)
Have a look at perlreftut, it will help you,
and see the answer below.
#!/usr/bin/perl
use strict;
use warnings;
my #aoaoh = (
[
{ a => 1, b => 2 },
{ c => 3, d => 4 },
],
[
{ a => 101, b => 102 },
{ c => 103, d => 104 },
],
);
for my $j (0 .. $#aoaoh) {
for my $aref (#{$aoaoh[$j]}) {
for my $test (keys %{$aref})
{
print"$test => ${$aref}{$test}\n";
}
}
}
output:
a => 1
b => 2
c => 3
d => 4
a => 101
b => 102
c => 103
d => 104
To loop through the whole kit & caboodle:
use 5.012;
use warnings;
my #array = (
[
{ a => 1, b => 2 },
{ c => 3, d => 4 },
],
[
{ a => 101, b => 102 },
{ c => 103, d => 104 },
],
);
for my $root (#array) {
for my $each_array_of_hashes (#$root) {
for my $k (keys %{ $each_array_of_hashes } ) {
say $k, ' => ', $each_array_of_hashes->{$k};
}
}
}
Is this what you're after?

Resources