RCR: Array#to_h

Hi there,

I have registered 2 accounts for RCRkive, but all failed... So I posted here.

Abstract

A simple method that construct a hash from an array. Just like Hash#to_a return an array from hash.

Problem

Consider this cenario: I programmed a multi-threaded web page downloader. One of its input is an array of urls to download. In the program, I wanted to use a hash like {'http://ruby-lang.org/index.html' => '200'}, i.e., use a hash to record the return code of the http request. This way I can avoid re-download a page, or miss a page.

For the user of this download program, it is much easier to use an Array instead of a Hash:

d = WebPageDownloader.new
d.links = IO.readlines('list.txt')

Hence, in the program we want to do:

@links = @links.to_h

Proposal

Add a method to_h (not to_hash) in the Array class, so that user can:

a = [1, 2, 3, 4, 5]
p a.to_h { |i, v| v * 2} --> {5=>10, 1=>2, 2=>4, 3=>6, 4=>8}
p a.to_h(3) --> {5=>3, 1=>3, 2=>3, 3=>3, 4=>3}

Anaysis

This is very convenient for users who need this feature, and it will not affect behavior of the Array class in anyway for those do not need this feature.

Please refer to RCR278, which is similar but not same.

Implementation

class Array
  public
  def to_h(value = nil)
    _hash = {}
    self.each_index do |i|
      v = self[i]
      if block_given? then
        _hash[v] = yield(i, v)
      else
        _hash[v] = value
      end
    end
    _hash
  end
end

Please comment.

Thanks,
Shannon

Abstract

A simple method that construct a hash from an array. Just like Hash#to_a
return an array from hash.

Having your proposed to_h accept a block is a nice idea but as it is you can
do the non-block version quite easily:

Hash[*%w(a b c d)]

=> {"a"=>"b", "c"=>"d"}

keys = %w(a b c d)

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

values = [1, 2, 3, 4]

=> [1, 2, 3, 4]

Hash[*keys.zip(values).flatten]

=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}

marcel

···

On Tue, Oct 25, 2005 at 12:10:18PM +0900, Shannon Fang wrote:
--
Marcel Molina Jr. <marcel@vernix.org>

Hi,

At Tue, 25 Oct 2005 12:10:18 +0900,
Shannon Fang wrote in [ruby-talk:162399]:

Abstract

A simple method that construct a hash from an array. Just like Hash#to_a
return an array from hash.

Agreed here, but

Proposal

Add a method to_h (not to_hash) in the Array class, so that user can:

a = [1, 2, 3, 4, 5]
p a.to_h { |i, v| v * 2} --> {5=>10, 1=>2, 2=>4, 3=>6, 4=>8}
p a.to_h(3) --> {5=>3, 1=>3, 2=>3, 3=>3, 4=>3}

I expect that method as:

  h = {"foo"=>1,"bar"=>2}
  a = h.to_a # => [["foo", 1], ["bar", 2]]
  a.to_h # => {"foo"=>1,"bar"=>2} == h

  module Enumerable
    def to_h
      hash = {}
      each {|key, value| hash[key] = value}
      hash
    end
  end

···

--
Nobu Nakada

Hi,

···

In message "Re: RCR: Array#to_h" on Tue, 25 Oct 2005 12:10:18 +0900, "Shannon Fang" <xrfang@hotmail.com> writes:

Abstract

A simple method that construct a hash from an array. Just like Hash#to_a
return an array from hash.

Problem

Consider this cenario: I programmed a multi-threaded web page downloader.
One of its input is an array of urls to download. In the program, I wanted
to use a hash like {'http://ruby-lang.org/index.html&#39; => '200'}, i.e., use a
hash to record the return code of the http request. This way I can avoid
re-download a page, or miss a page.

For the user of this download program, it is much easier to use an Array
instead of a Hash:

d = WebPageDownloader.new
d.links = IO.readlines('list.txt')

Hence, in the program we want to do:

@links = @links.to_h

Proposal

Add a method to_h (not to_hash) in the Array class, so that user can:

a = [1, 2, 3, 4, 5]
p a.to_h { |i, v| v * 2} --> {5=>10, 1=>2, 2=>4, 3=>6, 4=>8}
p a.to_h(3) --> {5=>3, 1=>3, 2=>3, 3=>3, 4=>3}

Rejected. Proposal Array#to_h(value=nil) does not actually solve the
problem above. If you want to have _this_ to_h, you need to have
a proper usecase.

              matz.

Hi --

Hi there,

I have registered 2 accounts for RCRkive, but all failed... So I posted here.

You'll have to give me more details if you want it fixed :slight_smile:

Abstract

A simple method that construct a hash from an array. Just like Hash#to_a return an array from hash.

See RCRS

David

···

On Tue, 25 Oct 2005, Shannon Fang wrote:

--
David A. Black
dblack@wobblini.net

I think the proposal is over specialized. Why should the array elements
neccessarily become keys?

Nobu, I agree except 1) key,value in Enumerable? 2) other clearly
polymorphic solution:

module Enumerable
  def to_h
    hash = {}
    each_with_index {|value,index| hash[i] = value}
    hash
  end
end

Hence hash key <-> array index. And it is by definition "Enumerable".

T.

The block may be useful because you may want to, for example, do some calculation, or even lookup the database...

Shannon

···

From: "Marcel Molina Jr." <marcel@vernix.org>
Reply-To: ruby-talk@ruby-lang.org
To: ruby-talk@ruby-lang.org (ruby-talk ML)
Subject: Re: RCR: Array#to_h
Date: Tue, 25 Oct 2005 12:16:04 +0900

On Tue, Oct 25, 2005 at 12:10:18PM +0900, Shannon Fang wrote:
> Abstract
>
> A simple method that construct a hash from an array. Just like Hash#to_a
> return an array from hash.

Having your proposed to_h accept a block is a nice idea but as it is you can
do the non-block version quite easily:

>> Hash[*%w(a b c d)]
=> {"a"=>"b", "c"=>"d"}
>> keys = %w(a b c d)
=> ["a", "b", "c", "d"]
>> values = [1, 2, 3, 4]
=> [1, 2, 3, 4]
>> Hash[*keys.zip(values).flatten]
=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}

marcel
--
Marcel Molina Jr. <marcel@vernix.org>

Ok, we may consider Array#to_h as a true reverse operation of Hash#to_a, however, I think the feature I proposed has different usage. May be anyone has a better name for easy understanding?

Also, I am not very clear about this code:

  module Enumerable
    def to_h
      hash = {}
      each {|key, value| hash[key] = value}
      hash
    end
  end

Array mixed Eumerable, hence, if use the above code, we have:

arr = [a, b, c, d, e]
arr.to_h => {1 => a, 2 => b, 3 => c, 4 => d, 5 =>e}

Which seems not very useful, I think the to_h operation of array should use array VALUE as the hash KEY.

Shannon

···

From: nobuyoshi nakada <nobuyoshi.nakada@ge.com>
Reply-To: ruby-talk@ruby-lang.org
To: ruby-talk@ruby-lang.org (ruby-talk ML)
Subject: Re: RCR: Array#to_h
Date: Tue, 25 Oct 2005 12:48:40 +0900

Hi,

At Tue, 25 Oct 2005 12:10:18 +0900,
Shannon Fang wrote in [ruby-talk:162399]:
> Abstract
>
> A simple method that construct a hash from an array. Just like Hash#to_a
> return an array from hash.

Agreed here, but

> Proposal
>
> Add a method to_h (not to_hash) in the Array class, so that user can:
>
> a = [1, 2, 3, 4, 5]
> p a.to_h { |i, v| v * 2} --> {5=>10, 1=>2, 2=>4, 3=>6, 4=>8}
> p a.to_h(3) --> {5=>3, 1=>3, 2=>3, 3=>3, 4=>3}

I expect that method as:

  h = {"foo"=>1,"bar"=>2}
  a = h.to_a # => [["foo", 1], ["bar", 2]]
  a.to_h # => {"foo"=>1,"bar"=>2} == h

  module Enumerable
    def to_h
      hash = {}
      each {|key, value| hash[key] = value}
      hash
    end
  end

--
Nobu Nakada

Hi Matz,

Rejected. Proposal Array#to_h(value=nil) does not actually solve the
problem above. If you want to have _this_ to_h, you need to have
a proper usecase.

I don't know what you mean the "problem above"... My purpose is to have a convenient way to convert array to hash for *indexing* or *fast searching* purpose.

This indeed is a bit ad-hoc, but it is useful in may cases. I deliberately wanted to map array value (not index) to hash key... otherwise I think it is not useful (i.e., map array key => hash key).

I think there might be 2 types of RCRs, one is more "scientific", those deep into the design of language. Another is more "engineering", those add a method/feature to a single class for convinience, like Array#nitems.

I think for the latter one, the criteria to accept/reject, is if it is useful for "lots of" Ruby programmers..., if that's your criteria, then I think I will wait to see if others want this :smiley:
else, if one of your criterion is to make Ruby "pure", all those things should belong to a library not the language itself (or core classes), then pls also let me know, so that I can have a better idea when propose RCR in the future.

Thanks,
Shannon

···

From: Yukihiro Matsumoto <matz@ruby-lang.org>
Reply-To: ruby-talk@ruby-lang.org
To: ruby-talk@ruby-lang.org (ruby-talk ML)
Subject: Re: RCR: Array#to_h
Date: Tue, 25 Oct 2005 15:17:00 +0900

Hi,

In message "Re: RCR: Array#to_h" > on Tue, 25 Oct 2005 12:10:18 +0900, "Shannon Fang" ><xrfang@hotmail.com> writes:

Abstract

A simple method that construct a hash from an array. Just like Hash#to_a
return an array from hash.

Problem

Consider this cenario: I programmed a multi-threaded web page downloader.
One of its input is an array of urls to download. In the program, I wanted
to use a hash like {'http://ruby-lang.org/index.html&#39; => '200'}, i.e., use

a

hash to record the return code of the http request. This way I can avoid
re-download a page, or miss a page.

For the user of this download program, it is much easier to use an Array
instead of a Hash:

d = WebPageDownloader.new
d.links = IO.readlines('list.txt')

Hence, in the program we want to do:

@links = @links.to_h

Proposal

Add a method to_h (not to_hash) in the Array class, so that user can:

a = [1, 2, 3, 4, 5]
p a.to_h { |i, v| v * 2} --> {5=>10, 1=>2, 2=>4, 3=>6, 4=>8}
p a.to_h(3) --> {5=>3, 1=>3, 2=>3, 3=>3, 4=>3}

Rejected. Proposal Array#to_h(value=nil) does not actually solve the
problem above. If you want to have _this_ to_h, you need to have
a proper usecase.

            matz.

Hi David,

I have registered RCR account using xrfang@hotmail.com and sales@spamweed.com. Please tell me when I can use one of these :slight_smile:

Thank you!
Shannon

···

From: "David A. Black" <dblack@wobblini.net>
Reply-To: ruby-talk@ruby-lang.org
To: ruby-talk@ruby-lang.org (ruby-talk ML)
Subject: Re: RCR: Array#to_h
Date: Tue, 25 Oct 2005 19:16:22 +0900

Hi --

On Tue, 25 Oct 2005, Shannon Fang wrote:

Hi there,

I have registered 2 accounts for RCRkive, but all failed... So I posted here.

You'll have to give me more details if you want it fixed :slight_smile:

Abstract

A simple method that construct a hash from an array. Just like Hash#to_a return an array from hash.

See RCRS

David

--
David A. Black
dblack@wobblini.net

"Marcel Molina Jr." <marcel@vernix.org> writes:

Abstract

A simple method that construct a hash from an array. Just like Hash#to_a
return an array from hash.

Having your proposed to_h accept a block is a nice idea but as it is you can
do the non-block version quite easily:

Hash[*%w(a b c d)]

=> {"a"=>"b", "c"=>"d"}

keys = %w(a b c d)

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

values = [1, 2, 3, 4]

=> [1, 2, 3, 4]

Hash[*keys.zip(values).flatten]

=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}

And the block version:

a = [1,2,3,4,5]

=> [1, 2, 3, 4, 5]

Hash[*a.map {|e| [e, 2*e]}.flatten]

=> {5=>10, 1=>2, 2=>4, 3=>6, 4=>8}

Steve

···

On Tue, Oct 25, 2005 at 12:10:18PM +0900, Shannon Fang wrote:

I think the proposal is over specialized. Why should the array elements
neccessarily become keys?

This is actually the useful feature! I already said not everybody need it... However, I don't see any need to map array key to hash key:

a = [1, 2, 3, 4, 5]
b = {1, 2, 3, 4, 5}

a[0] = 1
b[0] = 1

why we need to convert a to b?? The reason to use a hash is for fast access, not by using sequential index!

Shannon

···

Nobu, I agree except 1) key,value in Enumerable? 2) other clearly
polymorphic solution:

module Enumerable
  def to_h
    hash = {}
    each_with_index {|value,index| hash[i] = value}
    hash
  end
end

Hence hash key <-> array index. And it is by definition "Enumerable".

T.

Actually this has been bugging me as I currently have these:

module Enumerable

  # Produces a hash from an Enumerable with index for keys.

···

#
  # a1 = [ :a, :b ]
  # a1.to_h #=> { 0=>:a, 1=>:b }
  #
  def to_h( &blk )
    h = {}
    if block_given?
      each_with_index{ |e,i| h[i] = blk.call(e,i) }
    else
      each_with_index{ |e,i| h[i] = e }
    end
    h
  end

end

class Array

  # Produces a hash for an Array, or two arrays.
  # It is just like Enumerbale#to_h but with an
  # extra feature: If an array is given as the
  # values, it is zipped with the receiver,
  # to produce the hash.
  #
  # a1 = [ :a, :b ]
  # a2 = [ 1, 2 ]
  # a1.to_h(a2) #=> { :a=>1, :b=>2 }
  #
  def to_h(values=nil)
    h = {}
    if values
      size.times{ |i| h[at(i)] = values.at(i) }
    else
      each_with_index{ |e,i| h[i] = e }
    end
    h
  end

  # Converts an associative array into a hash.
  #
  # a = [ [:a,1], [:b,2] ]
  # a.assoc_to_h #=> { :a=>1, :b=>2 }
  #
  # a = [ [:a,1,2], [:b,3,4] ]
  # a.assoc_to_h(true) #=> { :a=>[1,2], :b=>[3,4] }
  #
  def assoc_to_h(arrayed=nil)
    h = {}
    if arrayed
      each{ |e| h[e.first] = e.slice(1..-1) }
    else
      each{ |e| h[e.first] = e.last }
    end
    h
  end

end

I wish there was a good way just to have a single Array#to_h, but it
doesn;t seem reasonable. Perhaps Enumerable#to_h could be #to_hash?

T.

Hi,

At Tue, 25 Oct 2005 13:37:01 +0900,
Trans wrote in [ruby-talk:162411]:

I think the proposal is over specialized. Why should the array elements
neccessarily become keys?

Yes, agreed, and I doubt that there is a solution which
satisfy everyone for this issue.

···

--
Nobu Nakada

Shannon Fang wrote:

Hi Matz,

Rejected. Proposal Array#to_h(value=nil) does not actually solve the
problem above. If you want to have _this_ to_h, you need to have
a proper usecase.

I don't know what you mean the "problem above"... My purpose is to
have a convenient way to convert array to hash for *indexing* or
*fast searching* purpose.

If you just want a fast access to those elements a Set is sufficient - and
you can use #to_set already:

require 'set'

=> true

a=%w{foo bar baz}

=> ["foo", "bar", "baz"]

s=a.to_set

=> #<Set: {"baz", "foo", "bar"}>

s.include? "foo"

=> true

s.include? "fo"

=> false

This indeed is a bit ad-hoc, but it is useful in may cases.

I think Matz just asked you to present these use cases. Apart from your
general description I couldn't find one in the thread. Did I miss
something?

I
deliberately wanted to map array value (not index) to hash key...
otherwise I think it is not useful (i.e., map array key => hash key).

Problem is, that there are other conversions that are at least equally
reasonable (e.g. the one Nobu presented). IMHO there is not a single
reasonable way to implement Array#to_h so it's better to leave it out.
Maybe it's just a naming issue though.

Kind regards

    robert

Hi --

···

On Tue, 25 Oct 2005, Trans wrote:

I think the proposal is over specialized. Why should the array elements
neccessarily become keys?

Nobu, I agree except 1) key,value in Enumerable? 2) other clearly
polymorphic solution:

module Enumerable
def to_h
   hash = {}
   each_with_index {|value,index| hash[i] = value}
   hash
end
end

Hence hash key <-> array index. And it is by definition "Enumerable".

But then what's the point? :slight_smile:

This raises the old question of why hashes have both keys and
numerical indices. I've always maintained that they shouldn't.

David

--
David A. Black
dblack@wobblini.net

I am just reading the PickAxe 2e recently... According to Dave, to_h means a representation of the object in hash, however to_hash means this object is inherently compatible with hash, like to_i and to_int...

We do need a good name for it :smiley:

Shannon

···

From: "Trans" <transfire@gmail.com>
Reply-To: ruby-talk@ruby-lang.org
To: ruby-talk@ruby-lang.org (ruby-talk ML)
Subject: Re: RCR: Array#to_h
Date: Tue, 25 Oct 2005 13:57:02 +0900

Actually this has been bugging me as I currently have these:

module Enumerable

  # Produces a hash from an Enumerable with index for keys.
  #
  # a1 = [ :a, :b ]
  # a1.to_h #=> { 0=>:a, 1=>:b }
  #
  def to_h( &blk )
    h = {}
    if block_given?
      each_with_index{ |e,i| h[i] = blk.call(e,i) }
    else
      each_with_index{ |e,i| h[i] = e }
    end
    h
  end

end

class Array

  # Produces a hash for an Array, or two arrays.
  # It is just like Enumerbale#to_h but with an
  # extra feature: If an array is given as the
  # values, it is zipped with the receiver,
  # to produce the hash.
  #
  # a1 = [ :a, :b ]
  # a2 = [ 1, 2 ]
  # a1.to_h(a2) #=> { :a=>1, :b=>2 }
  #
  def to_h(values=nil)
    h = {}
    if values
      size.times{ |i| h[at(i)] = values.at(i) }
    else
      each_with_index{ |e,i| h[i] = e }
    end
    h
  end

  # Converts an associative array into a hash.
  #
  # a = [ [:a,1], [:b,2] ]
  # a.assoc_to_h #=> { :a=>1, :b=>2 }
  #
  # a = [ [:a,1,2], [:b,3,4] ]
  # a.assoc_to_h(true) #=> { :a=>[1,2], :b=>[3,4] }
  #
  def assoc_to_h(arrayed=nil)
    h = {}
    if arrayed
      each{ |e| h[e.first] = e.slice(1..-1) }
    else
      each{ |e| h[e.first] = e.last }
    end
    h
  end

end

I wish there was a good way just to have a single Array#to_h, but it
doesn;t seem reasonable. Perhaps Enumerable#to_h could be #to_hash?

T.

Hi Robert,

1) Set won't work. I am not only test if an element exist or not, I want to assign value to the key to track its status.

2) I have not write any RCR before, so I may make mistakes. I will certainly propose a good use case, when I have a complete one.

3) >Problem is, that there are other conversions that are at least equally

reasonable (e.g. the one Nobu presented). IMHO there is not a single
reasonable way to implement Array#to_h so it's better to leave it out.
Maybe it's just a naming issue though.

Yes I agree it is a name issue.

Thanks,
Shannon

···

From: "Robert Klemme" <bob.news@gmx.net>
Reply-To: ruby-talk@ruby-lang.org
To: ruby-talk@ruby-lang.org (ruby-talk ML)
Subject: Re: RCR: Array#to_h
Date: Tue, 25 Oct 2005 16:32:02 +0900

Shannon Fang wrote:
> Hi Matz,
>
>> Rejected. Proposal Array#to_h(value=nil) does not actually solve the
>> problem above. If you want to have _this_ to_h, you need to have
>> a proper usecase.
>
> I don't know what you mean the "problem above"... My purpose is to
> have a convenient way to convert array to hash for *indexing* or
> *fast searching* purpose.

If you just want a fast access to those elements a Set is sufficient - and
you can use #to_set already:

>> require 'set'
=> true
>> a=%w{foo bar baz}
=> ["foo", "bar", "baz"]
>> s=a.to_set
=> #<Set: {"baz", "foo", "bar"}>
>> s.include? "foo"
=> true
>> s.include? "fo"
=> false

> This indeed is a bit ad-hoc, but it is useful in may cases.

I think Matz just asked you to present these use cases. Apart from your
general description I couldn't find one in the thread. Did I miss
something?

> I
> deliberately wanted to map array value (not index) to hash key...
> otherwise I think it is not useful (i.e., map array key => hash key).

Problem is, that there are other conversions that are at least equally
reasonable (e.g. the one Nobu presented). IMHO there is not a single
reasonable way to implement Array#to_h so it's better to leave it out.
Maybe it's just a naming issue though.

Kind regards

    robert

David A. Black wrote:

Hi --

> I think the proposal is over specialized. Why should the array elements
> neccessarily become keys?
>
> Nobu, I agree except 1) key,value in Enumerable? 2) other clearly
> polymorphic solution:
>
> module Enumerable
> def to_h
> hash = {}
> each_with_index {|value,index| hash[i] = value}
> hash
> end
> end
>
> Hence hash key <-> array index. And it is by definition "Enumerable".

But then what's the point? :slight_smile:

Perhaps little. But you may simply need a hash based off an enumerable
and this could be one step it getting it:

  ('a'..'c').to_h.invert #=> { 'a'=>1, 'b'=>2, 'c'=>3 }

And of course it's consistant with what Enumerable is.

This raises the old question of why hashes have both keys and
numerical indices. I've always maintained that they shouldn't.

Right. Hash doesn't really have index, it's a fake --a counter only. As
you'll recall, we've talked about this before, and if I recall
correctly, we even came to a shared conclusion, which is something I've
been meaning to ask Matz:

have you given anymore consideration to deprecating
Enumerabl#each_with_index in favor of Enumerable#each_with_counter;
Array would retain it's own #each_with_index.

T.

···

On Tue, 25 Oct 2005, Trans wrote:

BTW have you tried:

module Enumerable

  # Like <tt>#map</tt>/<tt>#collect</tt>, but it generates a Hash. The
block
  # is expected to return two values: the key and the value for the new
hash.

···

#
  # numbers = (1..3)
  # squares = numbers.graph { |n| [n, n*n] } # { 1=>1, 2=>4, 3=>9
}
  # sq_roots = numbers.graph { |n| [n*n, n] } # { 1=>1, 4=>2, 9=>3
}
  #
  #--
  # Credit for original version goes to Zallus Kanite and Gavin
Sinclair.
  #++
  def graph(&yld)
    if yld
      inject({}) do |h,kv|
        nk, nv = yld[*kv]
        h[nk] = nv
        h
      end
    else
      Hash[*self.to_a.flatten]
    end
  end

end