Say I have an array of hashes like so:
items = [
{:user=>1, :amount=>484},
{:user=>2, :amount=>0},
{:user=>3, :amount=>8633}
]
To get the amount for a user, I'd so something like this:
items.select{|key| key[:user] == 1 }.first[:amount]
But if there isn't a user with a given ID, then that doesn't work:
> items.select{|key| key[:user] == 8 }.first[:amount]
# NoMethodError: undefined method `[]' for nil:NilClass
So how can I return amount if it finds the item, but return 0 if there's nothing?
First of all, you can use find instead of select since you only want the first match. If you want a one-liner, you can do something like this:
(items.find { |key| key[:user] == 8 } || { :amount => 0 })[:amount]
If you happen to have Rails or ActiveSupport kicking around then you could use try and to_i (while remembering that nil.to_i == 0) like this:
items.find { |k| key[:user] == 1 }.try(:fetch, :amount).to_i
try just calls a method (fetch in this case) but always returns nil if the receiver is nil so nil.try(:fetch, :amount) is nil but some_hash.try(:fetch, :amount) is some_hash.fetch(:amount), it is a handy tool for swallowing up nils without adding a bunch of extra conditionals. AFAIK, the andand gem does similar things without requiring all the ActiveSupport.
You could catch the Exception NoMethodError
begin
#code you want to execute
rescue NoMethodError
puts "0"
end
Three ways:
#1
def my_method(user, items)
items.each {|h|
return h[:amount] if h.key?(:user) && h[:user] == user && h.key?(:amount)}
0
end
my_method(1, items) #=> 484
my_method(5, items) #=> 0
#2
def my_method(user, items)
items.reduce(0) { |v,h|
(h.key?(:user) && h[:user] == user && h.key?(:amount)) ? h[:amount] : v }
end
#3
def my_method(user, items)
hash = items.find { |h| h.key?(:user) && h[:user] == user) }
(hash && hash.key?(:amount)) ? hash[:amount] : 0
end
put rescue 0 on the end
items.select{|key| key[:user] == 8 }.first[:amount] rescue 0
Here's a variant using some built-in Ruby fallback mechanisms
items.find(->{{}}){|e| e[:user] == 8}.fetch(:amount, 0)
First, starting from the end of the line, fetch, which returns the value at a provided hash key. One thing that makes fetch different from using regular hash square brackets is that, if the key isn't there, fetch throws as a key error instead of returning nil. That's a neat trick, but the other useful thing is that it can take an optional second argument, which is the default thing to return if that key isn't found:
test = { a: 1, b:2 }
test[:c] #=> nil
test.fetch(:c) #=> raise KeyError
test.fetch(:c, 0) #=> 0
Handy, but you can't call fetch on nil, which is the default return for find if it finds nothing.
Luckily, find also lets you assign a default return value instead of nil. You can pass an object that responds to call, and that object will specify how to handle a find with no matches.
Here's a short lambda that returns an empty hash: ->{{}} and here it is passed as a first argument to find, before the usual block
items.find(->{{}}){ |key| key[:user] == 8 } #=> {}
All together: items.find(->{{}}){|e| e[:user] == 8}.fetch(:amount, 0) -- now find will always return some sort of hash, which will respond to fetch, and fetch can return a default if a key is missing.
Related
So the goal here is to print the index of the element if the element is in the array or print -1 if the element is not in the array. I have to do this using loops. PLEASE HELP!
def element_index(element, my_array)
while my_array.map.include? element do
puts my_array.index(element)
break
end
until my_array.include? element do
puts -1
break
end
end
p element_index("c", ["a","b","c"])
If it's OK to use Array#index, then
def element_index(elem, collection)
collection.index(elem) || -1
end
Or if it's a homework that you should not use Array#index, or you want to do this on arbitrary collections, then
def element_index(elem, collection)
collection.each_with_index.reduce(-1) do |default, (curr, index)|
curr == elem ? (return index) : default
end
end
By the way, I always turn to Enumerable#reduce when I want to iterate over a collection (array, map, set, ...) to compute one value.
This is an easy way but maybe it doesn't meet the criteria for "using loops":
def element_index(x, arr)
arr.index(x) || -1
end
element_index("c", ["a","b","c"]) #=> 2
element_index("d", ["a","b","c"]) #=> -1
To explicitly use a loop:
def element_index(x, arr)
arr.each_index.find { |i| arr[i] == x } || -1
end
As pointed out in the comments, we could instead write
arr.each_index.find(->{-1}) { |i| arr[i] == x }
element_index("c", ["a","b","c"]) #=> 2
element_index("d", ["a","b","c"]) #=> -1
I know this is an assignment, but I'll first cover this as if it were real code because it's teaching you some not-so-great Ruby.
Ruby has a method for doing this, Array#index. It returns the index of the first matching element (there can be more than one), or nil.
p ["a","b","c"].index("c") # 2
p ["a","b","c"].index("d") # nil
Returning -1 is inadvisable. nil is a safer "this thing does not exist" value because its never a valid value, always false (-1 and 0 are true in Ruby), and does not compare equal to anything but itself. Returning -1 indicates whomever came up with this exercise is converting it from another language like C.
If you must, a simple wrapper will do.
def element_index(element, array)
idx = array.index(element)
if idx == nil
return -1
else
return idx
end
end
I have to do this using loops.
Ok, it's homework. Let's rewrite Array#index.
The basic idea is to loop through each element until you find one which matches. Iterating through each element of an array is done with Array#each, but you need each index, that's done with Array#each_index. The element can be then gotten with array[idx].
def index(array, want)
# Run the block for each index of the array.
# idx will be assigned the index: 0, 1, 2, ...
array.each_index { |idx|
# If it's the element we want, return the index immediately.
# No need to spend more time searching.
if array[idx] == want
return idx
end
}
# Otherwise return -1.
# nil is better, but the assignment wants -1.
return -1
end
# It's better to put the thing you're working on first,
# and the thing you're looking for second.
# Its like verb( subject, object ) or subject.verb(object) if this were a method.
p index(["a","b","c"], "c")
p index(["a","b","c"], "d")
Get used to using list.each { |thing| ... }, that's how you loop in Ruby, along with many other similar methods. There's little call for while and for loops in Ruby. Instead, you ask the object to loop and tell it what to do with each thing. It's very powerful.
I have to do this using loops.
You approach is very creative. You have re-created an if statement using a while loop:
while expression do
# ...
break
end
Is equivalent to:
if expression
# ...
end
With expression being something like array.include? element.
How can I do the opposite?
To invert a (boolean) expression, you just prepend !:
if !expression
# ...
end
Applied to your while-hack:
while !expression do
# ...
break
end
The whole method would look like this:
def element_index(element, my_array)
while my_array.include? element do
puts my_array.index(element)
break
end
while !my_array.include? element do
puts -1
break
end
end
element_index("c", ["a","b","c"])
# prints 2
element_index("d", ["a","b","c"])
# prints -1
As I said at the beginning, this approach is very "creative". You are probably supposed to find the index using a loop (see Schwern's answer) instead of calling the built-in index.
I'm doing Ruby exercises for the Odin Project (programming newcomer), and we're tasked with recreating Ruby's #count method. Given an array like:
nil_list = [false, false, nil]
Observations:
nil_list.count == 3, its length.
nil_list.count(nil) == 1, the number of times nil is present in the list.
nil_list given a block behaves as expected.
When I try to recreate it, here's what I come up with:
module Enumerable
def my_count (find = nil)
result = 0
for i in self
if block_given?
result += 1 if yield(i)
elsif find != nil
result += 1 if i == find
else return self.length
end
end
return result
end
end
The problem here is that this doesn't actually count nils if we enter nil in as an argument, since this is the same (according to my code) as there not being an argument.
ie, nil_list.my_count(nil) == 3 instead of 1.
While typing this question I had a slightly different idea:
module Enumerable
def my_count (find = "")
result = 0
for i in self
if block_given?
result += 1 if yield(i)
elsif find != ""
result += 1 if i == find
else return self.length
end
end
return result
end
end
So this fixes the problem I was having with searches for nil, but now nil_list.count("") == 0 whereas nil_list.my_count("") == 3. Same issue, just relocated to "" which I assume doesn't ever get used.
At this point I'm just curious: how does the actual count method prevent this issue from happening?
You can write def my_count(*args) and check then length of args. I'd write:
module Enumerable
def my_count(*args)
case
when args.size > 1
raise ArgumentError
when args.size == 1
value = args.first
reduce(0) { |acc, x| value == x ? acc + 1 : acc }
when block_given?
reduce(0) { |acc, x| yield(x) ? acc + 1 : acc }
else
reduce(0) { |acc, x| acc + 1 }
end
end
end
The ugly truth is: in most Ruby implementations, Enumerable#count isn't actually written in Ruby. In MRI, YARV and MRuby, it's written in C, in JRuby and XRuby, it's written in Java, in IronRuby and Ruby.NET, it's written in C#, in MacRuby, it's written in Objective-C, in MagLev, it's written in Smalltalk, in Topaz, it's written in RPython, in Cardinal, it's written in PIR or PASM, and so on. And it not only is not written in Ruby, it's also got privileged access to the internals of the execution engine, in particular, it can access the arguments that were passed, which you cannot do from Ruby.
Such overloaded methods appear all over the core library and standard library, but they can't easily be written in Ruby. The implementers cheat by either writing them in languages that do support overloading (e.g. C# or Java), or they give them privileged access to the internals of the execution engine.
The standard workaround in Ruby is to (ab)use the fact that the default value of an optional parameter is just a normal Ruby expression and that local variables in a default value expression are visible inside the method body:
def my_count(find = (find_passed = false; nil))
if find_passed # find was passed
# do something
else
# do something else
end
end
A second possibility is to use some unforgeable unique token as the default value:
undefined = Object.new
define_method(:my_count) do |find = undefined|
if undefined.equal?(find) # find was not passed
# do something
else
# do something else
end
end
Can someone help me understand the code below? I have been trying to add 'puts' to see what it does but keep getting errors. This is a code from an exercise/example I was supposed to already know, yet I have no idea what it is doing. It seems to me that I should have an items array defined before the method for this to make sense, but even then I wasn't able to make sense of it.
# search for `target_item` in `items`; return the array index
# where it first occurs
def find_item(items, target_item)
i = 0
while i < items.count
current_item = items[i]
if current_item == target_item
# found item; return its index; stop here and exit the
# method
return i
end
i += 1
end
# return nil to mean item not found
nil
end
You are correct about needing an array items to run your code. You will also need a value for the variable target_item. These two variables are passed as arguments to the method find_item. If the method finds the value of target_item in the array items it will return the index of that item in the array (0 for the first element, 1 for the second, and so on.); if it does not, it will return nil.
If you run your code in IRB, it will return => :find_item. That means it found no errors. That does not mean there are no errors, however, as error can surface when the code is run.
Let's try it with some data. After running the code defining the method (in IRB), run these three lines:
items = ['dog', 'cat', 'pig']
target_item = 'cat'
find_item(items, target_item) #=> 1
where #=> 1 means that the method returned 1, meaning that it found target_items at offset 1 in items, which is what we would expect.
This is equivalent to:
find_item(['dog', 'cat', 'pig'], 'cat')
On the other hand,
find_item(items, 'bird') #=> nil
as the array does not contain 'bird'. The two lines
current_item = items[i]
if current_item == target_item
can be combined into one:
if items[i] == target_item
but that's beside the point, because Rubiests would not write the method this way. Instead, you would commonly do it like this:
def find_item(items, target_item)
items.each_with_index { |e,i| return i if e == target_item }
nil
end
[Edit: #Mark has correctly pointed out a much simpler way of writing the method. I will stick with this version, however, as I think you will learn something useful by seeing how it works.]
The built-in Ruby method each_with_index is from the Enumgerable module, which is always available to you (along with 60+ other method defined in that module). When you reference a method, it's best to include the class or module in which it was defined. This method is Enumerable#each_with_index. Among other things, its easier to find documentation for the method if you know the class or module its from.
Although each_with_index was defined in the Enumerable module, it is an "enumerator", meaning that it feeds the values from items to the following block, denoted by {...} (as here) or do ... end.
Run this in IRB:
items = ['dog', 'cat', 'pig']
target_item = 'cat'
enum = items.each_with_index
#=> #<Enumerator: ["dog", "cat", "pig"]:each_with_index>
You can convert this to an array to see what it will feed the block:
enum.to_a
#=> [["dog", 0], ["cat", 1], ["pig", 2]]
Consider now the first element the enumerator will be passed to the block:
["dog", 0]
The block variables, e and i in
items.each_with_index { |e,i| return i if e == target_item }
will therefore be set equal to:
e => "dog"
i => 0
Therefore, e == target_item becomes "dog" == "cat", which is false, so return 0 is not executed. For the next items, however,
e => "cat"
i => 1
As "cat" == "cat" is true, return 1 is executed, and we are finished. If items did not contain cat, the enumerator would enumerate all items and control would go to the following statement, nil. As that is the last statement executed, the method would return nil.
This code appears to be a poorly rewritten Array#index. This is identical:
items.index(target_item)
I'm trying to recreate Enumerable's count method as found in "Projects: Advanced Building Blocks".
The definition in the Ruby docs is that count
"Returns the number of items in enum through enumeration. If an argument is given, the number of items in enum that are equal to item are counted. If a block is given, it counts the number of elements yielding a true value."
What exactly is the default argument though?
The way I approached this so far is as follows:
The parameter is set to something when no argument is passed so:
Case, when self is not a string:
when argument given and block given (eg. [1,2,3].count(3) { |x| x == 3 }):
returns warning and count of the argument.
when argument given and no block (eg. [1,2,3].count(3)):
returns count of the argument.
when no argument and no block (eg. [1,2,3].count):
returns size of the instance.
else (no argument given and block given) (eg. [1,2,3].count { |x| x == 3 }:
returns count based on specifications given in block.
The two questions I have are basically:
What is the default argument for count?
What is the global variable used in warning?
Here's my code:
module Enumerable
def my_count arg='default value'
if kind_of? String
# code
else # I think count works the same way for hashes and arrays
if arg != 'default value'
count = 0
for index in 0...size
count += 1 if arg == self[index]
end
warn "#{'what goes here'}: warning: block not used" if block_given?
count
else
return size if arg == 'default value' && !block_given?
count = 0
for index in 0...size
count += 1 if yield self[index]
end
count
end
end
end
end
Don't use a default argument. Use *args to collect all the arguments into an array.
def my_count(*args, &block)
if args.length == 1 && !block_given?
# use args[0]
elsif args.length == 1 && block_given?
# use block
elsif args.length == 0 && !block_given?
# no argument/block
else
# raise error
end
end
I need to find out if data[i][j][k] element exists, but I don't know if data[i] or data[i][j] not nil themselves.
If I just data[i][j][k].nil?, it throw undefined method [] for nil:NilClass if data[i] or data[i][j] is nil
So, I am using now
unless data[i].nil? or data[i][j].nil? or data[i][j][k].nil?
# do something with data[i][j][k]
end
But it is somehow cumbersome.
Is there any other way to check if data[i][j][k] exists without data[i].nil? or data[i][j].nil? or data[i][j][k].nil? ?
I usually do:
unless (data[i][j][k] rescue false)
# do something
end
Here are three different alternatives:
Shorten it
You can shorten it slightly by using "!" instead of .nil?:
!data[i] or !data[i][j] or !data[i][j][k]
You could get rid of the repetition by doing this:
((data[i] || [])[j] || [])[k].nil?
Abstract away these details
Both of the code snippets above are nasty enough that I would probably not write them more than once in a code base.
A three-dimensional array seems complicated enough that you shouldn't be accessing it directly in lots of places in your code. You should consider wrapping it inside an object with an appropriate name:
class My3DWorld
def initialize
# set up #data
end
# Gets the point or returns nil if it doesn't exist.
def get_point(i, j, k)
#data[i] && #data[i][j] && #data[i][j][k]
end
end
Use a hash instead
However, ultimately, I wonder whether you really need a 3D array. Another more Ruby-like way to implement this data structure would be to use a hash and use i,j,k coordinate tuples as the keys. Unless this is a huge structure and you need the performance characteristics of a 3D array, I recommend looking at my other answer here:
https://stackoverflow.com/a/20600345/28128
The new feature "refinements" is an option:
module ResponsiveNil
refine NilClass do
def [](obj)
nil
end
end
end
using ResponsiveNil
a = [[1]]
p a[2][3][4] #=> nil
You can shorten slightly to
if data[i] && data[i][j] && data[i][j][k]
# do something with data[i][j][k]
end
You can also you the "andand" gem which allows you to write:
data[i].andand[j].andand[k]
If you are willing to monkey patch Array, you could define a method to enable this, such as:
class Array
def index_series(*args)
result = self
args.each do |key|
result = result[key]
return nil if result.nil?
end
result
end
end
which would let you do:
data.index_series(i, j, k)
The following permits any amount of nesting, and allows for the possibility that an element of the array has a value of nil:
def element_exists?(arr, *indices)
if arr.is_a? Array
return true if indices.empty?
return false if arr.size <= (i = indices.pop)
element_exists?(arr[i], *indices)
else
indices.empty?
end
end
data = [[0,1],[2,nil]]
element_exists?(data) # => true
element_exists?(data, 1) # => true
element_exists?(data, 2) # => false
element_exists?(data, 1, 1) # => true
element_exists?(data, 1, 2) # => false
element_exists?(data, 1, 1, 1) # => false