DevHeads.net

Iterate hash to make another hash

Hi folks. I can’t figure out a puzzle where I need to generate a Sequel hash from a reference hash and an array:

reference hash:

t = {:name => :company_name, :rank => :company_rank, :serial_no => :company_serial}

I want to iterate through each of those pairs and reference the value of each, into the following Sequel command:

companies.insert(t[0] => myarray[0], t[1] => myarray[1], t[2] => myarray[2])

myArray is another set of items. I can’t get my head around how I can provide some kind of iteration and call out the sequence of values from both items, into yet another hash for that .insert method.

Any insight appreciated.

Cheers, Bee

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Comments

Re: Iterate hash to make another hash

By John W Higgins at 10/01/2016 - 17:26

Afternoon,

companies.insert(t.values, myarray)

as the Sequel docs here -
<a href="http://sequel.jeremyevans.net/rdoc/classes/Sequel/Dataset.html#method-i-insert" title="http://sequel.jeremyevans.net/rdoc/classes/Sequel/Dataset.html#method-i-insert">http://sequel.jeremyevans.net/rdoc/classes/Sequel/Dataset.html#method-i-...</a>
- show that the insert method will take 2 arrays (columns and values) and
work just fine.

Not that learning what you asked about isn't cool - but other options are
cool as well :)

John

Re: Iterate hash to make another hash

By Bee.Lists at 10/01/2016 - 19:20

I moved to a different approach as I didn’t need one compound data type to accomplish what I needed.

Cheers, Bee

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Re: Iterate hash to make another hash

By Robert Klemme at 09/30/2016 - 08:14

On Thu, Sep 29, 2016 at 6:08 PM, Bee.Lists <bee. ... at gmail dot com> wrote:
As has been discussed already you reliance on order in myarray is
fragile. Basically there is a mapping missing from the Hash to the
Array. For example:

t = {:name => 0, :rank => 2, :serial_no => 1}
companies.insert(t.each_with_object({}) {|(k,v),h| h[k] = myarray[v]})

Kind regards

robert

Re: Iterate hash to make another hash

By =?ISO-8859-1?Q?... at 09/29/2016 - 12:25

On Thu, Sep 29, 2016 at 6:08 PM, Bee.Lists <bee. ... at gmail dot com> wrote:
If I understand correctly, myarray contains values for company_name,
company_rank and company_serial in that order. Then you can do the
following:

2.2.1 :001 > t = {:name => :company_name, :rank => :company_rank,
:serial_no => :company_serial}
=> {:name=>:company_name, :rank=>:company_rank, :serial_no=>:company_serial}
2.2.1 :003 > my_array = ["my company name", "my company rank", "my
company serial"]
=> ["my company name", "my company rank", "my company serial"]
2.2.1 :004 > t.values_at(:name, :rank, :serial_no).zip(my_array)
=> [[:company_name, "my company name"], [:company_rank, "my company
rank"], [:company_serial, "my company serial"]]
2.2.1 :006 > Hash[*t.values_at(:name, :rank, :serial_no).zip(my_array).flatten]
=> {:company_name=>"my company name", :company_rank=>"my company
rank", :company_serial=>"my company serial"}

This is a way. First you extract an array of values from the Hash with
values_at. Since the iteration order of the Hash might not be what you
want, it's better to explicitly choose the values you want in order.
See this example:

2.2.1 :008 > t.each {|k,v| puts [k,v].join(" => ")}
name => company_name
rank => company_rank
serial_no => company_serial
=> {:name=>:company_name, :rank=>:company_rank, :serial_no=>:company_serial}
2.2.1 :009 > h = {}
=> {}
2.2.1 :010 > h[:rank] = :company_rank
=> :company_rank
2.2.1 :011 > h[:serial_no] = :company_serial
=> :company_serial
2.2.1 :012 > h[:name] = :company_name
=> :company_name
2.2.1 :013 > h.each {|k,v| puts [k,v].join(" => ")}
rank => company_rank
serial_no => company_serial
name => company_name
=> {:rank=>:company_rank, :serial_no=>:company_serial, :name=>:company_name}

Hashes in Ruby iterate over the "insertion order" and for this use
case, it might be a good idea to not rely on that order.

Next, we use zip to pair the array coming from the Hash with myarray.
Then we use the Hash [] class method to construct a hash with a list
of pairs in order, which we get using flatten, because zip returns an
array of arrays.

Hope this helps,

Jesus.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Re: Iterate hash to make another hash

By Bee.Lists at 09/29/2016 - 12:38

Hi Jesus. Thanks for the reply.

This command seems to be the one I’d be looking for:

t.each {|k,v| puts [k,v].join(" => “)}

From first glance, it seems like it’s just an output, but that construction actually results in a properly formed hash. Interesting.

But you brought up another point of hash value sequencing, which I need to have in the proper order. I think I’m going to have to rethink all this.

Cheers, Bee

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Re: Iterate hash to make another hash

By John W Higgins at 10/02/2016 - 15:05

You continue to not see the forest for the trees

Lets assume you are 100% correct

t.each {|k,v| [k,v].join(" => “)}

returns a hash and not self

what should the following return?

t.each {|k,v| [v, k].join(" => “)}
(note k and v inverted in the array)

It should return, under your conceptual idea, an inverted hash with the
keys as the values and vice-versa.

Does it?

John

Re: Iterate hash to make another hash

By =?ISO-8859-1?Q?... at 09/29/2016 - 12:41

On Thu, Sep 29, 2016 at 6:38 PM, Bee.Lists <bee. ... at gmail dot com> wrote:
Well, the puts there was to show that you have to be careful about the
iteration order.

If you need the fields from the hash in a specific order it's better
to explicitly ask for that with values_at. If you don't know in
advance the contents of the Hash and have to dynamically create the
query, yes, you might need to rethink the strategy again to be sure
the order is correct.

Jesus.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Re: Iterate hash to make another hash

By Bee.Lists at 09/29/2016 - 14:04

Yes. Even so, I’m still hung up on that it is automatically coerced into a hash. Useful tool, that.

Yeah the application I’m using this for is a bit abstract, so I’ve changed to a single array, as the first reference hash isn’t really needed. So now abstraction with minimal interaction is just fine. Simple is good. Heh.

Cheers, Bee

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Re: Iterate hash to make another hash

By Eric Christopherson at 09/29/2016 - 14:12

Unless I'm misunderstanding, nothing is automatically coerced into a hash
here. t is already the right hash; the each iteration prints out each pair,
and returns t at the end, unchanged.

Re: Iterate hash to make another hash

By Bee.Lists at 09/29/2016 - 16:03

Joining using the string “ => “ results in a hash. It’s implied that the final structure is indeed a hash.

Cheers, Bee

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Re: Iterate hash to make another hash

By Ryan Davis at 09/30/2016 - 17:36

No, this is not the case on either sentence.

Joining using the string results in a string, not a hash. It might LOOK like a hash when you're printing it, but that's it.

The final structure you're seeing is your initial structure because #each returns self.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Re: Iterate hash to make another hash

By Bee.Lists at 09/30/2016 - 22:39

That’s exactly my point. But if you test it, it does indeed come out as a hash. Testing for class on that final result is indeed a hash. That’s the part I was saying was surprising and very useful.

Cheers, Bee

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Re: Iterate hash to make another hash

By Matthew Kerwin at 09/30/2016 - 23:48

Going back to this code:

~~~
t.each {|k,v| puts [k,v].join(" => ")}
~~~

There are some important points to note:

1. The result of this expression is not used. It's not being assigned
anywhere, or passed as a parameter to a function call, or anything. In
some languages we'd say it's being evaluated in a void context.

2. The only side-effect of this expression is that characters are written
to STDOUT using the `puts` function. No object are being mutated, no data
structures are being created, no *new* value is being returned. The *only*
thing it does is print characters to STDOUT.

3. Hash#each called with a block explicitly returns the original hash[1].
If this expression wasn't called in a void context -- if the resulting
value was captured or used -- it would be `t`.

Effectively you could replace this:

~~~
companies.insert( t.each {|k,v| puts [k,v].join(" => ")} )
~~~

with this:

~~~
companies.insert( t )
~~~

and the only difference would be that nothing is printed to STDOUT in the
second version. There is no magic coercion. This is what Eric
Christopherson said earlier in this thread. Please move more slowly -- *read
*the documentation for the functions you're using, *read *the code,
formulate *meaningful *ways to test your hypotheses, and listen to the
advice given to you by the people from whom you sought it.

Cheers

[1]: <a href="http://ruby-doc.org/core-2.3.1/Hash.html#method-i-each" title="http://ruby-doc.org/core-2.3.1/Hash.html#method-i-each">http://ruby-doc.org/core-2.3.1/Hash.html#method-i-each</a>

Re: Iterate hash to make another hash

By Robert Klemme at 10/01/2016 - 04:43

Excellent advice! Bee, I might add, if you do not understand how
something works, test it in IRB or a small script. If you try to
understand complex expressions, break them up into smaller parts and
look at their results.

Kind regards

robert

Re: Iterate hash to make another hash

By Bee.Lists at 10/01/2016 - 11:03

Hi Robert. Some advice. Don’t assume I didn’t do that. I’ve been doing stuff like that since the 80’s. Also Matthew, don’t assume I didn’t read the notes on this.

Joining two objects with a string, which by the documentation, results in a string. Convenient that it a “ => “ and results in a hash, does indeed go against any documentation.

Cheers, Bee

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Re: Iterate hash to make another hash

By A Berger at 10/01/2016 - 12:41

Hi Bee,
The RETURNED object (t) is a hash, because the prints dont modify the
object, which initially was a hash, as Matthew and Robert said.
print "xx => yy" doesnt return a hash, documentation is correct.
If you are convinced you're right, tell an example with detailed doku, than
we will find your mistake, or correct the documentation ;)

Bye
Berg

Re: Iterate hash to make another hash

By Bee.Lists at 10/01/2016 - 19:19

There is no print in any of this. Second, asking a hash for a key or a value both return a string. String plus string plus string, should result in a string, as per documentation.

Cheers, Bee

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Re: Iterate hash to make another hash

By Matthew Kerwin at 10/01/2016 - 20:15

Asking a hash for a key or a value both return whatever object is used in
the hash's key or value. If those are Strings, then it returns Strings.

~~~
hsh = { 0 => 3.14, :foo => Object.new }
hsh[0].class #=> Float
hsh.keys[1].class #=> Symbol

# and finally
hsh.each{nil} == hsh #=> true
~~~

​Yes, joining two Strings with a String results in a String... which is
then printed using `puts`. It's not returned in any way. I know I've said
this already, and I assume you've understood even though you haven't
acknowledged it (which is why I've repeated it again here.) I just want to
make sure the original issue is resolved and clear.

I'm glad you've been programming since I was a wee babby. I've only been
doing it since the 90s (Ruby for about a decade.) That's not relevant,
though.

The documentation for Hash#each
<http://ruby-doc.org/core-2.3.0/Hash.html#method-i-each> shows, somewhat
abstractly perhaps but consistent with the official Ruby docs idioms, that
it returns the receiver unmodified. Hence my request that you *read* (with
emphasis) the documentation. (And they're not notes, they're the official
human-readable contract for how the core libraries' APIs behave.)

The resulting object passes the `.class == Hash` test, but a deeper
inspection would have shown that the result comes out unmodified by `each`;
for example: `p(
t.each {|k,v| puts [
​v, k
].join(" => ")}
​ )​
​` would have shown that the final result is identical to `
t
`, even though I flipped the `
k
` and `
v
` inside the loop. Hence my request that you formulate *meaningful* ways to
test hypotheses. Testing a noop that, even if it did something would still
end up being a noop, is not very useful. Also note that `
[p,k].join("=>")
` removes all the quotation marks that would have made it parseable as a
Hash of Strings.

Please don't take my advice as admonishment (unless you've a guilty
conscience, and even then...) and certainly don't dismiss me because of
your life experiences -- fine whisky improves with age, but in the tech
world it's far more common for the synonym of "old" to be "obsolete."
Asking for advice on these mailing lists is a great thing; accepting the
advice is the hard part.

Cheers

Re: Iterate hash to make another hash

By Bee.Lists at 10/02/2016 - 14:24

A join on two symbols and an object which, in this case, was decidedly a string. The result is a hash because the string contains “ => “?

The puts has nothing to do with any of this. You can repeat it until next week, but it has absolutely nothing to do with any of this. Nothing. I’ll repeat myself: nothing. The original issue is not what was presented via the puts (or print, as somebody else said, which was never part of any discussion…but we move on). It was about the result through the join.

Well, you see, it is. First, you just brought it up. Second, don’t assume that everybody else is beneath you.

<a href="https://ruby-doc.org/core-2.3.1/Hash.html#method-i-key" title="https://ruby-doc.org/core-2.3.1/Hash.html#method-i-key">https://ruby-doc.org/core-2.3.1/Hash.html#method-i-key</a>

<a href="https://ruby-doc.org/core-2.3.1/Hash.html#method-i-values" title="https://ruby-doc.org/core-2.3.1/Hash.html#method-i-values">https://ruby-doc.org/core-2.3.1/Hash.html#method-i-values</a>

So you’re saying they don’t have a data type. Right. That makes no sense at all.

Please don’t take my correction as an indication you are wrong. Older people are the ones you received any education from. Second, your “advice” holds no water, considering the case. Two strings or symbols, joined with a string, returns a hash, and I said it was interesting and useful. You insist on talking about the puts, which is completely irrelevant, but you feel the need to repeat yourself.

Have a nice day.

Cheers, Bee

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Re: Iterate hash to make another hash

By Robert Klemme at 10/02/2016 - 18:08

This is wrong.

1. Documentation:
<a href="https://ruby-doc.org/core-2.3.1/Array.html#method-i-join" title="https://ruby-doc.org/core-2.3.1/Array.html#method-i-join">https://ruby-doc.org/core-2.3.1/Array.html#method-i-join</a>

2. Experiments:

irb(main):001:0> a = [1, "foo", :bar, Object.new]
=> [1, "foo", :bar, #<Object:0x00000001f16f20>]
irb(main):002:0> a.each {|x| a.each {|y| printf "Test: %p, %p\n", x,
y; r = [x,y].join("@"); printf "Result: %p, %s\n", r, r.class}}
Test: 1, 1
Result: "1@1", String
Test: 1, "foo"
Result: "1@foo", String
Test: 1, :bar
Result: "1@bar", String
Test: 1, #<Object:0x00000001f16f20>
Result: "1@#<Object:0x00000001f16f20>", String
Test: "foo", 1
Result: "foo@1", String
Test: "foo", "foo"
Result: "foo@foo", String
Test: "foo", :bar
Result: "foo@bar", String
Test: "foo", #<Object:0x00000001f16f20>
Result: "foo@#<Object:0x00000001f16f20>", String
Test: :bar, 1
Result: "bar@1", String
Test: :bar, "foo"
Result: "bar@foo", String
Test: :bar, :bar
Result: "bar@bar", String
Test: :bar, #<Object:0x00000001f16f20>
Result: "bar@#<Object:0x00000001f16f20>", String
Test: #<Object:0x00000001f16f20>, 1
Result: "#<Object:0x00000001f16f20>@1", String
Test: #<Object:0x00000001f16f20>, "foo"
Result: "#<Object:0x00000001f16f20>@foo", String
Test: #<Object:0x00000001f16f20>, :bar
Result: "#<Object:0x00000001f16f20>@bar", String
Test: #<Object:0x00000001f16f20>, #<Object:0x00000001f16f20>
Result: "#<Object:0x00000001f16f20>@#<Object:0x00000001f16f20>", String
=> [1, "foo", :bar, #<Object:0x00000001f16f20>]

robert

Re: Iterate hash to make another hash

By Matthew Kerwin at 10/02/2016 - 17:24

Just to be absolutely clear once again, this is the exact code we're
talking about:

~~~
t.each {|k,v| puts [k,v].join(" => ")}
~~~

If we unwrap it a bit it looks like this:

~~~
t.each do |k, v|
kv_string = [k,v].join(" => ")
puts kv_string
end
~~~

I hope that's uncontroversial.

*Joining two Strings with a String results in a String.*

As the puts is unimportant, we can reduce the unwrapped code to this:

~~~
t.each do |k, v|
kv_string = [k,v].join(" => ")
end
~~~

*It is not returned in any way.*

Since this is Ruby and everything is an expression, the return value from
the do-end block each time it is invoked is the kv_string. However
Hash#each completely ignores the value returned from its block. Since
Array#join has no side-effects aside from its return value, this code is
functionally equivalent to this:

~~~
t.each do |k, v|
end
~~~

which is functionally equivalent to this:

~~~
t
~~~

This is also uncontroversial, well documented, and testable.

So: the thing that returns a Hash is not Array#join, but is, in fact,
Hash#each. But it doesn't return a *new* Hash; it returns the original
receiving Hash object, unmodified.

If this is a String:

~~~
"hello world"
~~~

And this is a String that looks like it contains a valid Ruby value:

~~~
"[123, \"foo\"]"
~~~​

Then, by comparison, this is a String that does *not* look like it contains
valid Ruby values:

~~~
"foo => bar"
~~~

That is what I was saying. There are no quotation marks *inside the string*,
therefore it is not parseable as a Hash of Strings. So even if some voodoo
magic was evaluating the String returned by Array#join, it doesn't look
like valid Ruby code so it wouldn't evaluate anyway.

​I wasn't educated by older people, I was educated by wiser people. One
can't accumulate wisdom without age, but one can accumulate age without
wisdom.

My "advice" only holds no water if you don't understand it.

Two Strings or Symbols, joined with a String, returns a String. It doesn't
matter if you call #join out of the blue, or inside Hash#each, or anywhere
else: `[str1,str2].join(" => ")` returns a String. Always. Without
exception. This is clearly documented
<a href="http://ruby-doc.org/core-2.3.1/Array.html#method-i-join" title="http://ruby-doc.org/core-2.3.1/Array.html#method-i-join">http://ruby-doc.org/core-2.3.1/Array.html#method-i-join</a> and trivial to
test, if only you would *read *the documentation for the functions you're
using, *read *the code, formulate *meaningful *ways to test your
hypotheses, and listen to the advice given to you by the people from whom
you sought it. (Sounds familiar.)

The thing I feel the need to repeat, even now having stated it several
times because clearly it hasn't sunk in, is: if you don't use the value
returned by a function that has no other side-effects, that function is a
noop. It does nothing. Hash#each does nothing if you don't somehow use the
values you construct inside it. Which is not happening in this case.

See? I said it this time without mentioning the 'p' word.​

Re: Iterate hash to make another hash

By Martin DeMello at 10/02/2016 - 14:50

On Oct 2, 2016 11:25 AM, "Bee.Lists" <bee. ... at gmail dot com> wrote:
The result is a hash because hash.each is called for its side effects and
returns self. Nothing more.

Try to write a program that gives you different results for a and b in

hash = { ... }
a = hash.each {|k, v| "#{k} => #{v}"}
b = hash.each {|k, v| 42}

fill whatever combination of strings, symbols, etc. you like into hash.

Also check out the object_id method.

martin

Re: Iterate hash to make another hash

By Bee.Lists at 10/02/2016 - 15:56

You forgot what is important here. The join. The result of a join is not a hash.

Cheers, Bee

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Re: Iterate hash to make another hash

By Martin DeMello at 10/03/2016 - 03:16

Fill whatever code you like into the block too.

martin

Re: Iterate hash to make another hash

By A Berger at 10/04/2016 - 16:12

Hi
I'm surprised - 27 messages, the maximum for one help-topic in the whole
year...
Nobody whispering "a troll?" or even noob/... -- ok, the mailing-list has
evolved, which is positive.

Maybe I feel a little aggrieved, such detailed answers, repeated soooo many
times, and then no answer or something like "thank you folks".

Why dont I get these many replies?
The more difficult the question, the less answers?
What am I doing wrong?

Bye
Berg

Re: Iterate hash to make another hash

By Thomas Charbonnel at 10/02/2016 - 16:17

Hi!

Indeed, you're right, the result of the join is not a hash, entirely true.
But the result of the join is discarded. each does not return the result of
its inside block, it's not how this method works.
If you want that to happen, use map.

$ irb
irb(main):001:0> t = { 'pony' => 'unicorn', 42 => 24 }
=> {"pony"=>"unicorn", 42=>24}
irb(main):002:0> t.each { |k, v| [k, v].join(' => ') }
=> {"pony"=>"unicorn", 42=>24}
irb(main):003:0> t.map { |k, v| [v, k].join(' => ') }
=> ["unicorn => pony", "24 => 42"]

As you can see here, the result of ["lol", "rofl"].join(" whatever ") gives
"lol whatever rofl".

Best,
Thomas.