Enumerable with_position

Hi,

I think it would be nice to add a function to Enumerable which adds :first,
:middle and :last as parameters depending on the position of the
enumeration. Does anyone else think this would be a good idea and how best
to make a PR?

Kind regards,
Samuel

perhaps something like:

[1, 2, 3, 4]
=> [[1, :first], [2, :left_middle], [3, :right_middle], [4, :last]]

[1, 2, 3]
=>[[1, :first], [2, :left_middle], [2, :right_middle], [3, :last]]

[1]
=> [[1, :first], [1, :left_middle], [1, :right_middle], [1, :last]]

But yeah I'm wondering what would be a good case!

···

On Tue, Sep 15, 2015 at 10:36 AM, Greg Navis <contact@gregnavis.com> wrote:

Fantastic observation, Robert. I really love the single-element enumerable
corner case.

I think we should take a step back and ask Samuel what would the use case
be?

On Tue, Sep 15, 2015 at 4:31 PM, Robert Klemme <shortcutter@googlemail.com > > wrote:

On Tue, Sep 15, 2015 at 3:51 PM, Greg Navis <contact@gregnavis.com> >> wrote:
> I think that Samuel meant a method that would convert
>
> [1, 2, 3, 4]
>
> into
>
> [[1, :first], [2, :middle], [3, :middle], [4, :last]]

What do you do in case the collection just has one element?

> If that's the case then I think it can be easily implemented with
> #each_with_index without defining it on Enumerable:
>
> def with_position(enumerable)
> enumerable.each_with_index.map do |item, index|
> [
> item,
> case index
> when 0 then :first
> when enumerable.length - 1 then :last

That does not work for all Enumerables, because you cannot expect
#length to be implemented. The contract only requires method #each be
implemented. Your code needs to be more complex. Something like:

module Enumerable
  def each_with_label
    return to_enum(:each_with_label) unless block_given?

    count = 0
    item = nil

    each do |e|
      yield item, count == 1 ? :first : :middle if count > 0
      count += 1
      item = e
    end

    case count
    when 0
      # nothing
    when 1
      yield item, :first
    else
      yield item, :last
    end

    self
  end
end

irb(main):001:0> 5.times.each_with_label.to_a
=> [[0, :first], [1, :middle], [2, :middle], [3, :middle], [4, :last]]
irb(main):002:0> 3.times.each_with_label.to_a
=> [[0, :first], [1, :middle], [2, :last]]
irb(main):003:0> 2.times.each_with_label.to_a
=> [[0, :first], [1, :last]]
irb(main):004:0> 1.times.each_with_label.to_a
=> [[0, :first]]

> else :middle
> end
> ]
> end
> end

Cheers

robert

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

--

Alejandro Gonzalez Barrera.
Blog <http://alejandrok5.wordpress.com/> - LinkedIn
<https://www.linkedin.com/in/alejandrok5>
_______________________
Linux user number 411214 <http://linuxcounter.net/user/411214.html>

excellent question, I was expecting to return only :first

···

On Tue, Sep 15, 2015 at 11:28 AM, Paul McKibbin <pmckibbin@gmail.com> wrote:

How would .lazy qualifications on infinite lists work with this. Would you
only expect them to return :first or :left_middle?

On 15 September 2015 at 16:09, Alejandro Gonzalez Barrera < > alejandrok5@gmail.com> wrote:

perhaps something like:

[1, 2, 3, 4]
=> [[1, :first], [2, :left_middle], [3, :right_middle], [4, :last]]

[1, 2, 3]
=>[[1, :first], [2, :left_middle], [2, :right_middle], [3, :last]]

[1]
=> [[1, :first], [1, :left_middle], [1, :right_middle], [1, :last]]

But yeah I'm wondering what would be a good case!

On Tue, Sep 15, 2015 at 10:36 AM, Greg Navis <contact@gregnavis.com> >> wrote:

Fantastic observation, Robert. I really love the single-element
enumerable corner case.

I think we should take a step back and ask Samuel what would the use
case be?

On Tue, Sep 15, 2015 at 4:31 PM, Robert Klemme < >>> shortcutter@googlemail.com> wrote:

On Tue, Sep 15, 2015 at 3:51 PM, Greg Navis <contact@gregnavis.com> >>>> wrote:
> I think that Samuel meant a method that would convert
>
> [1, 2, 3, 4]
>
> into
>
> [[1, :first], [2, :middle], [3, :middle], [4, :last]]

What do you do in case the collection just has one element?

> If that's the case then I think it can be easily implemented with
> #each_with_index without defining it on Enumerable:
>
> def with_position(enumerable)
> enumerable.each_with_index.map do |item, index|
> [
> item,
> case index
> when 0 then :first
> when enumerable.length - 1 then :last

That does not work for all Enumerables, because you cannot expect
#length to be implemented. The contract only requires method #each be
implemented. Your code needs to be more complex. Something like:

module Enumerable
  def each_with_label
    return to_enum(:each_with_label) unless block_given?

    count = 0
    item = nil

    each do |e|
      yield item, count == 1 ? :first : :middle if count > 0
      count += 1
      item = e
    end

    case count
    when 0
      # nothing
    when 1
      yield item, :first
    else
      yield item, :last
    end

    self
  end
end

irb(main):001:0> 5.times.each_with_label.to_a
=> [[0, :first], [1, :middle], [2, :middle], [3, :middle], [4, :last]]
irb(main):002:0> 3.times.each_with_label.to_a
=> [[0, :first], [1, :middle], [2, :last]]
irb(main):003:0> 2.times.each_with_label.to_a
=> [[0, :first], [1, :last]]
irb(main):004:0> 1.times.each_with_label.to_a
=> [[0, :first]]

> else :middle
> end
> ]
> end
> end

Cheers

robert

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

--

Alejandro Gonzalez Barrera.
Blog <http://alejandrok5.wordpress.com/> - LinkedIn
<https://www.linkedin.com/in/alejandrok5>
_______________________
Linux user number 411214 <http://linuxcounter.net/user/411214.html>

--
Best wishes,
Paul McKibbin

m: +447990970862
l: +442032396225
s: blackratprime
b: http://blackrat.org/

--

Alejandro Gonzalez Barrera.
Blog <http://alejandrok5.wordpress.com/> - LinkedIn
<https://www.linkedin.com/in/alejandrok5>
_______________________
Linux user number 411214 <http://linuxcounter.net/user/411214.html>

Hi,

I'm not sure, if I understand what you're after but have you looked at
#each_cons(3) ? There's an example in the documentation, maybe that will
help? Joe

···

On Tue, Sep 15, 2015 at 2:11 PM, Samuel Williams < space.ship.traveller@gmail.com> wrote:

Hi,

I think it would be nice to add a function to Enumerable which adds
:first, :middle and :last as parameters depending on the position of the
enumeration. Does anyone else think this would be a good idea and how best
to make a PR?

Kind regards,
Samuel

I think that Samuel meant a method that would convert

[1, 2, 3, 4]

into

[[1, :first], [2, :middle], [3, :middle], [4, :last]]

If that's the case then I think it can be easily implemented with
#each_with_index without defining it on Enumerable:

def with_position(enumerable)
  enumerable.each_with_index.map do |item, index|
    [
      item,
      case index
      when 0 then :first
      when enumerable.length - 1 then :last
      else :middle
      end
    ]
  end
end

···

On Tue, Sep 15, 2015 at 3:39 PM, Joe Gain <joe.gain@gmail.com> wrote:

On Tue, Sep 15, 2015 at 2:11 PM, Samuel Williams < > space.ship.traveller@gmail.com> wrote:

Hi,

I think it would be nice to add a function to Enumerable which adds
:first, :middle and :last as parameters depending on the position of the
enumeration. Does anyone else think this would be a good idea and how best
to make a PR?

Kind regards,
Samuel

Hi,

I'm not sure, if I understand what you're after but have you looked at
#each_cons(3) ? There's an example in the documentation, maybe that will
help? Joe

I think that Samuel meant a method that would convert

[1, 2, 3, 4]

into

[[1, :first], [2, :middle], [3, :middle], [4, :last]]

If that's the case then I think it can be easily implemented with
#each_with_index without defining it on Enumerable:

def with_position(enumerable)
  enumerable.each_with_index.map do |item, index|
    [
      item,
      case index
      when 0 then :first
      when enumerable.length - 1 then :last
      else :middle
      end
    ]
  end
end

Cool example, Greg!

But how is this useful?
And isn't this basically Array#first and #last?

···

On Tue, Sep 15, 2015 at 3:51 PM, Greg Navis <contact@gregnavis.com> wrote:

On Tue, Sep 15, 2015 at 3:39 PM, Joe Gain <joe.gain@gmail.com> wrote:

On Tue, Sep 15, 2015 at 2:11 PM, Samuel Williams < >> space.ship.traveller@gmail.com> wrote:

Hi,

I think it would be nice to add a function to Enumerable which adds
:first, :middle and :last as parameters depending on the position of the
enumeration. Does anyone else think this would be a good idea and how best
to make a PR?

Kind regards,
Samuel

Hi,

I'm not sure, if I understand what you're after but have you looked at
#each_cons(3) ? There's an example in the documentation, maybe that will
help? Joe

--
joe gain

jacob-burckhardt-str. 16
78464 konstanz
germany

+49 (0)7531 60389

(...otherwise in ???)

How would .lazy qualifications on infinite lists work with this. Would you
only expect them to return :first or :left_middle?

···

On 15 September 2015 at 16:09, Alejandro Gonzalez Barrera < alejandrok5@gmail.com> wrote:

perhaps something like:

[1, 2, 3, 4]
=> [[1, :first], [2, :left_middle], [3, :right_middle], [4, :last]]

[1, 2, 3]
=>[[1, :first], [2, :left_middle], [2, :right_middle], [3, :last]]

[1]
=> [[1, :first], [1, :left_middle], [1, :right_middle], [1, :last]]

But yeah I'm wondering what would be a good case!

On Tue, Sep 15, 2015 at 10:36 AM, Greg Navis <contact@gregnavis.com> > wrote:

Fantastic observation, Robert. I really love the single-element
enumerable corner case.

I think we should take a step back and ask Samuel what would the use case
be?

On Tue, Sep 15, 2015 at 4:31 PM, Robert Klemme < >> shortcutter@googlemail.com> wrote:

On Tue, Sep 15, 2015 at 3:51 PM, Greg Navis <contact@gregnavis.com> >>> wrote:
> I think that Samuel meant a method that would convert
>
> [1, 2, 3, 4]
>
> into
>
> [[1, :first], [2, :middle], [3, :middle], [4, :last]]

What do you do in case the collection just has one element?

> If that's the case then I think it can be easily implemented with
> #each_with_index without defining it on Enumerable:
>
> def with_position(enumerable)
> enumerable.each_with_index.map do |item, index|
> [
> item,
> case index
> when 0 then :first
> when enumerable.length - 1 then :last

That does not work for all Enumerables, because you cannot expect
#length to be implemented. The contract only requires method #each be
implemented. Your code needs to be more complex. Something like:

module Enumerable
  def each_with_label
    return to_enum(:each_with_label) unless block_given?

    count = 0
    item = nil

    each do |e|
      yield item, count == 1 ? :first : :middle if count > 0
      count += 1
      item = e
    end

    case count
    when 0
      # nothing
    when 1
      yield item, :first
    else
      yield item, :last
    end

    self
  end
end

irb(main):001:0> 5.times.each_with_label.to_a
=> [[0, :first], [1, :middle], [2, :middle], [3, :middle], [4, :last]]
irb(main):002:0> 3.times.each_with_label.to_a
=> [[0, :first], [1, :middle], [2, :last]]
irb(main):003:0> 2.times.each_with_label.to_a
=> [[0, :first], [1, :last]]
irb(main):004:0> 1.times.each_with_label.to_a
=> [[0, :first]]

> else :middle
> end
> ]
> end
> end

Cheers

robert

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

--

Alejandro Gonzalez Barrera.
Blog <http://alejandrok5.wordpress.com/> - LinkedIn
<https://www.linkedin.com/in/alejandrok5>
_______________________
Linux user number 411214 <http://linuxcounter.net/user/411214.html>

--
Best wishes,
Paul McKibbin

m: +447990970862
l: +442032396225
s: blackratprime
b: http://blackrat.org/

I think that Samuel meant a method that would convert

[1, 2, 3, 4]

into

[[1, :first], [2, :middle], [3, :middle], [4, :last]]

What do you do in case the collection just has one element?

If that's the case then I think it can be easily implemented with
#each_with_index without defining it on Enumerable:

def with_position(enumerable)
  enumerable.each_with_index.map do |item, index|
    [
      item,
      case index
      when 0 then :first
      when enumerable.length - 1 then :last

That does not work for all Enumerables, because you cannot expect
#length to be implemented. The contract only requires method #each be
implemented. Your code needs to be more complex. Something like:

module Enumerable
  def each_with_label
    return to_enum(:each_with_label) unless block_given?

    count = 0
    item = nil

    each do |e|
      yield item, count == 1 ? :first : :middle if count > 0
      count += 1
      item = e
    end

    case count
    when 0
      # nothing
    when 1
      yield item, :first
    else
      yield item, :last
    end

    self
  end
end

irb(main):001:0> 5.times.each_with_label.to_a
=> [[0, :first], [1, :middle], [2, :middle], [3, :middle], [4, :last]]
irb(main):002:0> 3.times.each_with_label.to_a
=> [[0, :first], [1, :middle], [2, :last]]
irb(main):003:0> 2.times.each_with_label.to_a
=> [[0, :first], [1, :last]]
irb(main):004:0> 1.times.each_with_label.to_a
=> [[0, :first]]

      else :middle
      end
    ]
  end
end

Cheers

robert

···

On Tue, Sep 15, 2015 at 3:51 PM, Greg Navis <contact@gregnavis.com> wrote:

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

Not useful for me. We have to ask Samuel for the use case. It also works
for enumerables other than arrays (e.g. sets).

···

On Tue, Sep 15, 2015 at 3:55 PM, Joe Gain <joe.gain@gmail.com> wrote:

On Tue, Sep 15, 2015 at 3:51 PM, Greg Navis <contact@gregnavis.com> wrote:

I think that Samuel meant a method that would convert

[1, 2, 3, 4]

into

[[1, :first], [2, :middle], [3, :middle], [4, :last]]

If that's the case then I think it can be easily implemented with
#each_with_index without defining it on Enumerable:

def with_position(enumerable)
  enumerable.each_with_index.map do |item, index|
    [
      item,
      case index
      when 0 then :first
      when enumerable.length - 1 then :last
      else :middle
      end
    ]
  end
end

Cool example, Greg!

But how is this useful?
And isn't this basically Array#first and #last?

On Tue, Sep 15, 2015 at 3:39 PM, Joe Gain <joe.gain@gmail.com> wrote:

On Tue, Sep 15, 2015 at 2:11 PM, Samuel Williams < >>> space.ship.traveller@gmail.com> wrote:

Hi,

I think it would be nice to add a function to Enumerable which adds
:first, :middle and :last as parameters depending on the position of the
enumeration. Does anyone else think this would be a good idea and how best
to make a PR?

Kind regards,
Samuel

Hi,

I'm not sure, if I understand what you're after but have you looked at
#each_cons(3) ? There's an example in the documentation, maybe that will
help? Joe

--
joe gain

jacob-burckhardt-str. 16
78464 konstanz
germany

+49 (0)7531 60389

(...otherwise in ???)

Fantastic observation, Robert. I really love the single-element enumerable
corner case.

I think we should take a step back and ask Samuel what would the use case
be?

···

On Tue, Sep 15, 2015 at 4:31 PM, Robert Klemme <shortcutter@googlemail.com> wrote:

On Tue, Sep 15, 2015 at 3:51 PM, Greg Navis <contact@gregnavis.com> wrote:
> I think that Samuel meant a method that would convert
>
> [1, 2, 3, 4]
>
> into
>
> [[1, :first], [2, :middle], [3, :middle], [4, :last]]

What do you do in case the collection just has one element?

> If that's the case then I think it can be easily implemented with
> #each_with_index without defining it on Enumerable:
>
> def with_position(enumerable)
> enumerable.each_with_index.map do |item, index|
> [
> item,
> case index
> when 0 then :first
> when enumerable.length - 1 then :last

That does not work for all Enumerables, because you cannot expect
#length to be implemented. The contract only requires method #each be
implemented. Your code needs to be more complex. Something like:

module Enumerable
  def each_with_label
    return to_enum(:each_with_label) unless block_given?

    count = 0
    item = nil

    each do |e|
      yield item, count == 1 ? :first : :middle if count > 0
      count += 1
      item = e
    end

    case count
    when 0
      # nothing
    when 1
      yield item, :first
    else
      yield item, :last
    end

    self
  end
end

irb(main):001:0> 5.times.each_with_label.to_a
=> [[0, :first], [1, :middle], [2, :middle], [3, :middle], [4, :last]]
irb(main):002:0> 3.times.each_with_label.to_a
=> [[0, :first], [1, :middle], [2, :last]]
irb(main):003:0> 2.times.each_with_label.to_a
=> [[0, :first], [1, :last]]
irb(main):004:0> 1.times.each_with_label.to_a
=> [[0, :first]]

> else :middle
> end
> ]
> end
> end

Cheers

robert

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

Fantastic observation, Robert. I really love the single-element enumerable
corner case.

That was easy. :slight_smile:

I think we should take a step back and ask Samuel what would the use case
be?

Frankly, I am not sure I see this as generally useful.

Nevertheless, it would be easy to adjust my code to report :single for
a single element collection.

Cheers

robert

···

On Tue, Sep 15, 2015 at 4:36 PM, Greg Navis <contact@gregnavis.com> wrote:

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

> Fantastic observation, Robert. I really love the single-element
enumerable
> corner case.

That was easy. :slight_smile:

> I think we should take a step back and ask Samuel what would the use case
> be?

Frankly, I am not sure I see this as generally useful.

That's my impression as well. Let's wait for Samuel to weight in.

···

On Tue, Sep 15, 2015 at 4:45 PM, Robert Klemme <shortcutter@googlemail.com> wrote:

On Tue, Sep 15, 2015 at 4:36 PM, Greg Navis <contact@gregnavis.com> wrote:

Nevertheless, it would be easy to adjust my code to report :single for
a single element collection.

Cheers

robert

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

Could you elaborate a bit more. Like how it will work with sets, array etc.
Examples?

···

On 15 Sep 2015 17:41, "Samuel Williams" <space.ship.traveller@gmail.com> wrote:

Hi,

I think it would be nice to add a function to Enumerable which adds
:first, :middle and :last as parameters depending on the position of the
enumeration. Does anyone else think this would be a good idea and how best
to make a PR?

Kind regards,
Samuel

I think as with normal enumerations: :last is returned for the last element. :wink:

Cheers

robert

···

On Tue, Sep 15, 2015 at 5:44 PM, Alejandro Gonzalez Barrera <alejandrok5@gmail.com> wrote:

excellent question, I was expecting to return only :first

On Tue, Sep 15, 2015 at 11:28 AM, Paul McKibbin <pmckibbin@gmail.com> wrote:

How would .lazy qualifications on infinite lists work with this. Would you
only expect them to return :first or :left_middle?

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

Sorry to be so late back to the party. Here is what I ended up with:

class Enumerator
class Position
def initialize
@first = true
@last = false
end
def after_first!
@first = false
end
def at_end!
@last = true
end
def first?
@first
end
def middle?
not (first? or last?)
end
def last?
@last
end
end
def with_position
return to_enum(:with_position) unless block_given?
position = Position.new
while item = self.next
begin
self.peek
rescue StopIteration
position.at_end!
end
yield item, position
position.after_first!
end
rescue StopIteration
end
end

It's useful when you have a chain of operations, e.g.
array.last(5).reverse.each.with_position

···

On 16 September 2015 at 05:06, Robert Klemme <shortcutter@googlemail.com> wrote:

On Tue, Sep 15, 2015 at 5:44 PM, Alejandro Gonzalez Barrera > <alejandrok5@gmail.com> wrote:
> excellent question, I was expecting to return only :first
>
> On Tue, Sep 15, 2015 at 11:28 AM, Paul McKibbin <pmckibbin@gmail.com> > wrote:
>>
>> How would .lazy qualifications on infinite lists work with this. Would
you
>> only expect them to return :first or :left_middle?

I think as with normal enumerations: :last is returned for the last
element. :wink:

Cheers

robert

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

I'm not sure this makes sense in a lot of contexts. For example, we tend to use Enumerators to paginate large data sets. We'll load say 500 records and yield each of those, and then we'll load more for you when you need more allowing the others to GC. There isn't really a concept of :middle per-se since we'd have to enumerate all of the records to determine what the middle one was. Similarly with :last.
For an example in Rails, see `#find_each_in_batches` https://github.com/rails/rails/blob/2a7cf24cb7aab28f483a6772b608e2868a9030ba/activerecord/lib/active_record/relation/batches.rb#L98

I'm pretty sure calling `.to_a` will also force the enumeration and give you access to most of what you're looking for.
Josh

···

From: Arun kant sharma <iarunkant@gmail.com>
To: Ruby users <ruby-talk@ruby-lang.org>
Sent: Tuesday, September 15, 2015 8:53 AM
Subject: Re: Enumerable with_position
   
Could you elaborate a bit more. Like how it will work with sets, array etc. Examples?

On 15 Sep 2015 17:41, "Samuel Williams" <space.ship.traveller@gmail.com> wrote:

Hi,
I think it would be nice to add a function to Enumerable which adds :first, :middle and :last as parameters depending on the position of the enumeration. Does anyone else think this would be a good idea and how best to make a PR?

Kind regards,Samuel