-
-
Save JEG2/570556 to your computer and use it in GitHub Desktop.
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 |
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.
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
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 ?
Isn't there quite a bit of overlap between with_next/prev and each_cons(2)?
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]]
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.