パイプラインパターンでクイックソート

Haskellと言えば、数学の定義のようなクイックソートが有名です。ふつケル(ふつうのHaskellプログラミング)から引用させていただきます。

qsort []     = []
qsort (p:xs) = qsort lt ++ [p] ++ qsort gteq
                 where
                   lt   = [x | x <- xs, x < p]
                   gteq = [x | x <- xs, x >= p]

これをC#2.0でまねっこ!


using System;
using System.Collections.Generic;

namespace Functional
{
public class Program
{
public static void Main()
{
//int[] b = new int[ 0 ];
//int[] b = { 1 };
//int[] b = { 3, 2, 1 };
//int[] b = { 1, 2, 3 };
//int[] b = { 1, 3, 2 };
//int[] b = { 5, 8, 9, 3, 1, 4, 7, 4, 1, 0 };
int[] b = { 2, 7, 1, 8, 2, 8, 18, 28, 45, 90, 452, 353, 602, 874, 71, 35, 2 };

foreach ( int e in Qsort( b ) )
{
Console.Write( e + " " );
}
Console.ReadLine();
}

public static IEnumerable<int> Qsort( IEnumerable<int> source )
{
int? first = FirstOrNull<int>( source );
if ( !first.HasValue ) yield break;

IEnumerable<int> tail = Tail( source );

IEnumerable<int> lt = Where( delegate( int x ) { return x < first.Value; }, tail );
IEnumerable<int> gteq = Where( delegate( int x ) { return x >= first.Value; }, tail );

foreach ( int e in Qsort( lt ) )
yield return e;

yield return first.Value;

foreach ( int e in Qsort( gteq ) )
yield return e;
}

public static T? FirstOrNull<T>( IEnumerable<T> source ) where T : struct
{
if ( source == null ) throw new ArgumentNullException( "source" );
IList<T> list = source as IList<T>;
if ( list != null )
{
if ( list.Count > 0 ) return list[ 0 ];
}
else
{
using ( IEnumerator<T> e = source.GetEnumerator() )
{
if ( e.MoveNext() ) return e.Current;
}
}
return null;
}

public static IEnumerable<T> Tail<T>( IEnumerable<T> source )
{
if ( source == null ) throw new ArgumentNullException( "source" );
bool isFirst = true;
foreach ( T e in source )
{
if ( isFirst )
isFirst = false;
else
yield return e;
}
}

public static IEnumerable<T> Where<T>( Predicate<T> predicate, IEnumerable<T> e )
{
foreach ( T obj in e )
if ( predicate( obj ) )
yield return obj;
}
}
}

int固定になってしまってますが、IComparable制約使えばclass汎用になるかも。
LINQのSequence.csをかなり参考にしました。空シーケンスを検出するのにかなり苦労しました。苦し紛れにNullable使ってます。headとtailをわけるのも無理やりな感じ。本気でSequence.csをC#3.0に統合するなら、headとtailは言語でサポートしたほうがよいのかも。Haskellの(x:xs)みたいに書けるとうれしい。
ところで、あんまりこのコードを真に受けないでねw ほんのお遊びですから。念のため。4/1ですよ。
(追記:書いてからNyaRuRuさんとネタがかぶってるのを見つけたのでトラバ。C#3.0だとFirst()やSkip(1)でheadとtailを表現できるようです)