challenge 比較しないソートの作成

ソート対象のデータ同士で一切比較などを行わずにソートし、ソート結果を出力するプログラムを作成してください。条件は以下の通り。
・最低値・最大値・個数・並び替え対象の4つを引数として受け取る
・最大値と最低値はあくまで取りうる可能性であり、実際に出現することを保障するものではない。
・同値が複数出現することがある。
・入出力方法及びフォーマットは自由、関数として実装し引数に渡す形でも良い。
・小数点以下の数値が渡されることはないが、負の数は渡される可能性がある。
・最大値や最低値を元に算出した数値との比較は使用しても問題ありません。
・出来るだけ多様な条件のデータをソートできるアルゴリズムを使ってください(データが多少多いときや一定の並び順だとソート失敗するものはダメ)
・昇順降順はどちらでもかまいません

以下サンプル入出力
>>入力
-1 10 10
-1 9 4 8 9 6 3 9 5 2
>>出力
-1 2 3 4 5 6 8 9 9 9

Posted feedbacks - Nested

Flatten Hidden

Squeak Smalltalk で。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
| sort |
sort := [:min :max :size :data |
    | bag out |
    bag := data asBag.
    out := OrderedCollection new: size.
    min to: max do: [:each |
        (bag occurrencesOf: each) timesRepeat: [out add: each]].
    out asArray].

sort valueWithArguments: #(-1 10 10 (-1 9 4 8 9 6 3 9 5 2))
"=> #(-1 2 3 4 5 6 8 9 9 9) "

Counting sort

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
private import std.stdio;

void main() {
    int[] a = [1, 9, 4, 8, 9, 6, 3, 9, 5, 2];
    a.countingSort(1, 10);
    writeln(a);
}

void countingSort(int[] array, int min, int max) {
    auto counts = new uint[max - min + 1];
    foreach(n; array) {
        counts[n - min]++;
    }
    auto p = array.ptr;
    foreach(i, c; counts) {
        p[0..c] = i + min;
        p += c;
    }
}

Radix sort (内部アルゴリズムはBucket sort)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private import std.stdio, std.stdint;

void main() {
    int64_t[] a = [int64_t.max, 0, -1, 3, 1 << 17, -1 << 30, int64_t.min];
    writeln(a); // [9223372036854775807 0 -1 3 131072 -1073741824 -9223372036854775808]
    a.radixSort();
    writeln(a); // [-9223372036854775808 -1073741824 -1 0 3 131072 9223372036854775807]
}

private import std.traits : unsigned;

void radixSort(T)(T[] array) if(is(unsigned!(T))) {
    alias unsigned!(T) U;

    auto uarray = new U[array.length];
    foreach(i, ref n; uarray) {
        n = array[i] - T.min;
    }
    
    foreach(i; 0 .. T.sizeof * 8 / 4) {
        auto buckets = new U[][1 << 4];

        foreach(n; uarray) {
            buckets[n >> i * 4 & 0b1111] ~= n;
        }

        auto p = uarray.ptr;
        foreach(bucket; buckets) {
            p[0 .. bucket.length] = bucket;
            p += bucket.length;
        }
    }

    foreach(i, ref n; array) {
        n = uarray[i] + T.min;
    }
}
クイックソート的にやりました。 しかし、終了条件が悪いので遅いです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import Data.List (group,sort)

main :: IO ()
main = print $ qsort (-1) 10 [-1,9,4,8,9,6,3,9,5,2]

uniq :: Ord a => [a] -> [a]
uniq ns = map head $ group $ sort ns

qsort :: Integral a => a -> a -> [a] -> [a]
qsort _ _ [] = []
qsort _ _ ns | length (uniq ns) == 1 = ns
qsort min max ns = let mid = div (max + min) 2
                   in qsort min mid [y | y <- ns, y < mid] ++ qsort mid max [y | y <- ns, y >=mid ]

uniq の中で sort を使ってしまっていますよ。(^_^;)

以下のリスト内包表記内でも比較が使われていますね。

1
[y | y <- ns, y < mid] ++ qsort mid max [y | y <- ns, y >=mid ]
問題文に
「最大値や最低値を元に算出した数値との比較は使用しても問題ありません。」
とあるのでその比較は問題ないんじゃないでしょうか。

counting sortを実装しました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Arrays;

public class Sample187 {
    public static int[] countingSort(int min, int max, int[] data) {
        int[] counter = new int[max - min + 1];
        for (int i: data) {
            counter[i - min]++;
        }

        int[] result = new int[data.length];
        int index = 0;
        for (int i = 0; i < counter.length; i++) {
            for (int count = 0; count < counter[i]; count++) {
                result[index++] = i + min;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        int[] sort = countingSort(-1, 10, new int[] {-1, 9, 4, 8, 9, 6, 3, 9, 5, 2});
        System.out.println(Arrays.toString(sort));
    }
}
horiuchiさんの#6648をコピペしてC#のコードに改変しました。
殆ど一緒です。行数まで一致しています。
JavaとC#って似てますよね。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;

public class Sample187 {
    public static int[] countingSort(int min, int max, int[] data) {
        int[] counter = new int[max - min + 1];
        foreach(int i in data) {
            counter[i - min]++;
        }

        int[] result = new int[data.Length];
        int index = 0;
        for(int i = 0; i < counter.Length; i++) {
            for(int count = 0; count < counter[i]; count++) {
                result[index++] = i + min;
            }
        }
        return result;
    }

    public static void Main(String[] args) {
        int[] sort = countingSort(-1, 10, new int[] { -1, 9, 4, 8, 9, 6, 3, 9, 5, 2 });
        foreach(int i in sort) Console.Write(i + " ");
    }
}
バケツで。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
$min = -1;
$max = 10;
$input = array(-1, 9, 4, 8, 9, 6, 3, 9, 5, 2);
$sorted = bucketsort($min, $max, $input);

function bucketsort($min, $max, $input){
    for($i = $min; $i <= $max; $i++)
        $temp[$i] = 0;
    foreach($input as $v)
        $temp[$v]++;
    foreach($temp as $k => $v)
        for($i = 0; $i < $v; $i++)
            $sorted[] = $k;
    return $sorted;
}
Mosh (Scheme) で書きました。 データ個数を終了条件に使えばもっと効率よくできそうです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(define count-sort (lambda (range-min range-max range-len data)
  (let loop1 ((i range-min) (result '()))
    (if (>= i range-max)
      result
      (loop1 (+ i 1) (append result
        (let loop2 ((ls data) (rs '()))
          (if (pair? ls)
            (loop2 (cdr ls) (if (= i (car ls)) (cons i rs) rs))
            rs))))))))

(write (count-sort -1 10 10 '(-1 9 4 8 9 6 3 9 5 2)))
(newline)
しまった。。。
バケットソートというのがあるんですね。
あんまり余裕がないので Gauche で書き直し。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
(use gauche.sequence)

(define bucket-sort (lambda (range-min range-max data-len data)
  (let* ((range-len (+ 1 (- range-max range-min)))
         (bucket (make-vector range-len 0))
         (result '()))
    (for-each (lambda (i)
      (let ((j (- i range-min)))
        (vector-set! bucket j (+ 1 (vector-ref bucket j)))))
      data)
    (for-each-with-index
      (lambda (i count)
        (set! result (append result
          (make-list count (+ i range-min)))))
      bucket)
    result)))

(define main (lambda (args)
  (write (bucket-sort -1 10 10 '(-1 9 4 8 9 6 3 9 5 2)))
  (newline)
  0))
mc さんの #6658 を参考にして、だいぶすっきりしたコードになりました。
ありがとうございます。
1
2
3
4
5
6
7
8
(define bucket-sort (lambda (range-min range-max data-len data)
  (let ((bucket (make-vector (+ 1 (- range-max range-min)) '())))
    (for-each
      (lambda (i)
        (let ((j (- i range-min)))
          (vector-set! bucket j (cons i (vector-ref bucket j)))))
      data)
    (fold-right append '() (vector->list bucket)))))
JavaScript。クイックソート、かな?
効率悪そう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Array.prototype.sort2 = function(minVal, maxVal) {
    if (this.length <= 1) return this;
    var mid = (minVal + maxVal) / 2;
    var lt = [], eq = [], gt = [];
    for (var i=0; i < this.length; i++) {
        if (this[i] > mid)
            gt.push(this[i]);
        else if (this[i] < mid)
            lt.push(this[i]);
        else
            eq.push(this[i]);
    }
    return lt.sort2(minVal, mid).concat(eq, gt.sort2(mid, maxVal));
};

[-1,9,4,8,9,6,3,9,5,2].sort2(-1,10)
おっと。無駄が多すぎた。
以下の修正で関数に入る回数が109→15に改善しました。
1
2
3
4
3c3
<       var mid = (minVal + maxVal) / 2;
---
>       var mid = Math.floor((minVal + maxVal) / 2);

重複する要素が一個にまとめられてしまいます。

あぁ、情けなし。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
module Main where

sort_noComp min max xs = foldr (addCount) (map (flip (,) $ 0) [min..max]) xs >>= \(x, c) -> replicate c x
    where
        addCount :: Int -> [(Int, Int)] -> [(Int, Int)]
        addCount i [] = []
        addCount i ((j, x):xs)
            | i == j = (j, x + 1) : xs
            | otherwise = (j, x) : addCount i xs

main = print $ sort_noComp 1 9 [4, 8, 9, 6, 3, 9, 5, 2]
baalさんの #6653 を参考にバケットソートで書いてみました。

(bucket-sort -1 10 10 '(-1 9 4 8 9 6 3 9 5 2))
;=> (-1 2 3 4 5 6 8 9 9 9)
1
2
3
4
5
6
(defun bucket-sort (min max size data)
  (declare (integer min max size) (list data))
  (loop :with tem := (make-array (max size (- max min)) :initial-element () )
        :for item :of-type integer :in data
        :do (push item (svref tem (- item min)))
        :finally (return (reduce #'nconc tem))))

アルゴリズム的にはcounting sort相当になると思います。

1
2
3
sort.uncompare <- function(min, max, num, l){
   unlist(sapply(min:max, function(n)(l[l==n])))
}

これでいいのかな。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def sort(min, max, a):
    counts = [0] * (max - min + 1)
    for x in a:
        counts[x - min] += 1
    result = []
    for i, count in enumerate(counts):
        for _ in xrange(count):
            result.append(i + min)
    return result

def main():
    print sort(-1, 10, [-1, 9, 4, 8, 9, 6, 3, 9, 5, 2])

if __name__ == '__main__':
    main()

基数ソートです。 引数の最大値と最小値はメソッド内で使用してません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        int[] x = { -1, 9, 4, 8, 9, 6, 3, 9, 5, 2 };

        foreach (var item in RadixSort(x, int.MinValue, int.MaxValue))
        {
            Console.Write(item + "  ");
        }
        Console.WriteLine();
    }

    static int[] RadixSort(int[] a, int min, int max)
    {
        for (uint x = 1; x != 0; x <<= 1)
        {
            a = a.Where(n => (n & x) == 0).Concat(a.Where(n => (n & x) != 0)).ToArray();
        }
        return a.SkipWhile(n => 0 <= n).Concat(a.TakeWhile(n => 0 <= n)).ToArray();
    }
}
途中で (max-min+1)*n  の配列ができちゃう。メモリ使いすぎ。
1
2
3
4
5
6
7
8
sortx=:3 :0
  min=. 0 { y
  max=. 1 { y
  n=. 2 { y
  a=. 3 }. y
  b=. min + i. 1 + max - min
  (+/"1 b =/ a) # b
)

短くする方向で書いてみました。個数は要りません。

1
mysort min max ns = concat [filter (== x) ns | x <- [min..max]]
こんな感じでしょうか。配列と最小値だけ必要です。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
●sort(data,min)
    tmpとは配列
    bufとは配列
    iとは整数
    dataを反復
        tmp[対象-min]=tmp[対象-min]+1
    iで0から(tmpの配列要素数)-1まで繰り返す
        (tmp[i])回
            bufにi+minを配列追加
    bufで戻る

a="-1 9 4 8 9 6 3 9 5 2"を" "で区切る
sort(a,-1)を表示
counting sortを実装しました。
> ghc --make -O2 CountingSort.hs
> ./CountingSort
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import qualified Data.Map as M
import Control.Monad.State

counter min max xs = recur (M.fromAscList $ zip [min..max] (repeat 0)) xs
  where recur ctr [] = M.toAscList ctr
            recur ctr (x:xs) = recur (M.update (\v -> Just (succ v)) x ctr) xs

countingSort :: (Ord a, Enum a, Num a) => a -> a -> [a] -> [a]
countingSort min max xs = let ctr = counter min max xs
                                           result = M.fromAscList $ zip [0..len] (repeat 0)
                                      in evalState (countingSort_S min ctr) (0,result)
  where len = fromIntegral $ length xs - 1

countingSort_S :: (Ord a, Enum a, Num a) => a -> [(a,a)] -> State (a,M.Map a a) [a]
countingSort_S _ [] = get >>= return . M.elems . snd
countingSort_S min ((x,y):xs) = do
    forM_ [1..y] update
    countingSort_S min xs
  where update _ = do
                (index,result) <- get
                put (succ index, M.update f index result)
            f _ = Just x

main :: IO ()
main = do
    print $ countingSort (-1) 10 [-1,9,4,8,9,6,3,9,5,2]
    print $ countingSort (-100000) 100000 [-100000..100000]
ちょっと修正しました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import qualified Data.Map as M                                                                                               
import Control.Monad.State

counter min max xs = recur (M.fromAscList $ zip [min..max] (repeat 0)) xs
  where recur ctr [] = M.toAscList ctr 
        recur ctr (x:xs) = recur (M.update (\v -> Just (succ v)) x ctr) xs

countingSort :: (Ord a, Enum a, Num a) => a -> a -> [a] -> [a] 
countingSort min max xs = let ctr = counter min max xs
                          in evalState (countingSort_S min ctr) []
  where len = fromIntegral $ length xs - 1 

countingSort_S :: (Ord a, Enum a, Num a) => a -> [(a,a)] -> State [a] [a] 
countingSort_S _ [] = get >>= return . reverse
countingSort_S min ((x,y):xs) = do
    forM_ [1<