snip 関数

grep の -C オプションみたいなのって、意外に実装が難しいなと思いました。そこで考えた問題。
リストの中の条件を満たす要素の前後 n 個以外を省略する関数を書きなさい。例えば、

A B C D E F G H I

のリストに対して、E だけが条件を満たして、n = 2 であれば

..2.. C D E F G ..2..

を出力する感じ。細かい仕様は勝手に決めてください。


Ruby 1.9 の新機能をふんだんに使った富豪的な解答。

def snip(a, n)
  a.dup.tap do |a|
    t = (0...a.size).to_a & a.map.with_index {|x, i| [x, i-n..i+n] }
                             .select {|x, r| yield x }
                             .map {|x, r| r.to_a }
                             .flatten
    [-1, *t, a.size].each_cons(2)
                    .reject {|b, e| b + 1 == e }
                    .reverse
                    .each {|b, e| a[b+1..e-1] = "..#{ e - b - 1 }.." }
  end
end

p snip(%w(A B C D E F G H I), 2) {|x| x == "E" }.join(" ")
  #=> "..2.. C D E F G ..2.."

いや、普通はこんなコード書かないですけどね (特にインデント) 。ていうか、実用的には、ストリーム的に処理して、メモリ使用量を O(n) にすべきでしょうね。
この問題は、プログラムを書くよりテストを書く方が難しいと思う。上のもほんとにあってるか自信ない。
Haskell 版も書いてみたけど、あんまりきれいにならなかったので略。ちなみに型は

snip :: Int -> (a -> Bool) -> [a] -> [Either Int a]