Reputation: 233237
This has to be a FAQ, but after much web searching, I've failed to find an answer. I'm new to the Ruby programming language, so it's possible that I've misunderstood something fundamental, or that I'm projecting my knowledge of C#, F#, or Haskell onto Ruby.
If I have a method like the following, is there a one-liner to turn it into an array or other mutable data structure?
def foo
yield 8
yield 2
yield 5
end
I know that I can do this:
ary = Array.new
foo { |x| ary << x }
That is, however, not a one-liner. In C#, for example, I can use ToList
or ToArray
extension methods, or in F# e.g. Seq.toList
:
let foo = seq {
yield 8
yield 2
yield 5
}
let arr = foo |> Seq.toList
Is there a similar one-liner in Ruby?
Or did I completely misunderstand the nature of foo
?
Upvotes: 6
Views: 734
Reputation: 369536
There are two independent questions in your question, actually.
The first question is in the title:
One-liner to convert an iterator to an array in Ruby
That is easy: Enumerator
, which is what iterators are called in Ruby, mixes in (inherits from) Enumerable
. Enumerable
has the Enumerable#to_a
method that creates an Array
from an Enumerable
, and thus also from an Enumerator
, because Enumerator
IS-AN Enumerable
.
So, if you have an Enumerator
, all you need to do is call to_a
, e.g.
some_enum.to_a
However, you don't have an Enumerator
. You have a method. So, the real question that you are not asking but that is at the crux of the matter, is: how do I create an Enumerator
from a method?
And that's what Object#enum_for
does:
enum = enum_for(:foo)
#=> #<Enumerator: ...>
ary = enum.to_a
#=> [8, 2, 5]
Note, however, that in many cases, you do not actually need an Array
, since Enumerator
inherits from Enumerable
just like Array
does, and thus responds to many of the same messages.
Also note that by convention, many "iterator" methods in Ruby, e.g. Enumerable#map
, #each
, etc., will return an Enumerator
when they are called without a block:
def foo
return enum_for(__callee__) { 3 } unless block_given?
yield 8
yield 2
yield 5
end
Then you can simply do:
enum = foo
#=> #<Enumerator: ...>
ary = enum.to_a
#=> [8, 2, 5]
Upvotes: 8
Reputation: 114218
An idiomatic approach is to return an enumerator if no block is given:
def foo
return enum_for(__method__) unless block_given?
yield 8
yield 2
yield 5
end
This gives you:
foo #=> #<Enumerator: main:foo>
foo.to_a #=> [8, 2, 5]
Upvotes: 4