7

Assume I have an enumerable object enum and now I want to get the third item.

I know one of a general approach is convert into an array and then access with index like:

enum.to_a[2]

But this way will create a temporary array and it might be inefficient.

Now I use:

enum.each_with_index {|v, i| break v if i == 2}

But this is quite ugly and redundant.

What's the most efficient way to do this?

Translunar
  • 3,739
  • 33
  • 55
shouya
  • 2,863
  • 1
  • 24
  • 45

3 Answers3

6

You could use take to peel off the first three elements and then last to grab the third element from the array that take gives you:

third = enum.take(3).last

If you don't want to generate any arrays at all then perhaps:

# If enum isn't an Enumerator then 'enum = enum.to_enum' or 'enum = enum.each'
# to make it one.
(3 - 1).times { enum.next }
third = enum.next
mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • This looks better but, if I want a very large number as index, it will still generate a very big temporary array for it. – shouya Jun 11 '12 at 02:08
  • @ShouYa: I added another array-less option while you were commenting. You still have to tick through `enum` one-by-one but there's nothing you can do about that (in general). – mu is too short Jun 11 '12 at 02:10
  • Using next is a smart suggestion. I would be curious to see what the performances implications are compared to converting a collection to an array. – Oscar Del Ben Jun 11 '12 at 02:15
  • @OscarDelBen: That depends greatly on the enumerator. If you start with an enumerator that is backed by a 2G file then converting to an array will be horrendous but calling `next` a couple times will be almost free. Technically, an Enumerator doesn't even have to be finite so there's no guarantee that `to_a` will even work. – mu is too short Jun 11 '12 at 02:17
  • @muistooshort Well, but isn't values in ruby implemented as references? Passing references as argument won't consume too much, right? I like you manner too but I feel that is still lack of readability. – shouya Jun 11 '12 at 02:23
  • @ShouYa: But if your Enumerator is reading a 2G file line by line, you still have to read the entire thing into memory and wrap the strings with all the usual per-reference baggage from Ruby; there goes well over 2G of RAM. And if you have an Enumerator that simply returns `11` every time you call `next` then `to_a` cannot even work. You can monkey patch that code into Enumerator if you find it too difficult to read. – mu is too short Jun 11 '12 at 02:55
  • @muistooshort Um, it's reasonable. That is another advantage to use `next` indeed. Actually I don't actually have so high requirement for that. I was just feel strange how to implement this simple function efficiently. Thank you. – shouya Jun 11 '12 at 03:05
  • 1
    a more declarative way to convert something to enumerator: `xs.to_enum` – tokland Jun 11 '12 at 11:43
5

Alternative to mu's answer using enumerable-lazy or Ruby 2.1. As lazy as using next but much more declarative:

enum.lazy.drop(2).first
tokland
  • 66,169
  • 13
  • 144
  • 170
1

Unfortunately, to access a member by index it has to be converted to an array (otherwise you wouldn't have an index in the first place), so your code looks perfectly fine to me.

Oscar Del Ben
  • 4,485
  • 1
  • 27
  • 41
  • 1
    Um, no. If `e` is an `Enumerator` then `2.times { e.next }` will tick off and throw away the first two values and a third `e.next` will give you the third one without generating any arrays. – mu is too short Jun 11 '12 at 02:15
  • @muistooshort yep, I replied to your thread. And you're right in that respect. – Oscar Del Ben Jun 11 '12 at 02:16