Skip to content

Instantly share code, notes, and snippets.

@JEG2 JEG2/gist:570556 forked from pragdave/gist:570434
Created Sep 8, 2010

Embed
What would you like to do?
Trying to remove some noise, but I don't think I helped.
input = [ 1, 2, 3, 4, 5, 8, 9, 11, 12, 13, 15 ]
# divide the input into runs of consecutive numbers
last = input.first
s = input.slice_before(lambda { |i| [i != last + 1, (last = i)].first })
# replace runs of 3 or more with first-last
p s.map {|runs| runs.size < 3 ? runs : "#{runs.first}-#{runs.last}"}
.flatten
.join(', ') # => 1-5, 8, 9, 11-13, 15
@pragdave

This comment has been minimized.

Copy link

commented Sep 8, 2010

Yeah: I wanted to avoid adding a local, (a) because I don't much like the leakage, and (b) because I wanted to show the state parameter. I do like the array better than the parallel assignment.

@JEG2

This comment has been minimized.

Copy link
Owner Author

commented Sep 8, 2010

I completely understand the desire to avoid the local. The code is still pretty sloppy, even with it.

It's interesting to note that most of the included examples use the pattern or the block, but not both. In the two cases where they do use both, it's a Hash to box a variable just as your Struct does.

I think my complaint with this example, is that it's easy and cleaner with inject():

s = input.inject([ ]) do |groups, n|
  if groups.empty? or groups.last.last + 1 != n
    groups + [[n]]
  else
    groups.last << n
    groups
  end
end

I guess it's a fair bit of code, still but managing the context feels easier to me this way.

@pragdave

This comment has been minimized.

Copy link

commented Sep 8, 2010

I think we're all still feeling our way here. To me, it's like using #split, but having to manage more myself because the domain is more complex. I can live with that, but I'm expecting that over the next year or so we'll work out patterns that will help us

@eregon

This comment has been minimized.

Copy link

commented Sep 20, 2010

For the sake of one-liners:
s = input.each_with_index.slice_before { |e,i| e != input[i-1]+1 }.map { |group| group.map { |e,l| e } }

I also thought adding some with_{prev,next} Enumerator:

class Enumerator
  def with_next
    return enum_for __method__ unless block_given?
    loop { yield self.next, peek }
  end

  def with_prev
    return enum_for __method__ unless block_given?
    last = first
    loop {
      yield peek, last
      last = self.next
    }
  end
end

Then it would be:

s = input.each.with_prev.slice_before { |e,l| e != l.succ }.map { |group| group.map { |e,l| e } }

What do you think ?

@JEG2

This comment has been minimized.

Copy link
Owner Author

commented Sep 20, 2010

Isn't there quite a bit of overlap between with_next/prev and each_cons(2)?

@eregon

This comment has been minimized.

Copy link

commented Sep 20, 2010

Isn't there quite a bit of overlap between with_next/prev and each_cons(2)?

You are right about #with_next, I made a mistake in the code
(I actually wanted to yield each element with the next one, including the last one, with itself or nil).

I indeed felt first to use #each_cons, but the behavior is different:
#each_cons will only yield (size-1) times but #with_prev will yield size times.

This leads to the problem to know what is the "previous" of the first. I choose to have the first, as you do.

As we use #slice_before, we need to yield each element, and say we want a new slice at the first
(with #each_cons and considering it as (last,element), the first will be dropped).

[1,3,4,6,7,8].each_cons(2).slice_before { |l,e| e != l.succ }.map { |group| group.map { |l,e| e } }
# => [[3, 4], [6, 7, 8]] 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.