Parentheses in block variables - ruby

Given
a = [[:a, :b, :c]]
1) I understand this
a.each{|(x, y), z| p z} # => :b
that there are two variables (x, y) and z, so the third element :c is thrown away, and z matches :b. And I understand this
a.each{|(x, y), z| p y} # => nil
that (x, y) matches :a, and since it is not an array, there are no elements to it, and so y matches nil.
But how does
a.each{|(x, y), z| p x} # => :a
work? I expect nil to be returned.
2) Why are the return values like this?
a.each{|(x, y)| p x} #=> :a
a.each{|(x, y)| p y} #=> :b
I expect them to both return nil.

It's because of the syntax of parallel assignment.
a = [[:a, :b, :c]]
So a.each has only one element to iterate, which is [:a, :b, :c].
In the first case:
(x, y), z = [:a, :b, :c]
#=> x == :a, y == nil, z == :b
Here (x, y) is an array to match the first element :a, and x gets it, then z simply matches the second element :b.
And in the second case:
(x, y) = [:a, :b, :c]
#=> x == :a, y == :b
Here (x, y) as an entire array matches the array [:a, :b, :c], so x and y get :a and :b respectively.
This is just like requiring the "args + optional args (keyword args) + rest args" combination match provided arguments. It is just smart enough to take arguments by sequence.
Another smart example:
(a,b) = 1,2
=> [1, 2] # array match
#=> a == 1, b == 2
(a,b)=[1,2]
=> [1, 2] # array match
#=> a == 1, b == 2
In either case above, it will simply make the best guess on what it should take.

Related

How to run enum functions with conditions of another enumerable?

Finding it difficult to put my problem into words..
I have a function to sort an enumerable with specific conditions and i have another enumerable specified with Enum.take(x, -1)
Enum.map(list, fn x -> Enum.take(x, 1) end)
|> Enum.map(fn x -> Enum.join(x) end)
|> Enum.sort(&(byte_size(&1) > byte_size(&2)))
|> Enum.with_index
What i want to do is apply this function to the second enumerable but with the conditions of the first, such that the items match up with the original position of the first, sort of like this:
iex(1)> x = [1,2,3,4,5]
[1, 2, 3, 4, 5]
iex(2)> y = [:a,:b,:c,:d,:e]
[:a, :b, :c, :d, :e]
...
# arbitrary code to sort x such that x = [5,4,3,2,1]
# and y is sorted with the same conditions applied to x such that..
...
iex(4)> y = [:e,:d,:c,:b,:a]
[:e, :d, :c, :b, :a]
How might i achieve this? What sort of workaround can i use or are there any specific language features that can do what i need?
I hope my problem makes sense and thank you for reading :>
What I think you're trying to do is a list of one property sorted by another property. I think you could do something like this:
xs = [5, 4, 3, 2, 1]
ys = [:a, :b, :c, :d, :e]
Enum.zip(xs, ys) # Produces a list of pairs like {5, :a}, etc.
|> Enum.sort_by(fn {x, _y} -> x end)
|> Enum.map(&elem(&1, 1))
# => [:e, :d, :c, :b, :a]
So basically pack your data into pairs, sort the pairs and then unpack the second element.
You actually can just Enum.sort() instead of the sort_by, because the default order for tuples is to compare by first element, then the second if first is equal, etc., but this is maybe more explicit.

manual binary search wrap up

Would anyone be so kind to explain to me how I finish my recursive binary search problem? The recursive aspect is confusing me. I would love for an explanation on what thats doing if possible!!! I think I need to increment the 'half' value I have within the if or elsif but I don't know what it would look like. Please suggest ways to add to the code I currently have rather than refactor to something simpler... at least at first! Thanks!
def binary_search(letter, array)
half = (array.length - 1)/2
if letter == array[half]
return half
end
if letter > array[half] && letter <= array[-1]
array = array[half...array.length]
binary_search(letter, array)
elsif letter < array[half] && letter >= array[0]
array = array[0...half]
binary_search(letter, array)
else
nil
end
end
arr = [:A, :B, :C, :D, :E, :F, :G]
p binary_search(:C, arr)
half was part of the problem. With a length of 2, half would be 0, and you would "split" your array in a full array and an empty array : recursion would never end.
You also need to keep an index, and add half to it when you consider the 2nd Array :
def binary_search(letter, array, i=0)
puts "Been here for #{array} with #{i}"
half = array.length / 2
if letter == array[half]
return i + half
end
if letter > array[half] && letter <= array[-1]
binary_search(letter, array.drop(half), i + half)
elsif letter < array[half] && letter >= array[0]
binary_search(letter, array.take(half), i)
else
nil
end
end
arr = [:A, :B, :C, :D, :E, :F, :G]
p binary_search(:C, arr)
p binary_search(:G, arr)
It outputs
Been here for [:A, :B, :C, :D, :E, :F, :G] with 0
Been here for [:A, :B, :C] with 0
Been here for [:B, :C] with 1
2
Been here for [:A, :B, :C, :D, :E, :F, :G] with 0
Been here for [:D, :E, :F, :G] with 3
Been here for [:F, :G] with 5
6

Do block-local variables exist just to enhance readability?

Block-local variables are to prevent a block from tampering with variables outside of its scope.
Using a block-local variable
x = 10
3.times do |y; x|
x = y
end
x # => 10
But this is easily done by declaring a regular block parameter. A new local scope is created for that parameter, which takes precedence over previous variables/scopes.
Without using a block-local variable
x = 10
3.times do |y, x|
x = y
end
x # => 10
The variable x outside the block doesn't get changed in either case. Is there any need for block-local variables other than for enhancing readability?
The block parameter is a real parameter, while a block local variable is not.
If you give yield two parameters like this:
def foo
yield("hello", "world")
end
Calling
x = 10
foo do |y; x|
puts x
end
x is nil inside the function because only the first argument is assigned to y, the second argument is discarded.
Calling
x = 10
foo do |y, x|
puts x
end
#=>world
x gets the parameter correctly as "world".
To expand on Yu Hao's answer, the difference between block parameters and block-local not obvious when calling a method that only yields one value, but consider a method that yields multiple values:
def frob
yield 1, 2, 3
end
If you pass this a block with a single argument, you get the first value:
frob { |a| a.inspect }
# => "1"
But if you pass a block with multiple arguments, you get multiple values, even if you pass too few arguments, or too many:
frob { |a, b, c| [a, b, c].inspect }
# => "[1, 2, 3]"
frob { |a, b| [a, b].inspect }
# => "[1, 2]"
frob { |a, b, c, d| [a, b, c, d].inspect }
# => "[1, 2, 3, nil]"
If you pass block-scoped variables, however, those are independent of the yielded value(s):
frob { |a; b, c| [a, b, c].inspect }
# => "[1, nil, nil]"
Something similar happens with methods that yield an array, except that when you pass a block with a single argument, it gets the whole array:
def frobble
yield [1, 2, 3]
end
frobble {|a| a.inspect }
# => "[1, 2, 3]"
Multiple arguments, however, destructure the array --
frobble {|a, b| [a, b].inspect }
# => "[1, 2]"
-- while a block-scoped variable doesn't:
frobble {|a; b| [a, b].inspect }
# => "[[1, 2, 3], nil]"
(Even with a block-scoped variable present, though, multiple values will still destructure the array: frobble {|a, b; c| [a, b, c].inspect } will get you "[1, 2, nil]".)
For more discussion and examples, see also this answer.

Is it possible to iterate three arrays at the same time?

We can iterate two arrays at the same time using Array's zip method like:
#budget.zip(#actual).each do |budget, actual|
...
end
Is it possible to iterate three arrays? Can we use the transpose method to do the same?
>> [1,2,3].zip(["a","b","c"], [:a,:b,:c]) { |x, y, z| p [x, y, z] }
[1, "a", :a]
[2, "b", :b]
[3, "c", :c]
transpose also works but, unlike zip, it creates a new array right away:
>> [[1,2,3], ["a","b","c"], [:a,:b,:c]].transpose.each { |x, y, z| p [x, y, z] }
[1, "a", :a]
[2, "b", :b]
[3, "c", :c]
Notes:
You don't need each with zip, it takes a block.
Functional expressions are also possible. For example, using map: sums = xs.zip(ys, zs).map { |x, y, z| x + y + z }.
For an arbitrary number of arrays you can do xss[0].zip(*xss[1..-1]) or simply xss.transpose.

finding out which object has n instances in ruby

I have an array: x = [a, b, c, d, e, f, g, h] which can have objects from 1 to 9
Firstly, I have to count IF any of these objects is present 3 times. I don't want to write
if (x.count(1) == 3) or (x.count(2) == 3) ...etc...
is there a way to shorten this, like below?
x.count { |obj| obj } == 3
Secondly, if I know that an object has been found with 3 instances, how can I find out which one was it? (1 or 2 or 3.....)
x = [:a, :b, :b, :b, :c, :c, :c]
counted = Hash[
x.group_by do |e|
x.count(e)
end.map do |count, items|
[count, items.uniq]
end
]
p counted[3] #=> [:b, :c]
How does this work? Let's follow the steps. First, let's group the items by count:
grouped_by_count = x.group_by do |e|
x.count(e)
end
This produces a hash with the keys being the counts, and the values being the list of non-unique items having that count:
p grouped_by_count
#=> {1=>[:a], 3=>[:b, :b, :b, :c, :c, :c]}
We'd really rather have unique items, though, so let's do that transform:
grouped_by_count_unique = grouped_by_count.map do |count, items|
[count, items.uniq]
end
p grouped_by_count_unique
#=> [[1, [:a]], [3, [:b, :c]]]
That gives us an array of arrays, and not a hash. Fortunately, it's easy to turn an array of arrays into a hash:
counted = Hash[grouped_by_count_unique]
p counted
# => {1=>[:a], 3=>[:b, :c]}
Now just put the pieces together eliminating the temporaries and you get the answer at the top.

Resources