Forum: Ruby-core Why doesn’t Enumerable define a #last method?

3807680355002d0d5e8314a97333587e?d=identicon&s=25 Nikolai Weibull (Guest)
on 2010-09-17 12:24
(Received via mailing list)
Hi!

Why doesn’t Enumerable define a #last method that’s analogous to #first?

Here’s an ugly, ugly implementation that serves as a proof of concept:

module Enumerable
  def last(n = nil)
    if n.nil?
      result = nil
      each do |i|
        result = i
      end
      result
    else
      result = []
      each do |i|
        result.push i
        result.shift if result.length > n
      end
      result
    end
  end
end
9d2f78236e45a335301ba1195026105d?d=identicon&s=25 Urabe Shyouhei (Guest)
on 2010-09-17 13:00
(Received via mailing list)
(2010/09/17 19:19), Nikolai Weibull wrote:
> Why doesn’t Enumerable define a #last method that’s analogous to #first?

Simplest counterexample is [1].cycle.last
3807680355002d0d5e8314a97333587e?d=identicon&s=25 Nikolai Weibull (Guest)
on 2010-09-17 13:11
(Received via mailing list)
On Fri, Sep 17, 2010 at 13:00, Urabe Shyouhei <shyouhei@ruby-lang.org>
wrote:
> (2010/09/17 19:19), Nikolai Weibull wrote:
>> Why doesn’t Enumerable define a #last method that’s analogous to #first?
>
> Simplest counterexample is [1].cycle.last

I wouldn’t say that that example counters the validity of having #last
in Enumerable.  I mean, sure, there’s obviously no last element in
this example, but neither can #all? be answered:

[1].cycle.all?{ |i| i == 1 }

even though it has an obvious answer.
9d2f78236e45a335301ba1195026105d?d=identicon&s=25 Urabe Shyouhei (Guest)
on 2010-09-17 13:33
(Received via mailing list)
(2010/09/17 20:10), Nikolai Weibull wrote:
> [1].cycle.all?{ |i| i == 1 }
>
> even though it has an obvious answer.

OK, a bit more complicated example.

require "stringio"
s = StringIO.new "foobar"
e = s.enum_for :each_char
s.seek 6
e.last # => nil for your definition, but not sure if it should be?

The point is, an Enumerable's last may not exist for various reasons.
B397b498cc02503a2d86c86176f7fd3e?d=identicon&s=25 Magnus Holm (judofyr)
on 2010-09-17 13:46
(Received via mailing list)
That wouldn't be a problem if we simply define Enumerable#last as "The
last value yielded when you call #each".

// Magnus Holm
3807680355002d0d5e8314a97333587e?d=identicon&s=25 Nikolai Weibull (Guest)
on 2010-09-17 13:54
(Received via mailing list)
On Fri, Sep 17, 2010 at 13:32, Urabe Shyouhei <shyouhei@ruby-lang.org>
wrote:
> a bit more complicated example.
>
> require "stringio"
> s = StringIO.new "foobar"
> e = s.enum_for :each_char
> s.seek 6
> e.last # => nil for your definition, but not sure if it should be?
>
> The point is, an Enumerable's last may not exist for various reasons.

I realize that, but e.first return nil here as well (in 1.8.7).
0ea7f61aec8fee539be0cf39b7bab77c?d=identicon&s=25 Benoit Daloze (Guest)
on 2010-09-17 17:07
(Received via mailing list)
On 17 September 2010 12:19, Nikolai Weibull <now@bitwi.se> wrote:
> Hi!
>
> Why doesn’t Enumerable define a #last method that’s analogous to #first?

Well, I believe #last will always be very inefficient.

In fact, if you do not improve Enumerator, you will have to iterate
over all the elements, which would be almost equivalent to:
  enum.to_a.last(n)
except you would not need to take all the elements before in the
Array, but you would iterate over them (and since GC will likely not
run during the iteration, you will anyway have all the elements in
memory, the only difference is then the Array containing them).

A few months ago, somebody asked how to efficiently get the last n
lines from a text file.
Enumerable#last, could not be efficient, except if it is implemented
specially for String#lines, which means it should then be implemented
for each Enumerator possible, which is very much work I think.

However, you probably saw that Marc-Andre proposed a way to
efficiently calculate the size of an Enumerator:
http://redmine.ruby-lang.org/issues/show/3715
And he did indeed implemented #size for every Enumerator which size
calculation could be improved.

But #size seems easier than #last, no?
The subject is rather interesting, and it would be nice to see one day
a #reverse_each which can actually efficiently iterate in the reverse
order (and that would greatly help your #last, it would then just be a
shortcut for #reverse_each.first(n).reverse).

(The current documentation states: "Builds a temporary array and
traverses that array in reverse order."
Actually, the C implementation does #to_a and just yield in reverse
order)

As others mentioned, it just does not make sense to want the "end" of
some Enumerator, like the custom and the infinite ones (while it
always make sense to have the beginning of them).

So, I think it is normal we get edge cases for infinite iterators
(which would then loop forever), and for the custom ones (which would
then use the inefficient but the only way by calling #to_a).

I am against returning nil, because that is making bad code and it is
totally useless in this context.
I prefer having a infinite loop, which is logical on infinite
enumerators.

Sorry for this (too) long message, but hopefully there are some
interesting points which should be discussed further.

Regards,
B.D.
3807680355002d0d5e8314a97333587e?d=identicon&s=25 Nikolai Weibull (Guest)
on 2010-09-18 00:01
(Received via mailing list)
On Fri, Sep 17, 2010 at 17:04, Benoit Daloze <eregontp@gmail.com> wrote:
> On 17 September 2010 12:19, Nikolai Weibull <now@bitwi.se> wrote:
>> Why doesn’t Enumerable define a #last method that’s analogous to #first?

> Well, I believe #last will always be very inefficient.

I don’t care about efficiency.  I care about is expressiveness.
0ea7f61aec8fee539be0cf39b7bab77c?d=identicon&s=25 Benoit Daloze (Guest)
on 2010-09-18 01:48
(Received via mailing list)
On 18 September 2010 00:00, Nikolai Weibull <now@bitwi.se> wrote:
> On Fri, Sep 17, 2010 at 17:04, Benoit Daloze <eregontp@gmail.com> wrote:
>> On 17 September 2010 12:19, Nikolai Weibull <now@bitwi.se> wrote:
>>> Why doesn’t Enumerable define a #last method that’s analogous to #first?
>
>> Well, I believe #last will always be very inefficient.
>
> I don’t care about efficiency.  I care about is expressiveness.

Then your "ugly, ugly implementation" should be all you need.

If you only care about expressiveness, I believe defining your own
methods for yourself is the best.
But if you want to propose or ask "Why doesn’t Enumerable define a
#last method that’s analogous to #first?", please read the answers of
the others, and not only the "expressiveness" part.

Quick answer:
It may be analogous in the terms, but it would be completely different
in the implementation.
An Enumerator can be easily traversed in the sense it is meant, but
hardly in the other one.
And the (n) #last element(s) of an infinite enumerator does not make
sense.

As you only care of a finite set, why do you not use Array#last ?
This topic is locked and can not be replied to.