Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@JEG2
Forked from pragdave/gist:570434
Created September 8, 2010 18:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JEG2/570556 to your computer and use it in GitHub Desktop.
Save JEG2/570556 to your computer and use it in GitHub Desktop.
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
Copy link

pragdave 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
Copy link
Author

JEG2 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
Copy link

pragdave 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
Copy link

eregon 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
Copy link
Author

JEG2 commented Sep 20, 2010

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

@eregon
Copy link

eregon 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