Issue with troubleshooting an error I am getting - ruby

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"

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

Convert Array of Arrays into Combination of Arrays and Hashes

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

Is there a better way to write multiple OR statements in an if-statement?

def get_string(no_of_times)
1.upto(no_of_times) do
string_input = gets.chomp
count_holes(string_input)
end
end
def count_holes(word)
count = 0
word.each_char do |char|
if char == "A" || char == "D" || char == "O" || char == "P" || char == "Q" || char == "R"
count += 1
elsif char == "B"
count += 2
end
end
$arr_of_holes << count
end
test_cases = gets.chomp.to_i
$arr_of_holes = []
get_string(test_cases)
puts $arr_of_holes
Hi all. I do not like the long condition in if statement while iterating over each character. So i wanted to ask you all if there is a better way to do this in ruby.
Thanks
This can be done with a case selection, as multiple terms can be supplied to each when:
case char
when "A", "D", "O", "P", "Q", "R"
count += 1
when "B"
count += 2
end
You can use Array#include?:
if %q{A D O P Q R}.include? char
count += 1
elsif char == "B"
count += 2
end
Alternative way using Hash:
def count_holes(word)
holes = {
'A' => 1,
'D' => 1,
'O' => 1,
'P' => 1,
'Q' => 1,
'B' => 2,
}
count = word.chars.map { |char| holes.fetch(char, 0) }.inject :+
$arr_of_holes << count
end
Slightly more compact than nacyot's answer:
count += case char
when "B" then 2
when "A", "D", "O".."R" then 1
else 0
end
The else line may not be required if there is not such case.
One more way:
word = "BROADLY"
"ADOPQR".each_char.reduce(0) { |t,c| t + word.count(c) } + 2*word.count("B")
#=> 6

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.

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}

Resources