1 MB RAM ile 1 milyon 8 ondalık basamaklı sayıları sıralama


727

1 MB RAM ve başka bir yerel depolama alanına sahip bir bilgisayarım var. Bir TCP bağlantısı üzerinden 1 milyon 8 basamaklı ondalık sayıları kabul etmek, sıralamak ve daha sonra sıralı listeyi başka bir TCP bağlantısı üzerinden göndermek için kullanmalıyım.

Sayı listesi atmamam gereken kopyalar içerebilir. Kod ROM yerleştirilir, bu yüzden 1 MB kodumu boyutunu çıkarmak gerekmez. Zaten Ethernet portunu sürmek ve TCP / IP bağlantılarını işlemek için kodum var ve durum verisi için 2 KB, kodun veri yazıp okuyacağı 1 KB arabellek de dahil olmak üzere 2 KB gerektirir. Bu sorunun bir çözümü var mı?

Soru ve Cevap Kaynakları:

slashdot.org

cleaton.net


45
Ehm, bir milyon kez 8 basamaklı ondalık sayı (en az 27 bit tam sayı ikili)> 1MB ram
Mr47

15
1M RAM 2 ^ 20 bayt demektir? Ve bu mimaride bir bitte kaç bit var? Ve "1 milyon 8 basamaklı ondalık sayıdaki" milyon "bir SI milyon (10 ^ 6) mı? 8 basamaklı ondalık sayı, doğal sayı <10 ^ 8, ondalık gösterimi ondalık nokta hariç 8 basamak alan rasyonel sayı veya başka bir şey nedir?

13
1 milyon 8 ondalık basamaklı sayı veya 1 milyon 8 bitlik sayı?
Patrick White

13
bana "Dr Dobb's Journal" (1998-2001 arasında bir yerde) bir makaleyi hatırlatıyor, burada yazar telefon numaralarını okurken sıralamak için bir ekleme sıralaması kullandı: ilk kez, bazen daha yavaş olduğunu fark ettim algoritması daha hızlı olabilir ...
Adrien Plisson

103
Henüz kimsenin bahsetmediği bir çözüm daha var: 2MB RAM ile donanım satın alın. Çok daha pahalı olmamalı ve sorunu çözmeyi çok, çok daha kolay hale getirecek.
Daniel Wagner

Yanıtlar:


716

Şimdiye kadar burada belirtilmeyen oldukça sinsi bir numara var. Veri depolamak için ekstra bir yolunuz olmadığını varsayarız, ancak bu kesinlikle doğru değildir.

Sorununuzun bir yolu, hiçbir koşulda kimsenin denememesi gereken şu korkunç şeyi yapmaktır: Veri depolamak için ağ trafiğini kullanın. Ve hayır, NAS demek istemiyorum.

Rakamları yalnızca birkaç bayt RAM ile aşağıdaki şekilde sıralayabilirsiniz:

  • İlk önce 2 değişken alın: COUNTERve VALUE.
  • İlk olarak tüm kayıtları 0;
  • Eğer bir tamsayı almak her zaman I, artım COUNTERve set VALUEiçin max(VALUE, I);
  • Ardından Iyönlendiriciye veri ayarlı bir ICMP yankı istek paketi gönderin. Sil Ive tekrarla.
  • Döndürülen ICMP paketini her aldığınızda, tamsayıyı çıkarır ve başka bir yankı isteğinde yeniden gönderirsiniz. Bu, tamsayıları içeren ileri ve geri hareket eden çok sayıda ICMP isteği üretir.

Bir kez COUNTERulaşıldığında 1000000, ICMP isteklerinin sürekli akışında depolanan tüm değerlere sahipsiniz ve VALUEşimdi maksimum tamsayıyı içerir. Biraz seçin threshold T >> 1000000. COUNTERSıfıra ayarlayın . Her ICMP paketi aldığınızda COUNTER, içerilen tamsayıyı Ibaşka bir yankı isteğinde artırın ve gönderin, I=VALUEbu durumda sıralanan tamsayılar için hedefe iletmeyin. Bir kez COUNTER=Teksiltme, VALUEtarafından 1, reset COUNTERsıfır ve tekrarına. Bir kez VALUEulaşır sıfır Eğer hedefe büyükten en küçüğe doğru sırayla tüm tamsayılar bulaşan gerekirdi ve sadece iki inatçı değişkenler (ve küçük tutarda geçici değerler için gerekir) için RAM 47 hakkında bitlerini kullandık.

Bunun korkunç olduğunu biliyorum ve her türlü pratik sorun olabileceğini biliyorum, ama bazılarınızın güldüğünü veya en azından sizi dehşete düşüreceğini düşündüm.


27
Yani temelde ağ gecikmesinden faydalanıyor ve yönlendiricinizi bir tür que haline mi dönüştürüyorsunuz?
Eric R.

335
Bu çözüm sadece kutunun dışında değil; evde kutusunu unutmuş gibi görünüyor: D
Vladislav Zorov

28
Harika cevap ... Bu cevapları çok seviyorum çünkü gerçekten bir çözümün bir soruna ne kadar çeşitli olabileceğini gösteriyorlar
StackOverflowed

33
ICMP güvenilir değil.
sleeplessnerd

13
@MDMarra: En üstte şunu fark edeceksiniz: "Sorununuzun bir yolu, hiçbir koşulda kimsenin denememesi gereken şu korkunç şeyi yapmaktır". Bunu söylememin bir nedeni vardı.
Joe Fitzsimons

423

İşte sorunu çözen bazı çalışan C ++ kodu .

Bellek kısıtlamalarının karşılandığının kanıtı:

Editör: Bu yayında veya bloglarında yazarın sunduğu maksimum bellek gereksinimlerinin kanıtı yoktur. Bir değeri kodlamak için gereken bit sayısı önceden kodlanan değerlere bağlı olduğundan, bu tür bir kanıt muhtemelen önemsiz değildir. Yazar, ampirik olarak rastlayabileceği en büyük kodlanmış boyutun olduğunu 1011732ve arabellek boyutunu 1013000keyfi olarak seçtiğini belirtiyor .

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

    ...

Bu iki dizi birlikte 1045000 bayt depolama alanı alır. Bu, kalan değişkenler ve yığın alanı için 1048576 - 1045000 - 2 × 1024 = 1528 bayt bırakır.

Xeon W3520'imde yaklaşık 23 saniye içinde çalışıyor. Aşağıdaki Python komut dosyasını kullanarak programın çalıştığını doğrulayarak program adını varsayabilirsiniz 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!')

Algoritmanın ayrıntılı bir açıklaması aşağıdaki yazı dizisinde bulunabilir:


8
@preshing evet bunun ayrıntılı bir açıklamasını istiyoruz.
T Suds

25
Bence en önemli gözlem 8 basamaklı bir sayının yaklaşık 26.6 bit bilgi ve bir milyonunun 19.9 bit olmasıdır. Listeyi delta sıkıştırırsanız (bitişik değerlerin farklarını saklarsanız) farklar 0 (0 bit) ile 99999999 (26,6 bit) arasında değişir, ancak her çift arasında maksimum deltaya sahip olamazsınız . En kötü durum aslında bir milyon eşit dağılımlı değer olmalıdır ve delta başına (26.6-19.9) veya delta başına yaklaşık 6.7 bit gerektirir. 6 milyon bitlik bir milyon değeri depolamak 1M'ye kolayca sığar. Delta sıkıştırma sürekli birleştirme sıralaması gerektirir, böylece neredeyse ücretsiz olarak elde edersiniz.
Ben Jackson

4
tatlı çözüm. hepiniz açıklama için blogunu ziyaret etmeliyiz preshing.com/20121025/…
Davec

9
@BenJackson: Matematiğinizde bir yerde bir hata var. Saklanması 8.094 x 10 ^ 6 bit (yani bir megabayt altındaki bir saç) alan 2.265 x 10 ^ 2436455 benzersiz olası çıkış (sıralı 10 ^ 6 8 basamaklı tamsayılar kümesi) vardır. Hiçbir akıllı şema bu bilgi teorik sınırının ötesine kayıpsız sıkışamaz. Açıklamanız çok daha az alana ihtiyacınız olduğunu ve dolayısıyla yanlış olduğunu ima ediyor. Aslında, yukarıdaki çözümdeki "dairesel" sadece gerekli bilgileri tutacak kadar büyüktür, bu nedenle presleme bunu dikkate almıştır, ancak eksiksiniz.
Joe Fitzsimons

5
@JoeFitzsimons: Ben özyineleme (0..m gelen n sayıların benzersiz sıralanmış kümeleri (n+m)!/(n!m!)) işe yaramadı bu yüzden doğru olmalı. Muhtemelen benim tahminim b bit bir delta saklamak için bit bit alır - açıkça 0 0 delta saklamak için 0 bit almaz.
Ben Jackson

371

Lütfen ilk doğru cevaba veya daha sonra aritmetik kodlamaya sahip cevaba bakınız . Aşağıda biraz eğlence bulabilirsiniz, ancak% 100 kurşun geçirmez bir çözüm bulamazsınız.

Bu oldukça ilginç bir iş ve işte başka bir çözüm. Umarım biri sonucu faydalı bulur (ya da en azından ilginçtir).

Aşama 1: İlk veri yapısı, kaba sıkıştırma yaklaşımı, temel sonuçlar

Basit bir matematik yapalım: 10 ^ 6 8 basamaklı ondalık sayıları depolamak için başlangıçta 1M (1048576 bayt) RAM var. [0, 99999999]. Bu nedenle, bir sayı 27 bit saklamak gerekir (işaretsiz sayıların kullanılacağı varsayılarak). Bu nedenle, ham bir akışı depolamak için ~ 3.5M RAM gerekecektir. Birisi zaten bunun mümkün görünmediğini söyledi, ancak girdi "yeterince iyi" ise görevin çözülebileceğini söyleyebilirim. Temel olarak, girdi verilerini sıkıştırma faktörü 0.29 veya daha yüksek bir değerle sıkıştırmak ve uygun bir şekilde sıralama yapmaktır.

Önce sıkıştırma sorununu çözelim. Halihazırda mevcut bazı ilgili testler vardır:

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

"Çeşitli sıkıştırma biçimlerini kullanarak bir milyon ardışık tamsayıyı sıkıştırmak için bir test yaptım. Sonuçlar şu şekildedir:"

None     4000027
Deflate  2006803
Filtered 1391833
BZip2    427067
Lzma     255040

LZMA ( Lempel – Ziv – Markov zincir algoritması ) ile devam etmek için iyi bir seçim gibi görünüyor . Basit bir PoC hazırladım, ancak hala vurgulanması gereken bazı ayrıntılar var:

  1. Bellek sınırlıdır, bu nedenle fikir sayıları sunmak ve sıkıştırılmış kovaları (dinamik boyut) geçici depolama olarak kullanmaktır
  2. Öngörülen verilerle daha iyi bir sıkıştırma faktörü elde etmek daha kolaydır, bu nedenle her bir kova için statik bir tampon vardır (tampondaki sayılar LZMA'dan önce sıralanır)
  3. Her kova belirli bir aralık içerir, bu nedenle son sıralama her kova için ayrı ayrı yapılabilir
  4. Kova boyutu doğru şekilde ayarlanabilir, bu nedenle depolanan verileri açmak ve her bir kova için ayrı ayrı son sıralamayı yapmak için yeterli bellek olacaktır

Bellek içi sıralama

Ekli kodun bir POC olduğunu , nihai bir çözüm olarak kullanılamayacağını lütfen unutmayın, sadece raporlanan sayıları en uygun şekilde (muhtemelen sıkıştırılmış) depolamak için birkaç küçük tampon kullanma fikrini gösterir. LZMA nihai bir çözüm olarak önerilmemektedir. Bu PoC'ye bir sıkıştırma uygulamak için mümkün olan en hızlı yol olarak kullanılır.

Aşağıdaki PoC koduna bakın ( LZMA-Java'yı derlemek için sadece bir demoya dikkat edin ):

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

Rasgele sayılarla aşağıdakileri üretir:

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

Basit bir artan dizi için (bir kova kullanılır):

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

DÜZENLE

Sonuç:

  1. Doğayı kandırmaya çalışma
  2. Daha az bellek alanı ile daha basit sıkıştırma kullanın
  3. Bazı ek ipuçlarına gerçekten ihtiyaç vardır. Ortak kurşun geçirmez çözüm mümkün görünmüyor.

Aşama 2: Geliştirilmiş sıkıştırma, nihai sonuç

Önceki bölümde daha önce bahsedildiği gibi, herhangi bir uygun sıkıştırma tekniği kullanılabilir. Öyleyse daha basit ve daha iyi (mümkünse) yaklaşım lehine LZMA'dan kurtulalım. Aritmetik kodlama , Radix ağacı vb.Dahil olmak üzere birçok iyi çözüm var .

Her neyse, basit ama kullanışlı kodlama şeması, başka bir harici kütüphaneden daha açıklayıcı olacak ve bazı şık algoritmalar sağlayacaktır. Gerçek çözüm oldukça basittir: kısmen sıralanmış verilere sahip kovalar olduğundan, sayılar yerine deltalar kullanılabilir.

kodlama şeması

Rastgele giriş testi biraz daha iyi sonuçlar verir:

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

Basit kod

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

Lütfen bu yaklaşıma dikkat edin:

  1. çok fazla bellek tüketmiyor
  2. akışlarla çalışır
  3. o kadar kötü sonuçlar vermez

Tam kodu burada bulabilirsiniz , BinaryInput ve BinaryOutput uygulamaları burada bulunabilir

Final sonucu

Son sonuç yok :) Bazen bir seviye yukarı çıkmak ve görevi meta seviye bakış açısıyla gözden geçirmek gerçekten iyi bir fikirdir .

Bu görevle biraz zaman geçirmek eğlenceliydi. BTW, aşağıda birçok ilginç cevap var. İlginiz ve mutlu kodlamalarınız için teşekkür ederiz.


17
Inkscape kullandım . Bu arada harika bir araç. Bu şema kaynağını örnek olarak kullanabilirsiniz .
Renat Gilmanov

21
Elbette LZMA bu durumda çok fazla hafıza gerektiriyor mu? Bir algoritma olarak, bellekte verimli olmak yerine depolanması veya iletilmesi gereken veri miktarını en aza indirmek amaçlanmıştır.
Mjiig

67
Bu saçmalık ... 1 milyon rastgele 27 bit tamsayı alın, sıralayın, 7zip, xz, istediğiniz LZMA ile sıkıştırın. Sonuç 1 MB üzerinde. Üstteki öncül, sıralı sayıların sıkıştırılmasıdır. Bunun 0 bit ile delta kodlaması sadece sayı olacaktır, örneğin 1000000 (4 bayt cinsinden). Sıralı ve yinelemelerle (boşluk yok), 1000000 ve 1000000 bit sayısı = 128KB, yinelenen sayı için 0 ve bir sonraki işareti işaretlemek için 1. Rastgele boşluklarınız olduğunda, hatta küçük bile olsa, LZMA çok saçma. Bunun için tasarlanmamıştır.
alecco

30
Bu aslında işe yaramaz. Bir simülasyon çalıştırdım ve sıkıştırılmış veriler 1MB'den (yaklaşık 1.5MB) fazla olsa da, verileri sıkıştırmak için hala 100MB'ın üzerinde RAM kullanıyor. Sıkıştırılmış tamsayılar bile çalışma zamanı RAM kullanımından bahsetmemek bile soruna uymuyor. Ödül vermek, stackoverflow'daki en büyük hatam.
Favori Onwuemene

10
Bu cevap çok beğenildi, çünkü birçok programcı kanıtlanmış kod yerine parlak fikirleri seviyor. Bu fikir işe yararsa, sadece bunu yapabilen bir tane olduğunu iddia eden bir iddiadan ziyade seçilmiş ve kanıtlanmış gerçek bir sıkıştırma algoritması görürsünüz ... .
Olathe

185

Bir çözüm ancak 1 megabayt ile 1 milyon bayt arasındaki farktan dolayı mümkündür. 8093729.5 gücüne yaklaşık 2 tane var, kopyalara izin verilen 1 milyon 8 haneli sayıları seçmek ve önemsiz sipariş etmek, böylece sadece 1 milyon bayt RAM'e sahip bir makinenin tüm olasılıkları temsil etmek için yeterli durumu yok. Ancak 1M (TCP / IP için daha az 2k) 1022 * 1024 * 8 = 8372224 bittir, bu nedenle bir çözüm mümkündür.

Bölüm 1, ilk çözüm

Bu yaklaşım 1M'den biraz daha fazlasına ihtiyaç duyuyor, daha sonra 1M'ye sığacak şekilde rafine edeceğim.

7 bitlik sayıların alt listelerinden oluşan bir dizi olarak 0 ile 99999999 aralığındaki sıralı bir sayı listesi saklayacağım. İlk alt liste 0 ila 127 arasındaki sayıları, ikinci alt liste 128 ila 255 arasındaki sayıları içerir. 100000000/128 tam olarak 781250'dir, bu nedenle bu tür alt listelere ihtiyaç olacaktır.

Her alt liste 2 bitlik bir alt liste başlığından sonra bir alt liste gövdesinden oluşur. Alt liste gövdesi, alt liste girişi başına 7 bit alır. Alt listelerin tümü bir araya getirilir ve biçim, bir alt listenin nerede biteceğini ve diğerinin nerede başladığını söylemeyi mümkün kılar. Tamamen doldurulmuş bir liste için gereken toplam depolama alanı 2 * 781250 + 7 * 1000000 = 8562500 bit'tir, bu yaklaşık 1.021 M bayttır.

4 olası alt liste başlık değeri:

00 Boş alt liste, hiçbir şey takip etmiyor.

01 Singleton, alt listede sadece bir giriş var ve sonraki 7 bit bunu tutuyor.

10 Alt liste en az 2 ayrı numara içerir. Girişler, son girişin birinciden küçük veya ona eşit olması dışında, azalmayan bir sırada saklanır. Bu, alt listenin sonunun tanımlanmasını sağlar. Örneğin, 2,4,6 sayıları (4,6,2) olarak depolanır. 2,2,3,4,4 sayıları (2,3,4,4,2) olarak depolanır.

11 Alt liste, tek bir sayının 2 veya daha fazla tekrarını içerir. Sonraki 7 bit sayıyı verir. Sonra sıfır veya daha fazla 7-bit girişi 1 değeri, ardından 7-bit girişi 0 değeri ile gelir. Alt liste gövdesinin uzunluğu tekrar sayısını belirler. Örneğin, 12,12 sayıları (12,0), 12,12,12 sayıları (12,1,0), 12,12,12,12 (12,1) , 1,0) vb.

Boş bir listeyle başlıyorum, bir grup sayıyı okudum ve 32 bit tamsayı olarak saklıyorum, yeni sayıları yerinde sıralıyorum (muhtemelen heapsort kullanarak) ve sonra bunları yeni bir kompakt sıralı listeye birleştiriyorum. Okunacak başka sayı kalmayıncaya kadar tekrarlayın, ardından çıktıyı oluşturmak için kompakt listeyi bir kez daha yürütün.

Aşağıdaki satır, liste birleştirme işleminin başlamasından hemen önceki belleği temsil eder. "O" lar, sıralanan 32 bit tamsayıları barındıran bölgedir. "X" ler eski kompakt listenin bulunduğu bölgedir. "=" İşaretleri, kompakt listenin genişletme odasıdır, "O" larda her tamsayı için 7 bittir. "Z" ler diğer rastgele yüklerdir.

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

Birleştirme yordamı en soldaki "O" ve en soldaki "X" de okumaya başlar ve en soldaki "=" ile yazmaya başlar. Yazma işaretçisi, tüm yeni tamsayılar birleştirilene kadar küçük liste okuma işaretçisini yakalamaz, çünkü her iki işaretçi de her bir alt liste için 2 bit, eski kompakt listedeki her giriş için 7 bit ilerler ve Yeni numaralar için 7 bitlik girişler.

Bölüm 2, 1M içine tıka basa dolu

Yukarıdaki çözümü 1M'ye sıkıştırmak için, kompakt liste formatını biraz daha kompakt hale getirmem gerekiyor. Sadece 3 farklı olası alt liste başlık değeri olacak şekilde alt liste türlerinden birini kaldıracağım. Sonra alt liste başlık değerleri olarak "00", "01" ve "1" kullanabilir ve birkaç bit kaydedebilirsiniz. Alt liste türleri:

Boş bir alt liste, hiçbir şey gelmez.

B Singleton, alt listede yalnızca bir giriş vardır ve sonraki 7 bit tarafından tutulur.

C Alt liste en az 2 ayrı numara içerir. Girişler, son girişin birinciden küçük veya ona eşit olması dışında, azalmayan bir sırada saklanır. Bu, alt listenin sonunun tanımlanmasını sağlar. Örneğin, 2,4,6 sayıları (4,6,2) olarak depolanır. 2,2,3,4,4 sayıları (2,3,4,4,2) olarak depolanır.

D Alt liste, tek bir sayının 2 veya daha fazla tekrarından oluşur.

Benim 3 alt liste başlık değerleri "A", "B" ve "C" olacaktır, bu yüzden D-tipi alt listeleri temsil etmek için bir yol gerekir.

Diyelim ki C tipi alt liste başlığının ardından "C [17] [101] [58]" gibi 3 giriş var. Üçüncü giriş ikinciden daha az, ancak ilkinden daha fazla olduğundan, bu, yukarıda açıklandığı gibi geçerli bir C tipi alt listenin parçası olamaz. Bu tür bir yapıyı D-tipi alt listeyi temsil etmek için kullanabilirim. Bit terimleriyle, her yerde "C {00 ?????} {1 ??????} {01 ?????}" imkansız bir C tipi alt listedir. Bunu, tek bir sayının 3 veya daha fazla tekrarından oluşan bir alt listeyi temsil etmek için kullanacağım. İlk iki 7-bit kelime sayıyı kodlar (aşağıdaki "N" bitleri) ve ardından sıfır veya daha fazla {0100001} kelime ve ardından {0100000} kelime gelir.

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

Bu, tek bir sayının tam olarak 2 tekrarını içeren listeler bırakır. Başka bir imkansız C tipi alt liste modeline sahip olanları temsil edeceğim: "C {0 ??????} {11 ?????} {10 ?????}". İlk 2 kelimede sayının 7 biti için bolca yer var, ancak bu desen temsil ettiği alt listeden daha uzun, bu da işleri biraz daha karmaşık hale getiriyor. Sondaki beş soru işareti kalıbın bir parçası olarak değerlendirilemez, bu yüzden: "C {0NNNNNN} {11N ????} 10", kalıbım olarak tekrarlanacak sayı ile "N "s. Bu 2 bit çok uzun.

2 bit ödünç almam ve bunları bu kalıpta kullanılmayan 4 bitten geri ödemem gerekecek. Okurken, "C {0NNNNNN} {11N00AB} 10" ile karşılaştığınızda, "N" lerde sayının 2 örneğini çıktılayın, A ve B bitleri ile sondaki "10" un üzerine yazın ve okuma işaretçisini 2 geri sarar bit. Her bir küçük liste sadece bir kez yürüdüğü için yıkıcı okumalar bu algoritma için uygundur.

Tek bir sayının 2 tekrarından oluşan bir alt liste yazarken, "C {0NNNNNN} 11N00" yazın ve ödünç verilen bit sayacını 2 olarak ayarlayın. Ödünç verilen bit sayacının sıfır olmadığı her yazmada, yazılan her bit için azaltılır ve Sayaç sıfıra ulaştığında "10" yazılır. Böylece, yazılan sonraki 2 bit A ve B yuvalarına girecek ve daha sonra "10" sonuna düşecektir.

"00", "01" ve "1" ile temsil edilen 3 alt liste başlık değeriyle, en popüler alt liste türüne "1" atayabilirim. Alt liste başlık değerlerini alt liste türlerine eşlemek için küçük bir tabloya ihtiyacım olacak ve her alt liste türü için bir oluşum sayacına ihtiyacım olacak, böylece en iyi alt liste başlık eşlemesinin ne olduğunu biliyorum.

Tamamen doldurulmuş bir kompakt listenin en kötü durumu, tüm alt liste türleri eşit derecede popüler olduğunda oluşur. Bu durumda, her 3 alt liste başlığı için 1 bit kaydederim, böylece liste boyutu 2 * 781250 + 7 * 1000000 - 781250/3 = 8302083.3 bit olur. 32 bit sözcük sınırına yuvarlama, 8302112 bit veya 1037764 bayt.

1M eksi TCP / IP durumu ve arabellekler için 2k, 1022 * 1024 = 1046528 bayt, bana oynamak için 8764 bayt bırakıyor.

Peki, alt liste başlık eşlemesini değiştirme işlemi ne olacak? Aşağıdaki bellek haritasında "Z" rastgele yük, "=" boş alan, "X" kompakt listedir.

ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

En soldaki "X" de okumaya başlayın ve en soldaki "=" de yazmaya başlayın ve sağa doğru çalışın. Tamamlandığında, kompakt liste biraz daha kısa olacak ve belleğin yanlış ucunda olacak:

ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======

O zaman sağa doğru çevirmem gerekecek:

ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Üstbilgi eşleme değiştirme işleminde, alt liste üstbilgilerinin 1 / 3'üne kadar 1 bit ile 2 bit arasında değişir. En kötü durumda, bunlar listenin başında olacak, bu yüzden başlamadan önce en az 781250/3 bit ücretsiz depolama alanına ihtiyacım olacak, bu da beni kompakt listenin önceki sürümünün bellek gereksinimlerine geri götürüyor: (

Bu sorunu çözmek için, 781250 alt listelerini her biri 78125 alt listeden oluşan 10 alt liste grubuna ayıracağım. Her grubun kendi bağımsız alt liste başlık eşlemesi vardır. Gruplar için A'dan J'ye kadar olan harfleri kullanarak:

ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ

Her bir alt liste grubu, bir alt liste başlık eşlemesi değişikliği sırasında küçülür veya aynı kalır:

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

Bir eşleme değişikliği sırasında bir alt liste grubunun en kötü durum geçici genişlemesi 4k altında 78125/3 = 26042 bittir. Tam dolu bir kompakt liste için 4k artı 1037764 bayta izin verirsem, bu beni bellek haritasındaki "Z" ler için 8764 - 4096 = 4668 bayt bırakır.

Bu, 10 alt liste üstbilgi eşleme tablosu, 30 alt liste üstbilgi oluşum sayısı ve ihtiyaç duyacağım diğer birkaç sayaç, işaretçi ve küçük arabellek ve fark etmeden kullandığım, işlev çağrısı dönüş adresleri için yığın alanı ve yerel değişkenler.

Bölüm 3, çalıştırmak ne kadar sürer?

Boş bir küçük liste ile 1 bitlik liste başlığı boş bir alt liste için kullanılır ve listenin başlangıç ​​boyutu 781250 bit olur. En kötü durumda, liste eklenen her sayı için 8 bit büyür, bu nedenle liste tamponunun üstüne yerleştirilecek 32 bitlik sayıların her biri için 32 + 8 = 40 bit boş alan gerekir ve ardından sıralanır ve birleştirilir. En kötü durumda, alt liste başlık eşlemesinin değiştirilmesi 2 * 781250 + 7 * giriş - 781250/3 bit alan kullanımı ile sonuçlanır.

Listede en az 800000 sayı olduğunda, her beşinci birleştirme işleminden sonra alt liste üstbilgisi eşlemesini değiştirme politikasıyla, en kötü durum toplamda yaklaşık 30 milyon kompakt liste okuma ve yazma etkinliği içerir.

Kaynak:

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


15
Daha iyi bir çözümün mümkün olduğunu düşünmüyorum (sıkıştırılamaz değerlerle çalışmamız gerektiğinde). Ancak bu biraz geliştirilebilir. Alt liste başlıklarını 1 bit ve 2 bit gösterimleri arasında değiştirmek gerekli değildir. Bunun yerine , algoritmayı basitleştiren ve ayrıca başlık başına en kötü durumdaki bit sayısını 1.67'den 1.58'e düşüren aritmetik kodlamayı kullanabilirsiniz . Kompakt listeyi bellekte taşımanıza gerek yok; bunun yerine dairesel tampon kullanın ve yalnızca işaretçileri değiştirin.
Evgeny Kluev

5
Sonunda, bu bir röportaj sorusu muydu?
mlvljr

2
Diğer olası iyileştirmeler 128 elemanlı alt listeler yerine 100 elemanlı alt listeler kullanmaktır (çünkü alt liste sayısı veri kümesindeki eleman sayısına eşit olduğunda en kompakt temsili elde ederiz). Aritmetik kodlamayla kodlanacak alt listenin her bir değeri (her değer için 1/100 eşit frekansta). Bu, alt liste başlıklarının sıkıştırılmasından çok daha az olan yaklaşık 10000 bit kaydedebilir.
Evgeny Kluev

C durumunda, "Girdiler azalmayan sırada depolanır, ancak son giriş birinciden küçük veya ona eşittir" deyin. O zaman 2,2,2,3,5'i nasıl kodlarsınız? {2,2,3,5,2} sadece 2,2'ye benziyor
Rollie

1
Eşleme karmaşık geçiş olmadan alt başlık başına aynı sıkıştırma oranı 1,67 bit ile alt liste başlık kodlaması daha basit bir çözüm mümkündür. Çünkü birbirini takip eden her 3 alt başlığı bir araya getirebilirsiniz, çünkü bu 5 bit olarak kolayca kodlanabilir 3 * 3 * 3 = 27 < 32. Onları birleştiriyorsunuz combined_subheader = subheader1 + 3 * subheader2 + 9 * subheader3.
hynekcer

57

Gilmanov'un yanıtı varsayımlarında çok yanlış. Bir milyon ardışık tamsayının anlamsız ölçüsüne dayanarak spekülasyon yapmaya başlar . Yani boşluk yok. Bu rastgele boşluklar, küçük de olsa, onu gerçekten kötü bir fikir haline getiriyor.

Kendin dene. 1 milyon rastgele 27 bit tamsayı alın, sıralayın, 7-Zip , xz, istediğiniz LZMA ile sıkıştırın . Sonuç 1,5 MB'ın üzerindedir. Üstteki öncül, sıralı sayıların sıkıştırılmasıdır. Hatta delta kodlama bunun ise üzerinde 1.1 MB . Sıkıştırma için 100 MB'ın üzerinde RAM kullandığını unutmayın. Sıkıştırılmış tamsayılar bile soruna uymaz ve çalışma zamanı RAM kullanımını boşverir .

İnsanların güzel grafikleri ve rasyonalizasyonu nasıl yükselttiği beni üzüyor.

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

}

Şimdi LZMA ile ints.bin sıkıştırın ...

$ 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
Sözlük tabanlı sıkıştırma içeren herhangi algoritma sadece engelli ötesinde, ben birkaç özel olanları kodlu ve tüm aldıkları oldukça (o kaynaklar üzerinde ekstra aç olarak ve java hiçbir HashMap) sadece kendi denetim tablolarını yerleştirmek için bellek biraz. En yakın çözüm, değişken bit uzunluğu w / ile kodlamak ve sevmediğiniz TCP paketlerini geri sıçramak olacaktır. Akran yeniden iletecek, yine de en iyi ihtimalle boğucu.
bestsss

@bestsss evet! son devam eden yanıtımı kontrol et. Bence bu mümkün olabilir .
alecco

3
Üzgünüm ama bu aslında soruyu cevaplamıyor gibi görünüyor .
n611x007

@ naxa evet cevap veriyor: orijinal sorunun parametreleri içinde yapılamaz. Sadece sayıların dağılımı çok düşük entropiye sahipse yapılabilir.
alecco

1
Tüm bu yanıt, standart sıkıştırma rutinlerinin 1MB altındaki verileri sıkıştırmada zorluk yaşadığını göstermektedir. Verileri 1 MB'den daha azını gerektirecek şekilde sıkıştırabilen bir kodlama şeması olabilir veya olmayabilir, ancak bu cevap, verileri bu kadar sıkıştıracak hiçbir kodlama şemasının olmadığını kanıtlamaz.
Itsme2003

41

Bunu düşünmenin bir yolunun kombinatorik bakış açısından olduğunu düşünüyorum: sıralı sayı düzenlerinin kaç olası kombinasyonu var? 0,0,0, ...., 0 kombinasyonunu 0 ve 0,0,0, ..., 1 kodunu 1 ve 99999999, 99999999, ... 99999999 N kodunu verirsek, N nedir? Başka bir deyişle, sonuç alanı ne kadar büyük?

Bunu düşünmenin bir yolu, bunun bir N x M ızgarasında mon = N = 1,000,000 ve M = 100,000,000 olan monotonik yolların sayısını bulma sorununun bir sonucu olduğunu fark etmektir. Başka bir deyişle, 1.000.000 genişliğinde ve 100.000.000 yüksekliğinde bir ızgaranız varsa, sol alttan sağ üste doğru en kısa yol var mı? En kısa yollar elbette sadece sağa veya yukarı hareket etmenizi gerektirir (aşağı veya sola hareket ederseniz, daha önce başarılmış ilerlemeyi geri alırsınız). Bunun sayı sıralama sorunumuzun bir sonucu olduğunu görmek için aşağıdakilere dikkat edin:

Yolumuzdaki herhangi bir yatay bacağın siparişimizde bir sayı olarak hayal edebilirsiniz, burada bacağın Y konumu değeri temsil eder.

resim açıklamasını buraya girin

Eğer yol basitçe sonuna kadar sağa hareket ederse, o zaman en üste kadar atlar, bu 0,0,0, ..., 0 düzenine eşdeğerdir. bunun yerine en üste kadar atlayarak başlar ve sonra 1.000.000 kez sağa doğru hareket ederse, bu 99999999,99999999, ..., 99999999'a eşdeğerdir. Sağa bir kez, sonra bir kez yukarı, sonra sağa doğru hareket eden bir yol , bir kez yukarı, vb sonuna kadar (daha sonra mutlaka en üste kadar atlar), 0,1,2,3, ..., 999999'a eşdeğerdir.

Neyse ki bizim için bu sorun zaten çözüldü, böyle bir ızgara (N + M) Seçme (M) yollarına sahiptir:

(1.000.000 + 100.000.000) (100.000.000) seçin ~ = 2.27 * 10 ^ 2436455

N böylece 2.27 * 10 ^ 2436455'e eşittir ve bu nedenle 0 kodu 0,0,0, ..., 0'ı ve 2,27 * 10 ^ 2436455 kodunu temsil eder ve bazı değişiklikler 99999999,99999999, ..., 99999999'u temsil eder.

0 ile 2.27 * 10 ^ 2436455 arasındaki tüm sayıları saklamak için lg2 (2.27 * 10 ^ 2436455) = 8.0937 * 10 ^ 6 bite ihtiyacınız vardır.

1 megabayt = 8388608 bit> 8093700 bit

Sonuçta en azından sonucu saklamak için yeterli alanımız var gibi görünüyor! Şimdi elbette ilginç bit, sayılar akışı olarak sıralamayı yapıyor. Buna en iyi yaklaşımın verildiğinden emin değilsiniz, kalan 294908 bitimiz var. İlginç bir teknik her noktada bu tüm sipariş olduğunu varsayalım, bu sipariş için kodu bulmak ve sonra geri dönüyor ve önceki kodu güncelleyerek yeni bir sayı almak olacağını hayal ediyorum. El dalga el dalga.


Bu gerçekten çok fazla el sallıyor. Bir yandan, teorik olarak bu çözümdür, çünkü sadece büyük ama yine de sonlu durumlu bir makine yazabiliriz; Öte yandan, bu büyük durum makinesinin talimat göstergesinin boyutu bir megabayttan fazla olabilir ve bu da başlatıcı değildir. Verilen sorunu gerçekten çözmek için bundan biraz daha fazla düşünülmesi gerekiyor. Sadece tüm durumları değil, belirli bir sonraki giriş numarasında ne yapılacağını hesaplamak için gereken tüm geçiş durumlarını da temsil etmeliyiz.
Daniel Wagner

4
Bence diğer cevaplar el sallama konusunda daha incelikli. Artık sonuç alanının boyutunu bildiğimiz göz önüne alındığında, kesinlikle ne kadar alana ihtiyacımız olduğunu biliyoruz. Başka hiçbir cevap mümkün olan her cevabı 8093700 bit'ten daha küçük bir şeyde saklayamayacaktır, çünkü kaç tane son durum olabilir. Sıkıştırma (son durum) yapmak en iyi ihtimalle bazen alanı azaltabilir, ancak her zaman tam alanı gerektiren bazı yanıtlar olacaktır (hiçbir sıkıştırma algoritması her girişi sıkıştıramaz).
Francisco Ryan Tolmasky

Birkaç başka cevap zaten zaten zor alt sınırdan bahsetti (örneğin orijinal soru-sorucunun cevabının ikinci cümlesi), bu yüzden bu cevabın gestalt'a ne eklediğini göremiyorum.
Daniel Wagner

Ham akışı depolamak için 3.5M'den mi bahsediyorsunuz? (Değilse, özür dilerim ve bu yanıtı görmezden gelin). Eğer öyleyse, bu tamamen ilgisiz bir alt sınırdır. Alt sınırım, sonucun ne kadar yer kaplayacağı, alt sınır, girdilerin saklanması gerekiyorsa ne kadar alan alacağıdır - sorunun TCP bağlantısından gelen bir akış olarak ifade edildiği göz önüne alındığında aslında gerekip gerekmediği belli değil, her seferinde bir sayı okuyor ve durumunuzu güncelliyor olabilirsiniz, bu nedenle 3.5M'ye ihtiyaç duymayabilirsiniz - her iki durumda da, 3.5 bu hesaplamaya diktir.
Francisco Ryan Tolmasky

"Gücüne yaklaşık 8093729.5 2 yinelenen izin verilen 1 milyon 8 basamaklı sayı seçmek ve önemsiz sipariş etmek için farklı yollar var" <- orijinal soru soranın cevabından. Ne hakkında konuştuğum konusunda nasıl daha net olacağımı bilmiyorum. Son cümlemde bu cümleye özellikle değindim.
Daniel Wagner

20

Buradaki önerilerim Dan'ın çözümüne çok şey borçlu

Öncelikle, çözümün tüm olası giriş listelerini işlemesi gerektiğini varsayıyorum . Bence popüler cevaplar bu varsayımı yapmaz (IMO büyük bir hatadır).

Kayıpsız sıkıştırma biçiminin tüm girdilerin boyutunu azaltmayacağı bilinmektedir.

Tüm popüler cevaplar, ekstra alan sağlayacak kadar etkili sıkıştırma uygulayabileceklerini varsayar. Aslında, kısmen tamamlanmış listelerinin bir kısmını sıkıştırılmamış formda tutacak ve sıralama işlemlerini yapmalarına izin verecek kadar büyük bir ekstra alan yığını. Bu sadece kötü bir varsayım.

Böyle bir çözüm için, sıkıştırmalarını nasıl yaptıklarını bilen herkes, bu şema için iyi sıkıştırılmayan bazı giriş verilerini tasarlayabilecektir ve "çözüm" büyük olasılıkla alanın tükenmesi nedeniyle kırılacaktır.

Bunun yerine matematiksel bir yaklaşım benimsiyorum. Olası çıktılarımız, 0..MAX aralığındaki elemanlardan oluşan LEN uzunluk listeleridir. Burada LEN 1.000.000 ve MAX'ımız 100.000.000.

Keyfi LEN ve MAX için, bu durumu kodlamak için gereken bit miktarı:

Log2 (MAX Çok Seçimli LEN)

Bu nedenle, sayılarımız için, almayı ve sıralamayı tamamladıktan sonra, sonucumuzu tüm olası çıktıları benzersiz bir şekilde ayırt edebilecek şekilde saklamak için en az Log2 (100.000.000 MC 1.000.000) bite ihtiyacımız olacak.

Bu ~ = 988kb'dir . Yani sonuçlarımızı tutacak kadar yerimiz var. Bu açıdan mümkün.

[Daha iyi örneklerin var olduğu anlamsız başıboş silindi ...]

En iyi cevap burada .

Başka bir iyi yanıt buradadır ve temel olarak listeyi bir öğeyle genişletme işlevi olarak ekleme sıralamasını kullanır (aynı anda birden fazla öğenin eklenmesine izin vermek için birkaç öğeyi ve ön sıraları arabelleğe alır, biraz zaman kazandırır). güzel bir kompakt durum kodlaması kullanır, yedi bit deltasın kovaları


Ertesi gün kendi cevabınızı tekrar okumak her zaman eğlencelidir ... Bu yüzden en iyi cevap yanlış olsa da, kabul edilen bir stackoverflow.com/a/12978097/1763801 oldukça iyidir. Temel olarak LEN-1 listesini alma ve LEN'i döndürme işlevi olarak ekleme sıralama işlevini kullanır. Verimliliği artırmak için küçük bir set öneriyorsanız, hepsini bir geçişte ekleyebileceğinizden faydalanır. Durum gösterimi, el dalgalı önerimden daha kompakt ve daha sezgisel. benim comp coğrafi düşüncelerim bollocks, bunun için üzgünüm
davec

1
Bence aritmetik biraz kapalı. Lg2 alıyorum (100999999! / (99999999! * 1000000!)) = 1011718.55
NovaDenizen

Evet teşekkürler 985kb değil 965 idi. 1024'e karşı 1000'e özensiz davrandım. Hala 35kb ile oynamaya devam ediyoruz. Cevaba matematik hesaplamasına bir bağlantı ekledim.
davec

18

Bu görevin mümkün olduğunu varsayalım. Çıktıdan hemen önce, milyonlarca sıralanmış sayının bellek içi bir temsili olacaktır. Bu tür kaç farklı temsil var? Tekrarlanan sayılar olabileceğinden, nCr'yi (select) kullanamayız, ancak çoklu seçim üzerinde çalışan çoklu seçim adı verilen bir işlem vardır .

Bu nedenle teorik olarak, sıralı numaralar listesinin aklı başında (yeterli) bir temsilini bulabilirseniz mümkün olabilir. Örneğin, çılgın bir sunum 10 MB'lık bir arama tablosu veya binlerce satırlık kod gerektirebilir.

Bununla birlikte, "1M RAM" bir milyon bayt anlamına geliyorsa, açıkça yeterli alan yoktur. % 5 daha fazla belleğin teorik olarak mümkün kıldığı gerçeği bana, temsilin ÇOK verimli olması ve aklı başında olması gerekmediğini düşündürüyor.


Bir milyon sayı (2.2e2436455) seçmenin yol sayısı (256 ^ (1024 * 988)), yani (2.0e2436445) 'e yakındır. Ergo, 1M'den yaklaşık 32 KB bellek alırsanız, sorun çözülemez. Ayrıca en az 3 KB bellek ayrıldığını unutmayın.
johnwbyrd

Bu elbette verilerin tamamen rastgele olduğunu varsayar. Bildiğimiz kadarıyla öyle, ama ben sadece söylüyorum :)
Thorarin

Bu olası durum sayısını temsil etmenin geleneksel yolu, günlük tabanını 2 alarak ve bunları temsil etmek için gereken bit sayısını bildirmektir.
NovaDenizen

@Thorarin, yup, bir "çözüm" de sadece bazı girdiler için çalışan bir nokta görmüyorum.
Dan

12

(Orijinal cevabım yanlıştı, kötü matematik için özür dilerim, molanın altına bakın.)

Buna ne dersin?

İlk 27 bit, gördüğünüz en düşük sayıyı, daha sonra görülen bir sonraki sayıya olan farkı aşağıdaki şekilde kodlar: Farkı saklamak için kullanılan bit sayısını, daha sonra farkı saklamak için 5 bit. Bu numarayı tekrar gördüğünüzü belirtmek için 00000 kullanın.

Bu, daha fazla sayı eklendikçe, sayılar arasındaki ortalama farkın azaldığı için çalışır, bu nedenle daha fazla sayı eklerken farkı saklamak için daha az bit kullanırsınız. Buna delta listesi denildiğine inanıyorum.

Düşünebileceğim en kötü durum, tüm sayılar eşit aralıklı (100'e kadar), örneğin 0'ın ilk sayı olduğunu varsayarsak:

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

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

Kurtarmaya Reddit!

Yapmanız gereken tek şey onları sıralamak olsaydı, bu sorun kolay olurdu. Gördüğünüz sayıların saklanması 122k (1 milyon bit) (0 görülüyorsa 0'ıncı bit, 2300 göründüğünde 2300'üncü bit vb.)

Sayıları okur, bit alanında saklar ve bir sayıyı tutarken bitleri kaydırırsınız.

AMA, kaç tane gördüğünüzü hatırlamanız gerekiyor. Bu şemayı bulmak için yukarıdaki alt liste cevabından ilham aldım:

Bir bit kullanmak yerine 2 veya 27 bit kullanın:

  • 00, numarayı görmediğiniz anlamına gelir.
  • 01 bir kez gördüğün anlamına geliyor
  • 1, onu gördüğünüz anlamına gelir ve sonraki 26 bit, kaç kez olduğunu gösterir.

Bence bu işe yarıyor: yinelenen yoksa 244k'lık bir listeniz var. En kötü durumda, her sayıyı iki kez görürsünüz (bir sayıyı üç kez görürseniz, listenin geri kalanını sizin için kısaltır), bu da bir kereden fazla 50.000 gördüğünüz ve 0 veya 1 kez 950.000 öğe gördüğünüz anlamına gelir.

50.000 * 27 + 950.000 * 2 = 396.7k.

Aşağıdaki kodlamayı kullanırsanız daha fazla iyileştirme yapabilirsiniz:

0, 10 sayısını görmediğiniz anlamına gelir, bir kez gördüğünüz anlamına gelir 11 saymaya nasıl devam ettiğinizdir

Bu da ortalama olarak 280,7 bin depolama alanı ile sonuçlanacaktır.

EDIT: Pazar sabahı matematik yanlıştı.

En kötü durum, 500.000 sayıyı iki kez görmemiz, böylece matematik şöyle olur:

500.000 * 27 + 500.000 * 2 = 1.77 milyon

Alternatif kodlama,

500.000 * 27 + 500.000 = 1.70 milyon

: (


1
Eh, hayır, ikinci sayı 500000 olacağı için.
jfernand

Belki ara ara ekleyin (11 rakamı 64 kata kadar gördüğünüz anlamına gelir) (sonraki 6 biti kullanarak) ve 11000000, gördüğünüz sayıları saklamak için 32 bit daha kullanmak anlamına gelir.
τεκ

10
"1 milyon bit" sayısını nereden buldunuz? 2300. bitin 2300'ün görülüp görülmediğini temsil ettiğini söylediniz. (Sanırım aslında 2301'inci demek istediniz.) Hangi bit 99,999,999 görülüp görülmediğini gösterir (en büyük 8 haneli sayı)? Muhtemelen, 100 milyonuncu bit olurdu.
user94559

Bir milyonunu ve yüz milyonunu geri aldın. Bir değer muhtemelen en fazla 1 milyon olabilir ve bir değerin oluşum sayısını temsil etmek için yalnızca 20 bite ihtiyacınız vardır. Benzer şekilde, her olası değer için bir tane olmak üzere 100.000.000 bit alana (1 milyon değil) ihtiyacınız vardır.
Tim

Uh, 27 + 1000000 * (5 + 7) = 12000027 bit = 1.43M, 427K değil.
Daniel Wagner

10

Tüm olası girdilerde bu soruna bir çözüm vardır. Hile.

  1. TCP üzerinden m değerlerini okuyun, burada m bellekte sıralanabilecek maksimum değere yakındır, belki n / 4 olabilir.
  2. 250.000 (veya benzeri) sayıları sıralayın ve çıktılarını alın.
  3. Diğer 3 çeyrek için de tekrarlayın.
  4. Alıcının işlediği sırada aldığı 4 numara listesini birleştirmesine izin verin. (Tek bir liste kullanmaktan daha yavaş değildir.)

7

Bir Radix Ağacı denerdim . Verileri bir ağaçta depolayabiliyorsanız, verileri iletmek için sıralı bir geçiş yapabilirsiniz.

Bunu 1 MB'a sığabileceğinden emin değilim, ama denemeye değer olduğunu düşünüyorum.


7

Ne tür bir bilgisayar kullanıyorsunuz? Başka bir "normal" yerel depolamaya sahip olmayabilir, ancak video RAM'i var mı? 1 megapiksel x piksel başına 32 bit (diyelim) gerekli veri girişi boyutunuza oldukça yakın.

( Düşük çözünürlük veya düşük renk derinliği ekran modunu seçtiyseniz, VRAM'ı mevcut sistem RAM'ini genişletmek için 'ödünç alabilen' eski Acorn RISC PC'nin hafızasında büyük ölçüde istiyorum !). Bu, yalnızca birkaç MB normal RAM'e sahip bir makinede oldukça kullanışlıdır.


1
Yorum yapmak ister misin, downvoter? - Ben sadece sorunun görünen çelişkilerini uzatmaya çalışıyorum (yani yaratıcı bir şekilde hile ;-)
DNA

Bilgisayar olmayabilir, hacker haberlerindeki ilgili konu bir zamanlar Google röportaj sorusuydu.
mlvljr

1
Evet - Soru düzenlenmeden önce, bunun bir röportaj sorusu olduğunu belirtmek için cevap verdim!
DNA

6

Bir radix ağacı temsili bu sorunu ele almaya yaklaşır çünkü radix ağacı "önek sıkıştırması" ndan yararlanır. Ancak bir baytta tek bir düğümü temsil edebilecek bir sayı tabanı ağacı temsilini düşünmek zordur - iki muhtemelen sınırla ilgilidir.

Ancak, verilerin nasıl temsil edildiğine bakılmaksızın, sıralandıktan sonra önek-sıkıştırılmış biçimde saklanabilir, burada 10, 11 ve 12 sayıları, 1 artışını gösteren, örneğin 001b, 001b, 001b ile temsil edilir. önceki sayıdan. Belki de, o zaman 10101b, 5, 1101001b'lik bir artışı 9, vb.'lik bir artışı temsil eder.


6

10 ^ 8 aralığında 10 ^ 6 değeri vardır, bu nedenle ortalama yüz kod noktası başına bir değer vardır. N'inci nokta ile (N + 1) arasındaki mesafeyi saklayın. Yinelenen değerlerin atlaması 0'dır. Bu, atlamanın depolamak için ortalama 7 bit'in biraz altına ihtiyacı olduğu anlamına gelir, bu nedenle bir milyonu 8 milyon bitlik depolama alanımıza mutlu bir şekilde sığacaktır.

Bu atlamaların, Huffman kodlaması ile bir bit akışına kodlanması gerekir. Ekleme, bit akımı üzerinden yineleme ve yeni değerden sonra yeniden yazma işlemidir. Zımni değerleri yineleyerek ve yazarak çıktı. Pratiklik için, muhtemelen her biri 10 ^ 4 kod noktasını (ve ortalama 100 değer) kapsayan 10 ^ 4 liste şeklinde yapılması istenmektedir.

Rastgele veriler için iyi bir Huffman ağacı, atlamaların uzunluğu üzerinde bir Poisson dağılımı (ortalama = varyans = 100) varsayarak bir priori oluşturulabilir, ancak gerçek istatistikler girişte tutulabilir ve başa çıkmak için en uygun ağacı oluşturmak için kullanılabilir patolojik vakalar.


5

1M RAM'e sahip bir bilgisayarım var ve başka yerel depolama alanım yok

Hile yapmanın başka bir yolu: bunun yerine yerel olmayan (ağa bağlı) depolama kullanabilirsiniz (sorunuz bunu engellemez) ve doğrudan disk tabanlı birleştirme (veya bellekte sıralamak için yeterli RAM) kullanabilen ağa bağlı bir hizmeti çağırabilirsiniz. sadece 1M sayılarını kabul etmeniz gerekir), zaten verilen (kuşkusuz son derece ustaca) çözümlere ihtiyaç duymadan.

Bu hile olabilir, ancak gerçek dünya sorununa bir çözüm mü arıyorsunuz, yoksa kuralların bükülmesini davet eden bir bulmacayı mı aradığınız belli değil ... ancak "orijinal" çözüm (diğerlerinin belirttiği gibi, yalnızca sıkıştırılabilir girişler için kullanılabilir).


5

Çözüm, video kodlama tekniklerini, yani ayrık kosinüs dönüşümünü birleştirmek olduğunu düşünüyorum. Dijital videoda, videonun parlaklığını veya rengini 110 112 115 116 gibi normal değerler olarak değiştirmek yerine, her biri sondan çıkarılır (çalışma uzunluğu kodlamasına benzer). 110 112 115 116 110 2 3 1 olur. 2 3 1 değerleri orijinallerinden daha az bit gerektirir.

Diyelim ki sokete ulaştıklarında giriş değerlerinin bir listesini oluşturuyoruz. Her öğede değeri değil, ondan önceki değeri saklıyoruz. Biz giderken sıralama yaparız, böylece ofsetler sadece pozitif olur. Ancak ofset, 3 bayt sığacak şekilde 8 ondalık basamak genişliğinde olabilir. Her öğe 3 bayt olamaz, bu yüzden bunları paketlememiz gerekir. Her baytın üst bitini "devam biti" olarak kullanabiliriz, bu da bir sonraki baytın sayının bir parçası olduğunu ve her baytın daha düşük 7 bitinin birleştirilmesi gerektiğini gösterir. sıfır, kopyalar için geçerlidir.

Liste doldukça, sayılar birbirine yaklaşmalıdır, yani bir sonraki değere olan mesafeyi belirlemek için ortalama olarak sadece 1 bayt kullanılır. 7 bit değer ve uygunsa 1 bit ofset, ancak "devam" değeri için 8 bit'ten daha azını gerektiren tatlı bir nokta olabilir.

Her neyse, biraz deney yaptım. Rastgele bir sayı üreteci kullanıyorum ve yaklaşık 1279000 bayta bir milyon sıralanmış 8 basamaklı ondalık sayı sığdırabilirim. Her sayı arasındaki ortalama boşluk sürekli olarak 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

Tüm numaralara sahip olmadan numaraları sıralı olarak göndermek için ağ yığını ile oynayabiliriz. 1M veri gönderirseniz, TCP / IP 1500 baytlık paketlere böler ve bunları hedefe yönlendirmek için yayınlar. Her pakete bir sıra numarası verilecektir.

Bunu elle yapabiliriz. RAM'imizi doldurmadan hemen önce elimizdekileri sıralayabilir ve listeyi hedefimize gönderebiliriz, ancak her sayının etrafında sırayla delikler bırakabiliriz. Ardından, sırayla bu delikleri kullanarak sayıların 2 / 2'sini aynı şekilde işleyin.

Uzak uçtaki ağ yığını, sonuçta elde edilen veri akışını uygulamaya teslim etmeden önce sırayla bir araya getirecektir.

Birleştirme sıralaması yapmak için ağı kullanıyor. Bu tamamen bir saldırı, ama daha önce listelenen diğer ağ kesmek ilham oldu.


4

Google'ın HN iş parçacığından (kötü) yaklaşımı. RLE tarzı sayıları depolayın.

İlk veri yapınız '99999999: 0' dır (tüm sıfırlar, herhangi bir sayı görmedim) ve sonra 3.866.344 sayısını gördüğünüzü varsayalım, böylece veri yapınız '3866343: 0,1: 1,96133654: 0' olur sayıların her zaman sıfır bit sayısı ile '1' bit sayısı arasında değişeceğini görebilirsiniz, böylece tek sayıların 0 biti ve çift sayıları 1 biti olduğunu varsayabilirsiniz. Bu olur (3866343,1,96133654)

Sorunları kopyaları kapsamıyor gibi görünüyor, ancak diyelim ki kopyalar için "0: 1" kullanıyorlar.

Büyük sorun # 1: 1M tamsayıların eklenmesi çok uzun sürecektir .

Büyük sorun # 2: Tüm düz delta kodlama çözümleri gibi, bazı dağıtımlar bu şekilde ele alınamaz. Örneğin, 0:99 mesafeli 1m tamsayılar (örneğin her biri +99). Şimdi aynı ama düşünmek rastgele mesafe içinde 0:99 aralığında . (Not: 99999999/1000000 = 99,99)

Google'ın yaklaşımı hem değersiz (yavaş) hem de yanlıştır. Ancak savunmaları için sorunları biraz farklı olabilirdi.


3

Sıralanan diziyi temsil etmek için sadece ilk eleman ve bitişik elemanlar arasındaki fark saklanabilir. Bu şekilde, en fazla 10 ^ 8 toplayabilen 10 ^ 6 öğeyi kodlamakla ilgileniyoruz. Buna D diyelim . D öğelerini kodlamak için bir Huffman kodu kullanılabilir . Huffman kodunun sözlüğü hareket halindeyken oluşturulabilir ve sıralanmış diziye her yeni öğe eklendiğinde dizi eklenebilir (ekleme sıralaması). Yeni bir öğe nedeniyle sözlük değiştiğinde tüm dizinin yeni kodlamayla eşleşmesi gerektiğini unutmayın.

Her bir benzersiz elemanın eşit sayıda olması durumunda , D' nin her bir elemanını kodlamak için ortalama bit sayısı maksimuma çıkarılır. D' deki d1 , d2 , ..., dN öğelerinin her birinin F kez göründüğünü varsayalım . Bu durumda (en kötü durumda, giriş sırasında hem 0 hem de 10 ^ 8'e sahibiz)

toplamı (1 <= ı <= K ) F . di = 10 ^ 8

nerede

toplam (1 <= i <= N ) F = 10 ^ 6 veya F = 10 ^ 6 / N ve normalleştirilmiş frekans p = F / 10 ^ = 1 / N olacaktır

Ortalama bit sayısı -log2 (1 / P ) = log2 ( N ) olacaktır. Bu koşullar altında N'yi maksimuma çıkaran bir vaka bulmalıyız . Bu, di için 0'dan başlayan ardışık sayılar veya di = i -1 olduğunda olur, bu nedenle

10 ^ 8 = toplamı (1 <= ı <= K ) F . di = toplam (1 <= i <= N ) (10 ^ 6 / N ) (i-1) = (10 ^ 6 / N ) N ( N -1) / 2

yani

N <= 201. Ve bu durumda ortalama bit sayısı log2 (201) = 7.6511'dir, yani sıralanan diziyi kaydetmek için giriş öğesi başına yaklaşık 1 bayta ihtiyacımız olacaktır. Bu genel olarak D'nin 201'den fazla unsura sahip olamayacağı anlamına gelmez . Sadece D unsurları eşit olarak dağıtılırsa, 201'den fazla benzersiz değere sahip olamayacağını söyler .


1
Sanırım bu sayı yinelenebilir.
bestsss

Yinelenen sayılar için bitişik sayılar arasındaki fark sıfır olacaktır. Herhangi bir sorun yaratmaz. Huffman kodu sıfır olmayan değerler gerektirmez.
Mohsen Nosratinia

3

TCP'nin yeniden iletim davranışından yararlanırdım.

  1. TCP bileşeninin büyük bir alma penceresi oluşturmasını sağlayın.
  2. Bir ACK göndermeden bir miktar paket alın.
    • Bazı (önek) sıkıştırılmış veri yapısı oluşturarak geçişlerde olanları işleyin
    • Artık gerekli olmayan son paket için yinelenen onay gönder / yeniden iletim zaman aşımı için bekle
    • Git 2
  3. Tüm paketler kabul edildi

Bu, kovaların veya çoklu geçişlerin bir tür faydasını varsayar.

Muhtemelen partileri / kovaları sıralayıp birleştirerek. -> sayı tabanı ağaçları

İlk% 80'i kabul etmek ve sıralamak için bu tekniği kullanın, ardından son% 20'yi okuyun, son% 20'nin en düşük sayıların ilk% 20'sine inecek sayılar içermediğini doğrulayın. Sonra% 20 en düşük sayıları gönderin, bellekten kaldırın, yeni sayıların kalan% 20'sini kabul edin ve birleştirin. **


3

İşte bu tür bir soruna genel bir çözüm:

Genel prosedür

Alınan yaklaşım aşağıdaki gibidir. Algoritma 32 bitlik tek bir arabellek üzerinde çalışır. Aşağıdaki yordamı döngü içinde gerçekleştirir:

  • Son yinelemeden sıkıştırılmış verilerle dolu bir tamponla başlıyoruz. Arabellek şöyle görünür

    |compressed sorted|empty|

  • Sıkıştırılmış ve sıkıştırılmamış olarak bu arabellekte saklanabilecek maksimum sayı miktarını hesaplayın. Sıkıştırılmış veri alanı ile başlayıp sıkıştırılmamış verilerle biten arabelleği bu iki bölüme ayırın. Arabellek benziyor

    |compressed sorted|empty|empty|

  • Sıkıştırılmamış bölümü sıralanacak numaralarla doldurun. Arabellek benziyor

    |compressed sorted|empty|uncompressed unsorted|

  • Yeni numaraları yerinde sıralama ile sıralayın. Arabellek benziyor

    |compressed sorted|empty|uncompressed sorted|

  • Sıkıştırılmış bölümdeki önceki yinelemeden zaten sıkıştırılmış verileri sağa hizalayın. Bu noktada tampon bölümlenir

    |empty|compressed sorted|uncompressed sorted|

  • Sıkıştırılmamış bölümde sıralanan verileri birleştirerek sıkıştırılmış bölümde bir akış dekompresyon yeniden sıkıştırması gerçekleştirin. Yeni sıkıştırılmış bölüm büyüdükçe eski sıkıştırılmış bölüm tüketilir. Arabellek benziyor

    |compressed sorted|empty|

Bu prosedür, tüm sayılar sıralanana kadar gerçekleştirilir.

Sıkıştırma

Bu algoritma elbette sadece gerçekten ne sıkıştırılacağını bilmeden önce yeni sıralama tamponunun son sıkıştırılmış boyutunu hesaplamak mümkün olduğunda çalışır. Bunun yanında, sıkıştırma algoritmasının gerçek sorunu çözecek kadar iyi olması gerekir.

Kullanılan yaklaşım üç adım kullanır. İlk olarak, algoritma her zaman sıralı dizileri depolayacaktır, bunun yerine ardışık girişler arasındaki farkları tamamen depolayabiliriz. Her fark [0, 99999999] aralığındadır.

Bu farklılıklar daha sonra tekli bir bit akışı olarak kodlanır. Bu akıştaki bir 1 "Akümülatöre 1 ekle, A 0" Akümülatörü giriş olarak yay ve sıfırla "anlamına gelir. Böylece N farkı N1 ve bir 0 ile temsil edilecektir.

Tüm farklılıkların toplamı, algoritmanın desteklediği maksimum değere yaklaşır ve tüm farklılıkların sayısı, algoritmaya eklenen değerlerin miktarına yaklaşır. Bu, akışın sonunda maksimum değer 1'leri ve 0 sayısını saymasını beklediğimiz anlamına gelir. Bu, akışta 0 ve 1'in beklenen olasılığını hesaplamamızı sağlar. Yani, 0 count/(count+maxval)olasılığı ve 1 olasılığı maxval/(count+maxval).

Bu olasılıkları, bu bit akımı üzerinden bir aritmetik kodlama modeli tanımlamak için kullanıyoruz. Bu aritmetik kod, optimum boşlukta tam olarak bu 1 ve 0 miktarlarını kodlayacaktır. Biz herhangi bir ara veri akımı için bu modeli tarafından kullanılan alanı hesaplayabilirsiniz: bits = encoded * log2(1 + amount / maxval) + maxval * log2(1 + maxval / amount). Algoritma için gerekli toplam alanı hesaplamak encodediçin miktara eşit olarak ayarlayın .

Gülünç miktarda tekrarlama gerektirmemek için, tampona küçük bir ek yük eklenebilir. Bu, algoritmanın en azından bu ek yüke uyan sayılar üzerinde çalışmasını sağlayacaktır, çünkü algoritmanın en büyük zaman maliyeti aritmetik kodlama sıkıştırması ve her döngü dekompresyonudur.

Bunun yanında, defter tutma verilerini saklamak ve aritmetik kodlama algoritmasının sabit nokta yaklaşımında küçük yanlışlıkları işlemek için biraz ek yük gereklidir, ancak toplamda algoritma, 1MiB alana sığabilecek bir ekstra tamponla bile sığabilir. Toplam 1043916 bayt alan için 8000 sayı.

Optimalliği

Algoritmanın (küçük) ek yükünü azaltmanın dışında, daha küçük bir sonuç elde etmek teorik olarak imkansız olmalıdır. Sadece nihai sonucun entropisini içermek için 1011717 bayt gerekli olacaktır. Verimlilik için eklenen ekstra tamponu çıkarırsak, bu algoritma nihai sonucu + ek yükü depolamak için 1011916 bayt kullandı.


2

Giriş akışı birkaç kez alınabilseydi bu çok daha kolay olurdu (bu konuda hiçbir bilgi, fikir ve zaman-performans sorunu).

Sonra ondalık değerleri sayabiliriz. Sayılan değerlerle çıktı akışını yapmak kolay olurdu. Değerleri sayarak sıkıştırın. Giriş akışında ne olacağına bağlıdır.


1

Giriş akışı birkaç kez alınabilseydi bu çok daha kolay olurdu (bu konuda hiçbir bilgi, fikir ve zaman-performans sorunu). Sonra ondalık değerleri sayabiliriz. Sayılan değerlerle çıktı akışını yapmak kolay olurdu. Değerleri sayarak sıkıştırın. Giriş akışında ne olacağına bağlıdır.


1

Sıralama burada ikincil bir sorundur. Diğerleri gibi, sadece tamsayıları saklamak zordur ve her girdi üzerinde çalışamaz , çünkü sayı başına 27 bit gerekli olacaktır.

Bunu benim almam: büyük olasılıkla küçük olacakları için sadece ardışık (sıralı) tamsayılar arasındaki farkları saklar. Daha sonra, sayının kaç bitte depolandığını kodlamak için bir sıkıştırma şeması kullanın, örneğin girdi sayısı başına 2 ek bit ile. Gibi bir şey:

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

Verilen bellek kısıtlaması dahilinde çok sayıda olası giriş listesinin saklanması mümkün olmalıdır. Maksimum girdi sayısı üzerinde çalışmasını sağlamak için sıkıştırma şemasını nasıl seçeceğimin matematiği beni aşıyor.

Buna dayanarak yeterince iyi bir tamsayı sıkıştırma şeması bulmak için girdilerinizin alan adına özgü bilgisinden yararlanabileceğinizi umuyoruz .

Oh ve sonra, veri alırken bu sıralama listesinde bir ekleme sıralama yaparsınız.


1

Şimdi sadece 1MB RAM ile 8 haneli aralıktaki tüm giriş durumlarını kapsayan gerçek bir çözümü hedefliyoruz. NOT: devam eden çalışmalar, yarın devam edecek. Sıralanan interin deltalarının aritmetik kodlaması kullanılarak, 1M sıralı ints için en kötü durum giriş başına yaklaşık 7 bit'e mal olacaktır (99999999/1000000 99 olduğundan ve log2 (99) neredeyse 7 bittir).

Ancak 7 veya 8 bit'e ulaşmak için sıralanmış 1m tamsayılara ihtiyacınız var! Daha kısa seriler daha büyük deltalara sahiptir, bu nedenle eleman başına daha fazla bit vardır.

Mümkün olduğunca çok almak ve (neredeyse) yerinde sıkıştırmak için çalışıyorum. 250K'a yakın int için ilk parti, en iyi 9 bit'e ihtiyaç duyacaktır. Sonuç 275KB alacaktır. Kalan boş hafıza ile birkaç kez tekrarlayın. Sonra sıkıştırılmış parçaları sıkıştırın-yerinde birleştirin. Bu oldukça zor , ama mümkün. Bence.

Birleştirilen listeler, tamsayı hedefi başına 7 bit'e yaklaşır. Ancak birleştirme döngüsünde kaç yineleme olacağını bilmiyorum. Belki 3.

Ancak aritmetik kodlama uygulamasının belirsizliği bunu imkansız hale getirebilir. Bu sorun hiç mümkün olsaydı, çok sıkı olurdu.

Herhangi bir gönüllü?


Aritmetik kodlama uygulanabilir. Her ardışık deltanın negatif bir binom dağılımından çekildiğini fark etmeye yardımcı olabilir.
kalabalık

1

Sadece sayılar arasındaki farkları sırayla saklamanız ve bu sıra numaralarını sıkıştırmak için bir kodlama kullanmanız gerekir. 2 ^ 23 bitimiz var. Bunu 6 bitlik parçalara böleriz ve son bitin, sayının başka bir 6 bite (5 bit artı uzanan yığın) uzanıp uzanmadığını göstermesine izin verelim.

Böylece, 000010 1 ve 000100 2'dir. 000001100000 128'dir. Şimdi, 10.000.000'a kadar bir sayı dizisindeki farklılıkları temsil eden en kötü kadroyu düşünüyoruz. 2 ^ 5'ten daha büyük 10.000.000 / 2 ^ 5, 2 ^ 10'dan daha büyük 10.000.000 / 2 ^ 10 ve 2 ^ 15'den daha büyük 10.000.000 / 2 ^ 15 fark olabilir.

Bu nedenle, dizimizi temsil etmek için kaç bit alacağını ekliyoruz. 1.000.000 * 6 + toplama (10.000.000 / 2 ^ 5) * 6 + toplama (10.000.000 / 2 ^ 10) * 6 + toplama (10.000.000 / 2 ^ 15) * 6 + toplama (10.000.000 / 2 ^ 20) * 4 = 7.935.479.

2 ^ 24 = 8388608. 8388608> 7935479 tarihinden bu yana, kolayca yeterli belleğe sahip olmalıyız. Yeni numaralar eklediğimiz zamanların toplamını saklamak için muhtemelen biraz daha belleğe ihtiyacımız olacak. Daha sonra diziye girer ve yeni numaramızı nereye ekleyeceğimizi buluruz, gerekirse bir sonraki farkı azaltır ve her şeyi sağdan sonra kaydırırız.


Ben inanıyorum analizimi burada bu şema işini yapmaz ki (ve biz beş bitten daha başka boyutunu seçemezsiniz bile) gösterir.
Daniel Wagner

@Daniel Wagner - Öbek başına eşit sayıda bit kullanmak zorunda değilsiniz ve öbek başına tam sayı bit kullanmak zorunda bile değilsiniz.
kalabalık

@crowding Somut bir teklifiniz varsa, bunu duymak isterim. =)
Daniel Wagner

@crowding Aritmetik kodlamanın ne kadar yer alacağına dair matematik yapın. Biraz ağla. O zaman daha fazla düşün.
Daniel Wagner

Daha fazla bilgi edin. Sağ ara gösterimde sembollerin tam bir koşullu dağılımı (Francisco, Strilanc gibi en basit ara gösterime sahiptir) hesaplamak kolaydır. Böylece kodlama modeli tam anlamıyla mükemmel olabilir ve entropik sınırın bir biti içine girebilir. Sonlu duyarlıklı aritmetik birkaç bit ekleyebilir.
kalabalık

1

Bu sayılar hakkında hiçbir şey bilmiyorsak, aşağıdaki kısıtlamalarla sınırlıyız:

  • tüm sayıları sıralayabilmemiz için yüklememiz gerekir,
  • sayı kümesi sıkıştırılamaz.

Bu varsayımlar geçerliyse, en az 26.575.425 bit depolama alanına (3.321.929 bayt) ihtiyacınız olacağından görevinizi yerine getirmenin bir yolu yoktur.

Bize verileriniz hakkında ne söyleyebilirsiniz?


1
Onları okur ve giderken sıralarsınız. Teorik olarak lM2 (100999999! / (99999999! * 1000000!)) 1M ayrılmaz öğeleri 100M seçilebilir kutularda saklamak için 1 MB'nin% 96,4'üne kadar çalışır.
NovaDenizen

1

Hile, bir tamsayı çoklu kümesi olan algoritma durumunu, "artış sayacı" = "+" ve "çıktı sayacı" = "!" karakter. Örneğin, {0,3,3,4} kümesi "! +++ !! +!" Ve ardından herhangi bir sayıda "+" karakteri ile temsil edilir. Çoklu seti değiştirmek için karakterleri tek seferde yalnızca sabit bir miktarda sıkıştırılmış olarak tutarak akışa alırsınız ve sıkıştırılmış biçimde geri akışından önce yerinde değişiklikler yaparsınız.

ayrıntılar

Son sette tam olarak 10 ^ 6 sayı olduğunu biliyoruz, bu yüzden en fazla 10 ^ 6 "var!" karakter. Ayrıca, serimizin 10 ^ 8 boyutunda olduğunu biliyoruz, yani en fazla 10 ^ 8 "+" karakter var. 10 ^ 8 "+" s arasında 10 ^ 6 "!" S ayarlayabileceğimiz yol sayısıdır (10^8 + 10^6) choose 10^6ve bu nedenle belirli bir düzenlemenin belirtilmesi ~ 0.965 MiB alır `veri alır. Bu sıkı bir uyum olacak.

Her bir karakteri kotamızı aşmadan bağımsız olarak ele alabiliriz. "!" Den tam olarak 100 kat daha fazla "+" karakter vardır. bağımlı olduklarını unutursak, her karakterin 100: 1 oranını "+" olarak basitleştiren karakterler. 100: 101 oran, karakter başına ~ 0.08 bite karşılık gelir , neredeyse aynı toplam ~ 0.965 MiB için (bağımlılığın göz ardı edilmesi sadece ~ 12 bitlik bir maliyete sahiptir) bu durumda !).

Bilinen önceden olasılığı bulunan bağımsız karakterleri depolamanın en basit tekniği Huffman kodlamasıdır . Pratik olmayan büyük bir ağaca ihtiyacımız olduğunu unutmayın (10 karakterlik bloklar için bir huffman ağacının blok başına ortalama maliyeti yaklaşık 2,4 bit, yaklaşık ~ 2,9 Mib'dir. 20 karakterlik bloklar için bir huffman ağacının blok başına ortalama maliyeti vardır. yaklaşık 3 bitlik, ki bu toplam ~ 1.8 MiB'dir. Muhtemelen yüzlerce büyüklükte bir blok bloğuna ihtiyacımız olacak, bu bizim ağacımızda şimdiye kadar var olan tüm bilgisayar ekipmanlarının depolayabileceğinden daha fazla düğüm anlamına geliyor. ). Ancak, ROM teknik olarak "özgür" soruna göre ve ağaçta düzenlilik yararlanan pratik çözümler temelde aynı görünecektir.

Sözde kod

  • ROM'da yeterince büyük bir huffman ağacı (veya benzeri blok öbek sıkıştırma verileri) saklayın
  • 10 ^ 8 "+" karakterden oluşan sıkıştırılmış bir dize ile başlayın.
  • N sayısını eklemek için, sıkıştırılmış dizeyi N "+" karakterleri geçene kadar akıtın ve sonra bir "!" Yeniden sıkıştırılmış dizeyi gittikçe bir öncekinin üzerine akıtın, aşırı / düşük çalışmalardan kaçınmak için sabit bir miktarda tamponlu blok tutun.
  • Bir milyon kez tekrarlayın: [giriş, akış sıkıştırması> ekle> sıkıştır], daha sonra çıkışa sıkıştırması

1
Şimdiye kadar, sorunun gerçekten cevap verdiğini gördüğüm tek cevap bu! Aritmetik kodlamanın, Huffman kodlamasından daha basit bir uyum olduğunu düşünüyorum, çünkü bir kod çizelgesini saklama ve sembol sınırları hakkında endişe duyma ihtiyacını ortadan kaldırıyor. Bağımlılığı da hesaba katabilirsiniz.
kalabalık

Giriş tamsayıları sıralanmaz. Önce sıralamanız gerekir.
alecco

1
@alecco Algoritma ilerledikçe bunları sıralar. Asla ayrılmamış olarak depolanmazlar.
Craig Gidney

1

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

10 ^ 8 aralığında 10 ^ 6 numara verilir. Bu ortalama ~ 100 <2 ^ 7 = 128 boşluk verir

Önce tüm boşluklar <128 olduğunda oldukça eşit aralıklı sayılarla ilgili daha basit sorunu ele alalım. Bu kolaydır. Sadece ilk sayıyı ve 7 bit boşlukları saklayın:

(27 bit) + 10 ^ 6 7 bit boşluk sayısı = 7000027 bit gerekir

Tekrarlanan sayıların 0 boşluğu olduğuna dikkat edin.

Peki ya 127'den büyük boşluklarımız varsa?

Tamam, diyelim ki <127 boşluk aralığı doğrudan temsil edilir, ancak 127 boşluk boyutunun ardından gerçek boşluk uzunluğu için sürekli 8 bit kodlama gelir:

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

vb.

Bu sayı gösteriminin kendi uzunluğunu açıkladığına dikkat edin, böylece bir sonraki boşluk numarasının ne zaman başladığını biliyoruz.

Sadece küçük boşluklar <127 olduğunda, bu hala 7000027 bit gerektirir.

Fazladan 16 * 781,250 = 12,500,000 bit gerektiren çok fazla (10 ^ 8) / (2 ^ 7) = 781250 23 bit boşluk sayısı olabilir. Daha kompakt ve yavaş yavaş artan boşlukların temsiline ihtiyacımız var.

Ortalama boşluk boyutu 100'dür, bu yüzden onları [100, 99, 101, 98, 102, ..., 2, 198, 1, 199, 0, 200, 201, 202, ...] olarak yeniden sıralarsak ve endekslersek '00' ile sınırlanmış sayılarla sıfır ikili (örneğin, 11011 = 8 + 5 + 2 + 1 = 16) olmadan kodlayan yoğun bir ikili Fibonacci tabanı ile, o zaman boşluk gösterimini yeterince kısa tutabileceğimizi düşünüyorum, ancak daha fazla analiz.


0

Akışı alırken bu adımları uygulayın.

1. bazı makul yığın boyutu ayarlayın

Sözde Kod fikri:

  1. İlk adım, tüm kopyaları bulmak ve sayıları ile bir sözlüğe yapıştırmak ve kaldırmak olacaktır.
  2. Üçüncü adım, algoritmik adımlarının sırasıyla var olan sayıyı yerleştirmek ve bunları ilk sayıya sahip özel sözlüklere ve n, n + 1 ..., n + 2, 2n, 2n + 1 gibi adımlara yerleştirmek olacaktır. 2n + 2 ...
  3. Parçalar halinde, her 1000 veya daha az 10000 gibi tekrarlanan daha az sıklıkta kalan sayılar gibi makul sayı aralıklarını sıkıştırmaya başlayın.
  4. Bir sayı bulunursa bu aralığı açın ve aralığa ekleyin ve bir süre daha sıkıştırılmadan bırakın.
  5. Aksi takdirde bu sayıyı bir bayta ekleyin [chunkSize]

Akışı alırken ilk 4 adıma devam edin. Son adım, hafızayı aşarsanız veya aralıkları sıralamaya ve sonuçları sırayla tükürmeye ve sıkıştırılmamış olması ve bunları onlara ulaşırsınız.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.