Mark Seemann
Mark Seemann

Reputation: 233237

One-liner to convert an iterator to an array in Ruby

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

Answers (2)

J&#246;rg W Mittag
J&#246;rg W Mittag

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

Stefan
Stefan

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

Related Questions