I came across a weird behaviour in Ruby that I can't explain.
If we have an array of arrays and wanted to iterated over it with map. I tried to use map with two block variables expecting the second one to be the index, but it instead takes the values of the inner array. Why?
persons = [["john", 28], ["mary", 25],["emma", 30]]
persons.map do |param1, param2|
puts param1
puts param2
end
The output is
john
28
So how come that it takes the values of the iterators it should iterate over?
This is what you are looking for:
persons.map.with_index do |array, index|
...
end
You can pass an offset if you want the index to start in 1 instead of 0:
with_index(1)
You're using map but you don't seem to care about the result, so each is more appropriate. Unlike in JavaScript where you may be expecting an index to appear by default, in Ruby you have to ask for it.
If you wanted to display the values you'd do something like this:
persons.each_with_index do |(name, age), index|
puts '%d. %s (%s)' % [ index + 1, name, age ]
end
Note the use of (name, age) to unpack what is otherwise a pair of values that would be received as an array. This is because each-type methods treat arrays as singular objects. In the default case it'll auto-unpack for you.
If you wanted to transform the values then you'd use map:
persons.map.with_index do |(name, age), index|
'%d. %s (%s)' % [ index + 1, name, age ]
end
Remember, when using map you must capture as a variable, return it, or somehow use the result or it will get thrown away.
Related
I made a simple program with a single method and I'm trying to test it, but I keep getting this weird error, and I have no idea why it keeps happening.
Here's my code for the only method I wrote:
def make_database(lines)
i = 0
foods = hash.new()
while i < lines.length do
lines[i] = lines[i].chomp()
words = lines[i].split(',')
if(words[1].casecmp("b") == 0)
foods[words[0]] = words[3]
end
end
return foods
end
And then here's what I have for calling the method (Inside the same program).
if __FILE__ == $PROGRAM_NAME
lines = []
$stdin.each { |line| lines << line}
foods = make_database(lines).new
puts foods
end
I am painfully confused, especially since it gives me a different random number for each "Undefined method 'new' for (Random number)".
It's a simple mistake. hash calls a method on the current object that returns a number used by the Hash structure for indexing entries, where Hash is the hash class you're probably intending:
foods = Hash.new()
Or more succinctly:
foods = { }
It's ideal to use { } in place of Hash.new unless you need to specify things like defaults, as is the case with:
Hash.new(0)
Where all values are initialized to 0 by default. This can be useful when creating simple counters.
Ruby classes are identified by leading capital letters to avoid confusion like this. Once you get used to the syntax you'll have an easier time spotting mistakes like that.
Note that when writing Ruby code you will almost always omit braces/brackets on empty argument lists. That is x() is expressed simply as x. This keeps code more readable, especially when chaining, like x.y.z instead of x().y().z()
Other things to note include being able to read in all lines with readlines instead of what you have there where you manually compose it. Try:
make_database($stdin.readlines.map(&:chomp))
A more aggressive refactoring of your code looks like this:
def make_database(lines)
# Define a Hash based on key/value pairs in an Array...
Hash[
# ...where these pairs are based on the input lines...
lines.map do |line|
# ...which have comma-separated components.
line.split(',')
end.reject do |key, flag, _, value|
# Pick out only those that have the right flag.
flag.downcase == 'b'
end.map do |key, flag, _, value|
# Convert to a simple key/value pair array
[ key, value ]
end
]
end
That might be a little hard to follow, but once you get the hang of chaining together a series of otherwise simple operations your Ruby code will be a lot more flexible and far easier to read.
Let's say i have an array of hashes with identical set of keys like:
array = [
{attr1: val1, attr2: val2},
{attr1: val3, attr2: val4}
]
Thing is, i'd like to have some cool call like ActiveRecords' "where" to searh array above for specific elements. Something like
array.where(attr1: val1)
that will return all elements fitting the criteria. Using just plain Ruby.
Yes, there always is a good old .each but let's go full on pedal to the metal ruby-way here.
Thanks!
There is findand selectin ruby (for one or multiple results, respectively).
selected = array.select do |item|
item[:attr1] == 'something'
end
select will pass each element of array to the block and pick those where the block returns a truthy value.
find is similar but it will return the first element where the block returns a truthy value.
This has been asked before, but I can't find an answer that works. I have the following code:
[[13,14,16,11],[22,23]].each do |key,value|
puts key
end
It should in theory print:
0
1
But instead it prints:
13
22
Why does ruby behave this way?
Why does ruby behave this way?
It's because what actually happens internally, when each and other iterators are used with a block instead of a lambda, is actually closer to this:
do |key, value, *rest|
puts key
end
Consider this code to illustrate:
p = proc do |key,value|
puts key
end
l = lambda do |key,value|
puts key
end
Using the above, the following will set (key, value) to (13, 14) and (22, 23) respectively, and the above-mentioned *rest as [16, 11] in the first case (with rest getting discarded):
[[13,14,16,11],[22,23]].each(&p)
In contrast, the following will spit an argument error, because the lambda (which is similar to a block except when it comes to arity considerations) will receive the full array as an argument (without any *rest as above, since the number of arguments is strictly enforced):
[[13,14,16,11],[22,23]].each(&l) # wrong number of arguments (1 for 2)
To get the index in your case, you'll want each_with_index as highlighted in the other answers.
Related discussions:
Proc.arity vs Lambda.arity
Why does Hash#select and Hash#reject pass a key to a unary block?
You can get what you want with Array's each_index' method which returns the index of the element instead of the element itself. See [Ruby'sArray` documentation]1 for more information.
When you do:
[[13,14,16,11],[22,23]].each do |key,value|
before the first iteration is done it makes an assignment:
key, value = [13,14,16,11]
Such an assignment will result with key being 13 and value being 14. Instead you should use each_with_index do |array, index|. This will change the assignment to:
array, index = [[13,14,16,11], 0]
Which will result with array being [13,14,16,11] and index being 0
You have an array of arrays - known as a two-dimensional array.
In your loop, your "value" variable is assigned to the first array, [13,14,16,11]
When you attempt to puts the "value" variable, it only returns the first element, 13.
Try changing puts value to puts value.to_s which will convert the array to a string.
If you want every value, then add another loop block to your code, to loop through each element within the "value" variable.
[[1,2,3],['a','b','c']].each do |key,value|
value.each do |key2,value2|
puts value2
end
end
What I have:
hash = {id =>[string, number], id =>[string, number]}
I need to get the max value of number. I have been able to do this, but when I puts it.
I get:
id
string
number
What I need please
id string number
This is what I've tried:
This brings the max value to the top of list, but I need to exclude the rest of the list.
hash.each{|x, y| puts "#{x} #{y[0]} #{y[1]}"}.max
This returns the max value but displays it vertically
puts hash.max_by{|y| "#{y}"}
I have tried numerous other things and am having a hard time wrapping my head around this one.
Not sure if it matters but I am read this in from a file into a hash, the number is a float
The max here doesn’t do anything (since it is called on hash and its return value never used):
hash.each{|x, y| puts "#{x} #{y[0]} #{y[1]}"}.max
This is the same as doing puts on an array (since that’s what max_by returns), which prints each element on a separate line. You’re also unnecessarily converting your number to a string (which can result in unexpected comparison results):
puts hash.max_by{|y| "#{y}"}
Instead let’s just get the max key/value pair:
max = hash.max_by { |id, (string, number)| number }
#=> ["the-id", ["the-string", 3.14]]
Now we can flatten and join the array before puts-ing it:
puts max.flatten.join(' ')
# prints: the-id the-string 3.14
I would re-arrange the hash with number as the key, then use sort_by(): http://www.rubyinside.com/how-to/ruby-sort-hash
Following code return error "Can't convert String onto integer", please help
subject = ['eng','Math','Sci']
grade = ['grade 1','grade 2','grade 3']
subject.each do |sub|
puts ("some string")
grade[sub] .each do |grd|
puts ("some string")
end
end
grade[sub] .each do |grd| thats the problem.
Array elements are accessed by using a index of integer or a range of integers.
You are trying to access a array element by using the variable stored in sub. Since this is a ordinary .each loop it will loop all the elements in the array, in this case 'eng','Math','Sci'. If you want the position of for example 'eng' you could use a .each_with_index
it should probably just be
grade.each do |grd|
with each_with_index it would be
subject.each_with_index do |sub, index|
print sub
print grade[index]
end
If you want a subject -> grade collection it might be good to look into using a Hash like Dave Newton said.
{"eng" => "grade 1","Math" => "grade 2","Sci" => "grade 3"}.each do |subject, grade|
puts "#{subject| #{grade}"
end
When you do
grade[sub] .each do |grd|
Ruby expects sub to be using an integer to reference a position in the list of grades.
subject and grade are both arrays. They can only be accessed by their position. In your each loop, the block gets the actual element (in sub), not the position. So in line 5, you are trying to access grade['eng'], which will not work and produces the error. Note that unlike in e.g. PHP, an array and a hash (an associative array) are different things.
Guessing from your code, you might want to use each_index instead of each which will pass the index number to the block instead of the element.
I'm not sure I understand what you're trying to achieve; however, if you'd like to print subjects and grades and you're sure about the relative order of elements in the arrays, you could do this:
subject = ['eng','Math','Sci']
grade = ['grade 1','grade 2','grade 3']
subject.each_with_index do |sub, idx|
puts "#{sub} - #{grade[idx]}"
end
Output:
eng - grade 1
math - grade 2
sci - grade 3
An hash is however probably more suitable to your needs.