Home > database >  Ruby: I have a hash with keys = str.split(//) and values = indices of those chars. How do i add mult
Ruby: I have a hash with keys = str.split(//) and values = indices of those chars. How do i add mult

Time:10-29

I currently have this written up for turning my string elements into a hash[key] with its indices as values

def char_indices(str)
  array = str.split(//)
  hash = array.each_with_object({}).with_index {|(el, h), i| h[el] = []<<i}
  p hash
end

which returns

{"m"=>[0], "i"=>[10], "s"=>[6], "p"=>[9]}

{"c"=>[0], "l"=>[1], "a"=>[2], "s"=>[4], "r"=>[5], "o"=>[7], "m"=>[8]}

but I need

char_indices('mississippi')   # => {"m"=>[0], "i"=>[1, 4, 7, 10], "s"=>[2, 3, 5, 6], "p"=>[8, 9]}
char_indices('classroom')     # => {"c"=>[0], "l"=>[1], "a"=>[2], "s"=>[3, 4], "r"=>[5], "o"=>[6, 7], "m"=>[8]}

problem is my value is getting replaced each time and i only end up with the last value.

how can i add each recurring location to value in a ruby like fashion.

CodePudding user response:

The problem is h[el] = []<<i. Instead you can use conditional assignment to ensure that you're working with an array:

def char_indices(string)
  string.each_char
        .with_index
        .with_object({}) do |(char, index), hash|
          hash[char] ||= []
          hash[char] << index
       end
end

CodePudding user response:

def char_indices(str)
  str.each_char.
      with_index.
      with_object({}) do |(c,i),h|
        (h[c] ||= []) << i
      end
end
char_indices('mississippi')
  #=> {"m"=>[0], "i"=>[1, 4, 7, 10], "s"=>[2, 3, 5, 6], "p"=>[8, 9]}
char_indices('classroom')
  #=> {"c"=>[0], "l"=>[1], "a"=>[2], "s"=>[3, 4], "r"=>[5], "o"=>
[6, 7], "m"=>[8]}

Notice how I've written the block variables: |(c,i),h|. This makes use of Ruby's array decomposition rules. See also this article on the subject.


The method is equivalent to the following.

def char_indices(str)
  h = {}
  str.each_char.
      with_index do |c,i|
        h[c] = [] unless h.key?(c)
        h[c] << i
      end
  h
end

One could alternatively write the method like this:

def char_indices(str)       
  str.each_char.
      with_index.
      with_object(Hash.new { |h,k| h[k] = [] }) { |(c,i),h| h[c] << i }
end

This uses the form of Hash::new that takes a block and no argument. If

h = Hash.new { |h,k| h[k] = [] }

than, possibly after keys have been added, if h.key?(k) #=> false, h[k] = [] is executed. Here, when

h[c] << i

is encountered, if h.key?(c) #=> false, h[c] = [] is executed, after which h[c] << i is executed, resulting in h[c] #=> [c].

  •  Tags:  
  • ruby
  • Related