string = "test"
suitable_inventories = [1, 2, 3]
inventories ||= string.empty? ? suitable_inventories : []
inventories
Based on my understanding of Ruby, I was expecting the above code to produce:
[]
Instead, it is returning:
[1, 2, 3]
Did I misunderstand how ||= works?
||= is a conditional assignment. The below two are basically equivalent:
foo ||= 42
bar = 42 unless bar
In other words ||= will do an assignment only if the variable holds nil or false as a value ATM. Due to some peculiarities, this also means variable that didn't have a value assigned already.
So if inventories already holds some array, a new one will not be reassigned with ||=.
a ||= b means a || a = b that is,
If a is set (not nil or false), then a remains as it is, using short-circuit behaviour of || operator
Otherwise, a will be false or nil resulting in execution of second part of expression after ||, i.e a=b
In your case, inventories must be already assigned (not nil) thats why it is not []
inventories ||= string.empty? ? suitable_inventories : []
is equivalent to :
inventories || inventories = string.empty? ? suitable_inventories : []
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 11 months ago.
Improve this question
When there are only two states for instance variable "a", either it has a value or not, and there is only one possible state for instance variable "b", it has a value. I can set "a" equal to "b" if "a" does not have a value via:
#a = nil
#b = "world"
def set_a
#a ||= #b
end
set_a
p #a
=> "world"
#a = "hello"
#b = "world"
def set_a
#a ||= #b
end
set_a
p #a
=> "hello"
But, what if there is more than one option for "a" to be set to? For example, I want to set instance variable "a" to either "b" or "c" depending on which has a value. In the program, only either "b" or "c" will have a value, always one but never both. Is there a way, using operators to write something like the following:
if #a == nil
if #b == nil
#a = #c
else
#a = #b
end
end
I was trying to write something that looks like this:
def set_a
#a ||= (#b || #c)
end
But, if #a had a value it would remain. Which is what I want. However, if #a was nil, then it would not take on the value of either #b or #c, it would remain nil.
Thank you for you time.
You can write
Module.const_defined?(:A) ? A : (B == true ? B : C)
to obtain the value of A.
A not defined
B = true
Module.const_defined?(:A) ? A : (B == true ? B : C)
#=> true
A not defined
B = false
Module.const_defined?(:A) ? A : (B == true ? B : C)
#=> 42
A = "cat"
B = false
C = 42
Module.const_defined?(:A) ? A : (B == true ? B : C)
#=> "cat"
See Module#const_defined?.
Constants and local variables will raise NameError if not defined. You probably want to use instance variables instead, as they are auto-vivified as nil when referenced even if they haven't been assigned to yet. For example:
#a ||= (#b || #c)
#=> nil
If either #a or #b are truthy, then things will probably work as you expect. However, if all three variables are falsey, then #a will be assigned the value of #c even if #c evaluates as nil or false.
Just tripped over an instruction that is unclear:
a = 5 || 3 # ==> 5
verdict = true || false # ==> true
Since || is the same as or, how and/or why would this statement be used? It doesn't really demonstrate any decision making other than choosing the first option always.
I know the ||= assignment, but this is different. Looking for clarification on the usage above of || alone.
a = b || c
What this statement does is, it tells Ruby to 'assign the value of b to a, unless b is falsy, if b is falsy, assign the value of c to a. In case b isn't falsy, the c statement won't get executed.
A good example where you could use this is if you're getting a variable from somewhere and you're not sure if it's going to be nil or not, so you create a variable like c as a second option.
If you have a method that takes in a hash as a parameter for example, and you want to return the value of the element from the hash that has the key 'b' for example, but the hash parameter won't always have a 'b' key, so you write something like this
def value_of_b(hash)
b_val = hash['b'] || 'unknown'
puts "The value of b is :#{b_val}"
end
h = {'a' => 1, 'b' => 2}
value_of_b(h)
#=>The value of b is :2
m = {'a' => 1}
value_of_b(m)
#=>The value of b is :unknown
Another example that comes to my mind is accessing an array element that doesn't exist
[1,2,3][3] || "default"
#=> "default"
Or having a default value for Rails params hash:
#name = params[:name] || "no name provided"
From what I understand, a ||= 7 means the following:
if a has a value, continue using that value, but if it does NOT have one, then set it to 7.
Here is what happens though.
If i have a and b as:
a = true
b = false
then
a ||= b => true
(in my interpretation: since 'a' DOES have a value, it remains that, and does not get equated to 'false' - so far so good.)
However, if i have them switched up like:
a = false
b = true
then a ||= b => true
so in this case my logic does not work, since it should return false, as "since 'a' has a value, it should not be assigned the value of 'b'", which apparently happens here.
Am I missing something?
a ||= b
is equivalent to
a || a = b
this means b value is assigned to a if a is falsy, i.e. false or nil.
There's a nice idiom for adding to lists stored in a hash table:
(hash[key] ||= []) << new_value
Now, suppose I write a derivative hash class, like the ones found in Hashie, which does a deep-convert of any hash I store in it. Then what I store will not be the same object I passed to the = operator; Hash may be converted to Mash or Clash, and arrays may be copied.
Here's the problem. Ruby apparently returns, from the var= method, the value passed in, not the value that's stored. It doesn't matter what the var= method returns. The code below demonstrates this:
class C
attr_reader :foo
def foo=(value)
#foo = (value.is_a? Array) ? (value.clone) : value
end
end
c=C.new
puts "assignment: #{(c.foo ||= []) << 5}"
puts "c.foo is #{c.foo}"
puts "assignment: #{(c.foo ||= []) << 6}"
puts "c.foo is #{c.foo}"
output is
assignment: [5]
c.foo is []
assignment: [6]
c.foo is [6]
When I posted this as a bug to Hashie, Danielle Sucher explained what was happening and pointed out that "foo.send :bar=, 1" returns the value returned by the bar= method. (Hat tip for the research!) So I guess I could do:
c=C.new
puts "clunky assignment: #{(c.foo || c.send(:foo=, [])) << 5}"
puts "c.foo is #{c.foo}"
puts "assignment: #{(c.foo || c.send(:foo=, [])) << 6}"
puts "c.foo is #{c.foo}"
which prints
clunky assignment: [5]
c.foo is [5]
assignment: [5, 6]
c.foo is [5, 6]
Is there any more elegant way to do this?
Assignments evaluate to the value that is being assigned. Period.
In some other languages, assignments are statements, so they don't evaluate to anything. Those are really the only two sensible choices. Either don't evaluate to anything, or evaluate to the value being assigned. Everything else would be too surprising.
Since Ruby doesn't have statements, there is really only one choice.
The only "workaround" for this is: don't use assignment.
c.foo ||= []
c.foo << 5
Using two lines of code isn't the end of the world, and it's easier on the eyes.
The prettiest way to do this is to use default value for hash:
# h = Hash.new { [] }
h = Hash.new { |h,k| h[k] = [] }
But be ware that you cant use Hash.new([]) and then << because of way how Ruby store variables:
h = Hash.new([])
h[:a] # => []
h[:b] # => []
h[:a] << 10
h[:b] # => [10] O.o
it's caused by that Ruby store variables by reference, so as we created only one array instance, ad set it as default value then it will be shared between all hash cells (unless it will be overwrite, i.e. by h[:a] += [10]).
It is solved by using constructor with block (doc) Hash.new { [] }. With this each time when new key is created block is called and each value is different array.
EDIT: Fixed error that #Uri Agassi is writing about.
I am trying to understand this code snippet:
while row = input.gets
row.strip!
next if row.empty?
valuesplit = row.split("---")
a, b = valuesplit[1..2]
unless a == b
$hash1[a] ||= {} <--------------What is this line doing? How is the whole loop
$hash1[a][b] = true being traversed?
if $hash1[b] && $hash1[b][a] <-----Can you please describe this if() loop
$hash2[a] ||= []
$hash2[a] << b
$hash2[b] ||= []
$hash2[b] << a
end
end
end
NOTE: $hash1 = {}
$hash2 = {}
Thanks!
UPDATE
Input:
junkdata1 value1 value2
junkdata2 value3 value4
junkdata3 value5 value6
and so on.
Updated the code lines with comments too.
# loops by reading in every line of the input
# (input can be a file or another I/O object)
# every line is stored successively in a variable
# called "row"
while row = input.gets
# removes leading and trailing whitespace from
# the string that is stored in the "row" variable
row.strip!
# if the string is empty, continue to the next
# line (go back to beginning of loop)
next if row.empty?
# split the string into an array of substrings
# based on the "---" delimiter
valuesplit = row.split("---")
# assign the second substring in the valuesplit
# array to a variable called a, and the third to
# a variable called b
a, b = valuesplit[1..2]
# if a and b are different
unless a == b
# initialize the hash1 dictionary's a entry
# to an empty sub-dictionary if it is null
$hash1[a] ||= {}
# in the hash1 dictionary, set a's entry
# to a dictionary that has b as the entry
# and true as the value
$hash1[a][b] = true
# if the value for the b entry in the hash1
# dictionary is true (not false or null) AND the value for a's
# entry of the dictionary found at the b
# entry of the hash1 dictionary is true
if $hash1[b] && $hash1[b][a]
# initialize the hash2 dictionary's a entry
# to an empty arraylist if it null or false
$hash2[a] ||= []
# add b to this arraylist
$hash2[a] << b
# initialize the hash2 dictionary's b entry
# to an empty arraylist if it null or false
$hash2[b] ||= []
# add a to this arraylist
$hash2[b] << a
end # end of the if $hash1[b]... statement
end # end of the unless a == b statement
end # end of the gets loop
I still feel the question is a little vague. You should also note that I have ignored your example data. Given your example data, the results of both $hash1 and $hash2 are empty hashes.
For your first question:
$hash1[a] ||= {}
The above is a combination of two things
First is an index into a hash which I'll assume you're familiar with.
The second is sort of a conditional assignment. As an example:
blah ||= 1
The above says, assign the value 1 to blah as long as blah is nil. If blah is not nil then the assignment is not performed.
For the if statement we'll need some context:
if $hash1[b] && $hash1[b][a] #if the pair exists reversed
$hash2[a] ||= [] #initialize the array for the second
$hash2[a] << b #append the third to the second's array
$hash2[b] ||= [] #likewise for the reverse
$hash2[b] << a
end
If we assume that the initial values of $hash1 and $hash2 are {} as you note, and if we assume the input are a series of --- delimted values, then given the following data set:
foo---b---c
foo---c---a
foo---a---b
foo---b---a
foo---a---d
foo---d---a
The value of $hash1 would be:
{"a"=>{"b"=>true, "d"=>true}, "b"=>{"a"=>true, "c"=>true}, "c"=>{"a"=>true}, "d"=>{"a"=>true}}
Where $hash2 would be:
{"a"=>["b", "d"], "b"=>["a"], "d"=>["a"]}
Given this, I can make an educated guess that the block of code is generating a dictionary of relationships. In the above, $hash1 lists whether a given value refers to other values. A sort of a truth test. If you wanted to know if A referred to B you could just use:
$hash1['a']['b']
If the result is true then the answer is yes.
$hash2 is a sort of dictionary of two way relationships.
If you checked:
$hash2['a']
You would find an array of all the things to which A refers which also refer to A.
Cheers.
foo ||= bar
This is shorthand for
foo = foo || bar
Just like it is shorthand in Java. It basically means "set foo equal to the default of bar if foo is currently nil, otherwise leave it alone" (nil evaluates to false in ruby)