Why does each_cons yield arrays instead of multiple values? - arrays

I just wanted to apply a binary operation to consecutive elements in an array, e.g.:
[1, 2, 3, 4].each_cons(2).map { |a, b| a.quo(b) }
#=> [(1/2), (2/3), (3/4)]
This is a contrived example, the operation doesn't really matter.
I was surprised, that I couldn't just write:
[1, 2, 3, 4].each_cons(2).map(&:quo)
#=> NoMethodError: undefined method `quo' for [1, 2]:Array
This is because each_cons doesn't yield multiple values, but an array containing the values.
It works like this:
def each_cons_arrays
return enum_for(__method__) unless block_given?
yield [1, 2]
yield [2, 3]
yield [3, 4]
end
each_cons_arrays.map(&:quo)
#=> NoMethodError: undefined method `quo' for [1, 2]:Array
And I was hoping for:
def each_cons_values
return enum_for(__method__) unless block_given?
yield 1, 2
yield 2, 3
yield 3, 4
end
each_cons_values.map(&:quo)
#=> [(1/2), (2/3), (3/4)]
What's the rationale behind this? Why could it be preferable to always have an array?
And by the way, with_index on the other hand does yield multiple values:
[1, 1, 1].each.with_index(2).map(&:quo)
#=> [(1/2), (1/3), (1/4)]

From my experience, it helps to think of multiple values in ruby as an array.
It think of it at as
[1,2,3].each_cons(2) do |iter|
a,b = iter
do_stuff(a,b)
end
If you want to do it like that, I'd add the quo method to a custom class, and do
class Foobar
def initialize(a,b)
#a = a
#b = b
end
def quo
do_stuff
end
end
[1,2,3]
.each_cons(2)
.map { |a,b| Foobar.new(a,b) }
.map(:quo)
Would that work for your usecase?

Related

Idiomatic way to receive an object or array of objects, transform them, and add to existing array

I have a method that can receive either a single object, or an array of those objects. (The YAML file I'm pulling from sometimes has foo: bar and sometimes a list of foo: [bar1, bar2].)
Each time this method is called, it should transform the value or values, and add them to an internal array.
I originally had this:
class YamlEater
def initialize
#foos = []
end
def add(o)
if o.is_a?(Array)
o.each{ self.add(_1) }
else
#foos << FooItem.new(o)
end
end
end
After I needed to use that pattern in many different methods, I changed it to a non-recursive, more terse version that's splat-heavy and perhaps only a golfer could love:
def add(o)
#foos.push(*[*o].map{ FooItem.new(_1) })
end
I'm thinking there's a more elegant way to accomplish the same goal, and hoping someone will share it.
I'd probably use Kernel#Array and Array#concat:
def add(o)
#foos.concat(Array(o).map { FooItem.new(_1) })
end
That nicely handles o.nil? as a bonus.
As I'm sure you know, you could eliminate one splat by using Array#concat:
[1,2].concat([*[4,5]].map { |e| 2*e })
#=> [1, 2, 8, 10]
[1,2].concat([*4].map { |e| 2*e })
#=> [1, 2, 8]
or Array#+:
[1,2] + [*[4,5]].map { |e| 2*e }
#=> [1, 2, 8, 10]
[1,2] + [*4].map { |e| 2*e }
#=> [1, 2, 8]

Using self.dup, but failing rspec test to not modify original array

I'm creating a method to transpose square 2-d arrays. My method passes every test, except the "does not modify original array" one. I'm only working on the duped array, so I'm confused on why the test is failing.
Code:
class Array
def my_transpose
orig_arr = self.dup; array = []
orig_arr[0].length.times do
temp_arr = []
orig_arr.each { |arr| temp_arr << arr.shift }
array << temp_arr
end
array
end
end
RSpec:
describe Array do
describe "#my_transpose" do
let(:arr) { [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
] }
let(:small_arr) { [
[1, 2],
[3, 4]
] }
it "transposes a small matrix" do
expect(small_arr.my_transpose).to eq([
[1, 3],
[2, 4]
])
end
it "transposes a larger matrix" do
expect(arr.my_transpose).to eq([
[1, 4, 7],
[2, 5, 8],
[3, 6, 9]
])
end
it "should not modify the original array" do
small_arr.my_transpose
expect(small_arr).to eq([
[1, 2],
[3, 4]
])
end
it "should not call the built-in #transpose method" do
expect(arr).not_to receive(:transpose)
arr.my_transpose
end
end
end
Output:
7) Array#my_transpose should not modify the original array
Failure/Error: expect(small_arr).to eq([
expected: [[1, 2], [3, 4]]
got: [[], []]
(compared using ==)
# ./spec/00_array_extensions_spec.rb:123:in `block (3 levels) in <top (required)>'
When you call dup on an array, it only duplicates the array itself; the array's contents are not also duplicated. So, for example:
a = [[1,2],[3,4]]
b = a.dup
a.object_id == b.object_id # => false
a[0].object_id == b[0].object_id # => true
Thus, modifications to a itself are not reflected in b (and vice versa), but modifications in the elements of a are reflected in b, because those elements are the same objects.
That being the case, the problem crops up here:
orig_arr.each { |arr| temp_arr << arr.shift }
arr is an element of orig_arr, but it is also an element of self. If you did something like remove it from orig_arr, you would not also remove it from self, but if you change it, it's changed, no matter how you are accessing it, and as it turns out, Array#shift is a destructive operation.
Probably the smallest change you could make to your code to make it work as you expect would be to use each_with_index, and then use the index into arr, rather than calling arr.shift, so:
orig_arr.each_with_index { |arr,i| temp_arr << arr[i] }
In fact, though, once you're doing that, you're not doing any destructive operations at all and you don't need orig_arr, you can just use self.
The original array isn’t being modified, but the arrays within it are, as dup is a shallow clone.
xs = [[1,2],[3,4]]
ids = xs.map(&:object_id)
xs.my_transpose
ids == xs.map(&:object_id) #=> true
Since shift is a mutating operation (being performed on the nested array elements), you need to dup the elements within the array as well, e.g.
orig_arr = dup.map(&:dup)
With this modification, your test should pass.

Sort a hash of arrays by one of the arrays in ruby

In Ruby, is there a short and sweet way to sort this hash of arrays by score descending:
scored = {:id=>[1, 2, 3], :score=>[8.3, 5, 10]}
so it looks like this?:
scored = {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
I couldnt find an example where I can sort arrays within a hash like this? I could do this with some nasty code but I feel like there should be a 1 or 2 liner that does it?
You could use sort_by
scored = {:id=>[1, 2, 3], :score=>[8.3, 5, 10]}
scored.tap do |s|
s[:id] = s[:id].sort_by.with_index{ |a, i| -s[:score][i] }
s[:score] = s[:score].sort_by{ |a| -a }
end
#=> {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
order = scored[:score].each_with_index.sort_by(&:first).map(&:last).reverse
#=> [2,0,1]
scored.update(scored) { |_,a| a.values_at *order }
#=> {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
If scored is to not to be mutated, replace update with merge.
Some points:
Computing order makes it easy for the reader to understand what's going on.
The second line uses the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged (which here is all keys). This is a convenient way to modify hash values (generally), in part because the new hash is returned.
I sorted then reversed, rather than sorted by negated values, to make the method more rubust. (That is, the elements of the arrays that are the values can be from any class that implements <=>).
With Ruby 2.2+, another way to sort an array arr in descending order is to use Enumerable#max_by: arr.max_by(arr.size).to_a.
The first line could be replaced with:
arr = scored[:score]
order = arr.each_index.sort_by { |i| arr[i] }.reverse
#=> [2,0,1]
Here is one possible solution. It has an intermediate step, where it utilizes a zipped version of the scores object, but produces the correct output:
s = scored.values.inject(&:zip).sort_by(&:last).reverse
#=> [[3, 10], [1, 8.3], [2, 5]]
result = { id: s.map(&:first), score: s.map(&:last) }
#=> { :id => [3, 1, 2], :score => [10, 8.3, 5] }

Destructive reject from an array returning the values rejected

Is there a sensible way to do the following:
I want to take an array and select specific items from the array according to conditions, removing them from the array as they go.
(I basically want to split the contents of an array into categories).
array = [1,2,3,4,5,6,7,8]
less_than_three = array.reject_destructively{|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = array.reject_destructively{|v| v>5}
=> [6,7,8]
array
=> [3,4,5]
I've tried delete_if, select!, reject! and none of them seem to be able to give you the affected items whilst leaving the array with the rest.
Unless I'm going mad, which is entirely possible.
As I understood the question, you do not want to produce two new objects. Here you go:
class Array
def carve!
dup.tap { delete_if &Proc.new } - self
end
end
array = [1,2,3,4,5,6,7,8]
p array.carve! { |v| v < 3 }
#⇒ [1, 2] # returned by Array#carve method
p array
#⇒ [3, 4, 5, 6, 7, 8] # remained in original array
Using this solution, array.__id__ remains the same. And this is the golfiest answer all around :)
You can build your own method for this...
class Array
def extract(&block)
temp = self.select(&block)
self.reject!(&block)
temp
end
end
then...
a = [1, 2, 3, 4, 5]
a.extract{|x| x < 3}
=> [1,2]
p a
=> [3, 4, 5]
EDIT: If you don't want to monkey patch (but monkey patching isn't evil in itself) you can do it with a vanilla method...
def select_from_array(array, &block)
temp = array.select(&block)
array.reject!(&block)
temp
end
array = [1,2,3,4,5,6,7,8]
less_than_three = select_from_array(array){|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = select_from_array(array){|v| v>5}
=> [6,7,8]
array
=> [3,4,5]
In rails 6 there is a method extract!:
a = [1, 2, 3] #=> [1, 2, 3]
a.extract! { |num| num.odd? } #=> [1, 3]
a #=> [2]
irb(main):001:0> array = [1,2,3,4,5,6,7,8]
=> [1, 2, 3, 4, 5, 6, 7, 8]
irb(main):002:0> array.partition{|v| v < 3}
=> [[1, 2], [3, 4, 5, 6, 7, 8]]
is there a specific reason, why this has to be destructive ?
Will this help
class Array
def reject_destructively(&block)
arr = self.select(&block)
arr.each{ |i| self.delete(i) }
arr
end
end
array = [1,2,3,4,5,6,7,8]
p less_than_three = array.reject_destructively{|v| v<3}
#=> [1,2]
p array
#=> [3,4,5,6,7,8]
p more_than_five = array.reject_destructively{|v| v>5}
#=> [6,7,8]
p array
#=> [3,4,5]
The above code can be simplified further to look like:
class Array
def reject_destructively(&block)
self.select(&block).each{ |i| self.delete(i) }
end
end
Ok. This works, avoids monkey patching, keeps it to one line...etc, but it's damn ugly....
less_than_three = array.dup - array.reject!{|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = array.dup - array.reject!{|v| v>5}
=> [6,7,8]
array
=> [3,4,5]
module Enumerable
def reject_destructively
array=[]
self.each do |y|
if yield(y)
array<<y
end
end
array.each do |x|
self.delete(x)
end
return array
end
end
array=[10,9,2,1,3,45,52]
print less_than_three = array.reject_destructively{|v| v < 3}
print array
You can use group_by to get all of the elements that satisfy the condition in one group, and all of the rest in the other.
For example
[1,2,3,4,5].group_by{|i| i > 3}
gives
{false=>[1, 2, 3], true=>[4, 5]}
More information is available at http://ruby-doc.org/core-2.1.1/Enumerable.html#method-i-group_by

Rules on Parenthesis for Block Variables

I ran across the following piece of code while reading The Ruby Way:
class Array
def invert
each_with_object({}).with_index { |(elem, hash), index| hash[elem] = index }
end
end
I want to make sure that I understand what the parenthesis are doing in (elem, hash).
The first method (each_with_object({})) will yield two objects to the block. The first object will be the element in the array; the second object will be the hash. The parentheses make sure that those two objects are assigned to different block variables. If I had instead used { |elem, index} #code }, then elem would be an array consisting of the element and the hash. I think that is clear.
My confusion lies with the fact that if I didn't chain these two methods, I would not have to use the parentheses, and instead could use: each_with_object({}) { |elem, obj #code }.
What are the rules about when parentheses are necessary in block variables? Why do they differ between the two examples here? My simplistic explanation is that, when the methods are not chained, then the yield code looks like yield (elem, obj), but when the methods are chained, the code looks like yield([elem, obj], index). (We can surmise that a second array would be passed in if we chained a third method). Is this correct? Is the object(s) passed in from the last chained method not an array?
I guess instead of all this conjecture, the question boils down to: "What does the yield statement look like when chaining methods that accept blocks?
Your question is only tangentially concerned with blocks and block variables. Rather, it concerns the rules for "disambiguating" arrays.
Let's consider your example:
[1,2,3].each_with_object({}).with_index {|(elem, hash), index| hash[elem] = index}
We have:
enum0 = [1,2,3].each_with_object({})
#=> #<Enumerator: [1, 2, 3]:each_with_object({})>
We can see this enumerator's elements by converting it to an array:
enum0.to_a
#=> [[1, {}], [2, {}], [3, {}]]
We next have:
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3]:each_with_object({})>:with_index>
enum1.to_a
#=> [[[1, {}], 0], [[2, {}], 1], [[3, {}], 2]]
You might want to think of enum1 as a "compound enumerator", but it's just an enumerator.
You see that enum1 has three elements. These elements are passed to the block by Enumerator#each. The first is:
enum1.first
#=> [[1, {}], 0]
If we had a single block variable, say a, then
a #=> [[1, {}], 0]
We could instead break this down in different ways using "disambiguation". For example, we could write:
a,b = [[1, {}], 0]
a #=> [1, {}]
b #=> 0
Now let's stab out all the elements:
a,b,c = [[1, {}], 0]
a #=> [1, {}]
b #=> 0
c #=> nil
Whoops! That's not what we wanted. We've just experienced the "ambiguous" in "disambiguate". We need to write this so that our intentions are unambiguous. We do that by adding parenthesis. By doing so, you are telling Ruby, "decompose the array in this position to its constituent elements". We have:
(a,b),c = [[1, {}], 0]
a #=> 1
b #=> {}
c #=> 0
Disambiguation can be extremely useful. Suppose, for example, a method returned the array:
[[1,[2,3],[[4,5],{a: 6}]],7]
and we wish to pull out all the individual values. We could do that as follows:
(a,(b,c),((d,e),f)),g = [[1,[2,3],[[4,5],{a: 6}]],7]
a #=> 1
b #=> 2
c #=> 3
d #=> 4
e #=> 5
f #=> {:a=>6}
g #=> 7
Again, you just have to remember that the parentheses simply mean "decompose the array in this position to its constituent elements".
The rule is basic: every enumerator has a “signature.” E.g. it yields two parameters, then the proc to be passed should expect two parameters to receive:
[1,2,3].each_with_index { |o, i| ...}
When the object might be expanded, like hash item, it may be expanded using parenthesis. Assuming, the iterator yields an array, [*arr]-like operation is permitted with.
The following example might shed a light on this:
[1,2,3].each_with_object('first') # yielding |e, obj|
.with_index # yielding |elem, idx|
# but wait! elem might be expanded here ⇑⇑⇑⇑
# |(e, obj), idx|
.each_with_object('second') do |((elem, fst_obj), snd_idx), trd_obj|
puts "e: #{elem}, 1o: #{fst_obj}, 2i: #{snd_idx}, 3o: #{trd_obj}"
end
#⇒ e: 1, 1o: first, 2i: 0, 3o: second
#⇒ e: 2, 1o: first, 2i: 1, 3o: second
#⇒ e: 3, 1o: first, 2i: 2, 3o: second

Resources