I've been reading the Ruby docs, and looking at some other posts on the issue, but I am still wondering about this:
#counts each number in an array once
array = [1,1,2,5,3,2,5,3,3,3]
numbers = {}
array.each { |num| numbers[num] += 1 }
=> in `block in mode': undefined method `+' for nil:NilClass (NoMethodError)
In the Hash documentation the default value for a Hash is nil, which is why I am getting this error I assume. Is there a better way to insert each key/(value += 1) into the numbers array?
Try passing a default value to your new hash as such
numbers = Hash.new(0)
You can explicitly do it this way as well:
array.each { |num| numbers[num] = (numbers[num] || 0) + 1 }
Variant with inject and Hash.new(0)
numbers = [1,1,2,5,3,2,5,3,3,3].inject(Hash.new(0)){|numbers, number| numbers[number] +=1; numbers}
Aside from using the Hash default, you could also try something with group_by:
array = [1,1,2,5,3,2,5,3,3,3]
numbers = Hash[*array.group_by { |i| i }.flat_map { |k, v| [k , v.size] }]
There's probably a better way if you play around with it some.
Related
Given the hash
person = {
"cats"=> 2,
"dogs"=> 1
}
I wish to construct the array
["cats", "cats", "dogs"]
"cats" appears twice because person["cats"] #=> 2. For the same reason "dogs" appears once. If the hash had a third key-value pair "pigs"=>3, I would want to return the array
["cats", "cats", "dogs", "pigs", "pigs", "pigs"]
I tried the following code.
arr = person.to_a
i = 0
new_arr = []
while i < arr.length
el = arr[i][0]
final = [new_arr << el]
print final.flatten
i += 1
end
This displays
["cats"]["cats", "dogs"] => nil
but does not seem to return a value.
new_arr
#=> ["cats", "dogs"]
As you see, I am not getting the answer I wanted and do not understand why print displays what I show above.
I would like to know what is wrong with my code and what would be a better way of doing this.
flat_map method will flatten multiple arrays into one
Array operator * creates array with multiple values
result = person.flat_map {|key, value| [key] * value}
# => ["cats", "cats", "dogs"]
Ruby has a lot of nice methods to work with collections. I believe it is better to use them instead of while loop.
You can iterate through the hash using inject
method. The first parameter in the block is the resulting array, that accumulates the result of each iteration, the second is a key/value pair.
person.inject([]) do |array, (key, value)|
array + Array.new(value, key)
end
Or it can be rewritten as a one line.
person.inject([]) { |array, (key, value)| array + Array.new(value, key) }
I need some help understanding the below ruby code.
counted = Hash.new(0)
parsed_reponse["result"]["data"].each { |h| counted[h["version"]] += 1 }
counted = Hash[counted.map {|k,v| [k,v.to_s] }]
I understand line 1 creats a hash which I believe is similar to a
dictionary in python.
Line 2 loops through my json data set a adds a
new key and value pair if none exits and increments the count if 1
does exist.
What does the 3rd line do?
Your last line just converts all values to String:
Hash[{a: 2, b: 3}.map {|k, v| [k, v.to_s]}]
#=> {:a=>"2", :b=>"3"}
I would refactor it to:
counted.transform_values!(&:to_s) # for ruby >= 2.4
#=> {:a=>"1", :b=>"2"}
Or for older versions:
counted.each { |k, v| counted[k] = v.to_s }
Because:
counted.map {|k,v| [k,v.to_s] } - creates new array of arrays
Hash[result] - creates new Hash object from result array.
Both steps are redundant, you can just modify existing hash.
I have a hash like this
hash_variable = {"74"=> {"x"=>{"order_id"=>"3643731"}, "x"=>{"order_id"=>"618787", "detail_id"=>"115", "qty"=>"1"}}}
Note: "x" -> 1..n is from index of each.
I want to reject hash if detail_id is nil. I tried with regex:
hash_variable.each do |items|
unless items[/(\d+)/][:detail_id].nil?
p items
end
end
# => `NoMethodError: undefined method '[]' for nil:NilClass`
Can I use regex for "x" to get any hash have detail_id key ? if not, how can I reject hash if detail_id is nil?
Ok, I have solved my problem.
hash_variable = {"74"=> {"0"=>{"order_id"=>"3643731"}, "1"=>{"order_id"=>"618787", "detail_id"=>"115", "qty"=>"1"}}}
hash_variable.each do |key,items|
p items.select { |k,v| !v['detail_id'].nil? }
end
# {"1"=>{"order_id"=>"618787", "detail_id"=>"115", "qty"=>"1"}}
Update from tadman's suggestion better than above
hash_variable.each do |key,items|
p items.select { |k,v| v['detail_id'] }
end
I am really new at ruby. I have created a function to count occurrences of words in a string. However, I am getting NoMethodError for + all the time. I searched, tried different variations, but couldn't solve the problem. Here is the code:
def count_words(str)
str_down = str.downcase
arr = str_down.scan(/([\w]+)/).flatten
hash = Hash[]
arr.each {|x| hash[x] += 1 }
(hash.sort_by {|key, value| value}.reverse)
end
Here is the error:
NoMethodError: undefined method `+' for nil:NilClass
from ./***.rb:14:in `count_words'
from ./***.rb:14:in `each'
from ./***.rb:14:in `count_words'
from (irb):137
Change
hash = Hash[]
arr.each {|x| hash[x] += 1 }
To
hash = {}
arr.each {|x| hash[x] =0 unless hash[x]; hash[x] += 1 }
OR
hash = Hash.new(0)
arr.each {|x| hash[x] += 1 }
EXPLAINATION
hash = {}
hash[1] = "example1" #ASSIGNMENT gives hash = {1: "example1"}
p hash[2] #This gives `nil` by default, as key is not present in hash
To give default value to the key which is not present in hash we have to do the following:
hash = Hash.new("new value")
p hash #Gives {}
p hash[4] #gives "new value"
In the first iteration, h[x] is nil. Trying to add 1 to nil throws error. Setting the initial value of h[x] to 0 will solve the issue.
arr.each {|x| hash[x]||=0; hash[x] += 1 }
instead of
arr.each {|x| hash[x] += 1 }
I want a twodimensional array in Ruby, that I can access for example like this:
if #array[x][y] == "1" then #array[x][y] = "0"
The problem is: I don't know the initial sizes of the array dimensions and i grow the array (with the << operator).
How do I declare it as an instance variable, so I get no error like this?
undefined method `[]' for nil:NilClass (NoMethodError)
QUESTION UPDATED:
#array = Array.new {Array.new}
now works for me, so the comment from Matt below is correct!
I just found out the reason why I received the error was because I iterated over the array like this:
for i in 0..#array.length
for j in 0..#array[0].length
#array[i][j] ...
which was obviously wrong and produced the error. It has to be like this:
for i in 0..#array.length-1
for j in 0..#array[0].length-1
#array[i][j] ...
A simple implementation for a sparse 2-dimensional array using nested Hashes,
class SparseArray
attr_reader :hash
def initialize
#hash = {}
end
def [](key)
hash[key] ||= {}
end
def rows
hash.length
end
alias_method :length, :rows
end
Usage:
sparse_array = SparseArray.new
sparse_array[1][2] = 3
sparse_array[1][2] #=> 3
p sparse_array.hash
#=> {1=>{2=>3}}
#
# dimensions
#
sparse_array.length #=> 1
sparse_array.rows #=> 1
sparse_array[0].length #=> 0
sparse_array[1].length #=> 1
Matt's comment on your question is totally correct. However, based on the fact that you've tagged this "conways-game-of-life", it looks like you are trying to initialize a two dimensional array and then use this in calculations for the game. If you wanted to do this in Ruby, one way to do this would be:
a = Array.new(my_x_size) { |i| Array.new(my_y_size) { |i| 0 }}
which will create a my_x_size * my_y_size array filled with zeros.
What this code does is to create a new Array of your x size, then initialize each element of that array to be another Array of your y size, and initialize each element of each second array with 0's.
Ruby's Array doesn't give you this functionality.
Either you manually do it:
(#array[x] ||= [])[y] = 42
Or you use hashes:
#hash = Hash.new{|h, k| h[k] = []}
#hash[42][3] = 42
#hash # => {42 => [nil, nil, nil, 42]}