Reputation: 5251
I am trying to transform a given string into a hash with each its character = key and index = value.
For example, if I have str = "hello"
, I would like it to transform into {"h"=>0, "e"=>1, "l"=>2, "l"=>3, "o"=>4}
.
I created a method as such:
def map_indices(arr)
arr.map.with_index {|el, index| [el, index]}.to_h
end
#=> map_indices('hello'.split(''))
#=> {"h"=>0, "e"=>1, "l"=>3, "o"=>4}
The problem is it skips the first l
. If I reverse the order of el
and index
: arr.map.with_index {|el, index| [index, el]}.to_h
, I get all the letters spelled out: {0=>"h", 1=>"e", 2=>"l", 3=>"l", 4=>"o"}
But when I invert
it, I get the same hash that skips one of the l
's.
map_indices('hello'.split('')).invert
#=> {"h"=>0, "e"=>1, "l"=>3, "o"=>4}
Why is this behaving like such? How can I get it to print {"h"=>0, "e"=>1, "l"=>2, "l"=>3, "o"=>4}
?
Upvotes: 2
Views: 1921
Reputation: 110655
Any of the following could be used. For
str = "hello"
all return
{"h"=>[0], "e"=>[1], "l"=>[2, 3], "o"=>[4]}
str.each_char
.with_index
.with_object({}) { |(c,i),h| (h[c] ||= []) << i }
See String#each_char, Enumerator#with_index and Enumerator#with_object. The block variables have been written to exploit array decomposition.
str.each_char
.with_index
.with_object(Hash.new { |h,k| h[k] = [] }) { |(c,i),h| h[c] << i }
See the form of Hash::new that takes a block and no argument. If a hash has been defined
h = Hash.new { |h,k| h[k] = [] }
and later
h[c] << i
is executed, h[c]
is first set equal to an empty array if h
does not have a key c
.
str.size
.times
.with_object(Hash.new { |h,k| h[k] = [] }) { |i,h| h[str[i]] << i }
str.each_char
.with_index
.group_by(&:first)
.transform_values { |a| a.flat_map(&:last) }
See Enumerable#group_by, Hash#transform_values (introduced in Ruby v2.5) and Enumerable#flat_map.
Note that
str.each_char
.with_index
.group_by(&:first)
#=> {"h"=>[["h", 0]], "e"=>[["e", 1]], "l"=>[["l", 2], ["l", 3]],
# "o"=>[["o", 4]]}
Upvotes: 3
Reputation: 11000
I am new to Ruby and I am sure this can be refactored, but another alternative might be:
arr1 = "Hello".split(%r{\s*})
arr2 = []
for i in 0..arr1.size - 1
arr2 << i
end
o = arr1.zip(arr2)
a_h = []
o.each do |i|
a_h << Hash[*i]
end
p a_h.each_with_object({}) { |k, v| k.each { |kk,vv| (v[kk] ||= []) << vv } }
=> {"H"=>[0], "e"=>[1], "l"=>[2, 3], "o"=>[4]}
Upvotes: 0
Reputation: 80065
It can be done, but will confuse other Ruby programmers.A normal hash treats a key "a" as identical to another "a". Unless a little known feature .compare_by_identity
is used:
h = {}.compare_by_identity
"hello".chars.each_with_index{|c,i| h[c] = i}
p h # => {"h"=>0, "e"=>1, "l"=>2, "l"=>3, "o"=>4}
Upvotes: 8
Reputation: 33360
Another option you can use is zipping two enumerations together.
s = "hello"
s.chars.zip(0..s.size)
This yields: [["h", 0], ["e", 1], ["l", 2], ["l", 3], ["o", 4]]
Upvotes: 3