ruby convert array into function arguments - ruby

Say I have an array. I wish to pass the array to a function. The function, however, expects two arguments. Is there a way to on the fly convert the array into 2 arguments?
For example:
a = [0,1,2,3,4]
b = [2,3]
a.slice(b)
Would yield an error in Ruby. I need to input a.slice(b[0],b[1]) I am looking for something more elegant, as in a.slice(foo.bar(b))
Thanks.

You can turn an Array into an argument list with the * (or "splat") operator:
a = [0, 1, 2, 3, 4] # => [0, 1, 2, 3, 4]
b = [2, 3] # => [2, 3]
a.slice(*b) # => [2, 3, 4]
Reference:
Array to Arguments Conversion

Use this
a.slice(*b)
It's called the splat operator

Related

Why a new call of a method with exclamation mark affects all previous calls of that method?

I'm sorry if this is a duplicate - I couldn't find anything similar in the existing posts.
I understand the difference between methods like shuffle and shuffle!. However, I am confused why calling the method more than once would result in changing the variables of all objects that previously referred to it? I'd expect once we apply a method, that the variable gets a value and we're done with it. Not that it continues to refer to the method call and the argument passed and that it would get re-evaluated later on.
I thought it's best to demonstrate with an example:
irb(main):001:1* def shuffle(arr)
irb(main):002:1* arr.shuffle!
irb(main):003:0> end
=> :shuffle
irb(main):004:0> arr = [1,2,3,4]
=> [1, 2, 3, 4]
irb(main):005:0> one = shuffle(arr)
=> [4, 2, 3, 1]
irb(main):006:0> two = shuffle(arr)
=> [1, 2, 4, 3]
irb(main):007:0> one
=> [1, 2, 4, 3]
So, here I'd expect one to stay [4, 2, 3, 1]. However, with each new call, all previous ones would get equated to the latest result of the method call. I realise it should have something to do with calling it with the same argument arr, but still doesn't quite make sense.
Array#shuffle! shuffles the array in-place and returns its receiver:
ary = [1, 2, 3, 4]
ary.equal?(ary.shuffle!) #=> true
Assigning the result from shuffle! to another variable doesn't change this. It merely results in two variables referring to the same array:
a = [1, 2, 3, 4]
b = a.shuffle!
a #=> [2, 4, 1, 3]
b #=> [2, 4, 1, 3]
a.equal?(b) #=> true
You probably want a new array. That's what Array#shuffle (without !) is for:
a = [1, 2, 3, 4]
b = a.shuffle
a #=> [1, 2, 3, 4]
b #=> [2, 4, 1, 3]
Even if shuffle returns the element in the original order, you'll get another array instance:
a = [1, 2, 3, 4]
b = a.shuffle until b == a
a #=> [1, 2, 3, 4]
b #=> [1, 2, 3, 4]
a.equal?(b) #=> false

How to use a recursive array

I have array, named a and define it with [1, 2, 3].
Next, I pushed it to itself:
a = [1, 2, 3]
a << a
and the result I get is:
#=> [1, 2, 3, [...]]
When I want to get the last element of array using a.last I get:
a.last
#=> [1, 2, 3, [...]]
#even
a.last.last.last
#=> [1, 2, 3, [...]]
What is going on, when we would push array to itself?
Yes, I understand that this should create a recursive array, but what can we do with it?
In Ruby variables, array elements etc. are object references. So when you do a = [1, 2, 3], there will be an array somewhere in memory and the a variable is a reference to that memory. Now when you do a << a, a[4] will also be a reference to that object. So in effect a now contains a reference to itself.
a = [1, 2, 3]
a << a.dup
a.last
=> [1, 2, 3]
a.last.last
=> 3
Maybe this is what you wanted. This just insert an array [1, 2, 3] as the last item of the a array. In the way you did you put a reference at the end of the a array and this becomes recursive.

How to insert an array in the middle of an array?

I have a Ruby array [1, 4]. I want to insert another array [2, 3] in the middle so that it becomes [1, 2, 3, 4]. I can achieve that with [1, 4].insert(1, [2, 3]).flatten, but is there a better way to do this?
You could do it the following way.
[1,4].insert(1,*[2,3])
The insert() method handles multiple parameters. Therefore you can convert your array to parameters with the splat operator *.
One form of the method Array#[]= takes two arguments, index and length. When the latter is zero and the rvalue is an array, the method inserts the elements of the rvalue into the receiver before the element at the given index (and returns the rvalue). Therefore, to insert the elements of:
b = [2,3]
into:
a = [1,4]
before the element at index 1 (4), we write:
a[1,0] = b
#=> [2,3]
a #=> [1,2,3,4]
Note:
a=[1,4]
a[0,0] = [2,3]
a #=> [2,3,1,4]
a=[1,4]
a[2,0] = [2,3]
a #=> [1,4,2,3]
a=[1,4]
a[4,0] = [2,3]
a #=> [1,4,nil,nil,2,3]]
which is why the insertion location is before the given index.
def insert_array receiver, pos, other
receiver.insert pos, *other
end
insert_array [1, 4], 1, [2, 3]
#⇒ [1, 2, 3, 4]
or, the above might be achieved by monkeypatching the Array class:
class Array
def insert_array pos, other
insert pos, *other
end
end
I believe, this is short enough notation to have any additional syntax sugar. BTW, flattening the result is not a good idea, since it will corrupt an input arrays, already having arrays inside:
[1, [4,5]].insert 1, *[2,3]
#⇒ [1, 2, 3, [4,5]]
but:
[1, [4,5]].insert(1, [2,3]).flatten
#⇒ [1, 2, 3, 4, 5]
My option without array#insert method
array = [1,2,3,6,7,8]
new_array = [4,5]
array[0...array.size/2] + new_array + array[array.size/2..-1]

Three ways to create a range, hash, array in ruby

I am doing a tutorial course on ruby and it asks for 3 ways to create range, hash, array.
I can only think of 2: (1..3) and Range.new(1,3) (and similarly for hash and array).
What is the third way?
The tutorial in question is The Odin Project
Ranges may be constructed using the s..e and s...e literals, or with ::new.
Ranges constructed using .. run from the beginning to the end inclusively.
Those created using ... exclude the end value. When used as an iterator, ranges return each value in the sequence.
(0..2) == (0..2) #=> true
(0..2) == Range.new(0,2) #=> true
(0..2) == (0...2) #=> false
Read More Here
For Arrays there's Array::[] (example taken directly from the docs):
Array.[]( 1, 'a', /^A/ ) # => [1, "a", /^A/]
Array[ 1, 'a', /^A/ ] # => [1, "a", /^A/]
[ 1, 'a', /^A/ ] # => [1, "a", /^A/]
Similarly there's Hash::[]. Not sure about Ranges; in fact, the docs (as far as I can tell) only mention literals and Range::new.
I can't see why you'd use these over a literal, but there you go.
You can also make a exclusive range, using (1...4), which if turned into an array would become [1, 2, 3]
(1..3) is an inclusive range, so it contains all numbers, from 1 to 3, but if you used (1...3), having 3 dots instead of 2 makes it exclusive, so it contains all numbers from 1, up to but not including 3.
As for arrays and hashes, #to_a, Array#[], #to_h, and Hash#[] will work.
(1..3).to_a
=> [1, 2, 3]
Array[1, 2, 3]
=> [1, 2, 3]
[[1, 2], [3, 4], [5, 6]].to_h
=> {1=>2, 3=>4, 5=>6}
Hash[ [[1, 2], [3, 4], [5, 6]] ]
=> {1=>2, 3=>4, 5=>6}
But they are probably looking for Array#[] and Hash#[] on the array and hash part.

Confusion with splat operator and Range in Ruby

I was trying to see how splat operator worked with range in Ruby. To do so ran the below code in my IRB:
*a = (1..8)
#=> 1..8
When the above is fine, what happened with below? means why a gives []?
*a,b = (1..8)
#=> 1..8
b
#=> 1..8
a
#=> []
means why b gives []?
a,*b = (1..8)
#=> 1..8
a
#=> 1..8
b
#=> []
What precedence took place in the below Rvalues ?
a,*b = *(2..8),*3,*5
# => [2, 3, 4, 5, 6, 7, 8, 3, 5]
b
# => [3, 4, 5, 6, 7, 8, 3, 5]
a
# => 2
Here is another try to the splat operator(*) :-
While I know that in parallel assignment we couldn't use multiple splatted variable, but why not the same when splat is used with Rvalues?
*a,*b = [1,2,3,4,5]
SyntaxError: (irb):1: syntax error, unexpected tSTAR
*a,*b = [1,2,3,4,5]
^
from /usr/bin/irb:12:in `<main>'
The above is as expected.
a = *2,*3,*5
#=> [2, 3, 5]
But couldn't understand the above.
I think of parallel assignment as setting an array of variables equal to another array with pattern matching.
One point is that a range is a single value until you convert it to an array or splat it. For instance [1..5] which is a one element array of the range 1..5 and not [1,2,3,4,5]. To get the array of ints you need to do (1..5).to_a or [*(1..5)]
The first one i think is the trickiest. If the splatted var is assigned to one element, the var itself must be a one-element array:
*a = 5
a
# => [ 5 ]
For the next two, splat takes 0 or more not already assigned values into an array. So the following makes sense:
*a, b = (1..8)
is like
*a, b = "hey"
which is like
*a, b = [ "hey" ]
so *a is [] and b is "hey" and by the same logic that if *a is nothing, a must be an empty array. Same idea for
a, *b = (1..5)
For the next one, the range is splatted, so the assignment makes a lot of sense again:
[*(2..4), 9, 5]
# => [2, 3, 4, 9, 5]
And parallel assignment with a splat again. Next one is similar:
[*3, *4, *5]
# => [3, 4, 5]
So that's like
a = 3, 4, 5
which is like
a = [3, 4, 5]
splat has a very low precedence, almost anything will be executed earlier than the splat.
The code is splatting but the result is thrown away: b = *a = (1..8); p b #=> [1, 2, 3, 4, 5, 6, 7, 8]

Resources