web-dev-qa-db-ja.com

辞書式順序で数値を並べ替えるにはどうすればよいですか?

これがシナリオです。

整数の配列「A」が与えられます。配列のサイズは固定されていません。私が書くことになっている関数は、ほんの数個の整数の配列で一度呼び出されるかもしれませんが、別の時には、何千もの整数を含むかもしれません。さらに、各整数に同じ桁数を含める必要はありません。

結果の配列が辞書式順序で順序付けられた整数を持つように、配列内の数値を「ソート」することになっています(つまり、文字列表現に基づいてソートされます。ここで「123」は123の文字列表現です)。出力には、同等の文字列ではなく、整数のみを含める必要があることに注意してください。

例:入力が次の場合:

[12 | 2434 | 23 | 1 | 654 | 222 | 56 | 100000]

その場合、出力は次のようになります。

[1 | 100000 | 12 | 222 | 23 | 2434 | 56 | 654]

私の最初のアプローチ:各整数を文字列形式に変換し、右側にゼロを追加して、すべての整数に同じ桁数が含まれるようにしました(これは、追跡などを行うための面倒な手順でしたソリューションは非常に非効率的です)そして基数ソートを行いました。最後に、パディングされたゼロを削除し、文字列を整数に変換して、結果の配列に配置しました。これは非常に非効率的な解決策でした。

私は、このソリューションはパディングなどを必要とせず、結果を得るために何らかの方法で数値を処理する必要がある(ビット処理?)という単純なソリューションがあると信じるようになりました。

あなたが考えることができる空間的に最も効率的な解決策は何ですか?時間的に?

コードを提供する場合は、Javaまたは疑似コードを使用することをお勧めします。ただし、それが適切でない場合は、そのような言語で問題ありません。

13
Skylark

実行可能擬似コード(別名Python):thenumbers.sort(key=str)。ええ、私はPythonは不正行為のようなものです-それはただあまりにも強力です;-)であることを知っています。しかし、真剣に、これはまた意味します:Pythonのソートが本質的にできるように、文字列の配列を字句的にソートできる場合は、各数値から「キー文字列」を作成し、その補助配列をソートします(その後、次の方法で目的の数値配列を再構築できますstr-> int変換、または間接参照などを介してインデックスの並べ替えを行うことによって);これはDSU(Decorate、Sort、Undecorate)として知られており、Pythonのsortの_key=_引数が実装するものです。

より詳細に(擬似コード):

  1. aux配列である限り、char ** numbersの配列を割り当てます
  2. 0から_length of numbers-1_、aux[i]=stringify(numbers[i])までのiの場合
  3. 同じ長さのintindicesの配列を割り当てます
  4. 0から_length of numbers-1_、_indices[i]=i_までのiの場合
  5. cmp(i,j)strcmp(aux[i],aux[j])として使用してindicesを並べ替えます
  6. 同じ長さのintresultsの配列を割り当てます
  7. 0から_length of numbers-1_、_results[i]=numbers[indices[i]]_までのiの場合
  8. memcpy results over numbers
  9. _aux[i]_ごとに無料で、auxindicesresultsも無料
9
Alex Martelli

あなたが言ったのでJavaは問題の実際の言語です:

文字列との間で変換する必要はありません。代わりに、独自のコンパレータを定義し、それをソートで使用します。

具体的には:

Comparator<Integer> lexCompare = new Comparator<Integer>(){
   int compareTo( Integer x, Integer y ) {
      return x.toString().compareTo( y.toString() );
   }
};

次に、次のように配列を並べ替えることができます。

int[] array = /* whatever */;
Arrays.sort( array, lexCompare );

(注:int/Integerの不一致は、自動ボクシングによって自動的に機能します)

5
Jason Cohen

それらを文字列に変換してから、Lex比較を行うstrcmpを使用して並べ替えてから並べ替えます。

または、%10と/ 10を使用して2つの数値を比較する「lexcmp」関数を作成することもできますが、これは基本的にatoiを何度も呼び出すのと同じことなので、お勧めできません。

3
Brian Postow

実際の並べ替えは、任意のアルゴリズムで実行できます。この問題の鍵は、このスキームに従って、どの数値が他の数値より「小さい」必要があるかを適切に識別する比較関数を見つけることです。

bool isLessThan(int a, int b)
{
    string aString = ToString(a);
    string bString = ToString(b);

    int charCount = min(aString.length(), bString.length())
    for (charIndex = 0; charIndex < charCount; charIndex++)
    {
        if (aString[charIndex] < bString[charIndex]) { return TRUE; }
    }

    // if the numbers are of different lengths, but identical
    // for the common digits (e.g. 123 and 12345)
    // the shorter string is considered "less"
    return (aString.length() < bString.length());
}
3
e.James

私の誘惑は、intからstringへの変換は、一括ではなく比較コードで行われると言うことです。これはコードの観点からはよりエレガントかもしれませんが、各数値が数回比較される可能性があるため、実行の労力は大きくなると言わざるを得ません。

Int表現とstring表現の両方を含む新しい配列を作成し(指定した順序を生成するために文字列比較のために文字列バージョンをパディングする必要があるかどうかはわかりません)、文字列で並べ替えてからコピーする傾向がありますint値は元の配列に戻ります。

これをソートするスマートな数学的な方法は、辞書式にソートしたい独自のステートメントでは考えられないため、そのためには数値を文字列に変換する必要があります。

2
Lazarus

結果を埋める必要はありません。辞書式比較の順序は変更されず、エラーが発生しやすくなり、CPUサイクルが無駄になります。最も「スペース的に」効率的な方法は、数値を比較するときに数値を文字列に変換することです。そうすれば、追加の配列を割り当てる必要がなくなり、数値がその場で比較されます。

必要に応じて文字列に変換するだけで、適度に優れた実装をすばやく取得できます。数値の文字列化は特にコストがかかるわけではなく、一度に2つの文字列しか処理しないため、それらは常にCPUキャッシュに残る可能性が非常に高くなります。したがって、メインメモリからキャッシュにロードする必要がないため、配列全体を文字列に変換する場合よりもはるかに高速に比較できます。人々は、CPUにキャッシュがあり、メモリの小さなローカル領域で多くの作業を行うアルゴリズムがはるかに高速なキャッシュアクセスから大きな恩恵を受けることを忘れがちです。一部のアーキテクチャでは、キャッシュはメモリよりもはるかに高速であるため、メインメモリからデータをロードするのにかかる時間内にデータに対して何百もの操作を実行できます。したがって、比較関数でより多くの作業を行うと、実際には配列を前処理するよりも大幅に高速になる可能性があります。特に大きな配列がある場合。

コンパレータ関数で文字列のシリアル化と比較を実行し、それをベンチマークしてみてください。かなり良い解決策になると思います。 Java風の擬似コードの例:

public static int compare(Number numA, Number numB) {
    return numA.toString().compare(numB.toString());
}

あなたができるどんな派手なビットごとの比較も、数字を文字列に変換することに関係する仕事とほぼ同等でなければならないと思います。したがって、おそらく大きなメリットは得られないでしょう。ビット比較のために直接ビットを実行することはできません。これにより、辞書式順序とは異なる順序が得られます。とにかく数字の各桁を把握できる必要があるので、それらを文字列にするのが最も簡単です。巧妙なトリックがあるかもしれませんが、頭のてっぺんから考えることができるすべての方法は、トリッキーでエラーが発生しやすく、価値があるよりもはるかに多くの作業が必要です。

2
A. Levy

この質問は、辞書式順序で負の整数を処理する方法を示していません。前に示した文字列ベースのメソッドは、通常、負の値を先頭に並べ替えます。たとえば、{-123、-345、0、234、78}はこの順序で残されます。ただし、マイナス記号を無視することになっている場合、出力順序は{0、-123、234、-345、78}である必要があります。文字列ベースの方法を適応させて、やや面倒な追加テストによってその順序を生成することができます。

理論とコードの両方で、2つの整数の常用対数の小数部分を比較するコンパレータを使用する方が簡単な場合があります。つまり、2つの数値の10を底とする対数の仮数を比較します。対数ベースのコンパレータは、CPUの浮動小数点パフォーマンスの仕様と実装の品質に応じて、文字列ベースのコンパレータよりも高速または低速で実行されます。

この回答の最後に示されているJavaコードには、2つの対数ベースのコンパレータalogCompareslogCompareが含まれています。前者は符号を無視するため、{0 、-123、234、-345、78} from {-123、-345、0、234、78}。

次に示す番号グループは、Javaプログラムによって生成された出力です。

「darRand」セクションには、生成されたランダムデータ配列darが表示されます。 1行あたり5要素を横切ってから下に読み取ります。配列sarlara、およびlarsは、最初はdarのソートされていないコピーであることに注意してください。

「darsort」セクションは、Arrays.sort(dar);でソートした後はdarです。

「sarLex」セクションには、Arrays.sort(sar,lexCompare);でソートした後の配列sarが表示されます。ここで、lexCompareは、JasonCohenの回答に示されているComparatorに似ています。

「larslog」セクションでは、Arrays.sort(lars,slogCompare);で並べ替えた後の配列larsを示し、lexCompareやその他の文字列ベースと同じ順序を与える対数ベースのメソッドを示しています。メソッド。

「laralog」セクションには、Arrays.sort(lara,alogCompare);でソートした後の配列laraが表示され、マイナス記号を無視する対数ベースのメソッドが示されています。

dar Rand    -335768    115776     -9576    185484     81528
dar Rand      79300         0      3128      4095    -69377
dar Rand     -67584      9900    -50568   -162792     70992

dar sort    -335768   -162792    -69377    -67584    -50568
dar sort      -9576         0      3128      4095      9900
dar sort      70992     79300     81528    115776    185484

 sar Lex    -162792   -335768    -50568    -67584    -69377
 sar Lex      -9576         0    115776    185484      3128
 sar Lex       4095     70992     79300     81528      9900

lar s log    -162792   -335768    -50568    -67584    -69377
lar s log      -9576         0    115776    185484      3128
lar s log       4095     70992     79300     81528      9900

lar a log          0    115776   -162792    185484      3128
lar a log    -335768      4095    -50568    -67584    -69377
lar a log      70992     79300     81528     -9576      9900

Javaコードを以下に示します。

// Code for "How can I sort numbers lexicographically?" - jw - 2 Jul 2014
import Java.util.Random;
import Java.util.Comparator;
import Java.lang.Math;
import Java.util.Arrays;
public class Lex882954 {
// Comparator from Jason Cohen's answer
    public static Comparator<Integer> lexCompare = new Comparator<Integer>(){
        public int compare( Integer x, Integer y ) {
            return x.toString().compareTo( y.toString() );
        }
    };
// Comparator that uses "abs." logarithms of numbers instead of strings
    public static Comparator<Integer> alogCompare = new Comparator<Integer>(){
        public int compare( Integer x, Integer y ) {
            Double xl = (x==0)? 0 : Math.log10(Math.abs(x));
            Double yl = (y==0)? 0 : Math.log10(Math.abs(y));
            Double xf=xl-xl.intValue();
            return xf.compareTo(yl-yl.intValue());
        }
    };
// Comparator that uses "signed" logarithms of numbers instead of strings
    public static Comparator<Integer> slogCompare = new Comparator<Integer>(){
        public int compare( Integer x, Integer y ) {
            Double xl = (x==0)? 0 : Math.log10(Math.abs(x));
            Double yl = (y==0)? 0 : Math.log10(Math.abs(y));
            Double xf=xl-xl.intValue()+Integer.signum(x);
            return xf.compareTo(yl-yl.intValue()+Integer.signum(y));
        }
    };
// Print array before or after sorting
    public static void printArr(Integer[] ar, int asize, String aname) {
        int j;
        for(j=0; j < asize; ++j) {
            if (j%5==0)
                System.out.printf("%n%8s ", aname);
            System.out.printf(" %9d", ar[j]);
        }
        System.out.println();
    }
// Main Program -- to test comparators
    public static void main(String[] args) {
        int j, dasize=15, hir=99;
        Random rnd = new Random(12345);
        Integer[] dar = new Integer[dasize];
        Integer[] sar = new Integer[dasize];
        Integer[] lara = new Integer[dasize];
        Integer[] lars = new Integer[dasize];

        for(j=0; j < dasize; ++j) {
            lara[j] = lars[j] = sar[j] = dar[j] = rnd.nextInt(hir) * 
                rnd.nextInt(hir) * (rnd.nextInt(hir)-44);
        }
        printArr(dar, dasize, "dar Rand");
        Arrays.sort(dar);
        printArr(dar, dasize, "dar sort");
        Arrays.sort(sar, lexCompare);
        printArr(sar, dasize, "sar Lex");
        Arrays.sort(lars, slogCompare);
        printArr(lars, dasize, "lar s log");
        Arrays.sort(lara, alogCompare);
        printArr(lara, dasize, "lar a log");
    }
}

すべての数値が1E + 18未満の場合は、各数値をUINT64にキャストし、10を掛けて1を足した後、少なくとも1E +19になるまで10を掛けることができます。次に、それらを並べ替えます。元の数値に戻すには、最後の桁がゼロ以外になるまで(1である必要があります)、各数値を10で除算してから、もう一度10で除算します。

1
supercat

擬似コード:

sub sort_numbers_lexicographically (array) {
    for 0 <= i < array.length:
        array[i] = munge(array[i]);
    sort(array);  // using usual numeric comparisons
    for 0 <= i < array.length:
        array[i] = unmunge(array[i]);
}

では、mungeunmungeとは何ですか?

mungeは、整数のサイズによって異なります。例えば:

sub munge (4-bit-unsigned-integer n) {
    switch (n):
        case 0:  return 0
        case 1:  return 1
        case 2:  return 8
        case 3:  return 9
        case 4:  return 10
        case 5:  return 11
        case 6:  return 12
        case 7:  return 13
        case 8:  return 14
        case 9:  return 15
        case 10:  return 2
        case 11:  return 3
        case 12:  return 4
        case 13:  return 5
        case 14:  return 6
        case 15:  return 7
}

基本的に、mungeが行っているのは、字句的にソートされたときに4ビット整数がどのような順序で入力されるかを示すことです。ここにパターンがあることがわかると思います---スイッチを使用する必要はありませんでした---そして32ビット整数をかなり簡単に処理するバージョンのmungeを書くことができます。パターンがすぐにわからない場合は、5、6、および7ビット整数のmungeのバージョンをどのように作成するかを考えてください。

unmungemungeの逆数です。

したがって、文字列への変換を回避できます---余分なメモリは必要ありません。

1
dave4420

より良いpreprocess-sort-postprocessを試したい場合は、intが最大10桁であることに注意してください(当面は符号を無視します)。

したがって、その2進化10進データは64ビットに収まります。数字0-> 1、1-> 2などをマップし、0をNULターミネータとして使用します(「1」が「10」未満になるようにするため)。各桁を、最小のものから順に、長いものの先頭にシフトします。元のintの辞書式順序で出てくるロングを並べ替えます。次に、各ロングの先頭から一度に1つずつ数字をシフトして、元に戻します。

uint64_t munge(uint32_t i) {
    uint64_t acc = 0;
    while (i > 0) {
        acc = acc >> 4;
        uint64_t digit = (i % 10) + 1;
        acc += (digit << 60);
        i /= 10;
    }
    return acc;
}

uint32_t demunge(uint64_t l) {
    uint32_t acc = 0;
    while (l > 0) {
        acc *= 10;
        uint32_t digit = (l >> 60) - 1;
        acc += digit;
        l << 4;
    }
}

またはそのようなもの。 Javaにはunsignedintがないので、少し変更する必要があります。大量の作業メモリー(入力の2倍のサイズ)を使用しますが、それでも初期アプローチ。コンパレータでオンザフライで文字列に変換するよりも高速かもしれませんが、より多くのピークメモリを使用します。GCによっては、メモリの合計が少なくなり、収集が少なくて済む場合があります。

1
Steve Jessop

スペースの効率を上げる場合は、この種の比較機能で作業を行うだけです。

int compare(int a, int b) {
   // convert a to string
   // convert b to string
   // return -1 if a < b, 0 if they are equal, 1 if a > b
}

遅すぎる場合(確かに前処理よりも遅い場合)、比較関数が変換を実行し続ける必要がないように、どこかで変換を追跡します。

0
Lou Franco

可能な最適化:これの代わりに:

各整数を文字列形式に変換し、右側にゼロを追加して、すべての整数に同じ桁数が含まれるようにしました

各数値に(10 ^ N --log10(number))を掛けることができます。ここで、Nは任意の数値のlog10よりも大きい数値です。

0
Igor Oks
#!/usr/bin/Perl

use strict;
use warnings;

my @x = ( 12, 2434, 23, 1, 654, 222, 56, 100000 );

print $_, "\n" for sort @x;

__END__

いくつかのタイミング...まず、空の@xを使用します。

C:\Temp> timethis s-empty
TimeThis :  Elapsed Time :  00:00:00.188

現在、ランダムに生成された10,000個の要素があります。

TimeThis :  Elapsed Time :  00:00:00.219

これには、10,000個の要素を生成するのにかかる時間は含まれますが、コンソールに出力するのにかかる時間は含まれません。出力は約1秒追加されます。

したがって、プログラマーの時間を節約できます;-)

0
Sinan Ünür

(Cを使用した)本当にハッキーな方法の1つは次のとおりです。

  • floatに変換されたすべての値の新しい配列を生成します
  • 比較のために仮数(仮数)ビットを使用してソートを実行します

In Java(from here ):

long bits = Double.doubleToLongBits(5894.349580349);

boolean negative = (bits & 0x8000000000000000L) != 0; 
long exponent = bits & 0x7ff0000000000000L >> 52;
long mantissa = bits & 0x000fffffffffffffL;

したがって、ここでは長いmantissaでソートします。

0
Phil H