前後の値も利用したシーケンス処理 (その 2)
『前後の値も利用したシーケンス処理 - NyaRuRuの日記』の続き.
// 連続して同じ値が来る箇所だけを省いて取得する // この場合だと1,2,4,3,4,0が取れることを目指す int[] array = { 1, 2, 4, 4, 3, 3, 4, 0, 0 };
前回はこの問題を肴に Scan とか Pairwise を持ち出しましたが,この問題に特化して考えるとして,もうちょっと使いやすい関数はないでしょうか.
例えば,UNIX の uniq コマンド は要求されている仕様そのものスバリのように見えます.uniq コマンドに相当する PowerShell の Get-Unique を使ってみます.
PS Z:\> 1,2,4,4,3,3,4,0,0 | Get-Unique 1 2 4 3 4 0
同様の関数に,Haskell の group/groupBy,Python itertools の groupby などがあるようです.
Haskell.
Prelude> :m Data.List Prelude Data.List> group [1,2,4,4,3,3,4,0,0] [[1],[2],[4,4],[3,3],[4],[0,0]] Prelude Data.List> map head $ group [1,2,4,4,3,3,4,0,0] [1,2,4,3,4,0] Prelude Data.List> map head $ groupBy (==) [1,2,4,4,3,3,4,0,0] [1,2,4,3,4,0]
IronPython 2.0.1
>>> from itertools import groupby >>> [k for k, g in groupby([1,2,4,4,3,3,4,0,0])] [1, 2, 4, 3, 4, 0]
一方で LINQ の GroupBy および F# の Seq.groupby は,SQL の GROUP BY 句に似た動作をするため,上記のような使い方はできません.そこでちょっと発想を変えて,run-length エンコーディングを行う関数を作ってみました.
public static IEnumerable<TResult> RunLength<T, TResult>( this IEnumerable<T> source, Func<T, int, TResult> resultSelector) { //AssertNonNullArgument(source, "source"); //AssertNonNullArgument(resultSelector, "resultSelector"); using (var enumerator = source.GetEnumerator()) { if (!enumerator.MoveNext()) { yield break; } var count = 1; var first = enumerator.Current; var eq = EqualityComparer<T>.Default; while (enumerator.MoveNext()) { var cur = enumerator.Current; if (eq.Equals(first, cur)) { checked { ++count; } } else { yield return resultSelector(first, count); first = cur; count = 1; } } yield return resultSelector(first, count); } }
これで,個数の方を捨てれば題意を満たせます.
// test var source = new []{1,2,4,4,3,3,4,0,0}; var seq = source.RunLength((i,_) => i); var ans = new []{1,2,4,3,4,0}; Assert.IsTrue(seq.SequenceEqual(ans));