Finding indexes of each element in a multidimensional array in ruby - ruby

Eg
:a=[["hello", "world"], ["good", "lord"], ["hello", "lord"]]
I need to find and record the indexes of each word with respect to the super-array.
i.e
hello => 0,2
world => 0
lord => 1,2.
here's my shot ,but its very amateurish and lengthy.
all_tokens=tokens.flatten
all_tokens.each do|keyword|
tokens.each do|token_array|
if token_array.include?keyword
x << i
end
i=i+1
end
y[k] = x.clone
y=y.clear
end

Slight improvement (imho) on vava's solution:
tokens = [["hello", "world"], ["good", "lord"], ["hello", "lord"]]
tokens_hash = Hash.new{|h, k| h[k] = []}
tokens.each_with_index do |subarr, i|
subarr.each do |word|
tokens_hash[word] << i
end
end

ret = []
a.each_with_index {|x, i| if x.include?(keyword) then ret << i end }

a.each_with_index.inject({}){|acc,(elem,i)|
elem.each{|e|
acc[e] ||= []
acc[e] << i
}
acc
}
#=> {"hello"=>[0, 2], "world"=>[0], "good"=>[1], "lord"=>[1, 2]}

tokens = [["hello", "world"], ["good", "lord"], ["hello", "lord"]]
tokens_hash = Hash.new([])
tokens.each_with_index do |subarr, i|
subarr.each do |word|
tokens_hash[word] = tokens_hash[word] + [i]
end
end
p tokens_hash #=>{"good"=>[1], "world"=>[0], "lord"=>[1, 2], "hello"=>[0, 2]}
My solution will scan the whole structure just once.

Just for grins, a functional solution:
#!/usr/bin/ruby1.8
a = [["hello", "world"], ["good", "lord"], ["hello", "lord"]]
b = a.flatten.uniq.inject({}) do |hash, word|
hash.merge(word => a.each_with_index.collect do |list, i|
list.index(word) && i
end.compact)
end
p b # => {"world"=>[0], "good"=>[1], "lord"=>[1, 2], "hello"=>[0, 2]}

a=[["hello", "world"], ["good", "lord"], ["hello", "lord"]]
result = Hash.new{|k,v| k[v] = []}
a.each_with_index{|b,i| b.each{|c| result[c] << i} }
result
#=> {"good"=>[1], "world"=>[0], "lord"=>[1, 2], "hello"=>[0, 2]}

Related

Ruby - converting a string into hash with each character as key and index as value?

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}?
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}
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]]}
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]]
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]}

Ruby: Get all keys in a hash (including sub keys)

let's have this hash:
hash = {"a" => 1, "b" => {"c" => 3}}
hash.get_all_keys
=> ["a", "b", "c"]
how can i get all keys since hash.keys returns just ["a", "b"]
This will give you an array of all the keys for any level of nesting.
def get_em(h)
h.each_with_object([]) do |(k,v),keys|
keys << k
keys.concat(get_em(v)) if v.is_a? Hash
end
end
hash = {"a" => 1, "b" => {"c" => {"d" => 3}}}
get_em(hash) # => ["a", "b", "c", "d"]
I find grep useful here:
def get_keys(hash)
( hash.keys + hash.values.grep(Hash){|sub_hash| get_keys(sub_hash) } ).flatten
end
p get_keys my_nested_hash #=> ["a", "b", "c"]
I like the solution as it is short, yet it reads very nicely.
Version that keeps the hierarchy of the keys
Works with arrays
Works with nested hashes
keys_only.rb
# one-liner
def keys_only(h); h.map { |k, v| v = v.first if v.is_a?(Array); v.is_a?(Hash) ? [k, keys_only(v)] : k }; end
# nicer
def keys_only(h)
h.map do |k, v|
v = v.first if v.is_a?(Array);
if v.is_a?(Hash)
[k, keys_only(v)]
else
k
end
end
end
hash = { a: 1, b: { c: { d: 3 } }, e: [{ f: 3 }, { f: 5 }] }
keys_only(hash)
# => [:a, [:b, [[:c, [:d]]]], [:e, [:f]]]
P.S.: Yes, it looks like a lexer :D
Bonus: Print the keys in a nice nested list
# one-liner
def print_keys(a, n = 0); a.each { |el| el.is_a?(Array) ? el[1] && el[1].class == Array ? print_keys(el, n) : print_keys(el, n + 1) : (puts " " * n + "- #{el}") }; nil; end
# nicer
def print_keys(a, n = 0)
a.each do |el|
if el.is_a?(Array)
if el[1] && el[1].class == Array
print_keys(el, n)
else
print_keys(el, n + 1)
end
else
puts " " * n + "- #{el}"
end
end
nil
end
> print_keys(keys_only(hash))
- a
- b
- c
- d
- e
- f
def get_all_keys(hash)
hash.map do |k, v|
Hash === v ? [k, get_all_keys(v)] : [k]
end.flatten
end
Please take a look of following code:
hash = {"a" => 1, "b" => {"c" => 3}}
keys = hash.keys + hash.select{|_,value|value.is_a?(Hash)}
.map{|_,value| value.keys}.flatten
p keys
result:
["a", "b", "c"]
New solution, considering #Bala's comments.
class Hash
def recursive_keys
if any?{|_,value| value.is_a?(Hash)}
keys + select{|_,value|value.is_a?(Hash)}
.map{|_,value| value.recursive_keys}.flatten
else
keys
end
end
end
hash = {"a" => 1, "b" => {"c" => {"d" => 3}}, "e" => {"f" => 3}}
p hash.recursive_keys
result:
["a", "b", "e", "c", "d", "f"]
Also deal with nested arrays that include hashes
def all_keys(items)
case items
when Hash then items.keys + items.values.flat_map { |v| all_keys(v) }
when Array then items.flat_map { |i| all_keys(i) }
else []
end
end
class Hash
def get_all_keys
[].tap do |result|
result << keys
values.select { |v| v.respond_to?(:get_all_keys) }.each do |value|
result << value.get_all_keys
end
end.flatten
end
end
hash = {"a" => 1, "b" => {"c" => 3}}
puts hash.get_all_keys.inspect # => ["a", "b", "c"]
Here is another approach :
def get_all_keys(h)
h.each_with_object([]){|(k,v),a| v.is_a?(Hash) ? a.push(k,*get_all_keys(v)) : a << k }
end
hash = {"a" => 1, "b" => {"c" => {"d" => 3}}}
p get_all_keys(hash)
# >> ["a", "b", "c", "d"]
I'm sure there is a more elegant solution, but this option works:
blah = {"a" => 1, "b" => {"c" => 3}}
results = []
blah.each do |k,v|
if v.is_a? Hash
results << k
v.each_key {|key| results << key}
else
results << k
end
end
puts results
hash.keys is the simplest one I have seen to return an array of the key values in a hash.

Ruby calculate percentages of array objects

I have an array:
array = ["one", "two", "two", "three"]
Now I need to create a separate array with the percentages of each of these items out of the total array.
Final result:
percentages_array = [".25",".50",".50",".25]
I can do something like this:
percentage_array = []
one_count = array.grep(/one/).count
two_count = array.grep(/two/).count
array.each do |x|
if x == "one"
percentage_array << one_count.to_f / array.count.to_f
elsif x == "two"
....
end
end
But how can I write it a little more concise and dynamic?
I would use the group by function:
my_array = ["one", "two", "two", "three"]
percentages = Hash[array.group_by{|x|x}.map{|x, y| [x, 1.0*y.size/my_array.size]}]
p percentages #=> {"one"=>0.25, "two"=>0.5, "three"=>0.25}
final = array.map{|x| percentages[x]}
p final #=> [0.25, 0.5, 0.5, 0.25]
Alternative 2 without group_by:
array, result = ["one", "two", "two", "three"], Hash.new
array.uniq.each do |number|
result[number] = array.count(number)
end
p array.map{|x| 1.0*result[x]/array.size} #=> [0.25, 0.5, 0.5, 0.25]
You could do this, but you find it more useful to just use the hash h:
array = ["one", "two", "two", "three"]
fac = 1.0/array.size
h = array.reduce(Hash.new(0)) {|h, e| h[e] += fac; h}
# => {"one"=>0.25, "two"=>0.5, "three"=>0.25}
array.map {|e| h[e]} # => [0.25, 0.5, 0.5, 0.25]
Edit: as #Victor suggested, the last two lines could be replaced with:
array.reduce(Hash.new(0)) {|h, e| h[e] += fac; h}.values_at(*array)
Thanks, Victor, a definite improvement (unless use of the hash is sufficient).
percentage_array = []
percents = [0.0, 0.0, 0.0]
array.each do |x|
number = find_number(x)
percents[number] += 1.0 / array.length
end
array.each do |x|
percentage_array.append(percents[find_number(x)].to_s)
end
def find_number(x)
if x == "two"
return 1
elsif x == "three"
return 2
end
return 0
end
Here is a generalized way to do this:
def percents(arr)
map = Hash.new(0)
arr.each { |val| map[val] += 1 }
arr.map { |val| (map[val]/arr.count.to_f).to_s }
end
p percents(["one", "two", "two", "three"]) # prints ["0.25", "0.5", "0.5", "0.25"]

Given an array, returns a hash

Write a method, which given an array, returns a hash whose keys are words in the array and whose values are the number of times each word appears.
arr=["A", "man", "a", "plan", "a", "canal","Panama"]
# => {'a' => 3, 'man' => 1, 'canal' => 1, 'panama' => 1, 'plan' => 1}
How do I achieve that? Here's my code:
hash={}
arr.each do |i|
hash.each do |c,v|
hash[c]=v+1
end
end
hash = arr.inject({}) do |hash, element|
element.downcase!
hash[element] ||= 0
hash[element] += 1
hash
end
arr.inject­(Hash.new(­0)){|h,k| k.dow­ncase!; h[k] += 1; h}
arr = ["A", "man", "a", "plan", "a", "canal","Panama"]
r = {}
arr.each { |e| e.downcase!; r[e] = arr.count(e) if r[e].nil? }
Output
p r
#==> {"a"=>3, "man"=>1, "plan"=>1, "canal"=>1, "panama"=>1}

Converting a hash into a nested hash

This question is the inverse of this question.
Given a hash that has an array for each key like
{
[:a, :b, :c] => 1,
[:a, :b, :d] => 2,
[:a, :e] => 3,
[:f] => 4,
}
what is the best way to convert it into a nested hash like
{
:a => {
:b => {:c => 1, :d => 2},
:e => 3,
},
:f => 4,
}
Here's an iterative solution, a recursive one is left as an exercise to the reader:
def convert(h={})
ret = {}
h.each do |k,v|
node = ret
k[0..-2].each {|x| node[x]||={}; node=node[x]}
node[k[-1]] = v
end
ret
end
convert(your_hash) # => {:f=>4, :a=>{:b=>{:c=>1, :d=>2}, :e=>3}}
Functional recursive algorithm:
require 'facets'
class Hash
def nestify
map_by { |ks, v| [ks.first, [ks.drop(1), v]] }.mash do |key, pairs|
[key, pairs.first[0].empty? ? pairs.first[1] : Hash[pairs].nestify]
end
end
end
p {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}.nestify
# {:a=>{:b=>{:c=>1, :d=>2}, :e=>3}, :f=>4}
There is already a good answer, but I worked on this recursive solution, so here it is:
def to_nest(hash)
{}.tap do |nest|
hash.each_pair do |key, value|
nodes = key.dup
node = nodes.shift
if nodes.empty?
nest[node] = value
else
nest[node] ||= {}
nest[node].merge!({nodes => value})
end
end
nest.each_pair do |key, value|
nest[key] = to_nest(value) if value.kind_of?(Hash)
end
end
end
Another way:
def convert(h)
h.each_with_object({}) { |(a,n),f| f.update({ a.first=>(a.size==1 ? n :
convert({ a[1..-1]=>n })) }) { |_,ov,nv| ov.merge(nv) } }
end
Try it:
h = {
[:a, :b, :c] => 1,
[:a, :b, :d] => 2,
[:a, :e] => 3,
[:f] => 4,
}
convert(h) #=> {:a=>{:b=>{:d=>2}, :e=>3},
# :f=>4}
For a mixed hash/array nested structure you can use this. (Modified for arrays as well)
def unflatten(h={})
ret = {}
h.each do |k,v|
node = ret
keys = k.split('.').collect { |x| x.to_i.to_s == x ? x.to_i : x }
keys.each_cons(2) do |x, next_d|
if(next_d.is_a? Fixnum)
node[x] ||= []
node=node[x]
else
node[x] ||={}
node=node[x]
end
end
node[keys[-1]] = v
end
ret
end
provided you used the below for flattening. ( dot separate string for key instead of array [split on . if you need] )
def flatten_hash(hash)
hash.each_with_object({}) do |(k, v), h|
if v.is_a? Hash
flatten_hash(v).map do |h_k, h_v|
h["#{k}.#{h_k}"] = h_v
end
elsif v.is_a? Array
flatten_array(v).map do |h_k,h_v|
h["#{k}.#{h_k}"] = h_v
end
else
h[k] = v
end
end
end
def flatten_array(array)
array.each_with_object({}).with_index do |(v,h),i|
pp v,h,i
if v.is_a? Hash
flatten_hash(v).map do |h_k, h_v|
h["#{i}.#{h_k}"] = h_v
end
elsif v.is_a? Array
flatten_array(v).map do |h_k,h_v|
h["#{i}.#{h_k}"] = h_v
end
end
end
end
Using DeepEnumerable:
require DeepEnumerable
h = {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
h.inject({}){|hash, kv| hash.deep_set(*kv)}

Resources