13

これがシナリオです。

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

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

例:入力が次の場合:

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

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

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

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

私は、解決策にはパディングなどは必要なく、結果を得るために何らかの方法で数値を処理する必要がある単純な解決策があると信じるようになりました。

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

コードを提供する場合は、Java または疑似コードをお勧めします。しかし、それがあなたに合わない場合は、そのような言語で問題ありません。

4

14 に答える 14

9

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

詳細(疑似コード):

  1. aux配列が続く限り、char** のnumbers配列を割り当てます
  2. 0 から までの i に対してlength of numbers-1aux[i]=stringify(numbers[i])
  3. indices同じ長さの int の配列を割り当てます
  4. 0 から までの i に対してlength of numbers-1indices[i]=i
  5. 並べ替えindices、として使用cmp(i,j) strcmp(aux[i],aux[j])
  6. results同じ長さの int の配列を割り当てます
  7. 0 から までの i に対してlength of numbers-1results[i]=numbers[indices[i]]
  8. memcpyresultsオーバーnumbers
  9. 無料aux[i], またaux, indices,results
于 2009-05-19T14:02:46.600 に答える
6

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不一致は、自動ボクシングによって自動的に機能します)

于 2009-05-19T14:25:55.193 に答える
3

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

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

于 2009-05-19T14:08:46.960 に答える
3

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

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());
}
于 2009-05-19T14:15:22.690 に答える
2

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

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

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

于 2009-05-19T14:08:04.670 に答える
2

結果をパディングする必要は絶対にありません。辞書式比較の順序は変更されず、エラーが発生しやすくなり、CPU サイクルが浪費されるだけです。最も「空間的に」効率的な方法は、数値を比較するときに数値を文字列に変換することです。そうすれば、追加の配列を割り当てる必要がなくなり、数値がその場で比較されます。

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

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

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

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

于 2009-05-19T14:14:18.293 に答える
1

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

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

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

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

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

「dar sort」セクションはdar、 によるソート後Arrays.sort(dar);です。

「sar lex」セクションは、でsarソートした後の配列を示しています。これは、Jason Cohen の回答に示されているものと似ています。Arrays.sort(sar,lexCompare);lexCompareComparator

「lar s log」セクションはlars、 でソートした後の配列を示しArrays.sort(lars,slogCompare);ており、do と同じ順序を与える対数ベースの方法lexCompareや、他の文字列ベースの方法を示しています。

「lar a log」セクションは、マイナス記号を無視する対数ベースの方法を示す、 でlaraソートした後の配列を示しています。Arrays.sort(lara,alogCompare);

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");
    }
}
于 2014-07-02T16:38:18.517 に答える
1

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

于 2012-06-27T14:41:17.497 に答える
1

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

したがって、2 進化 10 進データは 64 ビットに収まります。数字 0->1、1->2 などをマップし、0 を NUL ターミネータとして使用します ("1" が "10" 未満になるようにするため)。最小の桁から始めて、各桁を long の先頭に順番にシフトします。long をソートすると、元の int の辞書順になります。次に、各 long の先頭から一度に 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 には unsigned int がないため、少し変更する必要があります。大量の作業メモリ (入力のサイズの 2 倍) を使用しますが、それでも最初のアプローチよりも少なくなります。コンパレータでオンザフライで文字列に変換するよりも高速かもしれませんが、より多くのピーク メモリを使用します。ただし、GC によっては、メモリの合計が少なくなり、必要なコレクションが少なくなる可能性があります。

于 2009-05-19T15:24:15.560 に答える
1

擬似コード:

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]);
}

mungeでは、とは何unmungeですか?

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 ビット整数がどのような順序で入力されるかを示していることです。ここにパターンがあることがわかると確信しています --- スイッチを使用する必要はありませんでした --- munge32 ビット整数を合理的に簡単に処理するバージョンを作成できます。mungeパターンがすぐにわからない場合は、5、6、および 7 ビット整数のバージョンをどのように記述するかを考えてみてください。

unmungeの逆ですmunge

したがって、何かを文字列に変換することを避けることができます --- 余分なメモリは必要ありません。

于 2009-05-19T14:35:17.750 に答える
0

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

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

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

于 2009-05-19T14:19:10.107 に答える
0
#!/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秒追加されます。

だから、プログラマーの時間を節約してください;-)

于 2009-05-19T14:20:02.960 に答える
0

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

  • float に変換されたすべての値の新しい配列を生成する
  • 比較のために仮数 (仮数) ビットを使用して並べ替えを行う

Java の場合 (ここから):

long bits = Double.doubleToLongBits(5894.349580349);

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

したがって、ここでは long を選択mantissaします。

于 2009-05-19T16:13:48.763 に答える
0

スペース効率を重視する場合は、並べ替えの比較関数で作業を行うだけです

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
}

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

于 2009-05-19T14:12:06.893 に答える