In Ruby, what does the Array#product method do when given a block? The documentation says "If given a block, product will yield all combinations and return self instead." What does it mean to yield all combinations? What does the method do with the given block?
By "yield all combinations" it means that it will yield (supply) all combinations of elements in the target (self) and other (argument) arrays to the given block.
For example:
a = [1, 2]
b = [:foo, :bar]
a.product(b) { |x| puts x.inspect } # => [1, 2]
# [1, :foo]
# [1, :bar]
# [2, :foo]
# [2, :bar]
It is roughly equivalent to this function:
class Array
def yield_products(other)
self.each do |x|
other.each do |y|
yield [x, y] if block_given?
end
end
end
end
Related
Let's say I have two enumerators, enum1 and enum2 that must be lazily iterated through (because they have side effects). How do I construct a third enumerator enum3 where enum3.each{|x| x} would lazily return the equivalent of enum1 + enum2?
In my real world use case, I'm streaming in two files, and need to stream out the concatenation.
This seems to work just how I want;
enums.lazy.flat_map{|enum| enum.lazy }
Here's the demonstration. Define these yielding methods with side-effects;
def test_enum
return enum_for __method__ unless block_given?
puts 'hi'
yield 1
puts 'hi again'
yield 2
end
def test_enum2
return enum_for __method__ unless block_given?
puts :a
yield :a
puts :b
yield :b
end
concated_enum = [test_enum, test_enum2].lazy.flat_map{|en| en.lazy }
Then call next on the result, showing that the side effects happen lazily;
[5] pry(main)> concated_enum.next
hi
=> 1
[6] pry(main)> concated_enum.next
hi again
=> 2
Here's some code I wrote for fun awhile back with lazy enumeration thrown in:
def cat(*args)
args = args.to_enum
Enumerator.new do |yielder|
enum = args.next.lazy
loop do
begin
yielder << enum.next
rescue StopIteration
enum = args.next.lazy
end
end
end
end
You would use it like this:
enum1 = [1,2,3]
enum2 = [4,5,6]
enum3 = cat(enum1, enum2)
enum3.each do |n|
puts n
end
# => 1
# 2
# 3
# 4
# 5
# 6
...or just:
cat([1,2,3],[4,5,6]).each {|n| puts n }
Since Ruby 2.6 you can use Enumerable#chain/Enumerator::Chain:
a = [1, 2, 3].lazy
b = [4, 5, 6].lazy
a.chain(b).to_a
# => [1, 2, 3, 4, 5, 6]
Enumerator::Chain.new(a, b).to_a
# => [1, 2, 3, 4, 5, 6]
If I do this:
class PseudoRelationship
def my_method(args)
args
end
end
a = PseudoRelationship.new
I got as output
x
#<PseudoRelationship:0x109c2ebb0>
I would like it to behave like a Enumerator or Array, so I get for example this output
x = PseudoRelationship.new [1,2,3]
x
[1,2,3]
Pd. This is not for rails.
What I'm trying to do is behave like an array.
Its something that rails 2.3 seems to use, for example, you can do
my_model.relationship # returns an array
my_model.relationship.find #is a method
I'm trying to replicate that behaviour.
jholtrop was close, you want to overwrite the inspect method
2.0.0p645>
class PseudoRelationship
def initialize(args)
#args = args
end
def inspect
#args.inspect
end
end
2.0.0p645> PseudoRelationship.new [2, 3, 5]
[2, 3, 5]
--Edit based on OP's reasons for wanting this behavior--
While the above class displays what we want to see in the console, it doesn't actually assume any management of args in a way that treats args as an Enumerable. The OP's inspiration is drawn from a Rails construct, the ActiveRecord::Relation*. To imulate that style of behavior, you have to include Enumerable.
class PseudoRelationship
include Enumerable
def initialize(args)
#args = args
end
def each(&block)
#args.each(&block)
end
def inspect
#args.inspect
end
# Add extra functions to operate on #args
# This is obviously a silly example
def foo?
#args.include? :foo
end
def [](key)
#args[key]
end
def last
#args[-1]
end
end
2.0.0p645> PseudoRelationship.new [2, 3, 5]
[2, 3, 5]
2.0.0p645> x = PseudoRelationship.new [2, 3, 5]
[2, 3, 5]
2.0.0p645> x.each
#<Enumerator: [2, 3, 5]:each>
2.0.0p645> x.each_with_index
#<Enumerator: [2, 3, 5]:each_with_index>
2.0.0p645> x.each_with_index { |e, i| puts "#{i} => #{e}" }
0 => 2
1 => 3
2 => 5
[2, 3, 5]
2.0.0p645> x.foo?
false
2.0.0p645> x.first
2
2.0.0p645> x.last
5
2.0.0p645> x[1]
3
2.0.0p645> x[5]
nil
2.0.0p645> x
[2, 3, 5]
* This construct wasn't explictly stated, but I'm assuming based on the context
It sounds like you are asking to alter the "representation" of the object, not the "return value." To give the object a new String representation, you can define the #to_s method on the class. For example:
class PseudoRelationship
def initialize(v)
#v = v
end
def to_s
#v.to_s
end
end
Then in irb, for example:
1.9.3-p551 :021 > x = PseudoRelationship.new [:a, 42, false]
=> [:a, 42, false]
If you want the variable x to equal the arguments passed when instantiating the object, you need to add an initialize method to your class. Something like this should work:
class PseudoRelationship
def initialize(args)
args
end
def my_method(args)
args
end
end
x = PseudoRelationship.new [1, 2, 3]
x == [1, 2, 3] #=> true
EDIT:
Yes, the above won't work, really. If you want it to behave like an array, you could inherit from Array, but that can lead to strange issues and is generally not recommended. However, the below would work:
class PseudoRelationship < Array
end
x = PseudoRelationship.new [1, 2, 3]
x.class #=> PseudoRelationship < Array
x == [1, 2, 3] #=> true
The code:
a = [1, 2, 3]
h = {a: 1}
def f args
p args
end
h.map(&method(:f))
a.map(&method(:f))
h.map do |k,v|
p [k,v]
end
The output:
[:a, 1]
1
2
3
[:a, 1]
Why can't I define f for a hash as follows?
def f k, v
p [k, v]
end
You are correct that the reason stems from the one of the two main differences between proc's and lambda's. I'll trying explaining it in a slightly different way than you did.
Consider:
a = [:a, 1]
h = {a: 1}
def f(k,v)
p [k, v]
end
a.each(&method(:f))
#-> in `f': wrong number of arguments (1 for 2) (ArgumentError)
h.each(&method(:f))
#-> in `f': wrong number of arguments (1 for 2) (ArgumentError)
where I use #-> to show what is printed and #=> to show what is returned. You used map, but each is more appropriate here, and makes the same point.
In both cases elements of the receiver are being passed to the block1:
&method(:f)
which is (more-or-less, as I will explain) equivalent to:
{ |k,v| p [k,v] }
The block is complaining (for both the array and hash) that it is expecting two arguments but receiving only one, and that is not acceptable. "Hmmm", the reader is thinking, "why doesn't it disambiguate in the normal way?"
Let's try using the block directly:
a.map { |k,v| p [k,v] }
#-> [:a, nil]
# [1, nil]
h.map { |k,v| p [k,v] }
#-> [:a, 1]
This works as expected, but does not return what we wanted for the array.
The first element of a (:a) is passed into the block and the block variables are assigned:
k,v = :a
#=> :a
k #=> :a
v #=> nil
and
p [k,v]
#-> :a
#-> nil
Next, 1 is passed to the block and [1,nil] is printed.
Let's try one more thing, using a proc created with Proc::new:
fp = Proc.new { |k,v| p [k, v] }
#=> #<Proc:0x007ffd6a0a8b00#(irb):34>
fp.lambda?
#=> false
a.each { |e| fp.call(e) }
#-> [:a, nil]
#-> [:a, 1]
h.each { |e| fp[e] }
#-> [:a, 1]
(Here I've used one of three aliases for Proc#call.) We see that calling the proc has the same result as using a block. The proc expects two arguments and but receives only one, but, unlike the lambda, does not complain2.
This tells us that we need to make small changes to a and f:
a = [[:a, 1]]
h = {a: 1}
def f(*(k,v))
p [k, v]
end
a.each(&method(:f))
#-> [:a, 1]
h.each(&method(:f))
#-> [:a, 1]
Incidentally, I think you may have fooled yourself with the variable name args:
def f args
p args
end
as the method has a single argument regardless of what you call it. :-)
1 The block is created by & calling Method#to_proc on the method f and then converting the proc (actually a lambda) to a block.
2 From the docs for Proc: "For procs created using lambda or ->() an error is generated if the wrong number of parameters are passed to a Proc with multiple parameters. For procs created using Proc.new or Kernel.proc, extra parameters are silently discarded."
As it appears, it must be some sort of implicit destructuring (or non-strict arguments handling), which works for procs, but doesn't for lambdas:
irb(main):007:0> Proc.new { |k,v| p [k,v] }.call([1,2])
[1, 2]
=> [1, 2]
irb(main):009:0> lambda { |k,v| p [k,v] }.call([1,2])
ArgumentError: wrong number of arguments (1 for 2)
from (irb):9:in `block in irb_binding'
from (irb):9:in `call'
from (irb):9
from /home/yuri/.rubies/ruby-2.1.5/bin/irb:11:in `<main>'
But one can make it work:
irb(main):010:0> lambda { |(k,v)| p [k,v] }.call([1,2])
[1, 2]
=> [1, 2]
And therefore:
def f ((k, v))
p [k, v]
end
So Hash#map always passes one argument.
UPD
This implicit destructuring also happens in block arguments.
names = ["Arthur", "Ford", "Trillian"]
ids = [42, 43, 44]
id_names = ids.zip(names) #=> [[42, "Arthur"], [43, "Ford"], [44, "Trillian"]]
id_names.each do |id, name|
puts "user #{id} is #{name}"
end
http://globaldev.co.uk/2013/09/ruby-tips-part-2/
UPD Don't take me wrong. I'm not suggesting writing such code (def f ((k, v))). In the question I was asking for explanation, not for the solution.
I need send in method argument which is array and hash simultaneously. But don't know, how. Here is example:
def method(here should be this argument)
end
def show(*a)
p a
if a.length.even? == true
p Hash[*a]
else
p "hash conversion not possible"
end
end
show(1,2,3,4)
show(1,2,3)
output:
[1, 2, 3, 4]
{1=>2, 3=>4}
[1, 2, 3]
"hash conversion not possible"
EDIT:
From comment of OP here is the code OP could use:
def add(*arg)
#entries = {}
arg.each_slice(2) do |a| #entries[a.first] = a.last end
p #entries
end
add(1,2,3,4)
Output:
{1=>2, 3=>4}
in recent versions of Ruby, many methods in Enumerable return an Enumerator when they are called without a block:
[1,2,3,4].map
#=> #<Enumerator: [1, 2, 3, 4]:map>
[1,2,3,4].map { |x| x*2 }
#=> [2, 4, 6, 8]
I want do do the same thing in my own methods like so:
class Array
def double(&block)
# ???
end
end
arr = [1,2,3,4]
puts "with block: yielding directly"
arr.double { |x| p x }
puts "without block: returning Enumerator"
enum = arr.double
enum.each { |x| p x }
The core libraries insert a guard return to_enum(:name_of_this_method, arg1, arg2, ..., argn) unless block_given?. In your case:
class Array
def double
return to_enum(:double) unless block_given?
each { |x| yield 2*x }
end
end
>> [1, 2, 3].double { |x| puts(x) }
2
4
6
>> ys = [1, 2, 3].double.select { |x| x > 3 }
#=> [4, 6]
use Enumerator#new:
class Array
def double(&block)
Enumerator.new do |y|
each do |x|
y.yield x*2
end
end.each(&block)
end
end
Another approach might be:
class Array
def double(&block)
map {|y| y*2 }.each(&block)
end
end
easiest way for me
class Array
def iter
#lam = lambda {|e| puts e*3}
each &#lam
end
end
array = [1,2,3,4,5,6,7]
array.iter
=>
3
6
9
12
15
18
21