Menyortir 1 juta angka 8-desimal-digit dengan 1 MB RAM


726

Saya memiliki komputer dengan 1 MB RAM dan tidak ada penyimpanan lokal lainnya. Saya harus menggunakannya untuk menerima 1 juta angka desimal 8 digit melalui koneksi TCP, mengurutkannya, dan kemudian mengirimkan daftar yang diurutkan melalui koneksi TCP lain.

Daftar angka mungkin berisi duplikat, yang tidak boleh saya buang. Kode akan ditempatkan di ROM, jadi saya tidak perlu mengurangi ukuran kode saya dari 1 MB. Saya sudah memiliki kode untuk menggerakkan port Ethernet dan menangani koneksi TCP / IP, dan memerlukan 2 KB untuk data statusnya, termasuk buffer 1 KB di mana kode akan membaca dan menulis data. Apakah ada solusi untuk masalah ini?

Sumber Tanya Jawab:

slashdot.org

cleaton.net


45
Ehm, sejuta kali angka desimal 8-digit (min. 27-bit integer binary)> ram 1MB
Mr47

15
RAM 1M berarti 2 ^ 20 byte? Dan berapa banyak bit dalam satu byte pada arsitektur ini? Dan apakah "juta" dalam "1 juta angka desimal 8 digit" adalah satu juta SI (10 ^ 6)? Berapakah angka desimal 8 digit, angka natural <10 ^ 8, angka rasional yang representasi desimalnya mengambil 8 digit tidak termasuk titik desimal, atau yang lainnya?

13
1 juta 8 angka angka desimal atau 1 juta angka 8 bit?
Patrick White

13
itu mengingatkan saya pada sebuah artikel di "Dr Dobb's Journal" (suatu tempat antara 1998-2001), di mana penulis menggunakan semacam penyisipan untuk mengurutkan nomor telepon ketika dia membacanya: itu adalah pertama kalinya saya menyadari bahwa, kadang-kadang, lebih lambat algoritma mungkin lebih cepat ...
Adrien Plisson

103
Ada solusi lain yang belum disebutkan: beli perangkat keras dengan RAM 2MB. Seharusnya tidak jauh lebih mahal, dan itu akan membuat masalahnya jauh, lebih mudah untuk dipecahkan.
Daniel Wagner

Jawaban:


716

Ada satu trik yang agak licik yang tidak disebutkan di sini sejauh ini. Kami berasumsi bahwa Anda tidak memiliki cara ekstra untuk menyimpan data, tetapi itu tidak sepenuhnya benar.

Salah satu cara mengatasi masalah Anda adalah melakukan hal mengerikan berikut, yang tidak boleh dilakukan oleh siapa pun dalam keadaan apa pun: Gunakan lalu lintas jaringan untuk menyimpan data. Dan tidak, maksud saya bukan NAS.

Anda dapat mengurutkan angka hanya dengan beberapa byte RAM dengan cara berikut:

  • Pertama ambil 2 variabel: COUNTERdan VALUE.
  • Pertama atur semua register ke 0;
  • Setiap kali Anda menerima bilangan bulat I, kenaikan COUNTERdan setel VALUEke max(VALUE, I);
  • Kemudian kirim paket permintaan gema ICMP dengan data yang disetel ke Irouter. Hapus Idan ulangi.
  • Setiap kali Anda menerima paket ICMP yang dikembalikan, Anda cukup mengekstrak integer dan mengirimkannya kembali dalam permintaan gema lainnya. Ini menghasilkan sejumlah besar permintaan ICMP yang bergerak mundur dan maju yang berisi bilangan bulat.

Setelah COUNTERmencapai 1000000, Anda memiliki semua nilai yang disimpan dalam aliran permintaan ICMP, dan VALUEsekarang berisi integer maksimum. Pilih beberapa threshold T >> 1000000. Setel COUNTERke nol. Setiap kali Anda menerima paket ICMP, naikkan COUNTERdan kirim bilangan bulat yang ada Ikembali dalam permintaan gema lain, kecuali I=VALUE, dalam hal ini mentransmisikannya ke tujuan bilangan bulat yang diurutkan. Sekali COUNTER=T, kurangi VALUEdengan 1, reset COUNTERke nol dan ulangi. Setelah VALUEmencapai nol, Anda harus mentransmisikan semua bilangan bulat secara berurutan dari yang terbesar ke yang terkecil ke tujuan, dan hanya menggunakan sekitar 47 bit RAM untuk dua variabel persisten (dan berapa pun jumlah kecil yang Anda perlukan untuk nilai sementara).

Saya tahu ini mengerikan, dan saya tahu mungkin ada berbagai masalah praktis, tetapi saya pikir itu mungkin membuat sebagian dari Anda tertawa atau setidaknya membuat Anda ngeri.


27
Jadi pada dasarnya Anda memanfaatkan latensi jaringan dan mengubah router Anda menjadi semacam que?
Eric R.

335
Solusi ini tidak hanya di luar kotak; tampaknya telah melupakan kotaknya di rumah: D
Vladislav Zorov

28
Jawaban yang bagus ... Saya suka jawaban ini karena mereka benar-benar mengungkapkan betapa beragamnya solusi untuk suatu masalah
StackOverflowed

33
ICMP tidak dapat diandalkan.
sleeplessnerd

13
@MDMarra: Anda akan melihat tepat di atas saya berkata "Salah satu cara untuk menyelesaikan masalah Anda adalah melakukan hal mengerikan berikut, yang tidak boleh dilakukan oleh siapa pun dalam keadaan apa pun". Ada alasan saya mengatakan ini.
Joe Fitzsimons

423

Inilah beberapa kode C ++ yang berfungsi untuk menyelesaikan masalah.

Bukti bahwa batasan memori terpenuhi:

Editor: Tidak ada bukti persyaratan memori maksimum yang ditawarkan oleh penulis baik dalam posting ini atau di blog-nya. Karena jumlah bit yang diperlukan untuk mengkodekan nilai tergantung pada nilai yang sebelumnya dikodekan, bukti seperti itu kemungkinan non-sepele. Penulis mencatat bahwa ukuran penyandian terbesar yang bisa ia temukan secara empiris adalah 1011732, dan memilih ukuran penyangga secara 1013000sewenang-wenang.

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

    ...

Bersama-sama, dua array ini membutuhkan 1045000 byte penyimpanan. Yang meninggalkan 1048576 - 1045000 - 2 × 1024 = 1528 byte untuk variabel yang tersisa dan ruang stack.

Ini berjalan dalam waktu sekitar 23 detik pada Xeon W3520 saya. Anda dapat memverifikasi bahwa program bekerja menggunakan skrip Python berikut, dengan asumsi nama program 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!')

Penjelasan terperinci dari algoritme dapat ditemukan dalam serangkaian posting berikut:


8
@ siapkan ya kami sangat ingin penjelasan rinci tentang ini.
T Suds

25
Saya pikir pengamatan utama adalah bahwa angka 8-digit memiliki sekitar 26,6 bit informasi dan satu juta adalah 19,9 bit. Jika Anda delta kompres daftar (menyimpan perbedaan nilai yang berdekatan) perbedaan berkisar dari 0 (0 bit) hingga 99999999 (26,6 bit) tetapi Anda tidak dapat memiliki delta maksimum di antara setiap pasangan. Kasus terburuk seharusnya adalah satu juta nilai yang didistribusikan secara merata, membutuhkan delta (26.6-19.9) atau sekitar 6,7 bit per delta. Menyimpan satu juta nilai 6,7 bit dengan mudah cocok dalam 1M. Kompresi Delta membutuhkan pengurutan gabungan terus menerus sehingga Anda hampir mendapatkannya secara gratis.
Ben Jackson

4
solusi manis. kalian semua harus mengunjungi blognya untuk penjelasan preshing.com/20121025/…
davec

9
@ BenJackson: Ada kesalahan di suatu tempat dalam matematika Anda. Ada 2,265 x 10 ^ 2436455 kemungkinan keluaran unik (set pesanan 10 ^ 6 bilangan bulat 8-digit) yang membutuhkan 8.094 x 10 ^ 6 bit untuk disimpan (yaitu rambut di bawah megabyte). Tidak ada skema pintar yang dapat mengompresi melampaui batas teoretis informasi ini tanpa kehilangan. Penjelasan Anda menyiratkan Anda membutuhkan lebih sedikit ruang, dan karenanya salah. Memang, "melingkar" dalam solusi di atas hanya cukup besar untuk menampung informasi yang dibutuhkan, jadi preshing tampaknya telah memperhitungkan ini, tetapi Anda melewatkannya.
Joe Fitzsimons

5
@ JoFitzsimons: Saya belum menyelesaikan rekursi (set angka unik yang diurutkan dari 0..m is (n+m)!/(n!m!)) jadi Anda pasti benar. Mungkin ini perkiraan saya bahwa delta b bit membutuhkan b bit untuk disimpan - jelas delta 0 tidak mengambil 0 bit untuk disimpan.
Ben Jackson

371

Silakan lihat jawaban yang benar pertama atau jawaban selanjutnya dengan pengkodean aritmatika . Di bawah ini Anda mungkin menemukan kesenangan, tetapi bukan solusi anti peluru 100%.

Ini tugas yang cukup menarik dan ini solusi lain. Saya harap seseorang akan menemukan hasilnya bermanfaat (atau paling tidak menarik).

Tahap 1: Struktur data awal, pendekatan kompresi kasar, hasil dasar

Mari kita lakukan beberapa perhitungan sederhana: kita memiliki 1M (1048576 byte) RAM yang awalnya tersedia untuk menyimpan angka desimal 10 ^ 6 8 digit. [0; 99999999]. Jadi untuk menyimpan satu angka, 27 bit diperlukan (dengan asumsi bahwa nomor yang tidak ditandatangani akan digunakan). Jadi, untuk menyimpan aliran mentah ~ 3.5M RAM akan dibutuhkan. Seseorang sudah mengatakan itu tampaknya tidak layak, tetapi saya akan mengatakan tugas itu dapat diselesaikan jika inputnya "cukup baik". Pada dasarnya, idenya adalah untuk mengompresi data input dengan faktor kompresi 0,29 atau lebih tinggi dan melakukan pengurutan dengan cara yang benar.

Mari kita selesaikan masalah kompresi terlebih dahulu. Ada beberapa tes yang relevan sudah tersedia:

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

"Saya menjalankan tes untuk mengompresi satu juta bilangan bulat berturut-turut menggunakan berbagai bentuk kompresi. Hasilnya adalah sebagai berikut:"

None     4000027
Deflate  2006803
Filtered 1391833
BZip2    427067
Lzma     255040

Sepertinya LZMA ( Algoritma rantai Lempel-Ziv-Markov ) adalah pilihan yang baik untuk melanjutkan. Saya sudah menyiapkan PoC sederhana, tetapi masih ada beberapa detail yang perlu disorot:

  1. Memori terbatas sehingga idenya adalah untuk mempresort angka dan menggunakan ember terkompresi (ukuran dinamis) sebagai penyimpanan sementara
  2. Lebih mudah untuk mencapai faktor kompresi yang lebih baik dengan data yang sudah ditentukan, sehingga ada buffer statis untuk setiap bucket (angka-angka dari buffer harus diurutkan sebelum LZMA)
  3. Setiap bucket memiliki rentang tertentu, sehingga pengurutan akhir dapat dilakukan untuk setiap bucket secara terpisah
  4. Ukuran bucket dapat diatur dengan benar, sehingga akan ada cukup memori untuk mendekompresi data yang disimpan dan melakukan pengurutan akhir untuk setiap bucket secara terpisah

Penyortiran dalam memori

Harap dicatat, kode terlampir adalah POC , tidak dapat digunakan sebagai solusi akhir, itu hanya menunjukkan gagasan untuk menggunakan beberapa buffer yang lebih kecil untuk menyimpan angka yang telah dipilih dengan cara yang optimal (mungkin dikompresi). LZMA tidak diusulkan sebagai solusi akhir. Ini digunakan sebagai cara tercepat untuk memperkenalkan kompresi pada PoC ini.

Lihat kode PoC di bawah ini (harap dicatat hanya demo, untuk mengompilasinya diperlukan 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);
}
}

Dengan angka acak menghasilkan sebagai berikut:

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

Untuk urutan menaik sederhana (satu ember digunakan) itu menghasilkan:

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

EDIT

Kesimpulan:

  1. Jangan mencoba menipu Alam
  2. Gunakan kompresi yang lebih sederhana dengan jejak memori yang lebih rendah
  3. Beberapa petunjuk tambahan sangat dibutuhkan. Solusi anti peluru yang umum tampaknya tidak layak.

Tahap 2: Peningkatan kompresi, kesimpulan akhir

Seperti yang telah disebutkan di bagian sebelumnya, teknik kompresi yang cocok dapat digunakan. Jadi mari kita singkirkan LZMA demi pendekatan yang lebih sederhana dan lebih baik (jika mungkin). Ada banyak solusi bagus termasuk coding Aritmatika , pohon Radix dll.

Bagaimanapun, skema pengkodean yang sederhana namun bermanfaat akan lebih ilustratif daripada perpustakaan eksternal lainnya, menyediakan beberapa algoritma yang bagus. Solusi sebenarnya cukup mudah: karena ada ember dengan data yang diurutkan sebagian, delta dapat digunakan sebagai ganti angka.

skema pengkodean

Tes input acak menunjukkan hasil yang sedikit lebih baik:

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

Kode sampel

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

Harap dicatat, pendekatan ini:

  1. tidak memakan banyak memori
  2. bekerja dengan stream
  3. memberikan hasil yang tidak begitu buruk

Kode lengkap dapat ditemukan di sini , implementasi BinaryInput dan BinaryOutput dapat ditemukan di sini

Kesimpulan akhir

Tidak ada kesimpulan akhir :) Terkadang ide yang bagus untuk naik satu tingkat dan meninjau tugas dari sudut pandang meta-level .

Menyenangkan menghabiskan waktu dengan tugas ini. BTW, ada banyak jawaban menarik di bawah ini. Terima kasih atas perhatian dan kode senang Anda.


17
Saya menggunakan Inkscape . Alat yang hebat. Anda dapat menggunakan sumber diagram ini sebagai contoh.
Renat Gilmanov

21
Tentunya LZMA membutuhkan terlalu banyak memori untuk berguna dalam kasus ini? Sebagai suatu algoritma, ini dimaksudkan untuk meminimalkan jumlah data yang harus disimpan atau dikirim, daripada menjadi efisien dalam memori.
Mjiig

67
Ini tidak masuk akal ... Dapatkan 1 juta bilangan bulat 27 bit acak, urutkan, kompres dengan 7zip, xz, LZMA apa pun yang Anda inginkan. Hasilnya lebih dari 1MB. Premis di atas adalah kompresi nomor urut. Pengkodean Delta dengan 0bit hanya berupa angka, misalnya 1000000 (katakanlah dalam 4 byte). Dengan berurutan dan duplikat (tanpa celah), angka 1000000 dan 1000000 bit = 128KB, dengan 0 untuk angka duplikat dan 1 untuk menandai berikutnya. Ketika Anda memiliki celah acak, meski kecil, LZMA adalah konyol. Ini tidak dirancang untuk ini.
alecco

30
Ini tidak akan berhasil. Saya menjalankan simulasi dan sementara data terkompresi lebih dari 1MB (sekitar 1,5MB), masih menggunakan lebih dari 100MB RAM untuk mengompresi data. Jadi bahkan bilangan bulat yang dikompresi tidak cocok dengan masalah belum lagi penggunaan RAM waktu berjalan. Memberi Anda hadiah adalah kesalahan terbesar saya di stackoverflow.
Favorit Onwuemene

10
Jawaban ini terangkat begitu banyak karena banyak programmer menyukai ide yang mengkilap daripada kode yang terbukti. Jika ide ini berhasil, Anda akan melihat algoritma kompresi yang sebenarnya dipilih dan terbukti daripada sekadar pernyataan bahwa tentu saja ada satu di luar sana yang dapat melakukannya ... ketika sangat mungkin bahwa tidak ada satu di luar sana yang dapat melakukannya .
Olathe

185

Suatu solusi dimungkinkan hanya karena perbedaan antara 1 megabyte dan 1 juta byte. Ada sekitar 2 pangkat 8093729.5 cara berbeda untuk memilih 1 juta nomor 8-digit dengan duplikat diperbolehkan dan memesan tidak penting, sehingga mesin dengan hanya 1 juta byte RAM tidak memiliki cukup negara untuk mewakili semua kemungkinan. Tapi 1M (kurang 2k untuk TCP / IP) adalah 1022 * 1024 * 8 = 8372224 bit, jadi solusinya mungkin.

Bagian 1, solusi awal

Pendekatan ini membutuhkan sedikit lebih dari 1M, saya akan memperbaikinya agar sesuai dengan 1M nanti.

Saya akan menyimpan daftar angka yang diurutkan secara kompak dalam kisaran 0 hingga 99999999 sebagai urutan sub daftar angka 7-bit. Sublist pertama memegang angka dari 0 hingga 127, sublist kedua memegang angka dari 128 hingga 255, dll. 100000000/128 persis 781250, jadi 781250 sublists semacam itu akan diperlukan.

Setiap sublist terdiri dari header sublist 2-bit diikuti oleh badan sublist. Badan sublist membutuhkan 7 bit per entri sublist. Sublists semuanya digabung bersama, dan formatnya memungkinkan untuk menentukan di mana satu sublist berakhir dan yang berikutnya dimulai. Total penyimpanan yang diperlukan untuk daftar yang terisi penuh adalah 2 * 781250 + 7 * 1000000 = 8562500 bit, yaitu sekitar 1,021 M-byte.

4 nilai header sublist yang mungkin adalah:

00 Sublist kosong, tidak ada yang mengikuti.

01 Singleton, hanya ada satu entri dalam sublist dan dan 7 bit berikutnya tahan.

10 Sublist memiliki setidaknya 2 nomor berbeda. Entri disimpan dalam urutan non-menurun, kecuali bahwa entri terakhir kurang dari atau sama dengan yang pertama. Ini memungkinkan akhir dari sublist diidentifikasi. Misalnya, angka 2,4,6 akan disimpan sebagai (4,6,2). Angka 2,2,3,4,4 akan disimpan sebagai (2,3,4,4,2).

11 Sublist memiliki 2 atau lebih pengulangan dari satu nomor. 7 bit berikutnya memberikan angka. Kemudian datang nol atau lebih entri 7-bit dengan nilai 1, diikuti oleh entri 7-bit dengan nilai 0. Panjang badan sublist menentukan jumlah pengulangan. Misalnya, angka 12,12 akan disimpan sebagai (12,0), angka 12,12,12 akan disimpan sebagai (12,1,0), 12,12,12,12 akan menjadi (12,1) , 1,0) dan seterusnya.

Saya mulai dengan daftar kosong, membaca banyak angka dan menyimpannya sebagai bilangan bulat 32 bit, mengurutkan angka-angka baru di tempat (menggunakan heapsort, mungkin) dan kemudian menggabungkannya ke dalam daftar ringkas yang diurutkan. Ulangi sampai tidak ada lagi angka untuk dibaca, lalu jalankan daftar ringkas sekali lagi untuk menghasilkan output.

Baris di bawah ini mewakili memori tepat sebelum dimulainya operasi penggabungan daftar. "O" adalah wilayah yang menyimpan bilangan bulat 32-bit yang diurutkan. "X" adalah wilayah yang menyimpan daftar compact lama. Tanda "=" adalah ruang ekspansi untuk daftar ringkas, 7 bit untuk setiap bilangan bulat di "O". "Z" adalah overhead acak lainnya.

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

Rutin gabungan mulai membaca di paling kiri "O" dan di paling kiri "X", dan mulai menulis di paling kiri "=". Pointer tulis tidak menangkap pointer baca daftar kompak sampai semua integer baru digabungkan, karena kedua pointer memajukan 2 bit untuk setiap sublist dan 7 bit untuk setiap entri dalam daftar kompak yang lama, dan ada cukup ruang tambahan untuk Entri 7-bit untuk nomor baru.

Bagian 2, menjejalkannya menjadi 1M

Untuk memeras solusi di atas ke dalam 1M, saya perlu membuat format daftar ringkas sedikit lebih kompak. Saya akan menyingkirkan salah satu jenis sublist, sehingga hanya akan ada 3 nilai header sublist yang berbeda. Kemudian saya dapat menggunakan "00", "01" dan "1" sebagai nilai header sublist dan menyimpan beberapa bit. Jenis sublist adalah:

Sublist kosong, tidak ada yang mengikuti.

B Singleton, hanya ada satu entri dalam sublist dan dan 7 bit berikutnya tahan.

C Sublist memiliki setidaknya 2 nomor berbeda. Entri disimpan dalam urutan non-menurun, kecuali bahwa entri terakhir kurang dari atau sama dengan yang pertama. Ini memungkinkan akhir dari sublist diidentifikasi. Misalnya, angka 2,4,6 akan disimpan sebagai (4,6,2). Angka 2,2,3,4,4 akan disimpan sebagai (2,3,4,4,2).

D Sublist terdiri dari 2 atau lebih pengulangan dari satu nomor.

3 nilai header sublist saya adalah "A", "B" dan "C", jadi saya perlu cara untuk mewakili sub-tipe D-daftar.

Misalkan saya memiliki header daftar-jenis C diikuti oleh 3 entri, seperti "C [17] [101] [58]". Ini tidak dapat menjadi bagian dari sublist tipe C yang valid seperti dijelaskan di atas, karena entri ketiga kurang dari yang kedua tetapi lebih dari yang pertama. Saya bisa menggunakan tipe konstruksi ini untuk mewakili sublist tipe-D. Dalam istilah bit, di mana pun saya memiliki "C {00 ?????} {1 ??????} {01 ?????}" "adalah sublist jenis-C yang mustahil. Saya akan menggunakan ini untuk mewakili sublist yang terdiri dari 3 atau lebih pengulangan satu nomor. Dua kata 7-bit pertama mengkodekan angka ("N" bit di bawah) dan diikuti oleh nol atau lebih {0100001} kata diikuti oleh kata {0100000}.

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

Itu hanya meninggalkan daftar yang memiliki 2 pengulangan dari satu nomor. Saya akan mewakili mereka dengan pola daftar-jenis C lain yang tidak mungkin: "C {0 ??????} {11 ?????} {10 ?????}" ". Ada banyak ruang untuk 7 bit angka dalam 2 kata pertama, tetapi pola ini lebih panjang dari sublist yang diwakilinya, yang membuat segalanya sedikit lebih rumit. Lima tanda tanya di bagian akhir dapat dianggap bukan bagian dari pola, jadi saya punya: "C {0NNNNNN} {11N ????} 10" sebagai pola saya, dengan nomor yang akan diulang disimpan di "N "s. Itu 2 bit terlalu lama.

Saya harus meminjam 2 bit dan mengembalikannya dari 4 bit yang tidak digunakan dalam pola ini. Saat membaca, saat menjumpai "C {0NNNNNN} {11N00AB} 10", hasilkan 2 instance dari angka dalam "N", timpa "10" di bagian akhir dengan bit A dan B, dan putar kembali pointer baca dengan 2 bit. Bacaan destruktif ok untuk algoritma ini, karena setiap daftar ringkas hanya berjalan satu kali.

Saat menulis sublist dari 2 repetisi dari satu nomor, tulis "C {0NNNNNN} 11N00" dan atur penghitung bit yang dipinjam menjadi 2. Pada setiap penulisan di mana penghitung bit yang dipinjam adalah nol, ia dikurangi untuk setiap bit yang ditulis dan "10" ditulis ketika penghitung mencapai nol. Jadi 2 bit berikutnya yang ditulis akan masuk ke slot A dan B, dan kemudian "10" akan jatuh ke ujung.

Dengan 3 nilai header sublist diwakili oleh "00", "01" dan "1", saya dapat menetapkan "1" untuk jenis sublist yang paling populer. Saya akan membutuhkan tabel kecil untuk memetakan nilai-nilai header sublist ke tipe sublist, dan saya akan membutuhkan penghitung kejadian untuk setiap tipe sublist sehingga saya tahu apa pemetaan header sublist terbaik.

Representasi minimal kasus terburuk dari daftar compact yang terisi penuh terjadi ketika semua jenis sublist sama-sama populer. Dalam hal ini saya menyimpan 1 bit untuk setiap 3 sublist header, sehingga ukuran daftar adalah 2 * 781250 + 7 * 1000000 - 781250/3 = 8302083,3 bit. Membulatkan batas kata 32 bit, yaitu 8302112 bit, atau 1037764 byte.

1M dikurangi 2k untuk keadaan TCP / IP dan buffer adalah 1022 * 1024 = 1046528 byte, membuat saya 8764 byte untuk dimainkan.

Tetapi bagaimana dengan proses mengubah pemetaan header sublist? Dalam peta memori di bawah, "Z" adalah overhead acak, "=" adalah ruang kosong, "X" adalah daftar ringkas.

ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Mulai membaca di "X" paling kiri dan mulai menulis di "" "paling kiri dan bekerja dengan benar. Ketika selesai, daftar ringkas akan sedikit lebih pendek dan akan berada di ujung memori yang salah:

ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======

Jadi saya harus men-shunt ke kanan:

ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Dalam proses perubahan pemetaan tajuk, hingga 1/3 dari sublist header akan berubah dari 1-bit ke 2-bit. Dalam kasus terburuk, semua ini akan menjadi yang teratas dalam daftar, jadi saya akan membutuhkan setidaknya 781250/3 bit penyimpanan gratis sebelum saya mulai, yang akan membawa saya kembali ke persyaratan memori dari versi sebelumnya dari daftar ringkas: (

Untuk menyiasatinya, saya akan membagi 781250 sublists menjadi 10 grup sublist masing-masing 78125 sublists. Setiap grup memiliki pemetaan header sublist yang independen. Menggunakan huruf A ke J untuk grup:

ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ

Setiap grup sublist menyusut atau tetap sama selama perubahan pemetaan header sublist:

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

Ekspansi sementara kasus terburuk dari sublist grup selama perubahan pemetaan adalah 78125/3 = 26042 bit, di bawah 4k. Jika saya mengizinkan 4k ditambah 1037764 byte untuk daftar kompak yang penuh, yang membuat saya 8764 - 4096 = 4668 byte untuk "Z" di peta memori.

Itu seharusnya cukup untuk 10 tabel pemetaan header sublist, 30 jumlah kejadian header sublist dan beberapa counter, pointer dan buffer kecil yang saya perlukan, dan ruang yang saya gunakan tanpa pemberitahuan, seperti ruang stack untuk fungsi, alamat pengirim, dan variabel lokal.

Bagian 3, berapa lama untuk berjalan?

Dengan daftar ringkas kosong tajuk daftar 1 bit akan digunakan untuk sublist kosong, dan ukuran awal daftar adalah 781250 bit. Dalam kasus terburuk daftar tumbuh 8 bit untuk setiap angka yang ditambahkan, jadi 32 + 8 = 40 bit ruang kosong diperlukan untuk masing-masing angka 32-bit yang ditempatkan di bagian atas daftar buffer dan kemudian disortir dan digabungkan. Dalam kasus terburuk, mengubah hasil pemetaan header sublist dalam penggunaan ruang entri 2 * 781250 + 7 * - 781250/3 bit.

Dengan kebijakan mengubah pemetaan tajuk sublist setelah setiap penggabungan kelima setelah setidaknya ada 800.000 angka dalam daftar, proses kasus terburuk akan melibatkan total sekitar 30 juta aktivitas membaca dan menulis daftar ringkas.

Sumber:

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


15
Saya tidak berpikir ada solusi yang lebih baik adalah mungkin (kalau-kalau kita perlu bekerja dengan nilai-nilai yang tidak dapat dimampatkan). Tapi yang ini mungkin sedikit membaik. Tidak perlu mengubah header sublist antara representasi 1-bit dan 2-bit. Sebagai gantinya, Anda dapat menggunakan pengkodean aritmatika , yang menyederhanakan algoritme dan juga mengurangi jumlah terburuk bit per header dari 1,67 menjadi 1,58. Dan Anda tidak perlu memindahkan daftar ringkas dalam memori; alih-alih gunakan buffer bundar dan ubah pointer saja.
Evgeny Kluev

5
Jadi, akhirnya, apakah itu pertanyaan wawancara?
mlvljr

2
Peningkatan lainnya yang mungkin adalah dengan menggunakan daftar-daftar 100-elemen dan bukan daftar-daftar-128-elemen (karena kita mendapatkan representasi paling kompak ketika jumlah sub-daftar sama dengan jumlah elemen dalam kumpulan data). Setiap nilai sublist yang akan dikodekan dengan pengkodean aritmatika (dengan frekuensi yang sama dengan 1/100 untuk setiap nilai). Ini dapat menghemat sekitar 10.000 bit, jauh lebih sedikit daripada kompresi header sublist.
Evgeny Kluev

Untuk kasus C, Anda mengatakan "Entri disimpan dalam urutan yang tidak menurun, kecuali bahwa entri terakhir kurang dari atau sama dengan yang pertama." Lalu bagaimana Anda mengkodekan 2,2,2,3,5? {2,2,3,5,2} akan terlihat seperti hanya 2,2
Rollie

1
Solusi yang lebih sederhana dari pengkodean sublist header dimungkinkan dengan rasio kompresi yang sama 1,67 bit per subheader tanpa rumit beralih pemetaan. Anda dapat menggabungkan setiap 3 subjudul berturut-turut bersama-sama, yang dapat dengan mudah dikodekan menjadi 5 bit karena 3 * 3 * 3 = 27 < 32. Anda menggabungkannya combined_subheader = subheader1 + 3 * subheader2 + 9 * subheader3.
hynekcer

57

Jawaban Gilmanov sangat salah dalam anggapannya. Itu mulai berspekulasi berdasarkan pada ukuran tak berguna dari sejuta bilangan bulat berturut-turut . Itu berarti tidak ada celah. Kesenjangan acak itu, betapapun kecilnya, benar-benar membuatnya menjadi ide yang buruk.

Cobalah sendiri. Dapatkan 1 juta bilangan bulat 27 bit acak, sortir, kompres dengan 7-Zip , xz, LZMA apa pun yang Anda inginkan. Hasilnya lebih dari 1,5 MB. Premis di atas adalah kompresi nomor urut. Bahkan encoding delta itu adalah lebih dari 1,1 MB . Dan tidak masalah ini menggunakan lebih dari 100 MB RAM untuk kompresi. Jadi, bahkan bilangan bulat yang dikompresi tidak cocok dengan masalah dan apalagi menjalankan penggunaan RAM .

Ini membuat saya sedih bagaimana orang-orang hanya memilih cukup grafis dan rasionalisasi.

#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);

}

Sekarang kompres ints.bin dengan LZMA ...

$ 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
algoritma yang melibatkan kamus kompresi berdasarkan hanya di luar terbelakang, saya telah dikodekan beberapa yang kustom sedikit dan semua mereka mengambil cukup sedikit memori hanya untuk menempatkan tabel hash mereka sendiri (dan tidak ada HashMap di java seperti itu ekstra lapar pada sumber daya). Solusi terdekat adalah delta encoding w / variable bit length dan memantulkan kembali paket TCP yang tidak Anda sukai. Rekan akan mentransmisikan ulang, masih bagus sekali.
bestsss

@bestss ya! lihat jawaban terakhir saya yang sedang dalam proses. Saya pikir itu mungkin .
alecco

3
Maaf, tapi ini sepertinya juga tidak menjawab pertanyaan .
n611x007

@naxa ya itu menjawab: itu tidak dapat dilakukan dalam parameter pertanyaan asli. Ini hanya dapat dilakukan jika distribusi angka memiliki entropi yang sangat rendah.
alecco

1
Semua jawaban ini menunjukkan bahwa rutinitas kompresi standar mengalami kesulitan mengompresi data di bawah 1MB. Mungkin ada atau tidak ada skema pengkodean yang dapat memampatkan data hingga membutuhkan kurang dari 1MB, tetapi jawaban ini tidak membuktikan bahwa tidak ada skema pengkodean yang akan memampatkan data sebanyak ini.
Itsme2003

41

Saya pikir salah satu cara untuk memikirkan hal ini adalah dari sudut pandang kombinatorik: ada berapa banyak kemungkinan kombinasi urutan nomor yang disortir? Jika kita memberikan kombinasi 0,0,0, ...., 0 kode 0, dan 0,0,0, ..., 1 kode 1, dan 99999999, 99999999, ... 99999999 kode N, apa itu N? Dengan kata lain, seberapa besar ruang hasil?

Nah, salah satu cara untuk memikirkan hal ini adalah dengan memperhatikan bahwa ini adalah peninggalan masalah menemukan jumlah jalur monotonik dalam kisi N x M, di mana N = 1.000.000 dan M = 100.000.000. Dengan kata lain, jika Anda memiliki kisi yang lebar 1.000.000 dan tinggi 100.000.000, berapa banyak jalur terpendek dari kiri bawah ke kanan atas? Jalur terpendek tentu saja mengharuskan Anda hanya bergerak ke kanan atau ke atas (jika Anda bergerak ke bawah atau ke kiri Anda akan membatalkan kemajuan yang telah dicapai sebelumnya). Untuk melihat bagaimana ini merupakan masalah dari masalah penyortiran nomor kami, perhatikan hal berikut:

Anda dapat membayangkan kaki horizontal apa pun di jalur kami sebagai angka dalam pemesanan kami, di mana lokasi Y dari kaki tersebut mewakili nilai.

masukkan deskripsi gambar di sini

Jadi jika jalur hanya bergerak ke kanan sampai ke ujung, kemudian melompat ke atas, itu setara dengan pemesanan 0,0,0, ..., 0. jika itu dimulai dengan melompat ke atas dan kemudian bergerak ke kanan 1.000.000 kali, itu setara dengan 99999999,99999999, ..., 99999999. Sebuah jalan di mana ia bergerak ke kanan sekali, lalu naik sekali, lalu ke kanan , lalu naik sekali, dll hingga akhir (lalu harus melompat ke atas), setara dengan 0,1,2,3, ..., 999999.

Untungnya bagi kami masalah ini telah dipecahkan, grid seperti itu memiliki (N + M) Pilih jalur (M):

(1.000.000 + 100.000.000) Pilih (100.000.000) ~ = 2.27 * 10 ^ 2436455

Dengan demikian N sama dengan 2.27 * 10 ^ 2436455, dan kode 0 mewakili 0,0,0, ..., 0 dan kode 2,27 * 10 ^ 2436455 dan beberapa perubahan mewakili 99999999,99999999, ..., 99999999.

Untuk menyimpan semua angka dari 0 hingga 2.27 * 10 ^ 2436455 Anda perlu lg2 (2.27 * 10 ^ 2436455) = 8.0937 * 10 ^ 6 bit.

1 megabyte = 8388608 bit> 8093700 bit

Jadi sepertinya kita setidaknya benar-benar memiliki cukup ruang untuk menyimpan hasilnya! Sekarang tentu saja bit yang menarik adalah melakukan pengurutan sebagai aliran angka masuk. Tidak yakin pendekatan terbaik untuk ini diberikan kita memiliki 294908 bit yang tersisa. Saya membayangkan teknik yang menarik adalah pada setiap titik mengasumsikan bahwa itu adalah seluruh pemesanan, menemukan kode untuk pemesanan itu, dan kemudian ketika Anda menerima nomor baru kembali dan memperbarui kode sebelumnya. Gelombang tangan gelombang tangan.


Ini benar-benar banyak gerakan tangan. Di satu sisi, secara teoritis ini adalah solusinya karena kita bisa menulis sebuah mesin besar - tetapi masih terbatas; di sisi lain, ukuran penunjuk instruksi untuk mesin negara besar itu mungkin lebih dari satu megabyte, menjadikan ini non-starter. Ini benar-benar membutuhkan sedikit lebih banyak pemikiran daripada ini untuk benar-benar menyelesaikan masalah yang diberikan. Kita tidak hanya harus mewakili semua negara bagian, tetapi juga semua negara transisi yang diperlukan untuk menghitung apa yang harus dilakukan pada setiap nomor masukan berikutnya.
Daniel Wagner

4
Saya pikir jawaban lain hanya lebih halus tentang lambaian tangan mereka. Mengingat bahwa kita sekarang tahu ukuran ruang hasil, kita tahu berapa banyak ruang yang benar-benar kita butuhkan. Tidak ada jawaban lain yang dapat menyimpan setiap jawaban yang mungkin dalam apa pun yang lebih kecil dari 80.937.000 bit, karena itulah berapa banyak status akhir yang ada. Melakukan kompres (kondisi akhir) paling tidak kadang-kadang dapat mengurangi ruang, tetapi akan selalu ada beberapa jawaban yang membutuhkan ruang penuh (tidak ada algoritma kompresi yang dapat mengompres setiap input).
Francisco Ryan Tolmasky I

Beberapa jawaban lain sudah menyebutkan batas bawah yang sulit (misalnya kalimat kedua dari jawaban pertanyaan-penanya yang asli), jadi saya tidak yakin saya melihat apa jawaban ini ditambahkan ke gestalt.
Daniel Wagner

Apakah Anda merujuk pada 3.5M untuk menyimpan aliran mentah? (Jika tidak, saya minta maaf dan abaikan respons ini). Jika demikian, maka itu adalah batas bawah yang sama sekali tidak terkait. Batas bawah saya adalah berapa banyak ruang hasil akan mengambil, batas bawah adalah berapa banyak ruang input akan mengambil jika perlu untuk menyimpannya - mengingat bahwa pertanyaan itu diungkapkan sebagai aliran yang datang dari koneksi TCP itu tidak jelas apakah Anda memang perlu melakukannya, Anda mungkin membaca satu angka pada satu waktu dan memperbarui keadaan Anda, sehingga tidak memerlukan 3,5M - baik, 3,5 itu ortogonal untuk perhitungan ini.
Francisco Ryan Tolmasky I

"Ada sekitar 2 pangkat 8093729.5 cara berbeda untuk memilih 1 juta nomor 8 digit dengan duplikat diperbolehkan dan memesan tidak penting" <- dari jawaban penanya yang asli. Tidak tahu bagaimana menjadi lebih jelas tentang apa yang saya bicarakan. Saya merujuk cukup spesifik ke kalimat ini dalam komentar terakhir saya.
Daniel Wagner

20

Saran saya di sini berutang banyak pada solusi Dan

Pertama saya menganggap solusinya harus menangani semua daftar input yang mungkin. Saya pikir jawaban populer tidak membuat asumsi ini (yang merupakan kesalahan besar IMO).

Diketahui bahwa tidak ada bentuk kompresi lossless akan mengurangi ukuran semua input.

Semua jawaban populer berasumsi mereka akan dapat menerapkan kompresi yang cukup efektif untuk memberikan mereka ruang ekstra. Bahkan, sepotong ruang ekstra yang cukup besar untuk menampung sebagian dari daftar mereka yang telah selesai sebagian dalam bentuk yang tidak terkompresi dan memungkinkan mereka untuk melakukan operasi penyortiran mereka. Ini hanya asumsi yang buruk.

Untuk solusi seperti itu, siapa pun yang memiliki pengetahuan tentang bagaimana mereka melakukan kompresi mereka akan dapat merancang beberapa data input yang tidak kompres dengan baik untuk skema ini, dan "solusi" kemungkinan besar akan pecah karena kehabisan ruang.

Sebagai gantinya saya mengambil pendekatan matematika. Output kami yang mungkin adalah semua daftar panjang LEN yang terdiri dari elemen dalam kisaran 0..MAX. Di sini LEN adalah 1.000.000 dan MAX kami adalah 100.000.000.

Untuk LEN dan MAX sewenang-wenang, jumlah bit yang diperlukan untuk menyandikan keadaan ini adalah:

Log2 (MAX Multichoose LEN)

Jadi untuk angka-angka kami, setelah kami menyelesaikan penerimaan dan penyortiran, kami akan membutuhkan setidaknya Log2 (100.000.000 MC 1.000.000) bit untuk menyimpan hasil kami dengan cara yang secara unik dapat membedakan semua kemungkinan keluaran.

Ini ~ = 988kb . Jadi kami sebenarnya memiliki cukup ruang untuk menampung hasil kami. Dari sudut pandang ini, itu mungkin.

[Berceloteh tak berguna yang dihapus sekarang karena ada contoh yang lebih baik ...]

Jawaban terbaik ada di sini .

Jawaban lain yang baik ada di sini dan pada dasarnya menggunakan jenis penyisipan sebagai fungsi untuk memperluas daftar dengan satu elemen (buffer beberapa elemen dan pra-macam, untuk memungkinkan penyisipan lebih dari satu pada satu waktu, menghemat sedikit waktu). menggunakan pengkodean keadaan kompak yang bagus juga, ember delta tujuh bit


Selalu menyenangkan untuk membaca kembali jawaban Anda sendiri pada hari berikutnya ... Jadi, sementara jawaban teratas salah, yang diterima stackoverflow.com/a/12978097/1763801 cukup bagus. Pada dasarnya menggunakan jenis penyisipan sebagai fungsi untuk mengambil daftar LEN-1 dan mengembalikan LEN. Memanfaatkan fakta bahwa jika Anda mengubah set kecil Anda dapat memasukkan semuanya dalam satu pass, untuk meningkatkan efisiensi. Representasi negara cukup kompak (ember dari angka 7 bit) lebih baik daripada saran bergelombang tangan saya dan lebih intuitif. pikiran geo comp saya
bollocks

1
Saya pikir aritmatika Anda sedikit salah. Saya mendapatkan lg2 (100999999! / (99999999! * 1000000!)) = 1011718.55
NovaDenizen

Ya terima kasih itu 988kb bukan 965. Saya ceroboh dalam hal 1024 vs 1000. Kami masih memiliki sekitar 35kb untuk bermain-main. Saya menambahkan tautan ke perhitungan matematika dalam jawabannya.
davec

18

Misalkan tugas ini mungkin. Tepat sebelum output, akan ada representasi dalam memori dari jutaan angka yang diurutkan. Ada berapa banyak representasi yang berbeda? Karena mungkin ada angka yang berulang, kita tidak bisa menggunakan nCr (pilih), tetapi ada operasi yang disebut multichoose yang berfungsi pada multiset .

  • Ada 2.2e2436455 cara untuk memilih satu juta angka di kisaran 0..99.999.999.
  • Itu membutuhkan 8.093.730 bit untuk mewakili setiap kemungkinan kombinasi, atau 1.011.717 byte.

Jadi secara teori itu mungkin, jika Anda bisa mendapatkan representasi waras dari angka-angka yang diurutkan. Misalnya, representasi gila mungkin memerlukan tabel pencarian 10MB atau ribuan baris kode.

Namun, jika "1M RAM" berarti satu juta byte, maka jelas tidak ada ruang yang cukup. Fakta bahwa 5% lebih banyak memori memungkinkan secara teori menunjukkan kepada saya bahwa representasi harus SANGAT efisien dan mungkin tidak waras.


Jumlah cara untuk memilih satu juta angka (2.2e2436455) kebetulan mendekati (256 ^ (1024 * 988)), yaitu (2.0e2436445). Ergo, jika Anda mengambil sekitar 32 KB memori dari 1M, masalahnya tidak dapat diselesaikan. Juga perlu diingat memori minimal 3 KB sudah dicadangkan.
johnwbyrd

Ini tentu saja mengasumsikan data benar-benar acak. Sejauh yang kami tahu, memang, tapi saya hanya mengatakan :)
Thorarin

Cara konvensional untuk mewakili jumlah keadaan yang memungkinkan ini adalah dengan mengambil basis log 2 dan melaporkan jumlah bit yang diperlukan untuk mewakili mereka.
NovaDenizen

@ Thorarin, ya, saya tidak melihat titik dalam "solusi" yang hanya berfungsi untuk beberapa input.
Dan

12

(Jawaban asli saya salah, maaf untuk matematika buruk, lihat di bawah ini.)

Bagaimana dengan ini?

27 bit pertama menyimpan angka terendah yang Anda lihat, kemudian perbedaan ke angka berikutnya terlihat, disandikan sebagai berikut: 5 bit untuk menyimpan jumlah bit yang digunakan dalam menyimpan perbedaan, lalu perbedaannya. Gunakan 00000 untuk menunjukkan bahwa Anda melihat nomor itu lagi.

Ini berfungsi karena semakin banyak angka yang dimasukkan, perbedaan rata-rata di antara angka-angka turun, jadi Anda menggunakan lebih sedikit bit untuk menyimpan perbedaan ketika Anda menambahkan lebih banyak angka. Saya percaya ini disebut daftar delta.

Kasus terburuk yang dapat saya pikirkan adalah semua angka diberi spasi secara merata (100), misalkan dengan asumsi 0 adalah angka pertama:

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

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

Reddit untuk menyelamatkan!

Jika yang harus Anda lakukan adalah mengurutkannya, masalah ini akan mudah. Dibutuhkan 122k (1 juta bit) untuk menyimpan angka yang telah Anda lihat (bit ke-0 jika 0 terlihat, bit ke-23 pada jika 2300 terlihat, dll.

Anda membaca angka-angka, menyimpannya di bidang bit, dan kemudian menggeser bit keluar sambil tetap menghitung.

TAPI, Anda harus ingat berapa banyak yang Anda lihat. Saya terinspirasi oleh jawaban sublist di atas untuk menghasilkan skema ini:

Alih-alih menggunakan satu bit, gunakan 2 atau 27 bit:

  • 00 berarti Anda tidak melihat nomornya.
  • 01 berarti Anda pernah melihatnya sekali
  • 1 berarti Anda melihatnya, dan 26 bit berikutnya adalah hitungan berapa kali.

Saya pikir ini berfungsi: jika tidak ada duplikat, Anda memiliki daftar 244k. Dalam kasus terburuk Anda melihat setiap angka dua kali (jika Anda melihat satu angka tiga kali, itu mempersingkat sisa daftar untuk Anda), itu berarti Anda telah melihat 50.000 lebih dari sekali, dan Anda telah melihat 950.000 item 0 atau 1 kali.

50.000 * 27 + 950.000 * 2 = 396,7rb.

Anda dapat membuat peningkatan lebih lanjut jika Anda menggunakan penyandian berikut:

0 berarti Anda tidak melihat angka 10 berarti Anda melihatnya sekali 11 adalah cara Anda menghitung

Yang rata-rata akan menghasilkan penyimpanan 280,7k.

EDIT: Matematika hari Minggu pagi saya salah.

Kasus terburuk adalah kita melihat angka 500.000 dua kali, sehingga matematika menjadi:

500.000 * 27 + 500.000 * 2 = 1.77M

Pengkodean alternatif menghasilkan penyimpanan rata-rata

500.000 * 27 + 500.000 = 1,70M

: (


1
Ya, tidak, karena angka kedua adalah
500000.

Mungkin menambahkan beberapa perantara, seperti di mana 11 berarti Anda telah melihat jumlahnya hingga 64 kali (menggunakan 6 bit berikutnya), dan 11000000 berarti menggunakan 32 bit lain untuk menyimpan berapa kali Anda melihatnya.
τεκ

10
Di mana Anda mendapatkan nomor "1 juta bit"? Anda mengatakan bit 2300 mewakili apakah 2300 terlihat. (Saya pikir Anda benar-benar berarti 2301). Bit mana yang mewakili apakah 99.999.999 terlihat (angka 8 digit terbesar)? Agaknya, itu akan menjadi bit 100 juta.
user94559

Anda mendapatkan satu juta dan seratus juta Anda mundur. Nilai yang paling sering terjadi adalah 1 juta, dan Anda hanya perlu 20 bit untuk mewakili jumlah kemunculan suatu nilai. Anda juga membutuhkan 100.000.000 bidang bit (bukan 1 juta), satu untuk setiap nilai yang mungkin.
Tim R.

Eh, 27 + 1000000 * (5 + 7) = 12000027 bit = 1,43M, bukan 427K.
Daniel Wagner

10

Ada satu solusi untuk masalah ini di semua input yang mungkin. Curang.

  1. Baca nilai m melalui TCP, di mana m berada di dekat maks yang dapat diurutkan dalam memori, mungkin n / 4.
  2. Urutkan 250.000 (atau lebih) angka dan hasilkan.
  3. Ulangi untuk 3 kuartal lainnya.
  4. Biarkan penerima menggabungkan 4 daftar angka yang telah diterimanya saat memprosesnya. (Ini tidak jauh lebih lambat daripada menggunakan satu daftar.)

7

Saya akan mencoba Pohon Radix . Jika Anda bisa menyimpan data dalam pohon, Anda bisa melakukan lintasan in-order untuk mengirimkan data.

Saya tidak yakin Anda bisa memasukkan ini ke dalam 1MB, tapi saya pikir ini patut dicoba.


7

Komputer jenis apa yang Anda gunakan? Mungkin tidak memiliki penyimpanan lokal "normal" lainnya, tetapi apakah ada RAM video, misalnya? 1 megapiksel x 32 bit per piksel (katakanlah) cukup dekat dengan ukuran input data yang Anda butuhkan.

(Saya sebagian besar bertanya dalam memori Acorn RISC PC lama , yang dapat 'meminjam' VRAM untuk memperluas RAM sistem yang tersedia, jika Anda memilih mode layar resolusi rendah atau rendah warna!). Ini agak berguna pada mesin dengan hanya beberapa MB RAM normal.


1
Mau berkomentar, downvoter? - Saya hanya mencoba untuk meregangkan kendala yang tampak dari pertanyaan (yaitu curang secara kreatif ;-)
DNA

Mungkin tidak ada komputer sama sekali, karena utas yang relevan tentang berita peretas menyebutkan ini pernah menjadi pertanyaan wawancara Google.
mlvljr

1
Ya - saya menjawab sebelum pertanyaan diedit untuk menunjukkan bahwa ini adalah pertanyaan wawancara!
DNA

6

Representasi pohon radix akan mendekati menangani masalah ini, karena pohon radix mengambil keuntungan dari "kompresi awalan". Tetapi sulit untuk membayangkan representasi pohon radix yang dapat mewakili satu node dalam satu byte - dua mungkin tentang batas.

Tapi, terlepas dari bagaimana data diwakili, setelah diurutkan itu dapat disimpan dalam bentuk awalan-terkompresi, di mana angka 10, 11, dan 12 akan diwakili oleh, katakanlah 001b, 001b, 001b, menunjukkan peningkatan 1 dari nomor sebelumnya. Mungkin, 10101b akan mewakili peningkatan 5, 1101001b peningkatan 9, dll.


6

Ada 10 ^ 6 nilai dalam kisaran 10 ^ 8, jadi ada satu nilai rata-rata per seratus poin kode. Menyimpan jarak dari titik N ke (N +1). Nilai duplikat memiliki lompatan 0. Ini berarti bahwa loncatan membutuhkan rata-rata hanya di bawah 7 bit untuk disimpan, jadi satu juta dari mereka akan dengan senang hati masuk ke dalam 8 juta bit penyimpanan kami.

Melompati ini perlu dikodekan ke dalam bitstream, katakanlah dengan pengkodean Huffman. Penyisipan adalah dengan mengulangi bitstream dan menulis ulang setelah nilai baru. Keluaran dengan mengulangi dan menuliskan nilai-nilai yang tersirat. Untuk kepraktisan, mungkin ingin dilakukan sebagai, katakanlah, 10 ^ 4 daftar yang mencakup 10 ^ 4 poin kode (dan rata-rata 100 nilai) masing-masing.

Pohon Huffman yang baik untuk data acak dapat dibangun apriori dengan mengasumsikan distribusi Poisson (rata-rata = varians = 100) pada panjang lompatan, tetapi statistik nyata dapat disimpan pada input dan digunakan untuk menghasilkan pohon optimal untuk berurusan dengan kasus patologis.


5

Saya memiliki komputer dengan 1M RAM dan tidak ada penyimpanan lokal lainnya

Cara lain untuk menipu: Anda dapat menggunakan penyimpanan non-lokal (jaringan) sebagai gantinya (pertanyaan Anda tidak menghalangi ini) dan memanggil layanan jaringan yang dapat menggunakan mergesort berbasis disk langsung (atau hanya cukup RAM untuk mengurutkan dalam memori, karena Anda hanya perlu menerima nomor 1M), tanpa perlu solusi (diakui sangat cerdik) yang sudah diberikan.

Ini mungkin curang, tetapi tidak jelas apakah Anda mencari solusi untuk masalah dunia nyata, atau teka-teki yang mengundang pembengkokan aturan ... jika yang terakhir, maka cheat sederhana mungkin mendapatkan hasil yang lebih baik daripada kompleks tetapi solusi "asli" (yang seperti yang ditunjukkan orang lain, hanya dapat bekerja untuk input yang dapat dikompresi).


5

Saya pikir solusinya adalah menggabungkan teknik dari video encoding, yaitu transformasi cosinus diskrit. Dalam video digital, alih-alih merekam perubahan kecerahan atau warna video sebagai nilai reguler seperti 110 112 115 116, masing-masing dikurangkan dari yang terakhir (mirip dengan pengodean panjang lari). 110 112 115 116 menjadi 110 2 3 1. Nilai-nilai, 2 3 1 membutuhkan lebih sedikit bit dari aslinya.

Jadi katakanlah kita membuat daftar nilai input saat mereka tiba di soket. Kami menyimpan di setiap elemen, bukan nilai, tetapi offset dari yang sebelumnya. Kami mengurutkan seiring berjalannya waktu, sehingga offset hanya akan menjadi positif. Tapi offset bisa 8 digit lebar desimal yang cocok dalam 3 byte. Setiap elemen tidak boleh 3 byte, jadi kita harus mengemasnya. Kita dapat menggunakan bit teratas dari setiap byte sebagai "terus bit", menunjukkan bahwa byte berikutnya adalah bagian dari angka dan 7 bit yang lebih rendah dari setiap byte perlu digabungkan. nol valid untuk duplikat.

Ketika daftar terisi, angka-angka harus semakin dekat, artinya rata-rata hanya 1 byte digunakan untuk menentukan jarak ke nilai berikutnya. 7 bit nilai dan 1 bit ofset jika nyaman, tetapi mungkin ada sweet spot yang membutuhkan kurang dari 8 bit untuk nilai "terus".

Lagi pula, saya melakukan beberapa percobaan. Saya menggunakan generator angka acak dan saya bisa memasukkan satu juta angka desimal diurutkan 8 digit menjadi sekitar 1279000 byte. Ruang rata-rata antara setiap angka secara konsisten 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

Kita bisa bermain dengan tumpukan jaringan untuk mengirim nomor dalam urutan diurutkan sebelum kita memiliki semua nomor. Jika Anda mengirim data 1M, TCP / IP akan memecahnya menjadi paket 1500 byte dan mengalirkannya ke target. Setiap paket akan diberi nomor urut.

Kita bisa melakukan ini dengan tangan. Tepat sebelum kita mengisi RAM kita, kita dapat mengurutkan apa yang kita miliki dan mengirimkan daftar ke target kita tetapi meninggalkan lubang di urutan kami di sekitar masing-masing nomor. Kemudian proses 2 1/2 angka dengan cara yang sama menggunakan lubang-lubang di urutan.

Tumpukan jaringan di ujung jauh akan mengumpulkan aliran data yang dihasilkan dalam urutan urutan sebelum menyerahkannya ke aplikasi.

Itu menggunakan jaringan untuk melakukan semacam penggabungan. Ini adalah retasan total, tetapi saya terinspirasi oleh retasan jaringan lain yang tercantum sebelumnya.


4

Pendekatan Google (buruk), dari utas HN. Simpan hitungan gaya-RLE.

Struktur data awal Anda adalah '99999999: 0' (semua nol, belum melihat angka) dan kemudian katakan Anda melihat angka 3,866,344 sehingga struktur data Anda menjadi '3866343: 0,1: 1,96133654: 0' saat Anda bisa melihat angka akan selalu bergantian antara jumlah bit nol dan jumlah bit '1' sehingga Anda bisa menganggap angka ganjil mewakili 0 bit dan angka genap 1 bit. Ini menjadi (3866343,1,96133654)

Masalah mereka tampaknya tidak mencakup duplikat, tetapi katakanlah mereka menggunakan "0: 1" untuk duplikat.

Masalah besar # 1: penyisipan untuk integer 1M akan memakan waktu lama .

Masalah besar # 2: seperti semua solusi pengkodean delta biasa, beberapa distribusi tidak dapat ditutup dengan cara ini. Misalnya, 1m bilangan bulat dengan jarak 0:99 (mis. Masing-masing +99). Sekarang pikirkan hal yang sama tetapi dengan jarak acak di kisaran 0:99 . (Catatan: 99999999/1000000 = 99,99)

Pendekatan Google tidak layak (lambat) dan salah. Tetapi untuk pertahanan mereka, masalah mereka mungkin sedikit berbeda.


3

Untuk mewakili array yang diurutkan kita bisa menyimpan elemen pertama dan perbedaan antara elemen yang berdekatan. Dengan cara ini kita peduli dengan pengkodean 10 ^ 6 elemen yang dapat meringkas paling banyak 10 ^ 8 Mari kita sebut ini D . Untuk mengkodekan elemen D, seseorang dapat menggunakan kode Huffman . Kamus untuk kode Huffman dapat dibuat di mana saja dan larik diperbarui setiap kali item baru dimasukkan dalam larik yang diurutkan (susunan penyisipan). Perhatikan bahwa ketika kamus berubah karena item baru seluruh array harus diperbarui agar sesuai dengan pengkodean baru.

Jumlah rata-rata bit untuk pengkodean setiap elemen D dimaksimalkan jika kita memiliki jumlah yang sama dari setiap elemen unik. Katakan elemen d1 , d2 , ..., dN dalam D masing-masing muncul F kali. Dalam hal itu (dalam kasus terburuk kita memiliki 0 dan 10 ^ 8 dalam urutan input) yang kita miliki

sum (1 <= i <= N ) F . di = 10 ^ 8

dimana

jumlah (1 <= i <= N ) F = 10 ^ 6, atau F = 10 ^ 6 / N dan frekuensi yang dinormalisasi adalah p = F / 10 ^ = 1 / N

Jumlah rata-rata bit adalah -log2 (1 / P ) = log2 ( N ). Dalam keadaan ini kita harus menemukan kasus yang memaksimalkan N . Ini terjadi jika kita memiliki angka berurutan untuk di mulai dari 0, atau, di = i -1

10 ^ 8 = sum (1 <= i <= N ) F . di = jumlah (1 <= i <= N ) (10 ^ 6 / N ) (i-1) = (10 ^ 6 / N ) N ( N -1) / 2

yaitu

N <= 201. Dan untuk kasus ini jumlah rata-rata bit adalah log2 (201) = 7.6511 yang berarti kita akan membutuhkan sekitar 1 byte per elemen input untuk menyimpan array yang diurutkan. Perhatikan bahwa ini tidak berarti D secara umum tidak dapat memiliki lebih dari 201 elemen. Ini hanya menabur bahwa jika elemen D didistribusikan secara seragam, itu tidak dapat memiliki lebih dari 201 nilai unik.


1
Saya pikir Anda sudah lupa bahwa nomor dapat digandakan.
bestsss

Untuk angka duplikat perbedaan antara angka yang berdekatan akan menjadi nol. Tidak membuat masalah. Kode Huffman tidak memerlukan nilai bukan nol.
Mohsen Nosratinia

3

Saya akan mengeksploitasi perilaku pengiriman ulang TCP.

  1. Buat komponen TCP membuat jendela penerimaan besar.
  2. Menerima sejumlah paket tanpa mengirim ACK untuk mereka.
    • Proses mereka yang lewat membuat beberapa (awalan) struktur data terkompresi
    • Kirim duplikat ack untuk paket terakhir yang tidak diperlukan lagi / tunggu batas waktu pengiriman ulang
    • Goto 2
  3. Semua paket diterima

Ini mengasumsikan semacam manfaat dari ember atau beberapa lintasan.

Mungkin dengan menyortir bets / ember dan menggabungkannya. -> pohon radix

Gunakan teknik ini untuk menerima dan mengurutkan 80% pertama kemudian membaca 20% terakhir, verifikasi bahwa 20% terakhir tidak mengandung angka yang akan mendarat di 20% pertama dari angka terendah. Kemudian kirim 20% angka terendah, hapus dari memori, terima 20% sisanya dari angka baru dan gabungkan. **


3

Berikut ini adalah solusi umum untuk masalah seperti ini:

Prosedur umum

Pendekatan yang diambil adalah sebagai berikut. Algoritma ini beroperasi pada satu buffer kata-kata 32-bit. Ia melakukan prosedur berikut dalam satu lingkaran:

  • Kami mulai dengan buffer yang diisi dengan data terkompresi dari iterasi terakhir. Buffer terlihat seperti ini

    |compressed sorted|empty|

  • Hitung jumlah maksimum angka yang dapat disimpan dalam buffer ini, baik terkompresi dan tidak terkompresi. Membagi buffer menjadi dua bagian ini, dimulai dengan ruang untuk data terkompresi, diakhiri dengan data yang tidak terkompresi. Buffer tampak seperti

    |compressed sorted|empty|empty|

  • Isi bagian yang tidak dikompres dengan angka yang akan disortir. Buffer tampak seperti

    |compressed sorted|empty|uncompressed unsorted|

  • Sortir nomor baru dengan jenis di tempat. Buffer tampak seperti

    |compressed sorted|empty|uncompressed sorted|

  • Sejajarkan kanan data yang sudah dikompresi dari iterasi sebelumnya di bagian terkompresi. Pada titik ini buffer dipartisi

    |empty|compressed sorted|uncompressed sorted|

  • Melakukan streaming dekompresi-rekompresi pada bagian terkompresi, menggabungkan data yang diurutkan di bagian terkompresi. Bagian terkompresi lama dikonsumsi saat bagian terkompresi baru tumbuh. Buffer tampak seperti

    |compressed sorted|empty|

Prosedur ini dilakukan sampai semua nomor telah diurutkan.

Kompresi

Algoritme ini tentu saja hanya berfungsi jika dimungkinkan untuk menghitung ukuran terkompresi terakhir dari penyortiran penyortiran baru sebelum benar-benar mengetahui apa yang sebenarnya akan dikompresi. Di samping itu, algoritma kompresi harus cukup baik untuk menyelesaikan masalah yang sebenarnya.

Pendekatan yang digunakan menggunakan tiga langkah. Pertama, algoritma akan selalu menyimpan urutan yang diurutkan, oleh karena itu kami dapat menyimpan perbedaan antara entri yang berurutan. Setiap perbedaan berada dalam kisaran [0, 99999999].

Perbedaan-perbedaan ini kemudian dikodekan sebagai bitstream unary. A 1 dalam aliran ini berarti "Tambahkan 1 ke akumulator, A 0 berarti" Keluarkan akumulator sebagai entri, dan reset ". Jadi perbedaan N akan diwakili oleh N 1 dan satu 0.

Jumlah semua perbedaan akan mendekati nilai maksimum yang didukung algoritma, dan jumlah semua perbedaan akan mendekati jumlah nilai yang dimasukkan dalam algoritma. Ini berarti kami mengharapkan aliran, pada akhirnya, berisi nilai maksimum 1 dan jumlah 0. Ini memungkinkan kami untuk menghitung probabilitas yang diharapkan dari 0 dan 1 dalam aliran. Yaitu, probabilitas 0 adalah count/(count+maxval)dan probabilitas 1 adalah maxval/(count+maxval).

Kami menggunakan probabilitas ini untuk mendefinisikan model pengkodean aritmatika pada bitstream ini. Kode aritmatika ini akan menyandikan jumlah 1 dan 0 ini dalam ruang optimal. Kita bisa menghitung ruang yang digunakan oleh model ini untuk setiap bitstream menengah sebagai: bits = encoded * log2(1 + amount / maxval) + maxval * log2(1 + maxval / amount). Untuk menghitung total ruang yang dibutuhkan untuk algoritma, setel encodedsama dengan jumlah.

Untuk tidak memerlukan jumlah iterasi yang konyol, overhead kecil dapat ditambahkan ke buffer. Ini akan memastikan bahwa algoritma setidaknya akan beroperasi pada jumlah angka yang sesuai dengan overhead ini, karena sejauh ini biaya waktu terbesar dari algoritma ini adalah kompresi pengkodean aritmatika dan dekompresi setiap siklus.

Di samping itu, beberapa overhead diperlukan untuk menyimpan data pembukuan dan untuk menangani sedikit ketidakakuratan dalam pendekatan titik tetap dari algoritma pengkodean aritmatika, tetapi secara total algoritma ini dapat masuk dalam ruang 1MiB bahkan dengan buffer tambahan yang dapat berisi 8000 angka, dengan total ruang 1043916 byte.

Optimalitas

Di luar mengurangi overhead (kecil) dari algoritma itu seharusnya secara teori tidak mungkin untuk mendapatkan hasil yang lebih kecil. Untuk hanya berisi entropi dari hasil akhir, 1011717 byte akan diperlukan. Jika kita mengurangi buffer tambahan yang ditambahkan untuk efisiensi, algoritma ini menggunakan 1011916 byte untuk menyimpan hasil akhir + overhead.


2

Jika aliran input dapat diterima beberapa kali ini akan jauh lebih mudah (tidak ada informasi tentang itu, ide dan masalah kinerja waktu).

Kemudian, kita bisa menghitung nilai desimal. Dengan nilai yang dihitung akan mudah untuk membuat aliran output. Kompres dengan menghitung nilai. Itu tergantung apa yang akan ada di aliran input.


1

Jika aliran input dapat diterima beberapa kali ini akan jauh lebih mudah (tidak ada info tentang itu, ide dan masalah kinerja waktu). Kemudian, kita bisa menghitung nilai desimal. Dengan nilai yang dihitung akan mudah untuk membuat aliran output. Kompres dengan menghitung nilai. Itu tergantung apa yang akan ada di aliran input.


1

Penyortiran adalah masalah sekunder di sini. Seperti kata yang lain, hanya menyimpan bilangan bulat itu sulit, dan tidak dapat bekerja pada semua input , karena diperlukan 27 bit per angka.

Pandangan saya tentang ini adalah: simpan hanya perbedaan antara integer (diurutkan) berurutan, karena kemungkinan besar kecil. Kemudian gunakan skema kompresi, misalnya dengan 2 bit tambahan per nomor input, untuk mengkodekan berapa banyak bit nomor disimpan. Sesuatu seperti:

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

Seharusnya dimungkinkan untuk menyimpan sejumlah daftar input yang mungkin dalam batasan memori yang diberikan. Matematika tentang cara memilih skema kompresi agar dapat bekerja pada jumlah input maksimum, berada di luar jangkauan saya.

Saya harap Anda dapat memanfaatkan pengetahuan khusus domain untuk input Anda untuk menemukan skema kompresi integer yang cukup baik berdasarkan ini.

Oh dan kemudian, Anda melakukan semacam penyisipan pada daftar yang diurutkan saat Anda menerima data.


1

Sekarang bertujuan untuk solusi yang sebenarnya, mencakup semua kemungkinan kasus input dalam kisaran 8 digit dengan hanya 1MB RAM. CATATAN: pekerjaan sedang berlangsung, besok akan dilanjutkan. Menggunakan kode aritmatika dari del int yang diurutkan, kasus terburuk untuk 1M int yang diurutkan akan menelan biaya sekitar 7 bit per entri (karena 99999999/1000000 adalah 99, dan log2 (99) hampir 7 bit).

Tetapi Anda membutuhkan integer 1m yang diurutkan untuk mendapatkan 7 atau 8 bit! Seri yang lebih pendek akan memiliki delta yang lebih besar, oleh karena itu lebih banyak bit per elemen.

Saya sedang berusaha mengambil sebanyak mungkin dan mengompres (hampir) di tempat. Batch pertama hampir 250K int akan membutuhkan sekitar 9 bit masing-masing di terbaik. Jadi hasilnya akan memakan waktu sekitar 275KB. Ulangi dengan sisa memori bebas beberapa kali. Kemudian dekompresi-gabungkan di tempat-kompres potongan terkompresi itu. Ini cukup sulit , tetapi mungkin. Kupikir.

Daftar yang digabungkan akan semakin dekat dan lebih dekat ke target 7bit per integer. Tapi saya tidak tahu berapa banyak iterasi yang dibutuhkan dari loop gabungan. Mungkin 3.

Tetapi ketidaktepatan implementasi kode aritmatika mungkin membuatnya tidak mungkin. Jika masalah ini mungkin terjadi, itu akan sangat ketat.

Ada sukarelawan?


Pengkodean aritmatika bisa diterapkan. Mungkin membantu untuk memperhatikan bahwa setiap delta berturut-turut diambil dari distribusi binomial negatif.
crowding

1

Anda hanya perlu menyimpan perbedaan antara angka-angka dalam urutan, dan menggunakan pengodean untuk mengompres nomor-nomor urutan ini. Kami memiliki 2 ^ 23 bit. Kami akan membaginya menjadi potongan-potongan 6bit, dan biarkan bit terakhir menunjukkan apakah jumlahnya meluas ke 6 bit lainnya (5bit ditambah bilah yang memperpanjang).

Jadi, 000010 adalah 1, dan 000100 adalah 2. 000001100000 adalah 128. Sekarang, kami mempertimbangkan pemeran terburuk dalam mewakili perbedaan dalam urutan angka hingga 10.000.000. Mungkin ada 10.000.000 / 2 ^ 5 perbedaan lebih besar dari 2 ^ 5, 10.000.000 / 2 ^ 10 perbedaan lebih besar dari 2 ^ 10, dan 10.000.000 / 2 ^ 15 perbedaan lebih besar dari 2 ^ 15, dll.

Jadi, kami menambahkan berapa banyak bit yang diperlukan untuk mewakili urutan kami. Kami memiliki 1.000.000 * 6 + roundup (10.000.000 / 2 ^ 5) * 6 + roundup (10.000.000 / 2 ^ 10) * 6 + roundup (10.000.000 / 2 ^ 15) * 6 + roundup (10.000.000 / 2 ^ 20) * 4 = 7935479

2 ^ 24 = 8388608. Sejak 8388608> 7935479, kita seharusnya dengan mudah memiliki cukup memori. Kita mungkin perlu sedikit memori untuk menyimpan jumlah di mana kita memasukkan angka baru. Kami kemudian pergi melalui urutan, dan menemukan tempat untuk memasukkan nomor baru kami, mengurangi perbedaan berikutnya jika perlu, dan menggeser semuanya setelah itu benar.


Saya percaya analisis saya di sini menunjukkan bahwa skema ini tidak berfungsi (dan bahkan tidak bisa jika kita memilih ukuran lain dari lima bit).
Daniel Wagner

@Daniel Wagner - Anda tidak harus menggunakan jumlah bit yang seragam per chunk, dan Anda bahkan tidak perlu menggunakan integer jumlah bit per chunk.
crowding

@crowding Jika Anda memiliki proposal yang konkret, saya ingin mendengarnya. =)
Daniel Wagner

@crowding Lakukan perhitungan matematika tentang seberapa banyak ruang yang diperlukan untuk pengkodean aritmatika. Menangislah sedikit. Kemudian berpikir lebih keras.
Daniel Wagner

Belajarlah lagi. Distribusi simbol bersyarat lengkap dalam representasi perantara kanan (Francisco memiliki representasi perantara paling sederhana, seperti halnya Strilanc) mudah untuk dihitung. Dengan demikian model pengkodean dapat benar-benar sempurna dan dapat datang dalam satu bit dari batas entropis. Aritmatika presisi terbatas dapat menambahkan beberapa bit.
berkerumun

1

Jika kami tidak tahu apa-apa tentang angka-angka itu, kami dibatasi oleh batasan-batasan berikut:

  • kita perlu memuat semua angka sebelum kita bisa mengurutkannya,
  • set angka tidak dapat dikompres.

Jika asumsi ini berlaku, tidak ada cara untuk melakukan tugas Anda, karena Anda akan memerlukan setidaknya 26.575.425 bit penyimpanan (3.321.929 byte).

Apa yang bisa Anda ceritakan tentang data Anda?


1
Anda membacanya dan memilahnya saat Anda pergi. Secara teoritis membutuhkan lg2 (100999999! / (99999999! * 1000000!)) Bit untuk menyimpan 1M item yang tidak bisa dibedakan dalam 100M kotak yang dibedakan, yang bekerja hingga 96,4% dari 1MB.
NovaDenizen

1

Caranya adalah dengan mewakili keadaan algoritma, yang merupakan integer multi-set, sebagai aliran terkompresi dari "increment counter" = "+" dan "output counter" = "!" karakter. Misalnya, set {0,3,3,4} akan direpresentasikan sebagai "! +++ !! +!", Diikuti oleh sejumlah karakter "+". Untuk memodifikasi multi-set Anda mengalirkan karakter, menjaga jumlah yang konstan didekompresi pada satu waktu, dan membuat perubahan di tempat sebelum mengalirkannya kembali dalam bentuk terkompresi.

Detail

Kita tahu persis ada 10 ^ 6 angka di set terakhir, jadi paling banyak ada 10 ^ 6 "!" karakter. Kita juga tahu bahwa rentang kita memiliki ukuran 10 ^ 8, artinya paling banyak ada 10 ^ 8 "+" karakter. Jumlah cara kita dapat mengatur 10 ^ 6 "!" Di antara 10 ^ 8 "+" adalah (10^8 + 10^6) choose 10^6, dan dengan demikian menentukan beberapa pengaturan tertentu membutuhkan ~ 0,965 MiB `data. Itu akan menjadi sangat ketat.

Kami dapat memperlakukan setiap karakter sebagai independen tanpa melebihi kuota kami. Tepatnya ada 100 kali lebih banyak "+" karakter daripada "!" karakter, yang menyederhanakan peluang 100: 1 dari setiap karakter menjadi "+" jika kita lupa bahwa mereka bergantung. Peluang 100: 101 sesuai dengan ~ 0,08 bit per karakter , untuk total yang hampir identik ~ 0,965 MiB (mengabaikan ketergantungan hanya membutuhkan biaya ~ 12 bit dalam kasus ini!).

Teknik paling sederhana untuk menyimpan karakter independen dengan probabilitas yang diketahui sebelumnya adalah pengkodean Huffman . Perhatikan bahwa kita memerlukan pohon besar yang tidak praktis (Sebuah pohon jagoan untuk blok 10 karakter memiliki biaya rata-rata per blok sekitar 2,4 bit, dengan total ~ 2,9 Mib. Pohon huffman untuk blok 20 karakter memiliki biaya rata-rata per blok sekitar 3 bit, yang merupakan total ~ 1,8 MiB. Kita mungkin akan membutuhkan satu blok ukuran pada urutan seratus, menyiratkan lebih banyak node di pohon kita daripada semua peralatan komputer yang pernah ada dapat menyimpan. ). Namun, ROM secara teknis "bebas" sesuai dengan masalah dan solusi praktis yang memanfaatkan keteraturan dalam pohon akan terlihat pada dasarnya sama.

Kode semu

  • Memiliki pohon huffman yang cukup besar (atau data kompresi blok demi blok serupa) disimpan dalam ROM
  • Mulai dengan string terkompresi 10 ^ 8 "+" karakter.
  • Untuk memasukkan angka N, streaming string terkompresi sampai N "+" karakter telah melewati lalu masukkan "!". Alirkan string terkompresi kembali ke yang sebelumnya saat Anda pergi, menjaga jumlah blok buffered konstan untuk menghindari over / under-berjalan.
  • Ulangi satu juta kali: [input, stream decompress> insert> kompres], lalu dekompresi ke output

1
Sejauh ini, ini adalah satu-satunya jawaban yang saya lihat yang benar-benar menjawab masalah! Saya pikir pengkodean aritmatika lebih cocok daripada pengkodean Huffman, karena hal ini menghilangkan kebutuhan untuk menyimpan codebook dan khawatir tentang batasan simbol. Anda dapat menjelaskan ketergantungan juga.
crowding

Bilangan bulat input TIDAK diurutkan. Anda perlu mengurutkan terlebih dahulu.
alecco

1
@ alecco Algoritme akan mengurutkannya saat berlangsung. Mereka tidak pernah disimpan tidak disortir.
Craig Gidney

1

Kami memiliki 1 MB - 3 KB RAM = 2 ^ 23 - 3 * 2 ^ 13 bit = 8388608 - 24576 = 8364032 bit tersedia.

Kami diberi 10 ^ 6 angka dalam kisaran 10 ^ 8. Ini memberikan jarak rata-rata ~ 100 <2 ^ 7 = 128

Pertama-tama mari kita pertimbangkan masalah yang lebih sederhana dari angka-angka dengan jarak yang cukup merata ketika semua celah <128. Ini mudah. Simpan saja angka pertama dan celah 7-bit:

(27 bit) + 10 ^ 6 Nomor 7-bit gap = dibutuhkan 7000027 bit

Catatan angka yang diulang memiliki celah 0.

Tetapi bagaimana jika kita memiliki celah lebih besar dari 127?

OK, katakanlah ukuran celah <127 diwakili secara langsung, tetapi ukuran celah 127 diikuti oleh pengodean 8-bit terus menerus untuk panjang celah aktual:

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

dll.

Perhatikan representasi nomor ini menjelaskan panjangnya sendiri sehingga kami tahu kapan nomor gap berikutnya dimulai.

Dengan celah kecil <127, ini masih membutuhkan 7000027 bit.

Mungkin ada hingga (10 ^ 8) / (2 ^ 7) = 781250 23-bit gap number, membutuhkan tambahan 16 * 781.250 = 12.500.000 bit yang terlalu banyak. Kita membutuhkan representasi kesenjangan yang lebih kompak dan perlahan-lahan meningkat.

Ukuran celah rata-rata adalah 100 jadi jika kita menyusun ulang mereka sebagai [100, 99, 101, 98, 102, ..., 2, 198, 1, 199, 0, 200, 201, 202, ...] dan indeks ini dengan pengkodean basis biner Fibonacci biner tanpa pasangan nol (misalnya, 11011 = 8 + 5 + 2 + 1 = 16) dengan angka dibatasi oleh '00' maka saya pikir kita dapat menjaga representasi celah cukup pendek, tetapi perlu analisis lebih lanjut.


0

Saat menerima aliran lakukan langkah-langkah ini.

1st mengatur beberapa ukuran chunk yang masuk akal

Gagasan Kode semu:

  1. Langkah pertama adalah menemukan semua duplikat dan memasukkannya ke dalam kamus dengan jumlah dan menghapusnya.
  2. Langkah ketiga adalah menempatkan nomor yang ada dalam urutan langkah-langkah algoritmik mereka dan menempatkannya di counter kamus khusus dengan angka pertama dan langkah mereka seperti n, n + 1 ..., n + 2, 2n, 2n + 1, 2n + 2 ...
  3. Mulailah mengompres dalam beberapa rentang angka yang masuk akal seperti setiap 1000 atau 10.000 yang tersisa yang jarang muncul berulang.
  4. Hapus kompresi rentang itu jika angka ditemukan dan tambahkan ke rentang itu dan biarkan tidak terkompresi untuk sementara waktu.
  5. Kalau tidak, tambahkan saja nomor itu ke byte [chunkSize]

Lanjutkan 4 langkah pertama saat menerima aliran. Langkah terakhir adalah gagal jika Anda melebihi memori atau mulai mengeluarkan hasilnya setelah semua data dikumpulkan dengan mulai mengurutkan rentang dan memuntahkan hasilnya dalam urutan dan mengompresi mereka dalam urutan yang perlu dikompresi dan mengurutkan mereka saat Anda mendapatkan mereka.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.