I have a function in Ruby:
def find_item(keyword)
potential = []
$items.each do |item|
puts item # <-- for debugging purposes
if item["name"].downcase== keyword
potential << item["name"].downcase
elsif item["keywords"].index(keyword) != nil
potential << item["name"].downcase
end
end
return potential
end
(The global variable $items is a Hash object that maps a few strings to some values that determine the properties of the item.)
When I puts the current item it is iterating over (the line with the comment does just that), it gives me:
{"name"=>"Thing1", "keywords"=>["thing", "green"], ...}
but when I try item["name"] on the next line (which should definitely return Thing1), it gives me:
C:/somepath/someprogram.rb:125:in '[]': can't convert String into Integer (TypeError)
if $items is a Hash, then $items.each do |item| will yield [key, value] pairs (Arrays) to the block. If you only want the values, use each_value.
Related
I have an array that contains a mixture of strings and symbols.
array = ["candy", :pepper, "wall", :ball, "wacky"]
The aim is to return the first word that begins with the letters "wa".
Here is my code:
def starts_with_wa
deleted_words = array.delete_if{|word| word.class == Symbol}
## deletes the symbols in the original array
deleted_words.find do |w|
##it should iterate through the deleted_Words array but it shows error of undefined local variable or method "array" for main:Object
w.start_with?('wa')
end
end
starts_with_wa
You need to pass the array to your method, otherwise, it is not visible in the scope of the method. Furthermore, I suggest a simple refactoring:
array = ["candy", :pepper, "wall", :ball, "wacky"]
def starts_with_wa(words)
words.find { |word| word.is_a?(String) && word.start_with?('wa') }
end
starts_with_wa(array)
#=> "wall"
When using an accumulator, does the accumulator exist only within the reduce block or does it exist within the function?
I have a method that looks like:
def my_useless_function(str)
crazy_letters = ['a','s','d','f','g','h']
str.split.reduce([]) do |new_array, letter|
for a in 0..crazy_letters.length-1
if letter == crazy_letters[a]
new_array << letter
end
end
end
return true if (new_array == new_array.sort)
end
When I execute this code I get the error
"undefined variable new_array in line 11 (the return statement)"
I also tried assigning the new_array value to another variable as an else statement inside my reduce block but that gave me the same results.
Can someone explain to me why this is happening?
The problem is that new_array is created during the call to reduce, and then the reference is lost afterwards. Local variables in Ruby are scoped to the block they are in. The array can be returned from reduce in your case, so you could use it there. However, you need to fix a couple things:
str.split does not break a string into characters in Ruby 2+. You should use str.chars, or str.split('').
The object retained for each new iteration of reduce must be retained by returning it from the block each time. The simplest way to do this is to put new_array as the last expression in your block.
Thus:
def my_useless_function(str)
crazy_letters = ['a','s','d','f','g','h']
crazy_only = str.split('').reduce([]) do |new_array, letter|
for a in 0..crazy_letters.length-1
if letter == crazy_letters[a]
new_array << letter
end
end
new_array
end
return true if (crazy_only == crazy_only.sort)
end
Note that your function is not very efficient, and not very idiomatic. Here's a shorter version of the function that is more idiomatic, but not much more efficient:
def my_useless_function(str)
crazy_letters = %w[a s d f g h]
crazy_only = str.chars.select{ |c| crazy_letters.include?(c) }
crazy_only == crazy_only.sort # evaluates to true or false
end
And here's a version that's more efficient:
def efficient_useless(str)
crazy_only = str.scan(/[asdfgh]/) # use regex to search for the letters you want
crazy_only == crazy_only.sort
end
Block local variables
new_array doesn't exist outside the block of your reduce call. It's a "block local variable".
reduce does return an object, though, and you should use it inside your method.
sum = [1, 2, 3].reduce(0){ |acc, elem| acc + elem }
puts sum
# 6
puts acc
# undefined local variable or method `acc' for main:Object (NameError)
Your code
Here's the least amount of change for your method :
def my_useless_function(str)
crazy_letters = ['a','s','d','f','g','h']
new_array = str.split(//).reduce([]) do |new_array, letter|
for a in 0..crazy_letters.length-1
if letter == crazy_letters[a]
new_array << letter
end
end
new_array
end
return true if (new_array == new_array.sort)
end
Notes:
return isn't needed at the end.
true if ... isn't needed either
for loop should never be used in Ruby
reduce returns the result of the last expression inside the block. It was for in your code.
If you always need to return the same object in reduce, it might be a sign you could use each_with_object.
"test".split is just ["test"]
String and Enumerable have methods that could help you. Using them, you could write a much cleaner and more efficient method, as in #Phrogz answer.
This code is in my book, there are 2 lines that I don't understand.
# library.rb
class Library
def initialize
#books ={} #creating a new hash
end
def addBook(book)
#books[book.category]||=[] #category is a symbol. I DONT UNDERSTAND THIS LINE
#books[book.category] << book #AND THIS ONE
end
end
#books ={}
Above line will create a new Hash
#books[book.category]||=[]
This means #books is a Hash and book.category is it's key and if that key not exist assign an empty array
||= -> So this means or-equals
|| means if #books has value it will not assign empty array, else it will put an empty array
So if #books[book.category] is a Array, in which you can insert as many category values
In this line we will insert book value into the hash, where book.category is the key
#books[book.category] << book
If you try this
#books ={}
#books[book.category].push(book) # This will give you the error `undefined method 'push' for nilclass`
because
#books[book.category].class will return NilClass
a ||= b means "if a is logically false (nil, false, undefined), assign b to it". You can read more about ||=, called "double pipe or equals" here.
So, in your code, #books[book.category] ||= [] means that an empty array ([]) will be assigned to books[book.category] if it's nil - otherwise it will remain intact.
Then, in the line below (#books[book.category] << book), book is appended (<<) to the array. Notice, however, that you have a syntax error (you open a { and you close with a [).
I have a hash that has an unknown collection and mixture of nested arrays, hashes, arrays of hashes and strings. This is the result of JSON.parse. The structure of the data must be the same as it started with. The end goal is to convert strings to Fixnums that could be Fixnums.
The following works just fine, but I was wondering if it could be shortened. Note how I need the key and the value in the clean method as not all strings that can be Fixnums should be. Any ideas?
def clean_node(node)
if node.class == String
clean(node)
elsif node.class == Array
node.each_with_index do |obj, i|
if obj.class == String
node[i] = clean(node[i], obj)
else
clean_node(obj)
end
end
elsif node.class == Hash
node.each_pair do |key, value|
if value.class == String
node[key] = clean(key, value)
else
clean_node(value)
end
end
end
end
def clean(key, value)
FIXNUM_KEYS.include?(key)? value.to_i : value
end
There is no need to split up the string processing, you can do it all in one recursive routine, if you return the value of node at the end of the routine. This has the side effect of removing some parameters, and you can use in-place .map! for handling arrays.
Using case makes the selection by type slightly easier to read.
Adding the ability to deal with arrays of strings could be done in lots of ways. I've chosen to add a state variable (a common pattern in recursion, for instance to count depth). Then in the case of arrays, the recursion inherits the state from the current level, whilst the hash case determines new state (to convert or not) based on lookup in the list of keys to apply conversions.
def clean_node( node, convert_item = false )
case node
when String
node = node.to_i if convert_item
when Array
node.map! do |obj|
clean_node( obj, convert_item )
end
when Hash
node.each_pair do |key, value|
node[key] = clean_node( value, FIXNUM_KEYS.include?(key) )
end
end
node
end
Although I have not looked into the recursion, I must comment on the fact that you are writing if statements that would be easier to read in a case statement:
def clean_node(node)
case node
when String then clean(node)
when Array
node.each_with_index do |obj, i|
case obj
when String
node[i] = clean(node[i], obj)
else
clean_node(obj)
end
end
when Hash....
I was wondering if it is possible to return to an Each iterator in Ruby from within a for-loop placed within the block passed to Each.
def find member = ""
productHash = {}
##entries is a hash, with both the keys and values being strings
#the member parameter is a string
#entries.each do |key, value|
for i in 0...member.size
if(key[i] != member[i])
next #the next keyword doesn't work...all it does is return to the for iterator. I'm looking for a keyword that would return to the each iterator, and allow each to pass back in the next key-value pair.
end
end
productHash[key] = value
end
productHash
end
What I'm trying to accomplish is this: the moment I see that a character in the member parameter doesn't match the corresponding character in a given key, I move on to the next key-value pair.
It looks like you're trying to do some kind of comparison where if the key matches a particular prefix specified by member then you would make an assignment.
This code should be functionally similar:
def find(member = "")
hash = { }
#entries.each do |key, value|
# Unless key begins with prefix, skip it.
next unless (key[0, prefix.length] == prefix)
hash[key] = value
end
hash
end
There's no official goto statement in Ruby, and many would argue this is a good thing, but it means that busting out of nested blocks can be a bit tricky.
Still, if you approach the problem in the right way, there's almost always a solution that's elegant enough.
Update:
To break out of nested loops, an approach might be:
list.each do |i|
broken = false
inner_list.each do |j|
if (j > 10)
broken = true
break
end
end
break if (broken)
end