Iterating over an Array of Hashes

P

Peter Hicks

All,

When iterating over an Array of Hashes repeatedly, the 'each' method
appear to pass a reference to - not a copy of - each element in the
array.

Is there a way to take a *copy* of the data, rather than a reference to
the data, so that any changes to each hash aren't?

Explaining it another way - I have code similar to the following:

a = [ '2011-01-01', '2011-01-02', '2011-01-03' ]
b = [ { :x => "foo" }, { :x => "bar" }, { :x => "baz" } ]

a.each do |a_item|

puts a_item

b.each do |b_item|
b_item[:date] = a_item
# DO SOMETHING WITH B
end

end

What I want to do within the b.each loop is work on a *copy* of each
element of b, such that if I mess around with it, the changes are lost
when when the loop exits.

What is the magic I am missing?


Peter
 
J

jake kaiden

hi Peter -

well, this is certainly no magic - but you could just make a temp
array, and check each entry to make sure you like it before committing
it to "b."
there's probably a better way to do this, but here's what i came up
with:

a = [ '2011-01-01', '2011-01-02', '2011-01-03' ]
b = [ { :x => "foo" }, { :x => "bar" }, { :x => "baz" } ]
temp = []

a.each{|a_item| temp << {:date => a_item}}

temp.each{|entry|
unless entry.inspect.include?("02") #or something else more relevant
idx = temp.index(entry)
b[idx] = entry
end
}

temp = []

p b

#=> [{:date=>"2011-01-01"}, {:x=>"bar"}, {:date=>"2011-01-03"}]


- j
 
7

7stud --

Peter Hicks wrote in post #996483:
What is the magic I am missing?

clone():

b = [ { :x => "foo" }, { :x => "bar" }, { :x => "baz" } ]
count = 0

b.each do |x|
copy = x.clone
copy[:x] = count
count += 1

puts copy[:x]
end

b.each do |x|
p x
end

--output:--
0
1
2
{:x=>"foo"}
{:x=>"bar"}
{:x=>"baz"}
 
J

John Feminella

Peter Hicks wrote in post #996483:

clone():

`clone` doesn't cut it, since it's creating shallow copies. An
illustration of the problem:

=3D=3D=3D=3D begin snippet =3D=3D=3D=3D
arr =3D [{:fruit =3D> {:kind =3D> 'apple'}}, {:fruit =3D> {:kind =3D> 'bana=
na'}}]
# =3D> [{:fruit=3D>{:kind=3D>"apple"}}, {:fruit=3D>{:kind=3D>"banana"}}]

arr.map(&:clone).each { |h| h[:fruit][:kind] =3D 'coconut' }
# =3D> [{:fruit=3D>{:kind=3D>"coconut"}}, {:fruit=3D>{:kind=3D>"coconut"}}]
=3D=3D=3D=3D end snippet =3D=3D=3D=3D

Notice that `arr` now has coconuts in it, instead of the original
apples and bananas.

~ jf
--
John Feminella
Principal Consultant, BitsBuilder
LI: http://www.linkedin.com/in/johnxf
SO: http://stackoverflow.com/users/75170/



Peter Hicks wrote in post #996483:
What is the magic I am missing?

clone():

b =3D [ { :x =3D> "foo" }, { :x =3D> "bar" }, { :x =3D> "baz" } ]
count =3D 0

b.each do |x|
=C2=A0copy =3D x.clone
=C2=A0copy[:x] =3D count
=C2=A0count +=3D 1

=C2=A0puts copy[:x]
end

b.each do |x|
=C2=A0p x
end

--output:--
0
1
2
{:x=3D>"foo"}
{:x=3D>"bar"}
{:x=3D>"baz"}
 
7

7stud --

John Feminella wrote in post #996498:
`clone` doesn't cut it, since it's creating shallow copies. An
illustration of the problem:

Really? Look at my output. It cuts it just fine.
 
A

Abinoam Jr.

Any problem with the marshal dump and load aproach of Chad Perrin?

Look...

arr =3D [{:fruit =3D> {:kind =3D> 'apple'}}, {:fruit =3D> {:kind =3D> 'bana=
na'}}]

Marshal.load(Marshal.dump arr).each do |h|
h[:fruit][:kind] =3D 'coconut'
end

If it's ugly, you can try to beautify it...

class Object
def deep_copy
Marshal.load(Marshal.dump self)
end
end

arr.deep_copy.each do |h|
h[:fruit][:kind] =3D 'coconut'
end

Abinoam Jr.

Your example doesn't contain nested hashes, while mine does. That's
what I was demonstrating -- it's a shallow copy, not a deep one.

~ jf
--
John Feminella
Principal Consultant, BitsBuilder
LI: http://www.linkedin.com/in/johnxf
SO: http://stackoverflow.com/users/75170/
 
C

Christopher Dicely

Your example doesn't contain nested hashes, while mine does.

Neither did Peter's example and request.
That's what I was demonstrating -- it's a shallow copy, not a deep
one.

Simply using #dup or #clone meets the original request as presented,
and does so for arbitrary Ruby objects in the hashes.

If the Peter needs a deep copy, which Peter didn't ask for (just a
copy of the data in each hash in the array -- not nested hashes -- so
that the originals in the area wouldn't be modified on iteration) its
true that simply using #dup or #clone won't work.

OTOH, if Peter needs to be able to handle arbitrary Ruby objects in
the hashes, using Marshal.load(Marshal.dump(whatever)) won't work,
since there are objects that can't be dumped.

What if the values in the hash are lambdas?

The load/dump mechanism meets some other need, but doesn't meet the
original request for hashes with arbitrary data (and is an
extraordinarily convoluted mechanism for meeting the original request
even where it works.)

Its possible to craft a generic deep-copying mechanism, if you need
it, but Marshal.load(Marshal.dump(whatever)) isn't it.
 
P

Patrick Lynch

Hi,
I'm running a pc with Windows Vista...
My email flags your emails with 'phishing' warning...
Thought you might want to know...
Good day

----- Original Message -----
From: "Chad Perrin" <[email protected]>
To: "ruby-talk ML" <[email protected]>
Sent: Wednesday, May 04, 2011 11:18 AM
Subject: Re: Iterating over an Array of Hashes
 
C

Christopher Dicely

Then yeah, the Marshal dump/load approach doesn't work. =C2=A0It *does* w= ork
for the presented example, though, whereas (given the implications of the
requests in the original questions) your dup-or-clone approach does *not*
work.

The #clone based method presented upthread (which wasn't mine, it was
7stud's) works perfectly for the data and requirements presented. It
doesn't work if, in addition to wanting to lose changes to the passed
hashes, one also wants to lose any changes resulting from manipulation
of the data in the hashes, but that wasn't requested.

That is, since you work on a local copy of each hash, mutating methods
called on the hash won't have any lasting effect, but mutating methods
called on keys or values extracted from the (local copy of the) hash
will affect the objects stored in the original hash. Avoiding this
kind of effect was not requested.
1. It meets the needs of the presented example data.

Sure, but its way overkill for it. You only need deep copies if you
have requirements that weren't stated (avoiding effects from mutating
methods called not on the hashes being passed but on the keys or
values of the hashes.)
2. Is there some less-convoluted approach that works for the more complex
needs you presented?

I'm not sure what relevance this has. A deep copying solution that
handles arbitrary rather than merely serializable objects at the
fringes is going to be more complex than
Marshal.load(Marshal.dump(...)), but since either approach is more
convoluted than what is needed here, I don't see why that matters here
(and, since Marshal.load(Marshal.dump(...)) doesn't work for the cases
where you'd need the more sophisticated deep copying solution, I don't
see why the fact that the latter would be more complex would matter
there, either -- a simpler approach that doesn't work is still no
solution at all.)
3. How exactly does this make a dup-or-clone approach work such as what
you presented work?

"this" doesn't make the dup-or-clone approach work, it is orthogonal
to the fact that the dup-or-clone approach, as presented (in #clone
form) by 7stud upthread _does_ work for the scenario presented.
 
C

Christopher Dicely

Are you seriously claiming that the person's statements seemed to you to
indicate a desire for the data to change in the original data structure?

No, I'm claiming that the original requester's statement didn't
indicate a need to protect against changes resulting from calling
mutating methods on the keys or values of the hashes, only protection
from changes resulting from mutations on the hash.

That could be because changes to things held in the hash were supposed
to be propagated, or it (perhaps more likely) could be because the
code in which it was to be used wasn't going to be calling mutating
methods on the keys or values from the hash in any case.

In the former case, a deep copy would be wrong, in the latter case it
would merely be unnecessary.
 
D

Douglas Seifert

[Note: parts of this message were removed to make it a legal post.]

2 solutions have been presented. I think the original poster's question was
sufficiently vague to make both of the solutions valid.

So, original poster: what were your intentions? Did you need the only the
hashes in the array preserved, or did you ALSO need the contents of the
hashes in the array preserved? If the former, then clone/dup is fine. If
the latter, then you need some kind of deep copy. The "simplest" deep copy
I can think of is the Marshal#dump/load one.

Saludos,
Doug
 
C

Christopher Dicely

He didn't specify "only protection from changes resulting from mutations
on the hash."

Yes, which is why I used the word "indicated" rather than "specified";
those words have very different meanings.

I certainly think that, as at one commenter has stated, the original
request was sufficiently ambiguous to support different readings. I
think we've addressed the relative utility of the various options that
have been present sufficiently that the OP (or others with similar
concerns) can make up their minds about what approach is best for
their use cases, or ask cogent follow-up questions if they need more
information.

Clearly, we disagree on what the best interpretation of the original
posters requirements is, but I think we are clearly past the point
where further discussion of that disagreement has any value for anyone
reading.
 
7

7stud --

Chad Perrin wrote in post #996688:
He didn't specify "only protection from changes resulting from mutations
on the hash." He said he didn't want his actions to change his data
structure, in a very general way.

So you think the folowing statement is a general, mamby-pamby statement:

In my opinion, you need to ask yourself two questions with regards to
that statement:

1) What are the elements of b?

2) Given the op's code example showing what each element of b looks
like, does copying such an element require a deep copy or a shallow
copy?
 
P

Peter Hicks

All,

Apologies, I lost track of the thread.

So, original poster: what were your intentions? Did you need the only the
hashes in the array preserved, or did you ALSO need the contents of the
hashes in the array preserved? If the former, then clone/dup is fine. If
the latter, then you need some kind of deep copy. The "simplest" deep copy
I can think of is the Marshal#dump/load one.

I just needed the hashes in the array preserved. I've done this
with .clone now, and it works like a treat. I now have other problems
over ActiveRecord's slowness and various other parts of the "correct but
slow" code :)

Thanks for your help - this was one area in which I truly had a surprise
when my unit tests failed!


Peter
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,540
Members
45,025
Latest member
KetoRushACVFitness

Latest Threads

Top