Find and Replace Specific Hash and It's Values Within Array

Find and replace specific hash and it's values within array

This will do what you're after looping through the records only once:

array.each { |hash| hash[:parameters][:omg] = "triple omg" if hash[:id] == 1 }

You could always expand the block to handle other conditions:

array.each do |hash| 
hash[:parameters][:omg] = "triple omg" if hash[:id] == 1
hash[:parameters][:omg] = "quadruple omg" if hash[:id] == 2
# etc
end

And it'll remain iterating over the elements just the once.

It might also be you'd be better suited adjusting your data into a single hash. Generally speaking, searching a hash will be faster than using an array, particularly if you've got unique identifier as here. Something like:

{ 
1 => {
parameters: {
omg: "lol"
},
options: {
lol: "omg"
}
},
2 => {
parameters: {
omg: "double lol"
},
options: {
lol: "double omg"
}
}
}

This way, you could just call the following to achieve what you're after:

hash[1][:parameters][:omg] = "triple omg"

Hope that helps - let me know how you get on with it or if you have any questions.

Replace a key value of a Hash nested in an array

You need to:

  • quote hash keys,
  • use => instead of = between hash keys and values,
  • put braces around the hashes,
  • remove an extra = in test=[0] [thing] ["idea"]:
test = [{'thing' => { 'idea'=>'Hi',         'dumb' => 'test' }},
{'thing2' => {'idea' => 'not sure', 'dumb' => 'not'}}]
puts test[0] # {"thing"=>{"idea"=>"Hi", "dumb"=>"test"}}
test[0]['thing']['idea']='Bye'
puts test[0] # {"thing"=>{"idea"=>"Bye", "dumb"=>"test"}}


Also note that your original code as posted gives me an error, contrary to what you observed:

test=[thing={"idea"=>"Hi", "dumb"=>"test"}, thing2={"idea"=>"not sure", "dumb"=>"not"}]
puts test [0]

test=[0] [thing] ["idea"]="Bye" # Error: no implicit conversion of Hash into Integer (TypeError)

This error message should alert you that something wrong is going on with this line (or before). The error may look cryptic. But the proximal cause of it is that thing has been assigned to a hash before: thing={"idea"=>"Hi", "dumb"=>"test"}. And on this line the interpreter tries to use thing hash as an (integer) index into the array [0] - and fails to cast hash as integer. Note that the interpreter tries to treat [0] [thing] ["idea"]="Bye" as an assignment.

Long story short, make sure the error messages are displayed. Perhaps they are hidden, for example, by redirecting stderr to a file or /dev/null.

How to replace array value with hash value?

You can use #fetch method

@a = ["a","b","c","d"]
#=> ["a", "b", "c", "d"]

@a_hash ={"b"=> ["1","2","3"]}
#=> {"b"=>["1", "2", "3"]}

@a.map! { |e| @a_hash.fetch(e, e) }
#=> ["a", ["1", "2", "3"], "c", "d"]

how to replace values in an array of hashes properly in Perl?

This clearly has to do with storing references on the array, instead of independent data. How that comes about isn't clear since details aren't given, but the following discussion should help.

Consider these two basic examples.

First, place a hash (reference) on an array, first changing a value each time

use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd);
# use Storable qw(dclone);

my %h = ( a => 1, b => 2 );

my @ary_w_refs;

for my $i (1..3) {
$h{a} = $i;
push @ary_w_refs, \%h; # almost certainly WRONG

# push @ary_w_refs, { %h }; # *copy* data
# push @ary_w_refs, dclone \%h; # may be necessary, or just safer
}

dd $_ for @ary_w_refs;

I use Data::Dump for displaying complex data structures, for its simplicity and default compact output. There are other modules for this purpose, Data::Dumper being in the core (installed).

The above prints


{ a => 3, b => 2 }
{ a => 3, b => 2 }
{ a => 3, b => 2 }

See how that value for key a, that we changed in the hash each time, and so supposedly set for each array element, to a different value (1, 2, 3) -- is the same in the end, and equal to the one we assigned last? (This appears to be the case in the question.)

This is because we assigned a reference to the hash %h to each element, so even though every time through the loop we first change the value in the hash for that key in the end it's just the reference there, at each element, to that same hash.

So when the array is queried after the loop we can only get what is in the hash (at key a it's the last assigned number, 3). The array doesn't have its own data, only a pointer to hash's data. (Thus hash's data can be changed by writing to the array as well, as seen in the example below.)

Most of the time, we want a separate, independent copy. Solution? Copy the data.

Naively, instead of

push @ary_w_refs, \%h;

we can do

push @ary_w_refs, { %h };

Here {} is a constructor for an anonymous hash, so %h inside gets copied. So actual data gets into the array and all is well? In this case, yes, where hash values are plain strings/numbers.

But what when the hash values themselves are references? Then those references get copied, and @ary_w_refs again does not have its own data! We'll have the exact same problem. (Try the above with the hash being ( a => [1..10] ))

If we have a complex data structure, carrying references for values, we need a deep copy. One good way to do that is to use a library, and Storable with its dclone is very good

use Storable qw(dclone);
...

push @ary_w_refs, dclone \%h;

Now array elements have their own data, unrelated (but at the time of copy equal) to %h.

This is a good thing to do with a simple hash/array as well, to be safe from future changes, whereby the hash is changed but we forget about the places where it's copied (or the hash and its copies don't even know about each other).

Another example. Let's populate an array with a hashref, and then copy it to another array

use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd pp);

my %h = ( a => 1, b => 2 );

my @ary_src = \%h;
say "Source array: ", pp \@ary_src;

my @ary_tgt = $ary_src[0];
say "Target array: ", pp \@ary_tgt;

$h{a} = 10;
say "Target array: ", pp(\@ary_tgt), " (after hash change)";

$ary_src[0]{b} = 20;
say "Target array: ", pp(\@ary_tgt), " (after hash change)";

$ary_tgt[0]{a} = 100;
dd \%h;

(For simplicity I use arrays with only one element.)

This prints


Source array: [{ a => 1, b => 2 }]
Target array: [{ a => 1, b => 2 }]
Target array: [{ a => 10, b => 2 }] (after hash change)
Target array: [{ a => 10, b => 20 }] (after hash change)
{ a => 100, b => 20 }

That "target" array, which supposedly was merely copied off of a source array, changes when the distant hash changes! And when its source array changes. Again, it is because a reference to the hash gets copied, first to one array and then to the other.

In order to get independent data copies, again, copy the data, each time. I'd again advise to be on the safe side and use Storable::dclone (or an equivalent library of course), even with simple hashes and arrays.

Finally, note a slightly sinister last case -- writing to that array changes the hash! This (second-copied) array may be far removed from the hash, in a function (in another module) that the hash doesn't even know of. This kind of an error can be a source of really hidden bugs.

Now if you clarify where references get copied, with a more complete (simple) representation of your problem, we can offer a more specific remedy.


An important way of using a reference that is correct, and which is often used, is when the structure taken the reference of is declared as a lexical variable every time through

for my $elem (@data) { 
my %h = ...
...
push @results, \%h; # all good
}

That lexical %h is introduced anew every time so the data for its reference on the array is retained, as the array persists beyond the loop, independently for each element.

It is also more efficient doing it this way since the data in %h isn't copied, like it is with { %h }, but is just "re-purposed," so to say, from the lexical %h that gets destroyed at the end of iteration to the reference in the array.

This of course may not always be suitable, if a structure to be copied naturally lives outside of the loop. Then use a deep copy of it.

The same kind of a mechanism works in a function call

sub some_func {
...
my %h = ...
...
return \%h; # good
}

my $hashref = some_func();

Again, the lexical %h goes out of scope as the function returns and it doesn't exist any more, but the data it carried and a reference to it is preserved, since it is returned and assigned so its refcount is non-zero. (At least returned to the caller, that is; it could've been passed yet elsewhere during the sub's execution so we may still have a mess with multiple actors working with the same reference.) So $hashref has a reference to data that had been created in the sub.

Recall that if a function was passed a reference, when it was called or during its execution (by calling yet other subs which return references), changed and returned it, then again we have data changed in some caller, potentially far removed from this part of program flow.

This is done often of course, with larger pools of data which can't just be copied around all the time, but then one need be careful and organize code (to be as modular as possible, for one) so to minimize chance of errors.

This is a loose use of the word "pointer," for what a reference does, but if one were to refer to C I'd say that it's a bit of a "dressed" C-pointer

In a different context it can be a block

How to replace a Hash's key names with the values from a separate array?

hash = {field1: 'name', field2: 'street1', field3: 'street2', field4: 'city'}
array = [:address1, :address2, :address3, :city]
hash.each_with_index.map { |(k, v), i| [array[i], v] }.to_h
#=> {:address1=>"name", :address2=>"street1", :address3=>"street2", :city=>"city"}

Ruby / Replace value in array of hash

Another way, using find

1.9.3p194 :007 > array1 = [{"name"=>"Bob"}, {"age"=>"30"}]
=> [{"name"=>"Bob"}, {"age"=>"30"}]
1.9.3p194 :008 > hash1 = array1.find { |h| h['age'] == "30" }
=> {"age"=>"30"}
1.9.3p194 :009 > hash1['age'] = 31
=> 31
1.9.3p194 :010 > array1
=> [{"name"=>"Bob"}, {"age"=>31}]

Find and replace string in associative array in bash

Something that pops in my mind is creating a copy of the element with UNKNOWN

hash[UNKNOWN]=hash[invalid]
unset hash[invalid]

Ruby delete specific value from hash and replace with another value

Possible solution:

# Input
fills = ['FOUR', 'FIVE', 'SIX']

hash = {
'AA' => ['1', 'ONE'],
'BB' => ['2', 'TWO'],
'CC' => ['3', 'THREE']
}
hash.transform_values { |i, _| [i, fills[i.to_i - 1]] }
# output

{
'AA' => ['1', 'FOUR'],
'BB' => ['2', 'FIVE'],
'CC' => ['3', 'SIX']
}

Update:

As your incoming data is changed the solution will be:

h = {"AA"=>["1", "FOUR"], "BB"=>["2", "FIVE"]}

h.transform_values { |x| x.insert(1, 'TEXT') }
# Output

{
'AA' => ['1', 'TEXT', 'FOUR'],
'BB' => ['2', 'TEXT', 'FIVE']
}


Related Topics



Leave a reply



Submit