1 MBのRAMで100万の8桁の数字を並べ替える


726

1 MBのRAMを搭載し、他にローカルストレージを搭載していないコンピューターを使用しています。私はそれを使用して、TCP接続で100万の8桁の10進数を受け入れ、それらを並べ替えてから、並べ替えたリストを別のTCP接続で送信する必要があります。

番号のリストに重複が含まれている可能性がありますが、これは破棄しないでください。コードはROMに配置されるため、1 MBからコードのサイズを減算する必要はありません。イーサネットポートを駆動してTCP / IP接続を処理するコードが既にあり、コードがデータを読み書きするための1 KBバッファを含め、状態データには2 KBが必要です。この問題の解決策はありますか?

質問と回答のソース:

slashdot.org

cleaton.net


45
Ehm、100万倍の8桁の10進数(最小27ビット整数バイナリ)> 1MB ram
Mr47

15
1MのRAMは2 ^ 20バイトを意味しますか?そして、このアーキテクチャではバイトに何ビットありますか?「100万8桁の10進数」の「ミリオン」はSIミリオン(10 ^ 6)ですか?8桁の10進数、自然数<10 ^ 8、10進数の表現が小数点を除いて8桁になる有理数、またはそれ以外のものとは何ですか?

13
100万の8桁の数字または100万の8ビットの数字?
Patrick White、

13
「Dr Dobb's Journal」(1998年から2001年のどこか)の記事を思い出します。著者は、挿入ソートを使用して、電話番号を読んでいるときにソートしました。これが初めて、ときどき遅いことに気づきました。アルゴリズムはより高速かもしれません...
Adrien Plisson 2012年

103
まだ誰も言及していない別のソリューションがあります。2MBRAMのハードウェアを購入してください。それははるかに高価であるべきではなく、それは問題をはるかに、はるかに解決しやすくします。
Daniel Wagner、

回答:


716

これまでここで言及されていない、かなり卑劣なトリックが1つあります。データを保存する特別な方法はないと想定していますが、それは厳密には当てはまりません。

問題を回避する1つの方法は、次の恐ろしいことを実行することです。これは、いかなる状況でも誰も試みてはなりません。ネットワークトラフィックを使用してデータを保存します。いいえ、NASを意味しているのではありません。

次のように、数バイトのRAMで数値を並べ替えることができます。

  • 最初に2つの変数を取ります:COUNTERVALUE
  • 最初にすべてのレジスタをに設定し0ます。
  • 整数を受け取るたびにI、インクリメントCOUNTERしてに設定VALUEmax(VALUE, I)ます。
  • 次に、データが設定されたICMPエコー要求パケットをIルーターに送信します。消去Iして繰り返します。
  • 返されたICMPパケットを受信するたびに、単に整数を抽出して、別のエコー要求で再度送信します。これにより、整数を含む前後に切り捨てられる膨大な数のICMP要求が生成されます。

COUNTER到達すると1000000、ICMPリクエストの絶え間ないストリームにすべての値が保存さVALUEれ、最大の整数が含まれるようになります。いくつか選んでくださいthreshold T >> 1000000COUNTERゼロに設定します。ICMPパケットを受信するたびCOUNTERに、含まれている整数をインクリメントIして別のエコー要求で送り返しI=VALUEます。ただし、の場合は、ソートされた整数の宛先に送信します。一度COUNTER=T、ずつデクリメントVALUE1COUNTERゼロにリセットして繰り返します。一度VALUEに達するがゼロますが、最大の目的地まで最小化するためにすべての整数を送信しているはず、と2つのだけ固定変数(とあなたが一時的な値のために必要なものは何でも少量)用RAMの47ビットについては使用しています。

これは恐ろしいことであり、あらゆる種類の実際的な問題が存在する可能性があることは知っていますが、一部のユーザーを笑わせたり、少なくとも恐怖に陥らせたりする可能性があると思いました。


27
つまり、基本的にネットワーク遅延を利用して、ルーターを一種のキューに変えていますか?
エリックR.

335
この解決策は単なる箱の外ではありません。自宅で箱を忘れたようです:D
Vladislav Zorov

28
すばらしい回答...これらの回答は、ソリューションが問題に対してどれほど多様であるかを実際に明らかにするため、大好きです
StackOverflowed

33
ICMPは信頼できません。
sleeplessnerd

13
@MDMarra:「問題を回避する1つの方法は、次の恐ろしいことをすることです。これは、いかなる状況でも誰も試みてはなりません」とすぐにわかります。私がこれを言った理由がありました。
Joe Fitzsimons、

423

問題を解決するいくつかの動作するC ++コードを次に示します。

メモリ制約が満たされていることの証明:

編集者:この投稿または彼のブログのいずれかで著者が提供する最大メモリ要件の証拠はありません。値をエンコードするために必要なビット数は、以前にエンコードされた値に依存するため、そのような証明はおそらく自明ではありません。著者は1011732、経験的につまずくことができる最大のエンコードされたサイズはであり、バッファーサイズを1013000任意に選択したことに注意します。

typedef unsigned int u32;

namespace WorkArea
{
    static const u32 circularSize = 253250;
    u32 circular[circularSize] = { 0 };         // consumes 1013000 bytes

    static const u32 stageSize = 8000;
    u32 stage[stageSize];                       // consumes 32000 bytes

    ...

これら2つのアレイを合わせると、1045000バイトのストレージが必要です。これにより、残りの変数とスタックスペース用に1048576-1045000-2×1024 = 1528バイトが残ります。

Xeon W3520では約23秒で実行されます。プログラム名がであると仮定して、次のPythonスクリプトを使用してプログラムが動作することを確認できsort1mb.exeます。

from subprocess import *
import random

sequence = [random.randint(0, 99999999) for i in xrange(1000000)]

sorter = Popen('sort1mb.exe', stdin=PIPE, stdout=PIPE)
for value in sequence:
    sorter.stdin.write('%08d\n' % value)
sorter.stdin.close()

result = [int(line) for line in sorter.stdout]
print('OK!' if result == sorted(sequence) else 'Error!')

アルゴリズムの詳細な説明は、次の一連の投稿にあります。


8
@preshingはい、これについての詳細な説明が必要です。
T Suds 2012年

25
重要な所見は、8桁の数値には約26.6ビットの情報があり、100万は19.9ビットであることです。リストをデルタ圧縮する(隣接する値の差を保存する)場合、差の範囲は0(0ビット)〜99999999(26.6ビット)ですが、すべてのペアの間に最大のデルタを持つことはできません。最悪のケースは実際には100万個の均等に分散された値であり、デルタ(26.6-19.9)またはデルタあたり約6.7ビットが必要です。6.7ビットの100万個の値を格納すると、1Mに簡単に収まります。デルタ圧縮には継続的なマージソートが必要なので、ほぼ無料でそれを得ることができます。
ベンジャクソン

4
甘い解決策。y'allは、彼のブログでpreshing.com/20121025/…
10/26

9
@BenJackson:数学のどこかにエラーがあります。2.265 x 10 ^ 2436455の固有の可能な出力(10 ^ 6の8桁の整数の順序付きセット)があり、8.094 x 10 ^ 6ビット(メガバイト以下の髪)を格納する必要があります。この情報理論上の制限を超えて、損失なしに圧縮できる巧妙なスキームはありません。あなたの説明はあなたがはるかに少ないスペースを必要とすることを意味し、したがって間違っています。実際、上記のソリューションの「循環」は、必要な情報を保持するのに十分な大きさなので、事前設定ではこれが考慮されているようですが、それがありません。
Joe Fitzsimons、2012年

5
@JoeFitzsimons:再帰を計算していなかったので(0..mからのn個の数値の一意にソートされたセットは(n+m)!/(n!m!))、正しいはずです。おそらく、bビットのデルタの格納にはbビットが必要となるのは、私の見積もりです。明らかに、0のデルタの格納には0ビットは必要ありません。
ベンジャクソン

371

最初の正解または算術エンコーディングを使用した後の解答を参照してください。以下は楽しいかもしれませんが、100%防弾ソリューションではありません。

これは非常に興味深いタスクであり、ここに別のソリューションがあります。私は誰かが結果が有用である(または少なくとも興味深い)であることを誰かが見つけることを望みます。

ステージ1:初期データ構造、大まかな圧縮アプローチ、基本的な結果

簡単な計算をしてみましょう。1M(1048576バイト)のRAMが10 ^ 6の8桁の10進数を格納するために最初から利用可能です。[0; 99999999]。したがって、1つの数値を格納するには27ビットが必要です(符号なしの数値が使用されることを想定しています)。したがって、未加工のストリームを保存するには、約3.5MのRAMが必要になります。誰かはすでにそれは実現可能ではないようだと言いましたが、入力が「十分」であればタスクは解決できると思います。基本的には、入力データを圧縮係数0.29以上で圧縮し、適切な方法で並べ替えを行うという考え方です。

最初に圧縮の問題を解決しましょう。すでに利用可能ないくつかの関連するテストがあります:

http://www.theeggeadventure.com/wikimedia/index.php/Java_Data_Compression

「さまざまな形式の圧縮を使用して100万の連続した整数を圧縮するテストを実行しました。結果は次のとおりです。」

None     4000027
Deflate  2006803
Filtered 1391833
BZip2    427067
Lzma     255040

LZMA(Lempel–Ziv–Markovチェーンアルゴリズム)は続行するのに適しているようです。簡単なPoCを用意しましたが、強調すべきいくつかの詳細があります。

  1. メモリが限られているため、数値を事前にソートし、圧縮バケット(動的サイズ)を一時ストレージとして使用するという考えです
  2. 事前に並べ替えられたデータを使用してより良い圧縮係数を実現する方が簡単なので、バケットごとに静的バッファーがあります(バッファーからの数値はLZMAの前に並べ替えられます)
  3. 各バケットは特定の範囲を保持するため、最終的なソートはバケットごとに個別に実行できます
  4. バケットのサイズは適切に設定できるため、保存されたデータを解凍し、各バケットの最終的なソートを個別に行うための十分なメモリがあります

インメモリソート

添付のコードはPOCであり、最終的なソリューションとして使用することはできません。いくつかの小さなバッファーを使用して、事前に並べ替えられた数値を最適な方法(おそらく圧縮)で格納するというアイデアを示すだけです。LZMAは最終的なソリューションとして提案されていません。これは、このPoCに圧縮を導入する最速の方法として使用されます。

下記のPoCコードを参照してください(LZMA-Javaをコンパイルするには、デモにすぎないことに注意してください)。

public class MemorySortDemo {

static final int NUM_COUNT = 1000000;
static final int NUM_MAX   = 100000000;

static final int BUCKETS      = 5;
static final int DICT_SIZE    = 16 * 1024; // LZMA dictionary size
static final int BUCKET_SIZE  = 1024;
static final int BUFFER_SIZE  = 10 * 1024;
static final int BUCKET_RANGE = NUM_MAX / BUCKETS;

static class Producer {
    private Random random = new Random();
    public int produce() { return random.nextInt(NUM_MAX); }
}

static class Bucket {
    public int size, pointer;
    public int[] buffer = new int[BUFFER_SIZE];

    public ByteArrayOutputStream tempOut = new ByteArrayOutputStream();
    public DataOutputStream tempDataOut = new DataOutputStream(tempOut);
    public ByteArrayOutputStream compressedOut = new ByteArrayOutputStream();

    public void submitBuffer() throws IOException {
        Arrays.sort(buffer, 0, pointer);

        for (int j = 0; j < pointer; j++) {
            tempDataOut.writeInt(buffer[j]);
            size++;
        }            
        pointer = 0;
    }

    public void write(int value) throws IOException {
        if (isBufferFull()) {
            submitBuffer();
        }
        buffer[pointer++] = value;
    }

    public boolean isBufferFull() {
        return pointer == BUFFER_SIZE;
    }

    public byte[] compressData() throws IOException {
        tempDataOut.close();
        return compress(tempOut.toByteArray());
    }        

    private byte[] compress(byte[] input) throws IOException {
        final BufferedInputStream in = new BufferedInputStream(new ByteArrayInputStream(input));
        final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(compressedOut));

        final Encoder encoder = new Encoder();
        encoder.setEndMarkerMode(true);
        encoder.setNumFastBytes(0x20);
        encoder.setDictionarySize(DICT_SIZE);
        encoder.setMatchFinder(Encoder.EMatchFinderTypeBT4);

        ByteArrayOutputStream encoderPrperties = new ByteArrayOutputStream();
        encoder.writeCoderProperties(encoderPrperties);
        encoderPrperties.flush();
        encoderPrperties.close();

        encoder.code(in, out, -1, -1, null);
        out.flush();
        out.close();
        in.close();

        return encoderPrperties.toByteArray();
    }

    public int[] decompress(byte[] properties) throws IOException {
        InputStream in = new ByteArrayInputStream(compressedOut.toByteArray());
        ByteArrayOutputStream data = new ByteArrayOutputStream(10 * 1024);
        BufferedOutputStream out = new BufferedOutputStream(data);

        Decoder decoder = new Decoder();
        decoder.setDecoderProperties(properties);
        decoder.code(in, out, 4 * size);

        out.flush();
        out.close();
        in.close();

        DataInputStream input = new DataInputStream(new ByteArrayInputStream(data.toByteArray()));
        int[] array = new int[size];
        for (int k = 0; k < size; k++) {
            array[k] = input.readInt();
        }

        return array;
    }
}

static class Sorter {
    private Bucket[] bucket = new Bucket[BUCKETS];

    public void doSort(Producer p, Consumer c) throws IOException {

        for (int i = 0; i < bucket.length; i++) {  // allocate buckets
            bucket[i] = new Bucket();
        }

        for(int i=0; i< NUM_COUNT; i++) {         // produce some data
            int value = p.produce();
            int bucketId = value/BUCKET_RANGE;
            bucket[bucketId].write(value);
            c.register(value);
        }

        for (int i = 0; i < bucket.length; i++) { // submit non-empty buffers
            bucket[i].submitBuffer();
        }

        byte[] compressProperties = null;
        for (int i = 0; i < bucket.length; i++) { // compress the data
            compressProperties = bucket[i].compressData();
        }

        printStatistics();

        for (int i = 0; i < bucket.length; i++) { // decode & sort buckets one by one
            int[] array = bucket[i].decompress(compressProperties);
            Arrays.sort(array);

            for(int v : array) {
                c.consume(v);
            }
        }
        c.finalCheck();
    }

    public void printStatistics() {
        int size = 0;
        int sizeCompressed = 0;

        for (int i = 0; i < BUCKETS; i++) {
            int bucketSize = 4*bucket[i].size;
            size += bucketSize;
            sizeCompressed += bucket[i].compressedOut.size();

            System.out.println("  bucket[" + i
                    + "] contains: " + bucket[i].size
                    + " numbers, compressed size: " + bucket[i].compressedOut.size()
                    + String.format(" compression factor: %.2f", ((double)bucket[i].compressedOut.size())/bucketSize));
        }

        System.out.println(String.format("Data size: %.2fM",(double)size/(1014*1024))
                + String.format(" compressed %.2fM",(double)sizeCompressed/(1014*1024))
                + String.format(" compression factor %.2f",(double)sizeCompressed/size));
    }
}

static class Consumer {
    private Set<Integer> values = new HashSet<>();

    int v = -1;
    public void consume(int value) {
        if(v < 0) v = value;

        if(v > value) {
            throw new IllegalArgumentException("Current value is greater than previous: " + v + " > " + value);
        }else{
            v = value;
            values.remove(value);
        }
    }

    public void register(int value) {
        values.add(value);
    }

    public void finalCheck() {
        System.out.println(values.size() > 0 ? "NOT OK: " + values.size() : "OK!");
    }
}

public static void main(String[] args) throws IOException {
    Producer p = new Producer();
    Consumer c = new Consumer();
    Sorter sorter = new Sorter();

    sorter.doSort(p, c);
}
}

乱数を使用すると、以下が生成されます。

bucket[0] contains: 200357 numbers, compressed size: 353679 compression factor: 0.44
bucket[1] contains: 199465 numbers, compressed size: 352127 compression factor: 0.44
bucket[2] contains: 199682 numbers, compressed size: 352464 compression factor: 0.44
bucket[3] contains: 199949 numbers, compressed size: 352947 compression factor: 0.44
bucket[4] contains: 200547 numbers, compressed size: 353914 compression factor: 0.44
Data size: 3.85M compressed 1.70M compression factor 0.44

単純な昇順シーケンス(1つのバケットが使用されます)の場合は、以下が生成されます。

bucket[0] contains: 1000000 numbers, compressed size: 256700 compression factor: 0.06
Data size: 3.85M compressed 0.25M compression factor 0.06

編集する

結論:

  1. 自然をだまそうとしないでください
  2. メモリフットプリントが少ないシンプルな圧縮を使用する
  3. いくつかの追加の手がかりが本当に必要です。一般的な防弾ソリューションは実現可能ではないようです。

ステージ2:圧縮の強化、最終的な結論

前のセクションですでに述べたように、任意の適切な圧縮手法を使用できます。それでは、LZMAを削除して、よりシンプルでより良い(可能な場合)アプローチを採用しましょう。算術コーディング基数ツリーなど、多くの優れたソリューションがあります

とにかく、シンプルだが便利なエンコーディングスキームは、いくつかの気の利いたアルゴリズムを提供する、さらに別の外部ライブラリよりもわかりやすくなります。実際のソリューションは非常に単純です。部分的にソートされたデータを含むバケットがあるため、数値の代わりにデルタを使用できます。

コード化スキーム

ランダム入力テストは少し良い結果を示しています:

bucket[0] contains: 10103 numbers, compressed size: 13683 compression factor: 0.34
bucket[1] contains: 9885 numbers, compressed size: 13479 compression factor: 0.34
...
bucket[98] contains: 10026 numbers, compressed size: 13612 compression factor: 0.34
bucket[99] contains: 10058 numbers, compressed size: 13701 compression factor: 0.34
Data size: 3.85M compressed 1.31M compression factor 0.34

サンプルコード

  public static void encode(int[] buffer, int length, BinaryOut output) {
    short size = (short)(length & 0x7FFF);

    output.write(size);
    output.write(buffer[0]);

    for(int i=1; i< size; i++) {
        int next = buffer[i] - buffer[i-1];
        int bits = getBinarySize(next);

        int len = bits;

        if(bits > 24) {
          output.write(3, 2);
          len = bits - 24;
        }else if(bits > 16) {
          output.write(2, 2);
          len = bits-16;
        }else if(bits > 8) {
          output.write(1, 2);
          len = bits - 8;
        }else{
          output.write(0, 2);
        }

        if (len > 0) {
            if ((len % 2) > 0) {
                len = len / 2;
                output.write(len, 2);
                output.write(false);
            } else {
                len = len / 2 - 1;
                output.write(len, 2);
            }

            output.write(next, bits);
        }
    }
}

public static short decode(BinaryIn input, int[] buffer, int offset) {
    short length = input.readShort();
    int value = input.readInt();
    buffer[offset] = value;

    for (int i = 1; i < length; i++) {
        int flag = input.readInt(2);

        int bits;
        int next = 0;
        switch (flag) {
            case 0:
                bits = 2 * input.readInt(2) + 2;
                next = input.readInt(bits);
                break;
            case 1:
                bits = 8 + 2 * input.readInt(2) +2;
                next = input.readInt(bits);
                break;
            case 2:
                bits = 16 + 2 * input.readInt(2) +2;
                next = input.readInt(bits);
                break;
            case 3:
                bits = 24 + 2 * input.readInt(2) +2;
                next = input.readInt(bits);
                break;
        }

        buffer[offset + i] = buffer[offset + i - 1] + next;
    }

   return length;
}

注意してください、このアプローチ:

  1. 多くのメモリを消費しない
  2. ストリームで動作します
  3. それほど悪い結果ではありません

完全なコードはここにあります、BinaryInputとBinaryOutputの実装はここにあります

最終結論

最終的な結論はありません:) 1レベル上に移動し、メタレベルの観点からタスクを確認することは、本当に良い考えです。

この仕事に時間を費やすのは楽しかったです。ところで、以下に興味深い答えがたくさんあります。ご清聴ありがとうございました。


17
Inkscapeを使用しました。ところで素晴らしいツールです。この図のソースを例として使用できます。
Renat Gilmanov 2012年

21
確かにLZMAは、この場合に役立つにはあまりにも多くのメモリを必要としますか?アルゴリズムとしては、メモリで効率的になるのではなく、保存または送信する必要があるデータの量を最小限にすることを意味します。
Mjiig 2​​012年

67
これはナンセンスです... 100万個のランダムな27ビット整数を取得し、それらを並べ替え、7zip、xz、任意のLZMAで圧縮します。結果が1MBを超えています。前提は、連番の圧縮です。0bitでのそれのデルタ符号化は、例えば1000000(例えば4バイトで言う)のような単なる数値です。順次および重複(ギャップなし)の場合、1000000ビットと1000000ビット= 128KB。重複番号は0、次にマークするのは1。あなたがランダムなギャップを持っているとき、たとえ小さなものであっても、LZMAはばかげています。これのために設計されていません。
alecco 2012年

30
これは実際には機能しません。シミュレーションを実行しましたが、圧縮データが1MB(約1.5MB)を超えていますが、100MBを超えるRAMを使用してデータを圧縮しています。そのため、圧縮された整数でさえ、実行時のRAM使用量は言うまでもなく問題に適合しません。賞金を授与することは、stackoverflowでの最大のエラーです。
お気に入りのOnwuemene

10
多くのプログラマーは、実績のあるコードではなく光沢のあるアイデアを好むため、この答えは非常に賛成です。このアイデアが機能した場合、実際にそれを実行できるものは確かにあるという主張ではなく、実際の圧縮アルゴリズムが選択されて証明されていることがわかります...それを実行できるものが存在しない可能性が非常に高い場合。
Olathe 2012年

185

解決策は、1メガバイトと100万バイトの違いが原因でのみ可能です。重複を許可して重要でない順序で100万個の8桁の数字を選択するには、約2のべき乗8093729.5のさまざまな方法があります。そのため、100万バイトのRAMしかないマシンでは、すべての可能性を表すのに十分な状態がありません。ただし、1M(TCP / IPの場合は2k未満)は1022 * 1024 * 8 = 8372224ビットなので、解決策は可能です。

パート1、初期ソリューション

このアプローチには100万個以上が必要です。後で100万に収まるように調整します。

7ビットの数値のサブリストのシーケンスとして、0から99999999の範囲の数値のコンパクトなソートリストを保存します。最初のサブリストは0から127までの数を保持し、2番目のサブリストは128から255までの数を保持します。100000000/ 128は正確に781250なので、781250のようなサブリストが必要になります。

各サブリストは、2ビットのサブリストヘッダーとそれに続くサブリスト本文で構成されます。サブリストボディは、サブリストエントリごとに7ビットを占めます。サブリストはすべて連結されており、このフォーマットにより、1つのサブリストがどこで終了し、次のサブリストがどこで始まるかがわかります。完全に実装されたリストに必要なストレージの合計は、2 * 781250 + 7 * 1000000 = 8562500ビットであり、約1.021 Mバイトです。

4つの可能なサブリストヘッダー値は次のとおりです。

00空のサブリスト、後に続くものはありません。

01シングルトン、サブリストにはエントリが1つだけあり、次の7ビットがそれを保持します。

10サブリストには、少なくとも2つの異なる番号が含まれます。エントリは、最後のエントリが最初のエントリ以下である場合を除いて、降順で格納されます。これにより、サブリストの終わりを識別できます。たとえば、数値2,4,6は(4,6,2)として格納されます。数値2、2、3、4、4は(2、3、4、4、2)として格納されます。

11サブリストは、1つの数値の2回以上の繰り返しを保持します。次の7ビットは数を示します。次に、値1の0個以上の7ビットエントリが続き、その後に値0の7ビットエントリが続きます。サブリストボディの長さによって、繰り返しの数が決まります。たとえば、数値12,12は(12,0)として格納され、数値12,12,12は(12,1,0)として格納され、数値12,12,12,12は(12,1 、1、0)など。

私は空のリストから始めて、大量の数値を読み込んで32ビット整数として保存し、新しい数値を(おそらくヒープソートを使用して)並べ替えてから、新しいコンパクトな並べ替えリストにマージします。読み取る数値がなくなるまで繰り返し、コンパクトリストをもう一度歩いて出力を生成します。

以下の行は、リストマージ操作の開始直前のメモリを表しています。「O」は、ソートされた32ビット整数を保持する領域です。「X」は、古いコンパクトリストを保持する領域です。「=」記号はコンパクトリストの拡張の余地であり、「O」の各整数に7ビットです。「Z」はその他のランダムなオーバーヘッドです。

ZZZOOOOOOOOOOOOOOOOOOOOOOOOOO==========XXXXXXXXXXXXXXXXXXXXXXXXXX

マージルーチンは、左端の「O」と左端の「X」から読み取りを開始し、左端の「=」から書き込みを開始します。すべての新しい整数がマージされるまで、書き込みポインターはコンパクトリスト読み取りポインターをキャッチしません。両方のポインターが、サブリストごとに2ビット、古いコンパクトリストの各エントリごとに7ビット進み、さらに十分な余地があるためです。新しい番号の7ビットのエントリ。

パート2、1Mに詰め込む

上記のソリューションを1Mに圧縮するには、コンパクトリスト形式をもう少しコンパクトにする必要があります。サブリストタイプの1つを取り除くので、可能なサブリストヘッダー値は3つだけになります。次に、「00」、「01」、および「1」をサブリストヘッダー値として使用して、数ビットを節約できます。サブリストのタイプは次のとおりです。

空のサブリスト。後に何も続きません。

Bシングルトン。サブリストにはエントリが1つしかなく、次の7ビットがそれを保持します。

Cサブリストは、少なくとも2つの異なる番号を保持します。エントリは、最後のエントリが最初のエントリ以下であることを除いて、降順ではなく保存されます。これにより、サブリストの終わりを識別できます。たとえば、数値2,4,6は(4,6,2)として格納されます。数値2、2、3、4、4は(2、3、4、4、2)として格納されます。

Dサブリストは、1つの数値の2回以上の繰り返しで構成されます。

私の3つのサブリストヘッダー値は「A」、「B」、「C」になるため、Dタイプのサブリストを表す方法が必要です。

「C [17] [101] [58]」のように、Cタイプのサブリストヘッダーの後に3つのエントリが続くとします。3番目のエントリは2番目のエントリよりも小さいが、最初のエントリよりも大きいため、これは上記の有効なCタイプのサブリストの一部にすることはできません。このタイプの構成を使用して、Dタイプのサブリストを表すことができます。ビットで言えば、「C {00 ?????} {1 ??????} {01 ?????}」がどこにあっても、それは不可能なCタイプのサブリストです。これを使用して、1つの数値の3回以上の繰り返しで構成されるサブリストを表します。最初の2つの7ビットワードは数値(以下の「N」ビット)をエンコードし、その後に0個以上の{0100001}ワードが続き、その後に{0100000}ワードが続きます。

For example, 3 repetitions: "C{00NNNNN}{1NN0000}{0100000}", 4 repetitions: "C{00NNNNN}{1NN0000}{0100001}{0100000}", and so on.

それはちょうど単一の数の正確に2回の繰り返しを保持するリストを残します。別の不可能なCタイプのサブリストパターン「C {0 ??????} {11 ?????} {10 ?????}」でそれらを表現します。最初の2ワードには7ビットの数値を収める十分な余地がありますが、このパターンはそれが表すサブリストよりも長いため、状況は少し複雑になります。末尾の5つの疑問符はパターンの一部ではないと考えることができるので、「C {0NNNNNN} {11N ????}} 10」をパターンとして使用し、繰り返される番号を「N 「2ビット長すぎます。

このパターンでは、2ビットを借りて、未使用の4ビットから払い戻す必要があります。読み取り時、「C {0NNNNNN} {11N00AB} 10」に遭遇すると、「N」の数値の2つのインスタンスを出力し、最後の「10」をビットAとBで上書きし、読み取りポインタを2だけ巻き戻します。ビット。このアルゴリズムでは、各コンパクトリストが1回しかウォークされないため、破壊的な読み取りは問題ありません。

1つの数値の2回の繰り返しのサブリストを書き込むときは、「C {0NNNNNN} 11N00」と書き込み、借用ビットカウンターを2に設定します。借用ビットカウンターがゼロ以外の書き込みごとに、書き込まれるビットごとにデクリメントされ、カウンタがゼロになると「10」が書き込まれます。したがって、書き込まれた次の2ビットはスロットAとBに入れられ、「10」は最後にドロップされます。

「00」、「01」、「1」で表される3つのサブリストヘッダー値により、最も人気のあるサブリストタイプに「1」を割​​り当てることができます。サブリストヘッダーの値をサブリストタイプにマップするには小さなテーブルが必要です。また、各サブリストタイプのオカレンスカウンターが必要です。これにより、最良のサブリストヘッダーマッピングがわかるようになります。

完全に入力されたコンパクトリストの最悪の場合の最小限の表現は、すべてのサブリストタイプが同等に人気がある場合に発生します。その場合、3つのサブリストヘッダーごとに1ビットを保存するので、リストのサイズは2 * 781250 + 7 * 1000000-781250/3 = 8302083.3ビットです。32ビットのワード境界、つまり8302112ビット、または1037764バイトに切り上げます。

1MからTCP / IP状態およびバッファーの2kを引いた値は1022 * 1024 = 1046528バイトで、残りの8764バイトが残ります。

しかし、サブリストヘッダーマッピングを変更するプロセスについてはどうでしょうか。以下のメモリマップでは、「Z」はランダムオーバーヘッド、「=」は空き領域、「X」はコンパクトリストです。

ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

一番左の「X」から読み始め、一番左の「=」から書き始めて、正しく作業します。完了すると、コンパクトなリストは少し短くなり、メモリの終わりが間違っています。

ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======

だから私はそれを右にシャントする必要があります:

ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ヘッダーマッピング変更プロセスでは、サブリストヘッダーの最大1/3が1ビットから2ビットに変更されます。最悪の場合、これらはすべてリストの先頭にあるため、開始する前に少なくとも781250/3ビットの空きストレージが必要です。これにより、以前のバージョンのコンパクトリストのメモリ要件に戻ります。 (

それを回避するために、781250サブリストをそれぞれ78125サブリストの10個のサブリストグループに分割します。各グループには、独自の独立したサブリストヘッダーマッピングがあります。グループにA〜Jの文字を使用します。

ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ

各サブリストグループは、サブリストヘッダーマッピングの変更中に縮小または変更されません。

ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAA=====BBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABB=====CCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCC======DDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDD======EEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEE======FFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFF======GGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGG=======HHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHH=======IJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHI=======JJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ=======
ZZZ=======AAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ

マッピング変更中のサブリストグループの最悪の場合の一時的な拡張は、4k未満で78125/3 = 26042ビットです。完全に入力されたコンパクトリストに4kプラス1037764バイトを許可すると、メモリマップの "Z"に8764-4096 = 4668バイトが残ります。

これは、10個のサブリストヘッダーマッピングテーブル、30個のサブリストヘッダーオカレンスカウント、その他の必要なカウンター、ポインター、小さなバッファー、および関数呼び出しの戻りアドレスのスタックスペースなど、気付かずに使用したスペースには十分なはずです。ローカル変数。

パート3、実行にどのくらいかかりますか?

空のコンパクトリストでは、1ビットのリストヘッダーが空のサブリストに使用され、リストの開始サイズは781250ビットになります。最悪の場合、リストは追加される数値ごとに8ビット増加するため、32ビットの数値のそれぞれをリストバッファーの上部に配置し、並べ替えてマージするには、32 + 8 = 40ビットの空き領域が必要です。最悪の場合、サブリストヘッダーマッピングを変更すると、2 * 781250 + 7 * entries-781250/3ビットのスペース使用になります。

リストに少なくとも800000個の数値が存在する場合、5回のマージごとにサブリストヘッダーマッピングを変更するポリシーでは、最悪の場合、合計約30Mのコンパクトリストの読み取りおよび書き込みアクティビティが発生します。

ソース:

http://nick.cleaton.net/ramsortsol.html


15
これ以上の解決策はあり得ないと思います(非圧縮性の値を扱う必要がある場合)。しかし、これは少し改善されるかもしれません。サブリストヘッダーを1ビット表現と2ビット表現の間で変更する必要はありません。代わりに、算術コーディングを使用できます。これにより、アルゴリズムが簡略化され、ヘッダーあたりの最悪の場合のビット数が1.67から1.58に減少します。また、メモリ内でコンパクトリストを移動する必要はありません。代わりに、循環バッファーを使用して、ポインターのみを変更します。
Evgeny Kluev、2012年

5
それで、最後に、それはインタビューの質問でしたか?
mlvljr 2012年

2
その他の可能な改善点は、128要素のサブリストではなく100要素のサブリストを使用することです(サブリストの数がデータセット内の要素の数と等しい場合、最もコンパクトな表現が得られるためです)。算術コーディングでエンコードされるサブリストの各値(各値の1/100の頻度が等しい)。これにより、サブリストヘッダーの圧縮よりもはるかに少ない、約10000ビットを節約できます。
Evgeny Kluev、2012年

ケースCの場合、「最後のエントリが最初のエントリ以下であることを除いて、エントリは降順に格納されます。」と言います。次に、2、2、2、3、5をどのようにエンコードしますか?{2,2,3,5,2}はちょうど2,2のように見えます
Rollie

1
マッピングを複雑に切り替えることなく、サブヘッダーあたり同じ圧縮率1.67ビットのサブリストヘッダーエンコーディングのより簡単なソリューションが可能です。連続する3つのサブヘッダーを1つにまとめることができます。これは、5ビットに簡単にエンコードできるため3 * 3 * 3 = 27 < 32です。それらを組み合わせますcombined_subheader = subheader1 + 3 * subheader2 + 9 * subheader3
hynekcer 2012年

57

ギルマノフの答えはその仮定において非常に間違っています。100万の連続した整数の無意味な測定に基づいて推測を開始します。それはギャップがないことを意味します。これらのランダムなギャップは、たとえどれほど小さくても、本当にそれを悪い考えにします。

自分で試してみてください。100万のランダムな27ビット整数を取得し、それらを並べ替え、7-Zip、xz、任意のLZMA で圧縮します。結果は1.5 MBを超えています。前提は、連番の圧縮です。そのデルタエンコーディングでも1.1 MBを超えています。また、圧縮に100 MBを超えるRAMを使用していることを気にしないでください。そのため、圧縮された整数でも問題に適合せず、実行時のRAM使用量を気にする必要はありません

人々がかわいらしいグラフィックスと合理化に賛成するのは悲しいことです。

#include <stdint.h>
#include <stdlib.h>
#include <time.h>

int32_t ints[1000000]; // Random 27-bit integers

int cmpi32(const void *a, const void *b) {
    return ( *(int32_t *)a - *(int32_t *)b );
}

int main() {
    int32_t *pi = ints; // Pointer to input ints (REPLACE W/ read from net)

    // Fill pseudo-random integers of 27 bits
    srand(time(NULL));
    for (int i = 0; i < 1000000; i++)
        ints[i] = rand() & ((1<<27) - 1); // Random 32 bits masked to 27 bits

    qsort(ints, 1000000, sizeof (ints[0]), cmpi32); // Sort 1000000 int32s

    // Now delta encode, optional, store differences to previous int
    for (int i = 1, prev = ints[0]; i < 1000000; i++) {
        ints[i] -= prev;
        prev    += ints[i];
    }

    FILE *f = fopen("ints.bin", "w");
    fwrite(ints, 4, 1000000, f);
    fclose(f);
    exit(0);

}

LZMAでints.binを圧縮します...

$ xz -f --keep ints.bin       # 100 MB RAM
$ 7z a ints.bin.7z ints.bin   # 130 MB RAM
$ ls -lh ints.bin*
    3.8M ints.bin
    1.1M ints.bin.7z
    1.2M ints.bin.xz

7
辞書ベースの圧縮を含むすべてのアルゴリズムは遅延を超えています、いくつかのカスタムのものをコーディングしましたが、それらはすべて、独自のハッシュテーブルを配置するためだけにかなりのメモリを必要します(リソースに余分に飢えているため、JavaのHashMapはありません)。最も近い解決策は、可変ビット長のデルタエンコーディングと、不要なTCPパケットのバウンスです。ピアは再送信しますが、せいぜい風変わりです。
bestss

@bestsssええ!私の最後の進行中の答えをチェックしてください。可能かもしれないと思います。
2012年

3
申し訳ありませんが、これは実際には質問に答えていないようです。
n611x007 2013年

@naxaはい、答えます。元の質問のパラメーター内では実行できません。これは、数値の分布のエントロピーが非常に低い場合にのみ実行できます。
alecco 2015年

1
この答えが示すのは、標準の圧縮ルーチンでは1MB未満のデータを圧縮するのが難しいということです。データを1MB未満に圧縮できるエンコードスキームがある場合とない場合がありますが、この回答は、データをそれほど圧縮するエンコードスキームがないことを証明していません。
itsme2003

41

これについて考える1つの方法は、組み合わせ論の観点からだと思います。ソートされた数値の順序の組み合わせはいくつありますか?組み合わせ0,0,0、....、0にコード0を、0,0,0、...、1にコード1を、99999999、99999999、... 99999999にコードNを与えると、 Nとは?言い換えれば、結果のスペースはどのくらいですか?

さて、これについて考える1つの方法は、これがN x Mグリッドで単調なパスの数を見つける問題の全単射であることであり、N = 1,000,000およびM = 100,000,000です。つまり、幅が1,000,000、高さが1億のグリッドがある場合、左下から右上に向かって最短パスはいくつあるでしょうか。もちろん、最短経路では、右または上に移動するだけで済みます(下または左に移動すると、以前に達成した進行状況が取り消されます)。これが番号並べ替えの問題の全単射であることを確認するには、次の点に注意してください。

パスの水平方向の脚は、順序付けの数値として想像できます。脚のY位置は値を表します。

ここに画像の説明を入力してください

したがって、パスが単純に最後まで右に移動し、次に最上部までジャンプする場合は、順序0,0,0、...、0と同じです。代わりに、一番上までジャンプして最初に右に1,000,000回移動すると、99999999,99999999、...、99999999に相当します。1回右に移動し、次に1回上に移動し、次に1つ右に移動するパス、次に1回など、最後まで(その後、必ず一番上にジャンプします)、0、1、2、3、...、999999と同等です。

幸いなことに、この問題はすでに解決されており、そのようなグリッドには(N + M)(M)のパスが選択されています。

(1,000,000 + 100,000,000)を選択(100,000,000)〜= 2.27 * 10 ^ 2436455

したがって、Nは2.27 * 10 ^ 2436455に等しいため、コード0は0,0,0、...、0を表し、コード2.27 * 10 ^ 2436455といくつかの変更は99999999,99999999、...、99999999を表します。

0から2.27 * 10 ^ 2436455までのすべての数値を格納するには、lg2(2.27 * 10 ^ 2436455)= 8.0937 * 10 ^ 6ビットが必要です。

1メガバイト= 8388608ビット> 8093700ビット

したがって、少なくとも実際には結果を保存するのに十分なスペースがあるようです!もちろん、興味深いビットは、数値が流れ込むときに並べ替えを行うことです。残りの294908ビットがあるので、これに対する最善の方法がわからないのです。興味深いテクニックは、それがすべての順序であると各ポイントで想定し、その順序のコードを見つけ、新しい番号を受け取ったら、前のコードに戻って更新することだと思います。ハンドウェーブハンドウェーブ。


これは本当にたくさん手を振っています。一方では、理論的にはこれがソリューションです。これは、大きな(ただし有限の)ステートマシンを作成できるためです。一方、その大きなステートマシンの命令ポインターのサイズは1メガバイトを超える可能性があり、これはスターターではありません。それは実際に与えられた問題を実際に解決するためにこれよりもむしろ少し考えが必要です。すべての状態を表すだけでなく、次の入力番号で何をするかを計算するために必要なすべての遷移状態も表す必要があります。
ダニエルワグナー

4
他の答えは、手を振っているだけの方が微妙だと思います。結果スペースのサイズがわかったので、絶対に必要なスペースの量がわかりました。他の回答では8093700ビット未満のあらゆる回答を格納することはできません。最高の状態で圧縮(最終状態)缶をやって時々(無圧縮アルゴリズムは、すべての入力を圧縮することはできません)スペースを削減するが、常に完全なスペースを必要とするいくつかの答えがあるでしょう。
Francisco Ryan Tolmasky I 2012

他のいくつかの回答では、いずれにせよハードの下限についてすでに言及しています(たとえば、元の質問者の回答の2番目の文)。この回答がゲシュタルトに追加している内容がよくわかりません。
ダニエルワグナー、

生ストリームを保存するために3.5Mを参照していますか?(そうでなければ、私の謝罪とこの応答を無視してください)。もしそうなら、それは完全に無関係な下限です。私の下限は、結果が占めるスペースの量であり、下限は、入力を保存する必要がある場合に入力が占めるスペースの量です-質問がTCP接続から入ってくるストリームとしてフレーズされた場合、実際に必要かどうかは明確ではありませんが、一度に1つの数値を読み取って状態を更新しているため、3.5Mは必要ありません-いずれにしても、3.5はこの計算に直交しています。
Francisco Ryan Tolmasky I 2012

「重複が許可され、重要でない順序で100万の8桁の数字を選択するには、約2のべき乗8093729.5のさまざまな方法があります」<-元の質問者の回答から。私が話している境界についてこれ以上明確にする方法がわかりません。私は私の最後のコメントでこの文にかなり具体的に言及しました。
Daniel Wagner、

20

ここでの私の提案は、Danの解決策のおかげです。

まず、ソリューションがすべての可能な入力リストを処理する必要があると想定しています。私は人気のある答えはこの仮定を行わないと思います(IMOは大きな間違いです)

ロスレス圧縮の形式がすべての入力のサイズを縮小しないことは知られています。

一般的な回答はすべて、追加のスペースを確保するのに十分効果的な圧縮を適用できると想定しています。実際、部分的に完成したリストの一部を非圧縮形式で保持し、並べ替え操作を実行するのに十分な大きさの余分なスペースのチャンク。これは悪い仮定です。

そのようなソリューションの場合、圧縮方法を知っている人なら誰でも、このスキームではうまく圧縮されない入力データを設計でき、「ソリューション」はスペース不足のために壊れる可能性が高くなります。

代わりに、私は数学的なアプローチをとります。可能な出力は、0..MAXの範囲の要素で構成される長さLENのすべてのリストです。ここで、LENは1,000,000、MAXは100,000,000です。

任意のLENおよびMAXの場合、この状態をエンコードするために必要なビット数は次のとおりです。

Log2(MAXマルチチョークLEN)

したがって、数値については、受信と並べ替えが完了したら、可能なすべての出力を一意に区別できる方法で結果を格納するために、少なくともLog2(100,000,000 MC 1,000,000)ビットが必要になります。

これは〜= 988kbです。したがって、実際には結果を保持するのに十分なスペースがあります。この観点から、それは可能です。

[より良い例が存在するため、無意味なとりとめのないことを削除...]

ベストアンサーはこちらです。

別の良い答えがここにあり、基本的には挿入ソートを関数として使用して、リストを1つの要素で拡張します(いくつかの要素と事前ソートをバッファーに入れ、一度に複数の挿入を可能にして、少し時間を節約します)。7ビットデルタのバケットである、素敵なコンパクトステートエンコーディングも使用します。


翌日、自分の答えを読み直すのはいつも楽しいです。ですから、一番上の答えは間違っていますが、受け入れられた1つのstackoverflow.com/a/12978097/1763801はかなり良いです。基本的に、リストLEN-1を取得してLENを返す関数として挿入ソートを使用します。小さなセットを事前に並べ替えると、すべてを1つのパスに挿入して効率を上げることができるという事実を利用します。状態の表現はかなりコンパクト(7ビットの数値のバケット)であり、私の波状の提案よりも優れており、より直感的です。私の
コンプジオの

1
あなたの算数は少しずれていると思います。lg2(100999999!/(99999999!* 1000000!))= 1011718.55
NovaDenizen

はい、ありがとうございました。965ではなく988kbでした。1024と1000の関係でずさんでした。答えに数学計算へのリンクを追加しました。
davec 2012年

18

このタスクが可能であるとします。出力の直前に、100万ソートされた数値のメモリ内表現があります。そのような表現はいくつありますか?繰り返し数がある可能性があるため、nCr(選択)を使用することはできませんが、マルチセットで機能するmultichooseと呼ばれる操作があります。

  • 範囲0..99,999,999で100万の数値を選択するには、2.2e2436455の方法があります。
  • 可能なすべての組み合わせを表すには、8,093,730ビット、つまり1,011,717バイトが必要です。

したがって、理論的には、ソートされた数値のリストの正気な(十分な)表現を思い付くことができる場合、それは可能かもしれません。たとえば、非常識な表現では、10MBのルックアップテーブルまたは数千行のコードが必要になる場合があります。

ただし、「1M RAM」が100万バイトを意味する場合、明らかに十分なスペースがありません。5%多くのメモリが理論的に可能にするという事実は、表現が非常に効率的でなければならず、おそらく正気ではないことを示唆しています。


100万の数値(2.2e2436455)を選択する方法の数は、(256 ^(1024 * 988))に近い(2.0e2436445)です。エルゴ、1Mから約32 KBのメモリを奪うと、問題は解決できません。また、少なくとも3 KBのメモリが予約されていることに注意してください。
johnwbyrd

もちろん、これはデータが完全にランダムであることを前提としています。私たちが知る限り、そうですが、私はただ言っています:)
Thorarin

この可能な数の状態を表す従来の方法は、対数の底2を取り、それらを表すために必要なビット数を報告することです。
NovaDenizen

@Thorarin、うん、一部の入力に対してのみ機能する「ソリューション」には意味がありません。
Dan

12

(私の元の答えは間違っていました。数学がおかしいので申し訳ありません。以下のブレークを参照してください。)

これはどう?

最初の27ビットは、表示された最小の数値を格納し、次に表示される数値との差を次のようにエンコードします。5ビットは、差の格納に使用されるビット数を格納し、次に差を格納します。00000を使用して、その番号が再び表示されたことを示します。

これが機能するのは、挿入される数値が増えると、数値間の平均差が減少するため、数値を追加するときに、少ないビットを使用して差を格納するためです。これはデルタリストと呼ばれていると思います。

私が考えることができる最悪のケースは、すべての数値が等間隔(100)であることです。たとえば、0が最初の数値であると仮定します。

000000000000000000000000000 00111 1100100
                            ^^^^^^^^^^^^^
                            a million times

27 + 1,000,000 * (5+7) bits = ~ 427k

救助にReddit!

あなたがしなければならないすべてはそれらを並べ替える場合、この問題は簡単でしょう。表示した数値を格納するのに122k(100万ビット)かかります(0が表示された場合は0番目のビットがオン、2300が表示された場合は2300番目のビットがオンなど)。

数値を読み取ってビットフィールドに格納し、カウントを維持しながらビットをシフトアウトします。

しかし、あなたはあなたが見た何人を覚えている必要があります。上記のサブリストの回答に刺激を受け、このスキームを思いつきました。

1ビットを使用する代わりに、2または27ビットを使用します。

  • 00は、番号が表示されなかったことを意味します。
  • 01は一度見たという意味です
  • 1はそれを見たことを意味し、次の26ビットは回数のカウントです。

これはうまくいくと思います:重複がない場合、244kのリストがあります。最悪の場合、各数値が2回表示されます(1つの数値が3回表示されると、残りのリストが短くなります)。つまり、50,000を複数回表示し、950,000の項目を0または1回表示しています。

50,000 * 27 + 950,000 * 2 = 396.7k。

次のエンコーディングを使用すると、さらに改善できます。

0は数字が表示されなかったことを意味します10は一度目にしたことを意味します11はカウントを続ける方法です

これにより、平均で280.7kのストレージになります。

編集:私の日曜日の朝の数学は間違っていました。

最悪の場合、500,000の数値が2回表示されるため、計算は次のようになります。

500,000 * 27 + 500,000 * 2 = 1.77M

代替エンコーディングの結果、平均ストレージは

500,000 * 27 + 500,000 = 1.70M

:(


1
まあ、ない、2番目の番号は500000になりますので、
jfernand

たとえば、11は64回まで(次の6ビットを使用して)数値を表示したことを意味し、11000000はさらに32ビットを使用して表示した回数を格納することを意味します。
τεκ

10
「100万ビット」の数値はどこで入手したのですか。あなたは、2300番目のビットが2300が見られたかどうかを表すと言いました。(私はあなたが実際に2301番目を意味していると思います。)どのビットが99,999,999(最大の8桁の数字)が見られたかどうかを表しますか?おそらく、1億ビットです。
user94559

あなたはあなたの100万とあなたの1億を後退させました。値が発生する可能性が最も高いのは100万回で、値の発生数を表すのに必要なのは20ビットだけです。同様に、100,000,000ビットフィールド(100万ではない)が必要です。
Tim R.

ええと、27 + 1000000 *(5 + 7)= 12000027ビット= 1.43M、427Kではありません。
ダニエルワグナー

10

考えられるすべての入力にわたって、この問題に対する1つの解決策があります。チート。

  1. TCPを介してm値を読み取ります。ここで、mはメモリー内でソートできる最大値(おそらくn / 4)に近い値です。
  2. 250,000程度の数値を並べ替えて出力します。
  3. 他の3つの四半期についても繰り返します。
  4. 受信者が処理するときに受け取った番号の4つのリストをマージします。(単一のリストを使用するよりも遅くはありません。)

7

私は基数ツリーを試します。データをツリーに格納できる場合は、順トラバースを実行してデータを送信できます。

これが1MBに収まるかどうかはわかりませんが、試してみる価値はあると思います。


7

どのようなコンピュータを使用していますか?他の「通常の」ローカルストレージはない可能性がありますが、たとえばビデオRAMがありますか?1メガピクセルx 32ビット/ピクセル(たとえば)は、必要なデータ入力サイズにかなり近いです。

(私は、低解像度または低色深度の画面モードを選択した場合、VRAMを「借りて」使用可能なシステムRAMを拡張できる古いAcorn RISC PCのメモリを主に要求します!)。これは、通常のRAMが数MBしかないマシンではかなり役に立ちました。


1
コメントに気をつけて、反対投票者?-私は質問の明らかな
DNA

ハッカーのニュースの関連するスレッドがこれはかつてグーグルのインタビューの質問であると述べているので、コンピュータはまったくないかもしれません。
mlvljr 2012年

1
はい-質問が編集される前に回答し、インタビューの質問であることを示しました!
DNA

6

基数ツリーは「プレフィックス圧縮」を利用するため、基数ツリー表現はこの問題の処理に近づきます。しかし、1つのノードを1バイトで表すことができる基数ツリー表現を想像するのは困難です。おそらく2つで限界です。

ただし、データがどのように表されているかに関係なく、並べ替えられたデータはプレフィックス圧縮形式で格納できます。10、11、12の数値は、たとえば001b、001b、001bで表され、1の増分を示します。前の番号から。おそらく、10101bは5の増分、1101001bは9の増分などを表します。


6

10 ^ 8の範囲には10 ^ 6の値があるため、平均で100コードポイントごとに1つの値があります。N番目の点から(N + 1)番目までの距離を格納します。重複する値のスキップは0です。これは、スキップを格納するために平均7ビット弱が必要であることを意味します。そのため、100万個は800万ビットのストレージにうまく適合します。

これらのスキップは、ハフマンエンコーディングなどによってビットストリームにエンコードする必要があります。挿入は、ビットストリームを反復処理し、新しい値の後に書き換えることです。暗黙の値を繰り返し処理して出力します。実用的には、たとえば、それぞれ10 ^ 4コードポイント(および平均100値)をカバーする10 ^ 4リストとして実行する必要があります。

ランダムデータに適したハフマンツリーは、スキップの長さについてポアソン分布(mean = variance = 100)を仮定することでアプリオリに構築できますが、実際の統計を入力で保持し、最適なツリーを生成して処理するために使用できます。病理学的症例。


5

1MのRAMを搭載し、他のローカルストレージを搭載していないコンピュータを使用しています

不正行為の別の方法:代わりに非ローカル(ネットワーク)ストレージを使用し(質問ではこれを排除できません)、単純なディスクベースのマージソート(またはメモリ内でソートするのに十分なRAM)を使用できるネットワークサービスを呼び出すことができます。 1Mの数値のみを受け入れる必要があります)。すでに与えられている(明らかに非常に独創的な)ソリューションは必要ありません。

これはごまかしかもしれませんが、実際の問題の解決策を探しているのか、それともルールの曲がりを招くパズルを探しているのかは明らかではありません...しかし、「本物の」ソリューション(他の人が指摘したように、圧縮可能な入力に対してのみ機能します)。


5

解決策は、ビデオエンコーディングの手法、つまり離散コサイン変換を組み合わせることだと思います。デジタルビデオでは、ビデオの明るさまたは色の変化を110、112、115、116などの通常の値として記録するのではなく、それぞれを最後から減算します(ランレングスエンコーディングと同様)。110 112 115 116は110 2 3 1になります。値2 3 1は、元のビットよりも必要なビットが少なくなります。

それで、ソケットに到着したときに入力値のリストを作成するとします。値ではなく各要素に格納していますが、その前の要素のオフセットを格納しています。ソートすると、オフセットが正になるだけです。ただし、オフセットは10進数8桁で、これは3バイトに収まります。各要素を3バイトにすることはできないため、これらをパックする必要があります。各バイトの上位ビットを「継続ビット」として使用できます。これは、次のバイトが数値の一部であり、各バイトの下位7ビットを組み合わせる必要があることを示します。ゼロは重複に有効です。

リストがいっぱいになると、数値は互いに近づくはずです。つまり、平均して1バイトだけが次の値までの距離を決定するために使用されます。7ビットの値と1ビットのオフセットが便利ですが、「継続」値に8ビット未満を必要とするスイートスポットがある場合があります。

とにかく、私はいくつかの実験をしました。私は乱数ジェネレーターを使用しており、100万ソートされた8桁の10進数を約1279000バイトに収めることができます。各数値間の平均間隔は常に99です...

public class Test {
    public static void main(String[] args) throws IOException {
        // 1 million values
        int[] values = new int[1000000];

        // create random values up to 8 digits lrong
        Random random = new Random();
        for (int x=0;x<values.length;x++) {
            values[x] = random.nextInt(100000000);
        }
        Arrays.sort(values);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        int av = 0;    
        writeCompact(baos, values[0]);     // first value
        for (int x=1;x<values.length;x++) {
            int v = values[x] - values[x-1];  // difference
            av += v;
            System.out.println(values[x] + " diff " + v);
            writeCompact(baos, v);
        }

        System.out.println("Average offset " + (av/values.length));
        System.out.println("Fits in " + baos.toByteArray().length);
    }

    public static void writeCompact(OutputStream os, long value) throws IOException {
        do {
            int b = (int) value & 0x7f;
            value = (value & 0x7fffffffffffffffl) >> 7;
            os.write(value == 0 ? b : (b | 0x80));
        } while (value != 0);
    }
}

4

すべての数値を取得する前に、ネットワークスタックで遊んで、並べ替えられた順序で数値を送信することができます。1Mのデータを送信すると、TCP / IPはそれを1500バイトのパケットに分割し、ターゲットにストリーミングします。各パケットにはシーケンス番号が付けられます。

これは手作業で行うことができます。RAMがいっぱいになる直前に、持っているものを並べ替えてリストをターゲットに送信できますが、各番号の周りにシーケンスの穴を残します。次に、シーケンスのそれらの穴を使用して、同じ方法で数値の2番目の1/2を処理します。

遠端のネットワークスタックは、結果のデータストリームをアプリケーションに渡す前に、シーケンス順にアセンブルします。

ネットワークを使用してマージソートを実行しています。これは完全なハックですが、私は以前にリストされた他のネットワークハックに触発されました。


4

HNスレッドからのGoogleの(悪い)アプローチ。RLEスタイルのカウントを保管します。

最初のデータ構造は「99999999:0」(すべてゼロで、数字はまだ表示されていません)であり、3,866,344という数字が表示されるとすると、データ構造は「3866343:0,1:1,96133654:0」になります。数字は常に0ビットの数と「1」ビットの数の間で交互に表示されるため、奇数は0ビットを表し、偶数は1ビットを表すと仮定できます。これは(3866343,1,96133654)になります

彼らの問題は重複をカバーしていないようですが、彼らが重複に「0:1」を使用するとします。

大きな問題#1:100万の整数の挿入には時間がかかる

大きな問題#2:すべてのプレーンデルタエンコーディングソリューションと同様に、一部のディストリビューションはこの方法ではカバーできません。たとえば、距離が0:99の1m整数(たとえば、それぞれ+99)。同じように考えますが、ランダムな距離は0:99範囲です。(注:99999999/1000000 = 99.99)

Googleのアプローチは、価値がなく(遅い)正しくありません。しかし、彼らの防御のために、彼らの問題は少し異なっていたかもしれません。


3

ソートされた配列を表すには、最初の要素と隣接する要素間の差を格納するだけです。このようにして、合計で最大10 ^ 8になる10 ^ 6要素をエンコードすることに関心があります。これをDとしましょう。Dの要素をエンコードするには、ハフマンコードを使用できます。ハフマンコードの辞書は外出先で作成することができ、ソートされた配列に新しい項目が挿入されるたびに配列が更新されます(挿入ソート)。新しいアイテムのためにディクショナリが変更された場合、配列全体を新しいエンコーディングに一致するように更新する必要があることに注意してください。

Dの各要素をエンコードするための平均ビット数は、各一意の要素の数が等しい場合に最大になります。Dの要素d1d2、...、dNがそれぞれF回出現するとします。その場合(最悪の場合、入力シーケンスに0と10 ^ 8の両方があります)、

sum(1 <= i <= N Fdi = 10 ^ 8

どこ

sum(1 <= i <= N F = 10 ^ 6、または F = 10 ^ 6 / Nと正規化された周波数は p = F / 10 ^ = 1 / N

ビットの平均数は-log2(1 / P)= log2(N)になります。これらの状況下では、Nを最大化するケースを見つける必要があります。これは、我々がために連続した番号がある場合はたまたま 0から始まる、または、ディ = I -1、したがって、

10 ^ 8 = sum(1 <= i <= N Fdi = sum(1 <= i <= N(10 ^ 6 / N)(i-1)=(10 ^ 6 / NNN -1)/ 2

すなわち

N <=201。この場合、ビットの平均数はlog2(201)= 7.6511です。これは、ソートされた配列を保存するために、入力要素ごとに約1バイトが必要であることを意味します。これは、Dが一般に201を超える要素を持つことができないことを意味しないことに注意してください。Dの要素が均一に分布している場合、201を超える一意の値を持つことはできません。


1
重複する可能性があることを忘れたと思います。
bestsss

重複する数値の場合、隣接する数値間の差はゼロになります。問題は発生しません。ハフマンコードはゼロ以外の値を必要としません。
Mohsen Nosratinia 2012年

3

TCPの再送信動作を利用します。

  1. TCPコンポーネントに大きな受信ウィンドウを作成させます。
  2. ACKを送信せずに一定量のパケットを受信します。
    • パスでそれらを処理して、いくつかの(プレフィックス)圧縮データ構造を作成します。
    • 不要になった最後のパケットの重複確認応答を送信する/再送信タイムアウトを待つ
    • 後藤2
  3. すべてのパケットが受け入れられました

これは、バケットまたはマルチパスのある種の利点を前提としています。

おそらく、バッチ/バケットをソートして、それらをマージすることによって。->基数の木

この手法を使用して、最初の80%を受け入れてソートし、最後の20%を読み取って、最後の20%に、最小の数字の最初の20%に入る数字が含まれていないことを確認します。次に、最低20%の数値を送信し、メモリから削除して、新しい数値の残りの20%を受け入れてマージします。**


3

この種の問題に対する一般的な解決策を次に示します。

一般的な手順

取られたアプローチは以下の通りです。このアルゴリズムは、32ビットワードの単一のバッファーで動作します。ループで次の手順を実行します。

  • 最後の反復からの圧縮データで満たされたバッファから始めます。バッファはこのようになります

    |compressed sorted|empty|

  • このバッファーに格納できる数値の最大量を計算します(圧縮および非圧縮の両方)。バッファをこれらの2つのセクションに分割します。圧縮データ用のスペースから始まり、非圧縮データで終わります。バッファは次のようになります

    |compressed sorted|empty|empty|

  • 圧縮されていないセクションにソートする番号を入力します。バッファは次のようになります

    |compressed sorted|empty|uncompressed unsorted|

  • 新しい数値をインプレースソートでソートします。バッファは次のようになります

    |compressed sorted|empty|uncompressed sorted|

  • 圧縮セクションで、前の反復から既に圧縮されたデータを右揃えにします。この時点でバッファは分割されています

    |empty|compressed sorted|uncompressed sorted|

  • 圧縮セクションでストリーミング解凍-再圧縮を実行し、非圧縮セクションのソートされたデータをマージします。古い圧縮セクションは、新しい圧縮セクションが大きくなるにつれて消費されます。バッファは次のようになります

    |compressed sorted|empty|

この手順は、すべての数値がソートされるまで実行されます。

圧縮

もちろん、このアルゴリズムは、実際に何が圧縮されるかを実際に知る前に、新しいソートバッファーの最終的な圧縮サイズを計算できる場合にのみ機能します。次に、圧縮アルゴリズムは、実際の問題を解決するのに十分なものである必要があります。

使用されるアプローチは3つのステップを使用します。まず、アルゴリズムは常にソートされたシーケンスを保存するため、代わりに連続するエントリ間の違いのみを保存できます。それぞれの差は[0、99999999]の範囲です。

これらの違いは、単項ビットストリームとしてエンコードされます。このストリームの1は「アキュムレータに1を追加し、0は「アキュムレータをエントリとして放出してリセットする」ことを意味します。したがって、差NはN 1と1の0で表されます。

すべての差異の合計は、アルゴリズムがサポートする最大値に近づき、すべての差異の数は、アルゴリズムに挿入される値の量に近づきます。これは、ストリームが最後に最大値1とカウント0を含むことを期待していることを意味します。これにより、ストリーム内の0と1の予想確率を計算できます。つまり、0 count/(count+maxval)の確率は1、1の確率はmaxval/(count+maxval)です。

これらの確率を使用して、このビットストリームに対して算術符号化モデルを定義します。この算術コードは、この量の1と0を最適な空間に正確にエンコードします。中間ビットストリームに対してこのモデルで使用されるスペースを次のように計算できますbits = encoded * log2(1 + amount / maxval) + maxval * log2(1 + maxval / amount)。アルゴリズムに必要な合計スペースを計算するには、encodedamount に設定します。

ばかげた量の反復を必要としないようにするために、小さなオーバーヘッドをバッファーに追加できます。これにより、アルゴリズムが少なくともこのオーバーヘッドに収まる数の数値で動作することが保証されます。これは、アルゴリズムの最大の時間コストが、各サイクルの算術符号化の圧縮と解凍であるためです。

その次に、簿記データを格納し、算術符号化アルゴリズムの固定小数点近似のわずかな不正確さを処理するために、ある程度のオーバーヘッドが必要ですが、アルゴリズムは、合計で1MiBのスペースに収めることができます。 8000の数値、合計1043916バイトのスペース。

最適性

アルゴリズムの(小さな)オーバーヘッドを削減する以外に、より小さな結果を得ることは理論的に不可能であるべきです。最終結果のエントロピーだけを含めるには、1011717バイトが必要になります。効率を上げるために追加された追加のバッファーを差し引くと、このアルゴリズムは1011916バイトを使用して最終結果+オーバーヘッドを格納します。


2

入力ストリームが数回受信される可能性がある場合、これははるかに簡単になります(そのことに関する情報、アイデア、および時間パフォーマンスの問題はありません)。

次に、10進数の値を数えます。カウントされた値を使用すると、出力ストリームを簡単に作成できます。値をカウントして圧縮します。入力ストリームの内容によって異なります。


1

入力ストリームが数回受信される可能性がある場合、これははるかに簡単になります(それに関する情報はなく、アイデアと時間パフォーマンスの問題)。次に、10進数の値を数えます。カウントされた値を使用すると、出力ストリームを簡単に作成できます。値をカウントして圧縮します。入力ストリームの内容によって異なります。


1

ここでは、並べ替えは二次的な問題です。他の人が言ったように、整数を格納するだけでは困難であり、すべての入力を処理することはできませんごとに27ビットが必要になるため、。

これについての私の見解は次のとおりです。連続する(ソートされた)整数間の差異のみを保存します。次に、入力スキームごとに2ビットを追加するなど、圧縮方式を使用して、その数値が格納されているビット数をエンコードします。何かのようなもの:

00 -> 5 bits
01 -> 11 bits
10 -> 19 bits
11 -> 27 bits

与えられたメモリ制約内で、かなりの数の可能な入力リストを格納できるはずです。最大数の入力で動作するように圧縮方式を選択する方法の数学は、私を超えています。

これに基づいて十分な整数圧縮方式を見つけるために、入力に関するドメイン固有の知識を活用できることを願っています。

ああ、それから、データを受け取ったら、ソートされたリストで挿入ソートを行います。


1

現在、実際のソリューションを目指しており、1 MBのRAMで8桁の範囲のすべての可能な入力ケースをカバーしています。注:作業中です。明日は続きます。ソート済み整数のデルタの算術コーディングを使用すると、1Mソート済み整数の最悪の場合、エントリあたり約7ビットのコストがかかります(99999999/1000000は99、log2(99)はほぼ7ビットであるため)。

ただし、7ビットまたは8ビットにするには、1mの整数をソートする必要があります。系列が短いほど、デルタが大きくなるため、要素あたりのビット数が多くなります。

私はできるだけ多くを取り、(ほぼ)インプレースで圧縮するように取り組んでいます。250Kに近いintの最初のバッチでは、それぞれ最大で約9ビットが必要です。したがって、結果は約275KBになります。残りの空きメモリを数回繰り返します。次に、それらの圧縮されたチャンクをdecompress-merge-in-place-compressします。これはかなり難しいですが、可能です。おもう。

マージされたリストは、整数ターゲットあたり7ビットにますます近づきます。しかし、マージループの反復回数はわかりません。たぶん3。

しかし、算術符号化の実装が不正確であると、それが不可能になる可能性があります。この問題が発生する可能性がある場合、それは非常に厳しいでしょう。

ボランティアはいますか?


算術コーディングが実行可能です。連続する各デルタが負の二項分布から引き出されていることに気付くと役立つかもしれません。
クラウディング

1

番号の違いを順番に保存し、エンコーディングを使用してこれらのシーケンス番号を圧縮するだけです。2 ^ 23ビットです。それを6ビットのチャンクに分割し、最後のビットに数値が別の6ビット(5ビットと拡張チャンク)に拡張するかどうかを示します。

したがって、000010は1、000100は2です。000001100000は128です。ここで、10,000,000までの数値のシーケンスの違いを表す際の最悪のキャストを考えます。2 ^ 5より大きい10,000,000 / 2 ^ 5の差、2 ^ 10より大きい10,000,000 / 2 ^ 10の差、2 ^ 15より大きい10,000,000 / 2 ^ 15の差などがあり得ます。

したがって、シーケンスを表すのに必要なビット数を追加します。1,000,000 * 6 +切り上げ(10,000,000 / 2 ^ 5)* 6 +切り上げ(10,000,000 / 2 ^ 10)* 6 +切り上げ(10,000,000 / 2 ^ 15)* 6 +切り上げ(10,000,000 / 2 ^ 20)* 4 = 7935479。

2 ^ 24 =8388608。8388608> 7935479なので、十分なメモリを簡単に確保できます。新しい数値を挿入するときの場所の合計を格納するために、もう少しメモリが必要になるでしょう。次に、シーケンスを調べて、新しい番号を挿入する場所を見つけ、必要に応じて次の差異を減らし、その後にすべてをシフトします。


私は信じている私の分析ここでは、この方式が機能しないことを示している(と私たちは、5ビットよりも別のサイズを選択することができない場合でも)。
Daniel Wagner、

@Daniel Wagner-チャンクごとに一定のビット数を使用する必要はなく、チャンクごとに整数のビット数を使用する必要もありません。
混雑

@crowding具体的なご提案がございましたら是非お聞かせください。=)
ダニエルワグナー

@crowding算術コーディングにかかる​​スペースを計算します。少し泣く。その後、もっと考えます。
Daniel Wagner、

もっと詳しく知る。正しい中間表現(FranciscoはStrilancと同様に最も単純な中間表現を持っています)でのシンボルの完全な条件付き分布は、計算が簡単です。したがって、エンコーディングモデルは文字通り完全で、エントロピー制限の1ビット以内に収まる可能性があります。有限精度の演算では、数ビットが追加される場合があります。
混雑

1

これらの数値について何もわからない場合、次の制約によって制限されます。

  • 並べ替える前にすべての数値をロードする必要があります。
  • 数値のセットは圧縮できません。

これらの仮定が成り立つ場合、少なくとも26,575,425ビットのストレージ(3,321,929バイト)が必要になるため、タスクを実行する方法はありません。

あなたのデータについて教えていただけますか?


1
あなたはそれらを読み、あなたが行くにつれてそれらを分類します。理論的には、100万個の区別できないボックスに100万個の区別できないアイテムを格納するためにlg2(100999999!/(99999999!* 1000000!))ビットを必要とし、1MBの96.4%になります。
NovaDenizen、

1

トリックは、アルゴリズムの状態(整数のマルチセット)を、「インクリメントカウンター」= "+"および "出力カウンター" = "!"の圧縮ストリームとして表すことです。文字。たとえば、セット{0,3,3,4}は "!+++ !! +!"として表され、その後に任意の数の "+"文字が続きます。マルチセットを変更するには、一度に一定量のみを解凍して、文字をストリーミングし、圧縮形式でストリーミングする前に変更を加えます。

詳細

最終的なセットには正確に10 ^ 6の数値があることがわかっているため、多くても10 ^ 6 "!"です。文字。また、範囲のサイズが10 ^ 8であることも知っています。つまり、最大で10 ^ 8個の "+"文字が存在します。10 ^ 8 "+"の中で10 ^ 6 "!"を配置できる方法の数はである(10^8 + 10^6) choose 10^6ため、特定の配置を指定するには〜0.965 MiBかかります、データのになります。それはタイトなフィットになります。

割り当てを超えることなく、各キャラクターを独立したキャラクターとして扱うことができます。「!」の100倍の「+」文字があります。これは、依存していることを忘れた場合、各文字が「+」であるオッズが100:1に単純化されます。100:101のオッズは、文字あたり〜0.08ビットに対応し、ほぼ同じ合計で〜0.965 MiBになります(この場合、依存関係を無視するとコストは〜12ビットのみになります!)。

既知の事前確率で独立した文字を保存する最も簡単な手法は、ハフマンコーディングです。非実用的な大きなツリーが必要であることに注意してください(10文字のブロックのハフマンツリーのブロックあたりの平均コストは約2.4ビットで、合計で約2.9 Mibです。20文字のブロックのハフマンツリーの平均コストはブロックあたりです。約3ビットで、合計は約1.8 MiBです。おそらく100程度のサイズのブロックが必要になります。これは、これまで存在していたすべてのコンピュータ機器が格納できるよりも多くのノードがツリーに含まれることを意味します。 )。ただし、ROMは問題により技術的には「無料」であり、ツリーの規則性を利用する実際的な解決策は基本的に同じに見えます。

疑似コード

  • 十分に大きなハフマンツリー(または同様のブロックごとの圧縮データ)をROMに格納する
  • 10 ^ 8 "+"文字の圧縮文字列から始めます。
  • 数値Nを挿入するには、Nの「+」文字が過ぎるまで圧縮文字列をストリームし、次に「!」を挿入します。再圧縮された文字列を前の文字列にストリーミングし、オーバー/アンダーランを回避するために一定量のバッファーブロックを維持します。
  • 100万回繰り返します:[input、stream decompress> insert> compress]、次に解凍して出力

1
これまでのところ、これが実際に問題に答える唯一の答えです。ただし、算術コーディングはハフマンコーディングよりも簡単に適合できると思います。コードブックを保存してシンボル境界を気にする必要がなくなるためです。依存関係も説明できます。
混雑

入力整数はソートされません。最初にソートする必要があります。
alecco 2012年

1
@aleccoアルゴリズムは、進行するにつれてそれらをソートします。それらはソートされずに保管されることはありません。
Craig Gidney、2012年

1

1 MB-3 KB RAM = 2 ^ 23-3 * 2 ^ 13ビット= 8388608-24576 = 8364032ビットが利用可能です。

10 ^ 8の範囲で10 ^ 6の数値が与えられます。これにより、〜100 <2 ^ 7 = 128の平均ギャップが得られます

最初に、すべてのギャップが128未満の場合に、かなり等間隔の数値という単純な問題を考えてみましょう。これは簡単です。最初の数値と7ビットのギャップを保存するだけです。

(27ビット)+ 10 ^ 6 7ビットのギャップ数= 7000027ビットが必要

繰り返される数値のギャップは0であることに注意してください。

しかし、127より大きいギャップがある場合はどうでしょうか。

OK、127未満のギャップサイズが直接表されているとしますが、127のギャップサイズの後に、実際のギャップ長のための連続的な8ビットエンコーディングが続きます。

 10xxxxxx xxxxxxxx                       = 127 .. 16,383
 110xxxxx xxxxxxxx xxxxxxxx              = 16384 .. 2,097,151

この数値表現はそれ自体の長さを表すため、次のギャップ番号がいつ始まるかがわかります。

127未満の小さなギャップがある場合でも、7000027ビットが必要です。

(10 ^ 8)/(2 ^ 7)= 781250までの23ビットのギャップ番号があり、余分な16 * 781,250 = 12,500,000ビットが必要であり、これは多すぎます。よりコンパクトでゆっくりと増加するギャップの表現が必要です。

平均ギャップサイズは100なので、[100、99、101、98、102、...、2、198、1、199、0、200、201、202、...]として並べ替え、これにインデックスを付けると'00'で区切られたゼロのペア(たとえば、11011 = 8 + 5 + 2 + 1 = 16)のない密なバイナリフィボナッチベースエンコーディングでは、ギャップ表現を十分に短く保つことができますが、それは必要ですより多くの分析。


0

ストリームを受信しながら、これらの手順を実行します。

1つ目は、適切なチャンクサイズを設定する

擬似コードのアイデア:

  1. 最初のステップは、すべての重複を見つけ、その数を辞書に入れて削除することです。
  2. 3番目のステップは、アルゴリズムのステップのシーケンスに存在する数を配置し、最初の数とn、n + 1 ...、n + 2、2n、2n + 1のようなそれらのステップを持つカウンター特殊辞書に配置することです。 2n + 2 ...
  3. 繰り返しの頻度が低い残りの数値の1000または10000ごとなど、いくつかの合理的な範囲の数値をチャンクで圧縮し始めます。
  4. 数値が見つかった場合はその範囲を解凍し、その範囲に追加して、しばらくの間非圧縮のままにします。
  5. それ以外の場合は、その数をバイトに追加します[chunkSize]

ストリームを受信しながら、最初の4つのステップを続行します。最後のステップは、メモリを超えた場合に失敗するか、すべてのデータが収集された後に結果の出力を開始することです。範囲の並べ替えを開始し、結果を順番に出力し、圧縮解除して必要なときにそれらを展開する必要があります。あなたはそれらに到達します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.