I'm simply curious about how the terms are used, so I have a question.
First, let me quote where the terms are used.
quote from Active Record document:
Active Record Query Interface — Ruby on Rails Guides
2 Conditions
Conditions can either be specified as a string, array, or hash.
2.2 Array Conditions
Now what if that number could vary, say as an argument from somewhere? The find
would then take the form:
Client.where("orders_count = ?", params[:orders])
I was confused
Client.where("orders_count = ?", params[:orders])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I was confused the document. Does the document refer to ^^^ part as Array?. I think ruby Array is [ ].
I found other site call this Argument Lists.
Programming Ruby: The Pragmatic Programmer's Guide
In Ruby It does what is called?
def f(a, b)
end
f(1, 2)
^^^^^^
Array or List?
Array and List?
How do you distinguish Array and List in Ruby?
Client.where("orders_count = ?", params[:orders]) ... this is Array?
No, this is not an array. These are two arguments.
How do you distinguish Array and List in Ruby?
Argument list refers to a method's arguments, it's not a class.
You can provide an argument list when defining a method:
def foo(a, b)
p a: a, b: b
end
foo is the method name and a, b is the argument list.
When calling a method, the passed arguments may also be called argument list:
foo 1, 2 # prints {:a=>1, :b=>2}
1, 2 is the argument list.
Converting between array and argument list
You can convert an array into a argument list by using *:
foo *[1, 2] # prints {:a=>1, :b=>2}
You can also convert an argument list to an array by prefixing an argument with * in the method definition:
def bar(*args)
p args
end
This allows the method to take a variable number of arguments:
bar 1, 2 # prints [1, 2]
In Ruby an argument list can be handled as both - an array or single arguments - depending on the assignment:
a, b, c = 1, 2, 3
a #=> 1
b #=> 2
c #=> 3
But:
array = 1, 2, 3
array #=> [1, 2, 3]
Related
I'm a confused with what .each_with_object does to an extent.
For example:
("a".."c").each_with_object("") {|i,str| str << i} # => "abc"
Also:
(1..3).each_with_object(0) {|i,sum| sum += i} #=> 0
(since integers are immutable).
After reading the example in the Ruby documentation, I'm
confused as to what the parameter inside object() actually does.
Regarding the flattify code below: I was confused with the usage of *; and why is the else statement just element? What is element intended to do?
def flattify(array)
array.each_with_object([]) do |element, flattened|
flattened.push *(element.is_a?(Array) ? flattify(element) : element)
end
end
confused with what #each_with_object does
You may have a better time understanding #each_with_object if you look at #inject first. #each_with_object is similar to #inject. Examples from http://blog.krishnaswamy.in/blog/2012/02/04/ruby-inject-vs-each-with-object/, included below:
#using inject
[[:tom,25],[:jerry,15]].inject({}) do |result, name_and_age|
name, age = name_and_age
result[name] = age
result
end
=> {:tom=>25, :jerry=>15}
#using each_with_object
[[:tom,25],[:jerry,15]].each_with_object({}) do |name_and_age, result|
name, age = name_and_age
result[name] = age
end
=> {:tom=>25, :jerry=>15}
See this Gist for example tests: https://gist.github.com/cupakromer/3371003
In depth article: http://engineering-blog.alphasights.com/tap-inject-and-each_with_object/
UPDATE
would #inject as opposed to #each_with_object work in this flattening code?
Yes, see below. I've illustratively refactored your flattening code to use #inject. Additionally, I removed the dependency on the "splat" operator (http://ruby-doc.org/core-2.3.1/doc/syntax/calling_methods_rdoc.html#label-Array+to+Arguments+Conversion)
# Flattens nested array; uses `Enumerable#inject`
# #see http://ruby-doc.org/core-2.3.1/Enumerable.html#method-i-inject
# #param arg [Array] contains objects of any type including any amount of nested arrays.
# #raise [StandardError] if arg is not Array class
# #return [Array] flat array comprised of elements from arg.
# #example
# flattify([nil, [1, [:two, [3.0], {4=>5}], "6"]]) #=> [nil, 1, :two, 3.0, {4=>5}, "6"]
def flattify(arg)
raise "arg is not Array" unless arg.is_a?(Array)
# variable ret_var used here to illustrate method's return in verbose fasion
# supplied [] used as initial value for flattened_array
ret_var = arg.inject([]) do |flattened_array, element|
# check if element class is Array
if element.is_a?(Array)
# Array#concat because flattify returns Array
# same as: a = a + b
# same as: a += b
flattened_array.concat(
# recursively call flattify with element as arg
# element is an Array
flattify(element)
)
else
# Array#push because element is not an Array
# same as: a << b
flattened_array.push(element)
end
# used in next iteration as value for first arg above in: "|flattened_array, element|"
# OR returned on last iteration, becoming value of ret_var above
flattened_array
end
# explicit return for illustrative purposes
return ret_var
end
UPDATE 2
may [I] ask why the splat operator is used here? I am still a bit
confused on that. It seems the code is [looping] each time and pushing
it in the flattened array, whats the point of the *?
flattened.push *(element.is_a?(Array) ? flattify(element) : element)
The above block is a "ternary operation" (see: https://en.wikipedia.org/wiki/Ternary_operation), which is explained here: https://stackoverflow.com/a/4252945/1076207 like so:
if_this_is_a_true_value ? then_the_result_is_this : else_it_is_this
Compare the flattify examples with each other:
# each_with_object
flattened.push *(flattify(element))
# inject
flattened_array.concat(flattify(element))
Here the * splat operator (see: https://stackoverflow.com/search?q=%5Bruby%5D+splat) is doing the same thing as Array#concat. However, the splat allows flattened.push to accept either of the two possible types the ternary operation returns: 1) an Array; or 2) whatever element is. For illustration, notice how the splat operator prevents nesting:
# each_with_object with splat
flattened = [1,2,3]
flattened.push *([4,5,6]) # => [1, 2, 3, 4, 5, 6]
flattened.push *(7) # => [1, 2, 3, 4, 5, 6, 7]
# each_with_object without splat
flattened = [1,2,3]
flattened.push ([4,5,6]) # => [1, 2, 3, [4, 5, 6]]
flattened.push (7) # => [1, 2, 3, [4, 5, 6], 7]
Conversely, Array#concat will only accept an array. If the same ternary operation was used and returned an element, it would cause an error:
# inject
flattened_array = [1,2,3]
flattened_array.concat([4,5,6]) # => [1, 2, 3, 4, 5, 6]
flattened_array.concat(7) # => TypeError: no implicit conversion of Fixnum into Array
In summary, both versions of flattify achieve the same result. However, #each_with_object uses #push, a ternary operation and a splat operator; while #inject uses an if/else statement, #concat and #push.
UPDATE 3
When we did each with object([]), the last parameter became an
array.
Yes. It becomes an array and continues to be that same array throughout the iterations until it's passed back.
So with inject the first one becomes an array?
Yes. The first one becomes the passed in array, but only for the first iteration, then it's replaced by the result of the code block for each subsequent iteration.
how does our code know if element is defined as an int and
flattened_Array is an array?
element.is_a?(Array) # => true or false
When element is Array class this method returns true, and if not returns false. false means that it's anything but an array including int.
For more info, see: http://ruby-doc.org/core-2.3.1/Object.html#method-i-is_a-3F
The parameter you pass to object() acts as accumulator for intermediate values between iterations. On entry to each iteration it is passed as flattened argument.
* is a splat operator. It converts an array to a list of arguments being passed to the push method.
Here element will take value of each array element consequently.
All this piece of code does is just recursively flat all nested arrays inside initial array.
But Ruby has built in flatten method which does the same thing.
For example
ar = [1, 2, [3, 4, [5, 6]]]
ar.flatten
#=> [1, 2, 3, 4, 5, 6]
Just to compare with your flattify
flattify ar
#=> [1, 2, 3, 4, 5, 6]
# flattened.push *(element.is_a?(Array) ? flattify(element) : element)
# flattened is the array ...object([])
# element.is_a?(Array) ...is the element in this iteration an array?
# if true flattify(element) again... meaning recursively apply method again
# if false push element onto the object([]) aka flattened
# the () around the ternary allow the recursion to complete
# the * operator can then pass the elements "passing the array condition"
# cont'd... onto flattened.push(4, 5, 6) as list of args instead of an array
# array object with range of string elements
("a".."c").each_with_object([]) do |element, the_object|
p the_object.class # returns Array
p element.class # returns String
end
# hash object with range of fixnum elements
(1..3).each_with_object({}) do |element, the_object|
p the_object.class # returns Hash
p element.class # returns Fixnum
end
I have a hash, whose values are an array of size 1:
hash = {:start => [1]}
I want to unpack the arrays as in:
hash.each_pair{ |key, value| hash[key] = value[0] } # => {:start=>1}
and I thought the *-operator as in the following would work, but it does not give the expected result:
hash.each_pair{ |key, value| hash[key] = *value } # => {:start=>[1]}
Why does *value return [1] and not 1?
Because the []= method applied to hash takes only one argument in addition to the key (which is put inside the [] part), and a splatted/expanded array, which is in general a sequence of values (which coincidentally happens to be a single element in this particular case) cannot be directly accepted as the argument as is splatted. So it is accepted by the argument of []= as an array after all.
In other words, an argument (of the []= method) must be an object, but splatted elements (such as :foo, :bar, :baz) are not an object. The only way to interpret them as an object is to put them back into an array (such as [:foo, :bar, :baz]).
Using the splat operator, you can do it like this:
hash.each_pair{|key, value| hash.[]= key, *value}
sawa and Ninigi already pointed out why the assignment doesn't work as expected. Here's my attempt.
Ruby's assignment features work regardless of whether you're assigning to a variable, a constant or by implicitly invoking an assignment method like Hash#[]= with the assignment operator. For the sake of simplicity, I'm using a variable in the following examples.
Using the splat operator in an assignment does unpack the array, i.e.
a = *[1, 2, 3]
is evaluated as:
a = 1, 2, 3
But Ruby also allows you to implicitly create arrays during assignment by listing multiple values. Therefore, the above is in turn equivalent to:
a = [1, 2, 3]
That's why *[1] results in [1] - it's unpacked, just to be converted back to an array.
Elements can be assigned separately using multiple assignment:
a, b = [1, 2, 3]
a #=> 1
b #=> 2
or just:
a, = [1, 2, 3]
a #=> 1
You could use this in your code (note the comma after hash[key]):
hash = {:start => [1]}
hash.each_pair { |key, values| hash[key], = values }
#=> {:start=>1}
But there's another and more elegant way: you can unpack the array by putting parentheses around the array argument:
hash = {:start => [1]}
hash.each_pair { |key, (value)| hash[key] = value }
#=> {:start=>1}
The parentheses will decompose the array, assigning the first array element to value.
Because Ruby is acting unexpectedly smart here.
True, the splash operator will "fold" and "unfold" an array, but the catch in your code is what you do with that fanned value.
Take this code into account:
array = ['a', 'b']
some_var = *array
array # => ['a', 'b']
As you can see the splat operator seemingly does nothing to your array, while this:
some_var, some_other_var = *array
some_var # => "a"
somet_other_var # => "b"
Will do what you'd expect it does.
It seems ruby just "figures" if you splat an array into a single variable, that you want the array, not the values.
EDIT: As sawa pointed out in the comments, hash[key] = is not identical to variable =. []= is an instance Method of Hash, with it's own C-Code under the hood, which COULD (in theory) lead to different behaviour in some instances. I don't know of any example, but that does not mean there is none.
But for the sake of simplicity, we can asume that the regular variable assignment behaves exactly identical to hash[key] =.
Please explain how this piece of code can return two arrays.
def keysAndValues(data)
[data.keys, data.values]
end
keysAndValues method does return a single array (of two arrays inside of it), but its output can be interpreted as two arrays. Let me explain:
single_array = ["hello", "world"]
puts single_array # => ["hello", "world"]
first_element, second_element = single_array
puts first_element # => "hello"
puts second_element # => "world"
The reason this notation is possible is the implementation of Ruby's assignment (=) operator. Thus, calling a, b = keysAndValues(data) makes both variables a and b filled. But beware, although the outcome technically makes sense this might be unexpected in some situations:
first, second = 1 # first is 1, second is nil
There are also some other uses for multiple assignment, consider the following case:
a, b, *c = [1, 2, 3, 4] # note the asterisk symbol here
puts a # => 1
puts b # => 2
puts c # => [3,4]
Ruby supports parallel assignment. This is a simple example:
foo, bar = 1, 2
foo # => 1
bar # => 2
Your method is returning two values inside an array:
keys_and_values(
{
a: 1,
b: 2
}
).size # => 2
which are assigned via = to the two values on the left side of the equation. The values in the array are references to where the keys and values sub-arrays are located:
foo, bar = keys_and_values(
{
a: 1,
b: 2
}
)
foo.object_id # => 70159936091440
bar.object_id # => 70159936091420
Ruby isn't unique with supporting parallel assignment; It is used in other languages too. Any decent Ruby manual will talk about this. It's good to understand because the assignment to variables, or passing multiple parameters to methods is something you'll encounter repeatedly in Ruby. Do a search for more information.
Also, in Ruby we don't name methods using camelCase, such as "keysAndValues". Instead we use snake_case: keys_and_values.
It seems that the arguments are copied when using the splat operator to pass arguments to a block by reference.
I have this:
def method
a = [1,2,3]
yield(*a)
p a
end
method {|x,y,z| z = 0}
#=> this puts and returns [1, 2, 3] (didn't modified the third argument)
How can I pass these arguments by reference? It seems to work if I pass the array directly, but the splat operator would be much more practical, intuitive and maintainable here.
In Ruby when you write x = value you are creating a new local variable x whether it existed previously or not (if it existed the name is simply rebound and the original value remains untouched). So you won't be able to change a variable in-place this way.
Integers are immutable. So if you send an integer there is no way you can change its value. Note that you can change mutable objects (strings, hashes, arrays, ...):
def method
a = [1, 2, "hello"]
yield(*a)
p a
end
method { |x,y,z| z[1] = 'u' }
# [1, 2, "hullo"]
Note: I've tried to answer your question, now my opinion: updating arguments in methods or blocks leads to buggy code (you have no referential transparency anymore). Return the new value and let the caller update the variable itself if so inclined.
The problem here is the = sign. It makes the local variable z be assigned to another object.
Take this example with strings:
def method
a = ['a', 'b', 'c']
yield(*a)
p a
end
method { |x,y,z| z.upcase! } # => ["a", "b", "C"]
This clearly shows that z is the same as the third object of the array.
Another point here is your example is numeric. Fixnums have fixed ids; so, you can't change the number while maintaining the same object id. To change Fixnums, you must use = to assign a new number to the variable, instead of self-changing methods like inc! (such methods can't exist on Fixnums).
Yes... Array contains links for objects. In your code when you use yield(*a) then in block you works with variables which point to objects which were in array. Now look for code sample:
daz#daz-pc:~/projects/experiments$ irb
irb(main):001:0> a = 1
=> 1
irb(main):002:0> a.object_id
=> 3
irb(main):003:0> a = 2
=> 2
irb(main):004:0> a.object_id
=> 5
So in block you don't change old object, you just create another object and set it to the variable. But the array contain link to the old object.
Look at the debugging stuff:
def m
a = [1, 2]
p a[0].object_id
yield(*a)
p a[0].object_id
end
m { |a, b| p a.object_id; a = 0; p a.object_id }
Output:
3
3
1
3
How can I pass these arguments by reference?
You can't pass arguments by reference in Ruby. Ruby is pass-by-value. Always. No exceptions, no ifs, no buts.
It seems to work if I pass the array directly
I highly doubt that. You simply cannot pass arguments by reference in Ruby. Period.
NOTE: mischa's splat on GitHub has lots of cool interactive examples of * in action.
By googling, I found that one way to iterate over a range of numbers in Ruby (your classic C-style for loop)
for (i = first; i <= last; i++) {
whatever(i);
}
is to do something like this
[*first..last].each do |i|
whatever i
end
But what exactly is going on with that [*first..last] syntax? I played around with irb and I see this:
ruby-1.9.2-p180 :001 > 0..5
=> 0..5
ruby-1.9.2-p180 :002 > [0..5]
=> [0..5]
ruby-1.9.2-p180 :003 > [*0..5]
=> [0, 1, 2, 3, 4, 5]
ruby-1.9.2-p180 :004 > *0..5
SyntaxError: (irb):4: syntax error, unexpected tDOT2, expecting tCOLON2 or '[' or '.'
*0..5
^
Everything I've read online discusses the unary asterisk as being useful for expanding and collapsing arguments passed to a method, useful for variable length argument lists
def foo(*bar)
bar
end
foo 'tater' # => ["tater"]
foo 'tater', 'tot' # => ["tater", "tot"]
and I get that, but I don't see how it applies to the expansion being done in my block example above.
To be clear, I know that The Ruby Way is to iterate over an array or collection, not to use the array length and iterate with an integer index. However, in this example, I really am dealing with a list of integers. :)
[*1..10]
is the same thing as
(1..10).to_a # call the "to array" method
Instances of the Array class you have created implement Enumerable so your loop works. On classes that define a to_a method, you can use the splat operator syntax with brackets. Splat does a lot more than just call #to_a though, and would be worth a Google search on its own.
Now, in your case, the Range class itself is already an Enumerable so you could just do:
(first..last).each do |v|
...
end
[first..last] is an array containing only 1 range object. [*first..last] is an array containing the elements of that range having been sent in as an argument list. * works in the context of an argument list.
It is called a splat operator. If you use it within certain positions like an argument position or an array, it will expand into its elements:
a = [1]
[*a, 3] # => [1, 3]
b = [1, 2]
[*b, 3] # => [1, 2, 3]
You can't use it bare or in ranges:
*a..3 # => error.
(*a..3) # => error.
When you have something that is not an array, it returns itself:
a = 1
[*a, 3] # => [1, 3]