My friend Tim had a programming problem, described at his joint.
I thought about the problem for a while, started implementing it in ruby, thought “I could implement this better in haskell”, so I did:
group_seq :: (Int -> Bool) -> [Int] -> [[Int]]
group_seq pred [] = []
group_seq pred (x:xs)
| pred x = let (group, rest) = span pred (x:xs)
in group:(group_seq pred rest)
| otherwise = ([x]):(group_seq pred xs)
Then I implemented it in ruby:
class Array
def group_sequential
result = []
i = 0
while i < length
group = [self[i]]
i += 1
if yield self[i-1]
while i < length && (yield self[i])
group << self[i]
i += 1
end
end
result << group
end
return result
end
end
And I thought, that would be much nicer if we had a enumerator interface with a “get next” and a “current value” method. Ruby doesn’t give us these toys, as far as I can see, but we can simulate them for a small dataset by doing Array.dup, then arr.shift is “move to next and return last” and “arr.first” is “current value”:
class Array
def group_sequential
arr = self.dup
result = []
while arr.first
group = [arr.first]
if yield arr.shift
group << arr.shift while arr.first && (yield arr.first)
end
result << group
end
return result
end
end
And that one feels pretty good to me… It feels like it’d be nicer in a language with an iterator interface of the right sort. Hey, python has one of those, more or less, let’s try that.
def group_sequential(arr, pred):
iterator = iter(arr)
result = []
group = []
value = iterator.next()
try:
while True:
group = [value]
lastvalue, value = value, iterator.next()
try:
while pred(lastvalue) and pred(value):
group.append(value)
value = iterator.next()
except:
pass
result.append(group)
except:
result.append(group)
return result
And that one feels completely dreadful! Not being able to interrogate the iterator for the current value makes things a bit awkward.
I’m not entirely happy with any of them. Wanting to go back to the drawing board, I translated the functional one fairly directly into Ruby, hoping to get beautiful code at the cost of weird performance characteristics:
class Array
def group_sequential(&block)
return [] if empty?
return [self] if length == 1
rest = self[1..-1].group_sequential(&block)
if (yield self.first) && (yield rest.first.first)
rest.first.unshift(self.first)
else
rest.unshift([self.first])
end
rest
end
end
Which is possibly the best yet, although it’s very strange-looking for ruby code. But I don’t like any of them, really.
In conclusion, I Don’t Know.
One thought on “Tim throws the gauntlet”