2009/11/07

C#で符号付整数/浮動小数点数対応の基数ソート(radix sort)

Radium Software Development経由でRadix Sort Revisitedを見て、さらに2006-07-23 - togeの日記経由でstereopsis : graphics : radix tricksも読んだから書いてみた。
ksksts's blog: C#で実装したradix sortとArray.Sortのquick sortとの比較で実行速度で.NET FrameworkのArray.Sort メソッド (System)とかを上回るのは難しいと感じた時点で目的を実装例を示すだけに変更。Decimal型String型に対応しようと思っていたけど、面倒くさくなったから符号なし整数型、符号付整数型、浮動小数点数型で止めとく。

stereopsis : graphics : radix tricksのコードを参考にして書いた。
おおまかな処理の流れは次の通り。
  1. ヒストグラムをまとめて作成(histgramming)
  2. ヒストグラムの値を加算(sum the histgrams)
  3. 要素の並べ替え(read/write histgram, copy)

インターフェースはこんな感じ。
RadixSorter.cs
public static void Sort<T>(IList<T> list, Func<T, UInt32> converter, SortOrder sortOrder)

ソートに使用するキーを指定しながらソートできるのがおもしろい。
struct Pair<TFirst, TSecond>
{
    public TFirst First { get; set; }
    public TSecond Second { get; set; }
}
このPair型に対して、
RadixSorter.Sort(result, x => x.Second, RadixSorter.SortOrder.Ascending);
RadixSorter.Sort(result, x => x.First, RadixSorter.SortOrder.Ascending);
とすると、次のCompareTo(Firstで比較&Firstが等しい場合はSecondで比較)でソートした場合と同じ結果が得られる。
public int CompareTo(Pair<TFirst, TSecond> x)
{
    var result = First.CompareTo(x.First);
    if (result == 0)
        result = Second.CompareTo(x.Second);
    return result;
}

あとは書いてみて思ったこととか。

ヒストグラムをずらして作っておくと加算する処理が簡単になること、降順にソートする場合はヒストグラムの加算の処理で対応できること。
NumberRadixSorter.csのSort(IList list, SortOrder sortOrder)
// histgramming
            var histgramOffset = ascending ? 1 : -1;
            foreach (var x in list)
            {
                var y = x;
                for (var p = 0; p < tables.Length; p++)
                {
                    tables[p][(y + histgramOffset) & 0xFF]++;
                    y = y >> 8;
                }
            }

            // sum the histgrams
            if (ascending)
            {
                foreach (var table in tables)
                {
                    table[0] = 0;
                    for (var n = 1; n < table.Length; n++)
                        table[n] += table[n - 1];
                }
            }
            else
            {
                foreach (var table in tables)
                {
                    table[0xFF] = 0;
                    for (var n = 0xFE; n >= 0x00; n--)
                        table[n] += table[n + 1];
                }
            }

符号付整数型でも同様。ただし符号ビットを含む部分だけ工夫する。
NumberRadixSorter.csのSort(IList list, SortOrder sortOrder)
// sum the histgrams
            if (ascending)
            {
                for (var p = 0; p < tables.Length - 1; p++)
                {
                    var table = tables[p];
                    table[0] = 0;
                    for (var n = 1; n < table.Length; n++)
                        table[n] += table[n - 1];
                }
                {
                    var table = tables[tables.Length - 1];
                    table[0x80] = 0;
                    for (var n = 0x81; n <= 0xFF; n++)
                        table[n] += table[n - 1];
                    table[0x00] += table[0xFF];
                    for (var n = 0x01; n < 0x80; n++)
                        table[n] += table[n - 1];
                }
            }
            else
            {
                for (var p = 0; p < tables.Length - 1; p++)
                {
                    var table = tables[p];
                    table[0xFF] = 0;
                    for (var n = 0xFE; n >= 0x00; n--)
                        table[n] += table[n + 1];
                }
                {
                    var table = tables[tables.Length - 1];
                    table[0x7F] = 0;
                    for (var n = 0x7E; n >= 0x00; n--)
                        table[n] += table[n + 1];
                    table[0xFF] += table[0x00];
                    for (var n = 0xFE; n >= 0x80; n--)
                        table[n] += table[n + 1];
                }
            }

浮動小数点数の場合は、要素が数値でそれを変更できる場合はヒストグラムを作るときにビットをflipして、ソート後に元に戻すとかできる(stereopsis : graphics : radix tricksのコードでやっている)。
NumberRadixSorter.csのSort(IList list, SortOrder sortOrder)
// histgramming
            var histgramOffset = ascending ? 1 : -1;
            for (var n = 0; n < list.Count; n++)
            {
                var y = BitConverter.DoubleToInt64Bits(list[n]);
                y ^= -(Int64)((UInt64)y >> 63) | unchecked((Int64)0x8000000000000000);  // flip
                list[n] = BitConverter.Int64BitsToDouble(y);
                for (var p = 0; p < tables.Length; p++)
                {
                    tables[p][(y + histgramOffset) & 0xFF]++;
                    y = y >> 8;
                }
            }
// read/write histgram, copy
            IList<Double> array = new Double[list.Count];
            Action swap = () => { var temp = list; list = array; array = temp; };
            for (var p = 0; p < tables.Length - 1; p++)
            {
                var table = tables[p];
                foreach (var x in list)
                {
                    var y = BitConverter.DoubleToInt64Bits(x);
                    y = (y >> p * 8) & 0xFF;
                    array[table[y]] = x;
                    table[y]++;
                }
                swap();
            }
            {
                var p = tables.Length - 1;
                var table = tables[p];
                foreach (var x in list)
                {
                    var w = BitConverter.DoubleToInt64Bits(x);
                    var y = (w >> p * 8) & 0xFF;
                    w ^= (Int64)((UInt64)w >> 63) - 1 | unchecked((Int64)0x8000000000000000);  // flip back
                    array[table[y]] = BitConverter.Int64BitsToDouble(w);
                    table[y]++;
                }
                swap();
            }

ksksts / junk / source — bitbucket.org

0 件のコメント: