Forum: Ruby Array#to_h

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Bec38d63650c8912b6ba9b557fb953b9?d=identicon&s=25 Roger Pack (rogerdpack)
on 2009-01-31 20:49
Not that I would find it useful at all, but is there is a Hash#to_a
should there not be an Array#to_h?
Thanks!
-=r
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2009-01-31 21:07
(Received via mailing list)
Hi --

On Sun, 1 Feb 2009, Roger Pack wrote:

> Not that I would find it useful at all, but is there is a Hash#to_a
> should there not be an Array#to_h?

Not if it's not useful :-) It might be, though. It's been talked about
a lot over the years. As I recall, part of the problem is the question
of what it would mean; for example, given this:

   ["a","b","c","d"].to_h

is it

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

or

   [0 => "a", 1 => "b", ....]

?


David

--
David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2)

http://www.wishsight.com => Independent, social wishlist management!
E088bb5c80fd3c4fd02c2020cdacbaf0?d=identicon&s=25 Jesús Gabriel y Galán (Guest)
on 2009-01-31 21:09
(Received via mailing list)
On Sat, Jan 31, 2009 at 8:49 PM, Roger Pack <rogerpack2005@gmail.com>
wrote:
> Not that I would find it useful at all, but is there is a Hash#to_a
> should there not be an Array#to_h?
> Thanks!

A possible implementation:

>> class Array
>> def to_h
>> Hash[*self]
>> end
>> end
=> nil
>> [1,2,3,4].to_h
=> {1=>2, 3=>4}

Jesus.
E8a419959139f3f505b49bb95f7e7afe?d=identicon&s=25 Joshua Ballanco (jballanc)
on 2009-01-31 21:12
(Received via mailing list)
53581739a445ad78250a676dabddf55f?d=identicon&s=25 James Coglan (Guest)
on 2009-01-31 21:47
(Received via mailing list)
2009/1/31 Joshua Ballanco <jballanc@gmail.com>

> See here:
> http://www.fivesevensix.com/posts/2005/05/20/array-to_h
>
> ...and here:
> http://drawohara.com/post/70998078/ruby-array-to-hash-rocks
>
> - Josh



This question came up a while back where I helped someone with yet
another
interpretation:

http://blog.jcoglan.com/2008/09/08/enumerableto_ha...

The core problem with Hash#to_a and any possible Enumerable#to_h is it's
not
intuitively obvious what either should do, though Hash#to_a is possibly
slightly narrower in scope. There are any number of ways you might want
to
map one to the other, and that's really what map() and inject() are for.
E1d641bfe4071a5413bac781f06d3fd1?d=identicon&s=25 Sean O'halpin (sean)
on 2009-02-01 18:32
(Received via mailing list)
On Sat, Jan 31, 2009 at 7:49 PM, Roger Pack <rogerpack2005@gmail.com>
wrote:
> Not that I would find it useful at all, but is there is a Hash#to_a
> should there not be an Array#to_h?
> Thanks!
> -=r

It's useful in Ruby 1.8.x for those enumerable methods which return
arrays, e..g

irb --> {:a => 1, :b => 2, :c => 3}.select{|k, v| v % 2 == 1 }
    ==> [[:c, 3], [:a, 1]]

irb --> {:a => 1, :b => 2, :c => 3}.select{|k, v| v % 2 == 1 }.to_hash
    ==> {:a=>1, :c=>3}

whereas in Ruby 1.9.x, select (map, etc.) return hashes:

irb(main):001:0> {:a => 1, :b => 2, :c => 3}.select{|k, v| v % 2 == 1 }
=> {:a=>1, :c=>3}

The problem is, as other posters have noted, that there is no 1-1
mapping between an array and a hash. Having said that, I've found the
following to be handy (mainly in restoring a hash after the 1.8.x
transformation to an array):

module ToHash
  if RUBY_VERSION =~ /^1.9/
    def to_hash
      Hash[*flatten(1)]
    end
  else
    def to_hash
      Hash[*(inject([]) {|arr, i| i.kind_of?(Array) ? arr.push(*i) :
arr.push(i) })]
    end
  end
end

used like this:

  b = [
     [:a, [[1, 2], [3, 4]]],
     [:b, 3],
     [:c, { :d => 4 }],
    ]

  p b.extend(ToHash).to_hash

Regards,
Sean
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-02-01 18:45
(Received via mailing list)
On 31.01.2009 21:03, David A. Black wrote:
>
> ?
Or even raise an Exception because Array#to_h expects a nested structure
as is returned from various Hash methods (e.g. #select, #to_a - note,
this is about to change in new versions of Ruby).

Kind regards

  robert
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Thomas Sawyer (7rans)
on 2009-02-06 15:13
(Received via mailing list)
On Jan 31, 2:49 pm, Roger Pack <rogerpack2...@gmail.com> wrote:
> Not that I would find it useful at all, but is there is a Hash#to_a
> should there not be an Array#to_h?

The variety of possible good definitions make this hard to define. So
it's understandable that is is not in Ruby core, though being able to
convert back and forth between Hash#to_a and Array#to_h makes the most
sense.

I was thinking about it some more and I was thinking about how the
various options might be addressed. So I came up with this:

  # Converts an associative array into a hash.
  #
  #   a = [ [:a,1], [:b,2] ]
  #   a.to_h  #=> { :a=>1, :b=>2 }
  #
  # When a mixed or multi-element accociative array
  # is used, the result is as follows:
  #
  #   a = [ [:a,1,2], [:b,2], [:c], :d ]
  #   a.to_h  #=> { :a=>[1,2], :b=>2, :c=>nil, :d=>nil }
  #
  # If the fist entry of the subelements is the same, then
  # the values will be merged using #concat.
  #
  #   a = [ [:a,1,2], [:a,3], [:a,4], [:a], :a ]
  #   a.to_h  #=> { :a=>[1,2,3,4,nil,nil] }
  #
  # The +mode+ can be set to effect the result. If it is
  # set to +:array+ or +true+ then trailing arrays
  # will be kept. Eg.
  #
  #   a = [ [:a,1,2], [:b,3], [:c] ]
  #   a.to_h(true)  #=> { :a=>[1,2], :b=>[3], :c=>[] }
  #
  # Setting the mode to +:splat+ will produce the same result
  # as calling +Hash[*array]+.
  #
  #   a = [:a,1,:b,2,:c]
  #   a.to_h:splat  #=> { :a=>1, :b=>2, :c=>nil }
  #
  # Setting the mode to +:flat+ will produce the same result
  # as calling +Hash[*array.flatten]+.
  #
  #   a = [:a,1,[:b,2,:c]]
  #   a.to_h:flat  #=> { :a=>1, :b=>2, :c=>nil }

  def to_h(mode=nil)
    case mode
    when :splat
      a = dup
      a << nil if a.size % 2 == 1
      Hash[*a]
    when :flat
      a = flatten
      a << nil if a.size % 2 == 1
      Hash[*a]
    when :array, True
      h = {}
      each do |k,*v|
        h[k] ||= []
        h[k].concat(v)
      end
      h
    else
      h = {}
      each do |k,*v|
        h[k] ||= []
        h[k].concat(v)
      end
      h.each do |k,v|
        h[k] = v[0] if v.size < 2
      end
      h
    end
  end

By using a +mode+ we can offer a variety of common means of
conversion.

Of course, we could just make them all separate methods, ie. #to_h,
#to_h_array, #to_h_splat, #to_h_flat. Maybe that is better? But then
that seems a bit more limiting, less dynamic, less open for new modes
or multiple labels for a single mode, and the method names look funny
(imo).

Thoughts?

T.
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-02-06 15:36
(Received via mailing list)
2009/2/6 Trans <transfire@gmail.com>:
> various options might be addressed. So I came up with this:
>
>  # Converts an associative array into a hash.

<snip/>

> By using a +mode+ we can offer a variety of common means of
> conversion.
>
> Of course, we could just make them all separate methods, ie. #to_h,
> #to_h_array, #to_h_splat, #to_h_flat. Maybe that is better?

This is generally considered better practice over switching behavior
of a method with an argument.  Just think about the length of the
method which increases in with the number of different algorithms
(modes).  I prefer short methods.

Alternatively make the algorithm detection automatic. Even in that
case I had to_h only do the detection and then delegate to any one of
a number of to_h_<alg> methods.  The rule I follow is to create
methods and classes to do _one_ thing good.

> But then
> that seems a bit more limiting, less dynamic, less open for new modes
> or multiple labels for a single mode, and the method names look funny

Not at all: you can simply add another method.

This is how I'd approach it:

module Enumerable
  def to_h
    pairs = arr = 0

    each do |e|
      if Array === e
        if e.size <= 2
          pairs += 1
        else
          arr += 1
        end
      end
    end

    case
    when pairs == size
      to_h_pairs
    when arr > 0
      to_h_multi
    else
      to_h_flat
    end
  end

  def to_h_pairs
    inject({}) {|ha,(k,v)| ha[k]=v; ha}
  end

  def to_h_multi
    inject({}) {|ha,ar| ha[ar.first] = ar[1..-1]; ha}
  end

  def to_h_flat
    each_slice(2).inject({}) {|ha,(k,v)| ha[k]=v; ha}
  end
end

[
  [1,2,3,4,5],
  [[1,2],[3,4],[5,6]],
  [[1,2],[3,4],[5]],
  [[1,2,3],[4],[5,6]],
].each do |x|
  p x, x.to_h, "---"
end


Kind regards

robert
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Thomas Sawyer (7rans)
on 2009-02-06 18:25
(Received via mailing list)
On Feb 6, 9:35 am, Robert Klemme <shortcut...@googlemail.com> wrote:

> > But then
> > that seems a bit more limiting, less dynamic, less open for new modes
> > or multiple labels for a single mode, and the method names look funny
>
> Not at all: you can simply add another method.

True, but adding a new method is a "bigger deal" than just adding
another parameter option.

But I like your idea, I could create the different methods and then
dispatch from to_h, offering the best of both options.

>         else
>           arr += 1
>         end
>       end
>     end

I'm not sure. On one hand I like it, though I am hesitant about it b/c
it means a whole pass over the array upfront, it won't be very fast.
What do you think about the performance characteristics? On the other
hand, it means the one method #to_h will do quite different things
depending on the form of the data structure passed to it. Is that a
good idea?

T.
2ee1a7960cc761a6e92efb5000c0f2c9?d=identicon&s=25 unknown (Guest)
on 2009-02-06 18:40
(Received via mailing list)
On Jan 31, 1:49 pm, Roger Pack <rogerpack2...@gmail.com> wrote:
> Not that I would find it useful at all, but is there is a Hash#to_a
> should there not be an Array#to_h?
> Thanks!
> -=r
> --
> Posted viahttp://www.ruby-forum.com/.

irb(main):003:0> Hash[ *[:foo,22, :bar,44] ]
=> {:foo=>22, :bar=>44}
9b905791cbdbb1af35b65e02c3217e23?d=identicon&s=25 Tom Link (Guest)
on 2009-02-06 18:53
(Received via mailing list)
> As I recall, part of the problem is the question
> of what it would mean; for example, given this:
>
>    ["a","b","c","d"].to_h
>
> is it
>
>    ["a" => "b", "c" => "d"]

This could be achieved by means of Hash[*array]:

a = ["a","b","c","d"]
Hash[*a]
=> {"a"=>"b", "c"=>"d"}
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-02-06 20:05
(Received via mailing list)
On 06.02.2009 18:24, Trans wrote:
>> a number of to_h_<alg> methods.  The rule I follow is to create
>> methods and classes to do _one_ thing good.
>>
>>> But then
>>> that seems a bit more limiting, less dynamic, less open for new modes
>>> or multiple labels for a single mode, and the method names look funny
>> Not at all: you can simply add another method.
>
> True, but adding a new method is a "bigger deal" than just adding
> another parameter option.

Not for me.

> But I like your idea, I could create the different methods and then
> dispatch from to_h, offering the best of both options.

Exactly.

>>         else
>>           arr += 1
>>         end
>>       end
>>     end
>
> I'm not sure. On one hand I like it, though I am hesitant about it b/c
> it means a whole pass over the array upfront, it won't be very fast.

But you get automatism.  If you know the structure beforehand, you can
call one of the other methods.  That's the nice thing about this
approach: all these methods do one thing good:

> What do you think about the performance characteristics? On the other
> hand, it means the one method #to_h will do quite different things
> depending on the form of the data structure passed to it. Is that a
> good idea?

Yes.  I view it differently: to_h always makes the same thing which can
be described as "analyze contents of the Enum and select a proper
algorithm".  All other methods do the actual conversions.

If you lump everything into a single method then you cannot use them
separately.  I would dig up quotes about not using these mode parameters
if I had more time right now but I'm in a hurry unfortunately.  Trust
me, parameters that change a methods behavior are inferior to having
separate methods.

Kind regards

  robert
3131fcea0a711e5ad89c8d49cc9253b4?d=identicon&s=25 Julian Leviston (Guest)
on 2009-02-07 01:24
(Received via mailing list)
It's fairly trivial, isn't it?

 >> class Array
 >> def to_h
 >> Hash[*self]
 >> end
 >> end
=> nil
 >> [1,2,3,4].to_h
=> {1=>2, 3=>4}
 >>
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2009-02-07 01:27
(Received via mailing list)
Hi --

On Sat, 7 Feb 2009, Julian Leviston wrote:

>> a lot over the years. As I recall, part of the problem is the question
>>  [0 => "a", 1 => "b", ....]
> => nil
>>> [1,2,3,4].to_h
> => {1=>2, 3=>4}

Yes, if that's the semantics you want. If you're inclined toward
thinking it should use the array indices as hash keys and the array
values as hash values, then that implementation wouldn't work. My
point was that there's been debate about the semantics -- i.e., what
Array#to_h should actually do.


David

--
David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2)

http://www.wishsight.com => Independent, social wishlist management!
3131fcea0a711e5ad89c8d49cc9253b4?d=identicon&s=25 Julian Leviston (Guest)
on 2009-02-07 01:50
(Received via mailing list)
Sorry David I've since read the other posts :) there must be SOME good
solution to mail catchup. Guess for now I just have to read a whole
thread before I reply to any of it :)

Blog: http://random8.zenunit.com/
Learn rails: http://sensei.zenunit.com/
1d53b088a989e069b94597c282eebbbc?d=identicon&s=25 Simon Krahnke (Guest)
on 2009-02-07 05:36
(Received via mailing list)
* Robert Klemme <shortcutter@googlemail.com> (20:01) schrieb:

> Trust me, parameters that change a methods behavior are inferior to
> having separate methods.

One can read Robert C. Martin's "Clean Code" for the same advice and
many more. A devastating read for me.

mfg,                       simon .... l
9b905791cbdbb1af35b65e02c3217e23?d=identicon&s=25 Tom Link (Guest)
on 2009-02-07 07:23
(Received via mailing list)
> Yes, if that's the semantics you want. If you're inclined toward
> thinking it should use the array indices as hash keys and the array
> values as hash values, then that implementation wouldn't work.

With that constructor at hand, it wouldn't be too difficult to get
that behaviour though:

a = [[1,2],[3,4]]
> Hash[(0..(a.size - 1)).zip(a)]
=> {0=>[1, 2], 1=>[3, 4]}

Looks like php.
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2009-02-07 13:57
(Received via mailing list)
Hi --

On Sat, 7 Feb 2009, Tom Link wrote:

>> Yes, if that's the semantics you want. If you're inclined toward
>> thinking it should use the array indices as hash keys and the array
>> values as hash values, then that implementation wouldn't work.
>
> With that constructor at hand, it wouldn't be too difficult to get
> that behaviour though:
>
> a = [[1,2],[3,4]]
>> Hash[(0..(a.size - 1)).zip(a)]
> => {0=>[1, 2], 1=>[3, 4]}

None of this is hard to do without Array#to_h, but there's still the
question of what Array#to_h would do if it existed.


David

--
David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2)

http://www.wishsight.com => Independent, social wishlist management!
This topic is locked and can not be replied to.