I have been trying to convert an array of arrays into a combination of arrays and hashes. Let me explain what I am trying to achieve:
Input:
[['a'], ['b'], ['a', 'b', 'c'], ['a', 'b', 'd']]
Expected Output:
[:a => [{:b => [:c, :d]}], :b]
What I have come up with so far is:
def converter(array)
tree_hash = {}
array.each do |path|
path.each_with_index.inject(tree_hash) do |node, (step, index)|
step = step.to_sym
if index < path.size - 1
node[step] ||= {}
else
node[step] = nil
end
end
end
tree_hash
end
and it gives me the following result:
converter([['a'], ['b'], ['a', 'b', 'c'], ['a', 'b', 'd']])
=> {:a=>{:b=>{:c=>nil, :d=>nil}}, :b=>nil}
Can anyone throw some light so that I can solve this problem. Is there any name to this problem, direct graph / indirect graph / graph theory? I am willing to study and improve my knowledge on graphs and trees.
I would appreciate it if you help me on solving this or give me some instructions on how to solve this.
Thanks.
def group_by_prefix(elements)
elements.group_by(&:first).map do |prefix, elements|
remainders = elements.map { |element| element.drop(1) }.reject(&:empty?)
if remainders.empty?
prefix.to_sym
else
{prefix.to_sym => group_by_prefix(remainders)}
end
end
end
foo = [['a'], ['b'], ['a', 'b', 'c'], ['a', 'b', 'd']]
group_by_prefix(foo) # => [{:a=>[{:b=>[:c, :d]}]}, :b]
It looks very similar to a Trie.
This structure is usually used with Strings ('apple', seen as an Array of chars : %w(a p p l e) ), but it can be easily modified to accept an Array of Strings (%w(bar car)) :
class Node
attr_reader :key, :children
def initialize(key, children = [])
#key = key
#children = children
end
def to_s
key.to_s
end
def to_sym
key.to_sym
end
end
class Trie
attr_reader :root
def initialize
#root = Node.new('')
end
def self.from_array(array)
array.each_with_object(Trie.new) { |letters, trie| trie.add(letters) }
end
def add(letters)
node = root
letters.each do |c|
next_node = node.children.find { |child| child.key == c }
if next_node
node = next_node
else
next_node = Node.new(c)
node.children.push(next_node)
node = next_node
end
end
end
def show(node = root, indent = 0)
puts ' ' * indent + node.to_s
node.children.each do |child|
show(child, indent + 1)
end
end
def to_arrays_and_hashes
recursive_to_arrays_and_hashes(root)[root.to_sym]
end
private
def recursive_to_arrays_and_hashes(node)
if node.children.empty?
node.to_sym
else
{node.to_sym => node.children.map{|c| recursive_to_arrays_and_hashes(c)}}
end
end
end
array1 = [['a'], ['b'], ['a', 'b', 'c'], ['a', 'b', 'd']]
array2 = [['foo'], ['bar', 'car']]
[array1, array2].each do |array|
trie = Trie.from_array(array)
trie.show
p trie.to_arrays_and_hashes
end
It returns :
a
b
c
d
b
[{:a=>[{:b=>[:c, :d]}]}, :b]
foo
bar
car
[:foo, {:bar=>[:car]}]
Related
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]}
This is the code I am trying to run which counts the frequency of chars in the array passed to it, and this is the error I am getting frequency_string': undefined methodeach' for "a":String (NoMethodError). I'm not entirely sure what is wrong because this is code that should work and was given to me. Very new to Ruby, and have spent hours trying to solve this on my own. Will take any help I can get.
def frequency_string(chars)
frequencies = {}
result = ""
chars.each do |char|
if frequencies.has_key? char then
frequencies = 1
else
frequencies[char] += 1
end
end
frequencies.each do |char, freq|
result << char << freq
end
return result
end
data1 = ['a', 'b', 'c', 'c', 'd', 'a', '#', 'b', '#', 'a']
puts "Data 1: " + data1.join(' ')
puts
puts frequency_string(data1)
You have two problems:
the line frequencies = 1 should be frequencies[char] = 1. As it stands, you are converting a hash to the number 1.
you have frequency[char] = 1 and frequency[char] = 1 reversed.
Here is your corrected code:
def frequency_string(chars)
frequencies = {}
result = ""
chars.each do |char|
if frequencies.has_key? char then
frequencies[char] += 1
else
frequencies[char] = 1
end
end
frequencies.each do |char, freq|
result << char << freq.to_s
end
result
end
data1 = ['a', 'b', 'c', 'c', 'd', 'a', '#', 'b', '#', 'a']
puts "Data 1: " + data1.join(' ')
puts
puts frequency_string(data1)
#=> a3b2c2d1#2
As you gain experience with Ruby you will find that the language allows you to write operations such as this one with very little code. Here's one way:
def frequency_string(chars)
chars.each_with_object({}) { |char,hash|
hash[char] = (hash[char] ||= 0) + 1 }.to_a.flatten.join
end
puts frequency_string(data1)
#=> a3b2c2d1#2
This may look complex, but it won't be long before you find this to be more natural and straightforward than what you have written. You don't have to worry about this now; I just wanted to give you a taste of what you can look forward to.
chars.each do |char|
if frequencies.has_key? char then
frequencies = 1
else
frequencies[char] += 1
end
end
should be:
chars.each do |char|
if frequencies.has_key? char
frequencies[char] += 1
else
frequencies[char] = 1
end
end
or
chars.each { |char| frequencies.has_key?(char) ? frequencies[char] += 1 : frequencies[char] = 1 }
You also can make it like this:
group_by usage
irb(main):038:0> data1.group_by { |e| e }
=> {"a"=>["a", "a", "a"], "b"=>["b", "b"], "c"=>["c", "c"], "d"=>["d"], "#"=>["#", "#"]}
irb(main):041:0> data1.group_by { |e| data1.count(e) }
=> {3=>["a", "a", "a"], 2=>["b", "c", "c", "#", "b", "#"], 1=>["d"]} # it can be an optional if it can meet your demands
after improvement:
irb(main):053:0> result = ''
=> ""
irb(main):054:0> data1.group_by { |e| e }.each { |e| result += e[0] + e[1].count.to_s }
=> {"a"=>["a", "a", "a"], "b"=>["b", "b"], "c"=>["c", "c"], "d"=>["d"], "#"=>["#", "#"]}
irb(main):055:0> result
=> "a3b2c2d1#2"
or use inject
irb(main):040:0> data1.group_by { |e| e }.inject('') {|r, e| r + e[0] + e[1].count.to_s}
=> "a3b2c2d1#2"
I was wondering if there was an Array method in Ruby that allows to filter an array based on another array or a bitmask.
Here is an example and a quick implementation for illustration purposes:
class Array
def filter(f)
res = []
if f.is_a? Integer
(0...self.size).each do |i|
res << self[i] unless f[i].nil? || 2**i & f == 0
end
else
(0...self.size).each do |i|
res << self[i] unless f[i].nil? || f[i] == 0
end
end
return res
end
end
Example:
%w(a b c).filter([1, 0, 1]) ==> ['a', 'c']
%w(a b c).filter(4) ==> ['c']
%w(a b c).filter([1]) ==> ['a']
Thanks!
In ruby 1.9 Fixnum#[] gives you bit values at a particular position, so it will work for both integers and arrays. I'm thinking something like this:
class Array
def filter f
select.with_index { |e,i| f[i] == 1 }
end
end
%w(a b c).filter([1, 0, 1]) #=> ['a', 'c']
%w(a b c).filter(4) #=> ['c']
%w(a b c).filter(5) #=> ['a', c']
%w(a b c).filter([1]) #=> ['a']
class Array
def filter(f)
f = f.to_s(2).split("").map(&:to_i) unless Array === f
reverse.reject.with_index{|_, i| f[-i].to_i.zero?}
end
end
I have a hash like
h = {1 => {"inner" => 45}, 2 => {"inner" => 46}, "inner" => 47}
How do I delete every pair that contains the key "inner"?
You can see that some of the "inner" pairs appear directly in h while others appear in pairs in h
Note that I only want to delete the "inner" pairs, so if I call my mass delete method on the above hash, I should get
h = {1 => {}, 2 => {}}
Since these pairs don't have a key == "inner"
Really, this is what reject! is for:
def f! x
x.reject!{|k,v| 'inner' == k} if x.is_a? Hash
x.each{|k,v| f! x[k]}
end
def f x
x.inject({}) do |m, (k, v)|
v = f v if v.is_a? Hash # note, arbitrarily recursive
m[k] = v unless k == 'inner'
m
end
end
p f h
Update: slightly improved...
def f x
x.is_a?(Hash) ? x.inject({}) do |m, (k, v)|
m[k] = f v unless k == 'inner'
m
end : x
end
def except_nested(x,key)
case x
when Hash then x = x.inject({}) {|m, (k, v)| m[k] = except_nested(v,key) unless k == key ; m }
when Array then x.map! {|e| except_nested(e,key)}
end
x
end
Here is what I came up with:
class Hash
def deep_reject_key!(key)
keys.each {|k| delete(k) if k == key || self[k] == self[key] }
values.each {|v| v.deep_reject_key!(key) if v.is_a? Hash }
self
end
end
Works for a Hash or a HashWithIndifferentAccess
> x = {'1' => 'cat', '2' => { '1' => 'dog', '2' => 'elephant' }}
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}
> y = x.with_indifferent_access
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}
> x.deep_reject_key!(:"1")
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}
> x.deep_reject_key!("1")
=> {"2"=>{"2"=>"elephant"}}
> y.deep_reject_key!(:"1")
=> {"2"=>{"2"=>"elephant"}}
Similar answer but it is a whitelist type approach. For ruby 1.9+
# recursive remove keys
def deep_simplify_record(hash, keep)
hash.keep_if do |key, value|
if keep.include?(key)
deep_simplify_record(value, keep) if value.is_a?(Hash)
true
end
end
end
hash = {:a => 1, :b => 2, :c => {:a => 1, :b => 2, :c => {:a => 1, :b => 2, :c => 4}} }
deep_simplify_record(hash, [:b, :c])
# => {:b=>2, :c=>{:b=>2, :c=>{:b=>2, :c=>4}}}
Also here are some other methods which I like to use for hashes.
https://gist.github.com/earlonrails/2048705
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)}