Sắp xếp 1 triệu số có 8 chữ số thập phân với 1 MB RAM


726

Tôi có một máy tính có 1 MB RAM và không có bộ nhớ cục bộ nào khác. Tôi phải sử dụng nó để chấp nhận 1 triệu số thập phân 8 chữ số qua kết nối TCP, sắp xếp chúng và sau đó gửi danh sách được sắp xếp qua kết nối TCP khác.

Danh sách các số có thể chứa các bản sao mà tôi không được loại bỏ. Mã sẽ được đặt trong ROM, vì vậy tôi không cần phải trừ kích thước mã của mình khỏi 1 MB. Tôi đã có mã để điều khiển cổng Ethernet và xử lý các kết nối TCP / IP và nó yêu cầu 2 KB cho dữ liệu trạng thái của nó, bao gồm bộ đệm 1 KB qua đó mã sẽ đọc và ghi dữ liệu. Có một giải pháp cho vấn đề này?

Nguồn câu hỏi và trả lời:

slashdot.org

cleaton.net


45
Ehm, một số thập phân 8 chữ số (số nhị phân nguyên tối thiểu 27 bit)> ram 1MB
Mr47

15
1M RAM có nghĩa là 2 ^ 20 byte? Và có bao nhiêu bit trong một byte trên kiến ​​trúc này? Và "triệu" trong "1 triệu số thập phân 8 chữ số" có phải là SI triệu (10 ^ 6) không? Số thập phân 8 chữ số là gì, số tự nhiên <10 ^ 8, số hữu tỷ có biểu diễn thập phân có 8 chữ số không bao gồm dấu thập phân hoặc số gì khác?

13
1 triệu 8 chữ số thập phân hay 1 triệu số 8 bit?
Patrick White

13
nó làm tôi nhớ đến một bài báo trong "Tạp chí Tiến sĩ Dobb" (đâu đó trong khoảng thời gian 1998-2001), trong đó tác giả đã sử dụng một kiểu sắp xếp để sắp xếp các số điện thoại khi đọc chúng: đó là lần đầu tiên tôi nhận ra rằng, đôi khi, chậm hơn thuật toán có thể nhanh hơn ...
Adrien Plisson

103
Có một giải pháp khác chưa ai đề cập đến: mua phần cứng với RAM 2 MB. Nó không nên đắt hơn nhiều, và nó sẽ làm cho vấn đề trở nên dễ dàng hơn nhiều .
Daniel Wagner

Câu trả lời:


716

Có một mẹo khá lén lút không được đề cập ở đây cho đến nay. Chúng tôi cho rằng bạn không có cách nào khác để lưu trữ dữ liệu, nhưng điều đó không hoàn toàn đúng.

Một cách xung quanh vấn đề của bạn là làm điều kinh khủng sau đây, điều mà không ai nên cố gắng trong bất kỳ trường hợp nào: Sử dụng lưu lượng truy cập mạng để lưu trữ dữ liệu. Và không, ý tôi không phải là NAS.

Bạn có thể sắp xếp các số chỉ với một vài byte RAM theo cách sau:

  • Đầu tiên lấy 2 biến: COUNTERVALUE.
  • Đầu tiên thiết lập tất cả các thanh ghi thành 0;
  • Mỗi khi bạn nhận được một số nguyên I, tăng dần COUNTERvà đặt VALUEthành max(VALUE, I);
  • Sau đó gửi gói yêu cầu echo ICMP với dữ liệu được đặt tới Ibộ định tuyến. Xóa Ivà lặp lại.
  • Mỗi khi bạn nhận được gói ICMP được trả về, bạn chỉ cần trích xuất số nguyên và gửi lại trong một yêu cầu tiếng vang khác. Điều này tạo ra một số lượng lớn các yêu cầu ICMP quét ngược và xuôi có chứa các số nguyên.

Khi COUNTERđạt đến 1000000, bạn có tất cả các giá trị được lưu trữ trong luồng yêu cầu ICMP không liên tục và VALUEhiện chứa số nguyên tối đa. Chọn một số threshold T >> 1000000. Đặt COUNTERthành không. Mỗi khi bạn nhận được gói ICMP, hãy tăng COUNTERvà gửi số nguyên chứa Itrong một yêu cầu tiếng vang khác, trừ khi I=VALUE, trong trường hợp đó, truyền nó đến đích cho các số nguyên được sắp xếp. Một lần COUNTER=T, giảm dần VALUEtheo 1, đặt lại COUNTERvề 0 và lặp lại. Khi VALUEđạt đến 0, bạn nên truyền tất cả các số nguyên theo thứ tự từ lớn nhất đến nhỏ nhất đến đích và chỉ sử dụng khoảng 47 bit RAM cho hai biến liên tục (và bất kỳ số lượng nhỏ nào bạn cần cho các giá trị tạm thời).

Tôi biết điều này thật kinh khủng, và tôi biết có thể có tất cả các vấn đề thực tế, nhưng tôi nghĩ nó có thể mang lại cho bạn một tiếng cười hoặc ít nhất là làm bạn kinh hoàng.


27
Vì vậy, về cơ bản, bạn đang tận dụng độ trễ của mạng và biến bộ định tuyến của mình thành một loại hàng đợi?
Eric R.

335
Giải pháp này không nằm ngoài hộp; nó dường như đã quên chiếc hộp của nó ở nhà: D
Vladislav Zorov

28
Câu trả lời tuyệt vời ... Tôi thích những câu trả lời này bởi vì chúng thực sự phơi bày mức độ khác nhau của một giải pháp đối với một vấn đề
StackOverflow

33
ICMP không đáng tin cậy.
mất ngủ

13
@MDMarra: Bạn sẽ chú ý ngay trên đầu Tôi nói "Một cách xoay quanh vấn đề của bạn là làm điều kinh khủng sau đây, điều mà không ai nên cố gắng trong bất kỳ trường hợp nào". Có một lý do tôi nói điều này.
Joe Fitzsimons

423

Đây là một số mã C ++ hoạt động để giải quyết vấn đề.

Bằng chứng là các ràng buộc bộ nhớ được thỏa mãn:

Biên tập viên: Không có bằng chứng về các yêu cầu bộ nhớ tối đa được cung cấp bởi tác giả trong bài đăng này hoặc trong blog của mình. Vì số lượng bit cần thiết để mã hóa một giá trị phụ thuộc vào các giá trị được mã hóa trước đó, nên một bằng chứng như vậy có thể không tầm thường. Tác giả lưu ý rằng kích thước được mã hóa lớn nhất mà anh ta có thể vấp ngã theo kinh nghiệm là 1011732và chọn kích thước bộ đệm 1013000tùy ý.

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

    ...

Cùng với nhau, hai mảng này có 1045000 byte lưu trữ. Điều đó để lại 1048576 - 1045000 - 2 × 1024 = 1528 byte cho các biến còn lại và không gian ngăn xếp.

Nó chạy trong khoảng 23 giây trên Xeon W3520 của tôi. Bạn có thể xác minh rằng chương trình hoạt động bằng cách sử dụng tập lệnh Python sau, giả sử tên chương trình là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!')

Một lời giải thích chi tiết về thuật toán có thể được tìm thấy trong loạt bài viết sau:


8
@preshing vâng, chúng tôi rất muốn có một lời giải thích chi tiết về điều này.
T Suds

25
Tôi nghĩ rằng quan sát chính là một số có 8 chữ số có khoảng 26,6 bit thông tin và một triệu là 19,9 bit. Nếu bạn nén danh sách (lưu trữ sự khác biệt của các giá trị liền kề), phạm vi chênh lệch từ 0 (0 bit) đến 99999999 (26,6 bit) nhưng bạn không thể có đồng bằng tối đa giữa mỗi cặp. Trường hợp xấu nhất thực sự phải là một triệu giá trị phân bố đồng đều, yêu cầu đồng bằng (26,6-19,9) hoặc khoảng 6,7 bit mỗi delta. Lưu trữ một triệu giá trị 6,7 bit dễ dàng phù hợp trong 1M. Nén Delta yêu cầu sắp xếp hợp nhất liên tục để bạn gần như có được điều đó miễn phí.
Ben Jackson

4
dung dịch ngọt. bạn nên ghé thăm blog của mình để được giải thích preshing.com/20121025/ từ
davec

9
@BenJackson: Có lỗi ở đâu đó trong toán học của bạn. Có 2.265 x 10 ^ 2436455 các đầu ra duy nhất có thể có (bộ được đặt hàng gồm 10 ^ 6 số nguyên 8 chữ số), cần 8.094 x 10 ^ 6 bit để lưu trữ (tức là một sợi tóc dưới một megabyte). Không có sơ đồ thông minh có thể nén vượt quá giới hạn lý thuyết thông tin này mà không mất. Giải thích của bạn ngụ ý rằng bạn cần ít không gian hơn, và do đó là sai. Thật vậy, "thông tư" trong giải pháp trên chỉ đủ lớn để chứa thông tin cần thiết, do đó, preshing dường như đã tính đến điều này, nhưng bạn đang thiếu nó.
Joe Fitzsimons

5
@JoeFitzsimons: Tôi đã không tìm ra đệ quy (bộ n số được sắp xếp duy nhất từ ​​0..m là (n+m)!/(n!m!)) vì vậy bạn phải đúng. Có lẽ đó là ước tính của tôi rằng một delta b bit mất b bit để lưu trữ - rõ ràng các đồng bằng 0 không mất 0 bit để lưu trữ.
Ben Jackson

371

Vui lòng xem câu trả lời đúng đầu tiên hoặc câu trả lời sau với mã hóa số học .Dưới đây bạn có thể tìm thấy một số niềm vui, nhưng không phải là một giải pháp chống đạn 100%.

Đây là một nhiệm vụ khá thú vị và đây là một giải pháp khác. Tôi hy vọng ai đó sẽ tìm thấy kết quả hữu ích (hoặc ít nhất là thú vị).

Giai đoạn 1: Cấu trúc dữ liệu ban đầu, phương pháp nén thô, kết quả cơ bản

Chúng ta hãy thực hiện một số phép toán đơn giản: ban đầu chúng ta có 1M (1048576 byte) RAM để lưu trữ 10 ^ 6 8 chữ số thập phân. [0; 99999999]. Vì vậy, để lưu trữ một số 27 bit là cần thiết (lấy giả định rằng số không dấu sẽ được sử dụng). Do đó, để lưu trữ một luồng thô ~ 3,5M RAM sẽ cần thiết. Ai đó đã nói rằng nó dường như không khả thi, nhưng tôi sẽ nói rằng nhiệm vụ có thể được giải quyết nếu đầu vào là "đủ tốt". Về cơ bản, ý tưởng là nén dữ liệu đầu vào với hệ số nén 0,29 hoặc cao hơn và sắp xếp theo cách phù hợp.

Trước tiên hãy giải quyết vấn đề nén. Có một số thử nghiệm có liên quan đã có sẵn:

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

"Tôi đã chạy thử nghiệm để nén một triệu số nguyên liên tiếp bằng nhiều hình thức nén khác nhau. Kết quả như sau:"

None     4000027
Deflate  2006803
Filtered 1391833
BZip2    427067
Lzma     255040

Có vẻ như LZMA ( thuật toán chuỗi Lempel Z Zio Markov ) là một lựa chọn tốt để tiếp tục. Tôi đã chuẩn bị một PoC đơn giản, nhưng vẫn còn một số chi tiết cần làm nổi bật:

  1. Bộ nhớ bị hạn chế, vì vậy, ý tưởng là sắp đặt trước các số và sử dụng các thùng nén (kích thước động) làm bộ nhớ tạm thời
  2. Dễ dàng đạt được hệ số nén tốt hơn với dữ liệu được định sẵn, do đó, có một bộ đệm tĩnh cho mỗi nhóm (các số từ bộ đệm sẽ được sắp xếp trước LZMA)
  3. Mỗi nhóm chứa một phạm vi cụ thể, do đó, loại cuối cùng có thể được thực hiện cho từng nhóm riêng biệt
  4. Kích thước của nhóm có thể được đặt đúng, do đó sẽ có đủ bộ nhớ để giải nén dữ liệu được lưu trữ và thực hiện sắp xếp cuối cùng cho từng nhóm riêng biệt

Sắp xếp trong bộ nhớ

Xin lưu ý, mã đính kèm là POC , nó không thể được sử dụng như một giải pháp cuối cùng, nó chỉ thể hiện ý tưởng sử dụng một số bộ đệm nhỏ hơn để lưu trữ các số được đặt trước theo cách tối ưu (có thể được nén). LZMA không được đề xuất như một giải pháp cuối cùng. Nó được sử dụng như một cách nhanh nhất có thể để giới thiệu nén cho PoC này.

Xem mã PoC bên dưới (xin lưu ý đây chỉ là bản demo, để biên dịch mã LZMA-Java sẽ cần thiết):

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

Với các số ngẫu nhiên, nó tạo ra như sau:

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

Đối với một chuỗi tăng dần đơn giản (một thùng được sử dụng), nó tạo ra:

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

BIÊN TẬP

Phần kết luận:

  1. Đừng cố đánh lừa thiên nhiên
  2. Sử dụng nén đơn giản hơn với dung lượng bộ nhớ thấp hơn
  3. Một số manh mối bổ sung là thực sự cần thiết. Giải pháp chống đạn thông thường dường như không khả thi.

Giai đoạn 2: Tăng cường nén, kết luận cuối cùng

Như đã được đề cập trong phần trước, bất kỳ kỹ thuật nén phù hợp đều có thể được sử dụng. Vì vậy, hãy loại bỏ LZMA theo hướng tiếp cận đơn giản và tốt hơn (nếu có thể). Có rất nhiều giải pháp tốt bao gồm mã hóa số học , cây Radix , v.v.

Dù sao, sơ đồ mã hóa đơn giản nhưng hữu ích sẽ mang tính minh họa hơn so với một thư viện bên ngoài khác, cung cấp một số thuật toán tiện lợi. Giải pháp thực tế khá đơn giản: vì có các thùng với dữ liệu được sắp xếp một phần, deltas có thể được sử dụng thay vì số.

sơ đồ mã hóa

Kiểm tra đầu vào ngẫu nhiên cho thấy kết quả tốt hơn một chút:

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

Mã mẫu

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

Xin lưu ý, cách tiếp cận này:

  1. không tiêu tốn nhiều bộ nhớ
  2. làm việc với các luồng
  3. cung cấp kết quả không quá tệ

Mã đầy đủ có thể được tìm thấy ở đây , triển khai BinaryInput và BinaryOutput có thể được tìm thấy ở đây

Kết luận cuối cùng

Không có kết luận cuối cùng :) Đôi khi, thực sự là một ý tưởng tốt để di chuyển lên một cấp và xem xét nhiệm vụ từ quan điểm cấp độ meta .

Thật vui khi dành thời gian cho nhiệm vụ này. BTW, có rất nhiều câu trả lời thú vị dưới đây. Cảm ơn bạn đã quan tâm và mã hóa hạnh phúc.


17
Tôi đã sử dụng Inkscape . Công cụ tuyệt vời bằng cách này. Bạn có thể sử dụng nguồn sơ đồ này làm ví dụ.
Renat Gilmanov

21
Chắc chắn LZMA đòi hỏi quá nhiều bộ nhớ để có ích trong trường hợp này? Là một thuật toán, nó có nghĩa là để giảm thiểu lượng dữ liệu phải được lưu trữ hoặc truyền đi, thay vì hiệu quả trong bộ nhớ.
Mjiig

67
Điều này là vô nghĩa ... Nhận 1 triệu số nguyên 27 bit ngẫu nhiên, sắp xếp chúng, nén với 7zip, xz, bất cứ LZMA nào bạn muốn. Kết quả là hơn 1MB. Tiền đề trên cùng là nén các số liên tiếp. Mã hóa Delta của 0bit sẽ chỉ là số, ví dụ 1000000 (giả sử bằng 4 byte). Với tuần tự và trùng lặp (không có khoảng trống), số 1000000 và 1000000 bit = 128KB, với 0 cho số trùng lặp và 1 để đánh dấu tiếp theo. Khi bạn có những khoảng trống ngẫu nhiên, dù nhỏ, LZMA thật lố bịch. Nó không được thiết kế cho việc này.
alecco

30
Điều này sẽ không thực sự làm việc. Tôi đã chạy một mô phỏng và trong khi dữ liệu nén hơn 1 MB (khoảng 1,5 MB), nó vẫn sử dụng hơn 100 MB RAM để nén dữ liệu. Vì vậy, ngay cả các số nguyên nén cũng không phù hợp với vấn đề không đề cập đến việc sử dụng RAM thời gian chạy. Trao cho bạn tiền thưởng là lỗi lớn nhất của tôi trên stackoverflow.
Onwuemene yêu thích

10
Câu trả lời này được nâng cấp rất nhiều bởi vì rất nhiều lập trình viên thích những ý tưởng sáng bóng hơn là mã đã được chứng minh. Nếu ý tưởng này có hiệu quả, bạn sẽ thấy một thuật toán nén thực tế được chọn và chứng minh chứ không phải là một xác nhận đơn thuần rằng chắc chắn có một thuật toán nào đó có thể thực hiện được ... khi hoàn toàn không có khả năng nào có thể thực hiện được .
Oledit

185

Một giải pháp có thể chỉ vì sự khác biệt giữa 1 megabyte và 1 triệu byte. Có khoảng 2 đến 8093729,5 cách khác nhau để chọn 1 triệu số có 8 chữ số với các số trùng lặp được phép và không quan trọng, do đó, một máy chỉ có 1 triệu byte RAM không có đủ trạng thái để thể hiện tất cả các khả năng. Nhưng 1M (ít hơn 2k cho TCP / IP) là 1022 * 1024 * 8 = 8372224 bit, vì vậy một giải pháp là có thể.

Phần 1, giải pháp ban đầu

Cách tiếp cận này cần nhiều hơn 1M một chút, tôi sẽ tinh chỉnh nó để phù hợp với 1M sau.

Tôi sẽ lưu trữ một danh sách các số được sắp xếp nhỏ gọn trong phạm vi từ 0 đến 99999999 dưới dạng một chuỗi các danh sách con của các số 7 bit. Danh sách con đầu tiên giữ các số từ 0 đến 127, danh sách phụ thứ hai giữ các số từ 128 đến 255, v.v. 100000000/128 chính xác là 781250, vì vậy cần có 781250 danh sách phụ như vậy.

Mỗi danh sách con bao gồm một tiêu đề danh sách con 2 bit, theo sau là phần thân danh sách con. Phần thân danh sách con chiếm 7 bit cho mỗi mục nhập danh sách phụ. Tất cả các danh sách con được nối với nhau và định dạng cho phép biết nơi một danh sách con kết thúc và tiếp theo bắt đầu. Tổng dung lượng cần thiết cho danh sách được điền đầy đủ là 2 * 781250 + 7 * 1000000 = 8562500 bit, khoảng 1.021 M-byte.

4 giá trị tiêu đề danh sách con có thể là:

00 sách con trống, không có gì sau.

01 Singleton, chỉ có một mục trong danh sách con và 7 bit tiếp theo giữ nó.

10 Danh sách con giữ ít nhất 2 số riêng biệt. Các mục được lưu trữ theo thứ tự không giảm, ngoại trừ mục cuối cùng nhỏ hơn hoặc bằng mục đầu tiên. Điều này cho phép kết thúc danh sách con được xác định. Ví dụ, các số 2,4,6 sẽ được lưu trữ dưới dạng (4,6,2). Các số 2,2,3,4,4 sẽ được lưu trữ dưới dạng (2,3,4,4,2).

11 Danh sách con giữ 2 hoặc nhiều lần lặp lại của một số. 7 bit tiếp theo cho số. Sau đó đến 0 hoặc nhiều hơn các mục 7 bit có giá trị 1, tiếp theo là mục 7 bit có giá trị 0. Độ dài của thân danh sách phụ quy định số lần lặp lại. Ví dụ: các số 12,12 sẽ được lưu trữ dưới dạng (12,0), các số 12,12,12 sẽ được lưu trữ dưới dạng (12,1,0), 12,12,12,12 sẽ là (12,1 , 1,0) và như vậy.

Tôi bắt đầu với một danh sách trống, đọc một loạt các số và lưu trữ chúng dưới dạng số nguyên 32 bit, sắp xếp các số mới tại chỗ (có thể sử dụng heapsort), sau đó hợp nhất chúng vào một danh sách sắp xếp nhỏ gọn mới. Lặp lại cho đến khi không còn số nào để đọc, sau đó đi bộ danh sách nhỏ gọn một lần nữa để tạo đầu ra.

Dòng bên dưới đại diện cho bộ nhớ ngay trước khi bắt đầu hoạt động hợp nhất danh sách. "O" là vùng chứa các số nguyên 32 bit được sắp xếp. "X" là khu vực chứa danh sách nhỏ gọn cũ. Dấu "=" là phòng mở rộng cho danh sách nhỏ gọn, 7 bit cho mỗi số nguyên trong "O" s. Các chữ "Z" là các chi phí ngẫu nhiên khác.

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

Thói quen hợp nhất bắt đầu đọc ở "O" ngoài cùng bên trái và ở "X" ngoài cùng bên trái, và bắt đầu viết ở ngoài cùng "=". Con trỏ ghi không bắt được con trỏ đọc danh sách nhỏ gọn cho đến khi tất cả các số nguyên mới được hợp nhất, bởi vì cả hai con trỏ đều tăng 2 bit cho mỗi danh sách con và 7 bit cho mỗi mục trong danh sách rút gọn cũ và có đủ chỗ cho Các mục 7 bit cho các số mới.

Phần 2, nhồi nhét nó vào 1M

Để Bóp giải pháp trên thành 1M, tôi cần làm cho định dạng danh sách nhỏ gọn hơn một chút. Tôi sẽ loại bỏ một trong các loại danh sách phụ, do đó sẽ chỉ có 3 giá trị tiêu đề danh sách phụ khác nhau có thể. Sau đó, tôi có thể sử dụng "00", "01" và "1" làm giá trị tiêu đề của danh sách phụ và lưu một vài bit. Các loại danh sách con là:

Một danh sách con trống, không có gì sau.

B Singleton, chỉ có một mục trong danh sách con và 7 bit tiếp theo giữ nó.

C Danh sách con giữ ít nhất 2 số riêng biệt. Các mục được lưu trữ theo thứ tự không giảm, ngoại trừ mục cuối cùng nhỏ hơn hoặc bằng mục đầu tiên. Điều này cho phép kết thúc danh sách con được xác định. Ví dụ, các số 2,4,6 sẽ được lưu trữ dưới dạng (4,6,2). Các số 2,2,3,4,4 sẽ được lưu trữ dưới dạng (2,3,4,4,2).

D Danh sách con bao gồm 2 hoặc nhiều lần lặp lại của một số.

3 giá trị tiêu đề danh sách phụ của tôi sẽ là "A", "B" và "C", vì vậy tôi cần một cách để thể hiện danh sách phụ loại D.

Giả sử tôi có tiêu đề danh sách phụ loại C theo sau là 3 mục, chẳng hạn như "C [17] [101] [58]". Đây không thể là một phần của danh sách con loại C hợp lệ như được mô tả ở trên, vì mục nhập thứ ba ít hơn mục thứ hai nhưng nhiều hơn mục thứ nhất. Tôi có thể sử dụng kiểu cấu trúc này để thể hiện một danh sách con loại D. Về mặt bit, bất cứ nơi nào tôi có "C {00 ?????} {1 ??????} {01 ?????}" là một danh sách con loại C không thể. Tôi sẽ sử dụng điều này để đại diện cho một danh sách con bao gồm 3 hoặc nhiều lần lặp lại của một số. Hai từ 7 bit đầu tiên mã hóa số (các bit "N" bên dưới) và được theo sau bởi 0 hoặc nhiều hơn {0100001} từ theo sau là từ {0100000}.

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

Điều đó chỉ để lại danh sách chứa chính xác 2 lần lặp lại của một số duy nhất. Tôi sẽ đại diện cho những người có mẫu danh sách phụ loại C không thể khác: "C {0 ??????} {11 ?????} {10 ?????}". Có rất nhiều chỗ cho 7 bit của số trong 2 từ đầu tiên, nhưng mẫu này dài hơn danh sách con mà nó đại diện, khiến mọi thứ phức tạp hơn một chút. Năm dấu hỏi ở cuối có thể được coi là không phải là một phần của mẫu, vì vậy tôi có: "C {0NNNNNN} {11N ????} 10" làm mẫu của tôi, với số được lặp lại được lưu trữ trong "N "S. Đó là 2 bit quá dài.

Tôi sẽ phải mượn 2 bit và trả lại từ 4 bit không sử dụng trong mẫu này. Khi đọc, khi gặp "C {0NNNNNN} {11N00AB} 10", xuất ra 2 trường hợp của số trong "N", ghi đè lên "10" ở cuối bằng các bit A và B và tua lại con trỏ đọc bằng 2 chút ít. Đọc phá hủy là ok cho thuật toán này, vì mỗi danh sách nhỏ gọn chỉ được đi một lần.

Khi viết một danh sách con gồm 2 lần lặp lại của một số duy nhất, hãy viết "C {0NNNNNN} 11N00" và đặt bộ đếm bit đã mượn thành 2. Tại mỗi lần ghi mà bộ đếm bit mượn là khác không, nó sẽ bị giảm cho mỗi bit được ghi và "10" được viết khi bộ đếm chạm 0. Vì vậy, 2 bit tiếp theo được viết sẽ đi vào các khe A và B, và sau đó "10" sẽ được thả vào cuối.

Với 3 giá trị tiêu đề danh sách phụ được biểu thị bằng "00", "01" và "1", tôi có thể gán "1" cho loại danh sách phụ phổ biến nhất. Tôi sẽ cần một bảng nhỏ để ánh xạ các giá trị tiêu đề của danh sách phụ thành các loại danh sách phụ và tôi sẽ cần một bộ đếm xuất hiện cho từng loại danh sách phụ để tôi biết ánh xạ tiêu đề danh sách phụ tốt nhất là gì.

Trường hợp xấu nhất đại diện tối thiểu của một danh sách nhỏ gọn đầy đủ xảy ra khi tất cả các loại danh sách con đều phổ biến như nhau. Trong trường hợp đó, tôi tiết kiệm 1 bit cho mỗi 3 tiêu đề danh sách phụ, vì vậy kích thước danh sách là 2 * 781250 + 7 * 1000000 - 781250/3 = 8302083.3 bit. Làm tròn đến một ranh giới từ 32 bit, đó là 8302112 bit, hoặc 1037764 byte.

1M trừ đi 2k cho trạng thái TCP / IP và bộ đệm là 1022 * 1024 = 1046528 byte, để lại cho tôi 8764 byte để chơi.

Nhưng những gì về quá trình thay đổi ánh xạ tiêu đề danh sách phụ? Trong bản đồ bộ nhớ bên dưới, "Z" là chi phí ngẫu nhiên, "=" là không gian trống, "X" là danh sách nhỏ gọn.

ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Bắt đầu đọc ở "X" ngoài cùng bên trái và bắt đầu viết ở ngoài cùng "=" và làm việc bên phải. Khi hoàn thành, danh sách nhỏ gọn sẽ ngắn hơn một chút và nó sẽ ở cuối bộ nhớ sai:

ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======

Vì vậy, sau đó tôi sẽ cần chuyển nó sang bên phải:

ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Trong quy trình thay đổi ánh xạ tiêu đề, tối đa 1/3 tiêu đề danh sách phụ sẽ được thay đổi từ 1 bit sang 2 bit. Trong trường hợp xấu nhất, tất cả sẽ nằm ở đầu danh sách, vì vậy tôi sẽ cần ít nhất 781250/3 bit lưu trữ miễn phí trước khi bắt đầu, điều này đưa tôi trở lại các yêu cầu bộ nhớ của phiên bản trước của danh sách rút gọn: (

Để giải quyết vấn đề đó, tôi sẽ chia 781250 danh sách phụ thành 10 nhóm danh sách phụ gồm 78125 danh sách phụ mỗi nhóm. Mỗi nhóm có ánh xạ tiêu đề danh sách con độc lập riêng. Sử dụng các chữ cái từ A đến J cho các nhóm:

ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ

Mỗi nhóm danh sách phụ thu nhỏ hoặc giữ nguyên trong khi thay đổi ánh xạ tiêu đề danh sách phụ:

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

Trường hợp xấu nhất mở rộng tạm thời của một nhóm danh sách con trong khi thay đổi ánh xạ là 78125/3 = 26042 bit, dưới 4k. Nếu tôi cho phép 4k cộng với 1037764 byte cho danh sách nhỏ gọn được điền đầy đủ, thì tôi sẽ để lại 8764 - 4096 = 4668 byte cho "Z" trong bản đồ bộ nhớ.

Đó là rất nhiều cho 10 bảng ánh xạ tiêu đề danh sách phụ, 30 số lần xuất hiện tiêu đề danh sách phụ và một số bộ đếm, con trỏ và bộ đệm nhỏ khác mà tôi cần, và không gian tôi đã sử dụng mà không nhận thấy, như không gian ngăn xếp cho địa chỉ trả lời cuộc gọi chức năng và biến cục bộ.

Phần 3, sẽ mất bao lâu để chạy?

Với danh sách rút gọn trống, tiêu đề danh sách 1 bit sẽ được sử dụng cho danh sách con trống và kích thước bắt đầu của danh sách sẽ là 781250 bit. Trong trường hợp xấu nhất, danh sách tăng 8 bit cho mỗi số được thêm vào, do đó, cần có 32 + 8 = 40 bit không gian trống cho mỗi số 32 bit được đặt ở đầu bộ đệm danh sách, sau đó được sắp xếp và hợp nhất. Trong trường hợp xấu nhất, việc thay đổi ánh xạ tiêu đề danh sách phụ sẽ dẫn đến việc sử dụng khoảng trắng của các mục 2 * 781250 + 7 * - 781250/3 bit.

Với chính sách thay đổi ánh xạ tiêu đề danh sách phụ sau mỗi lần hợp nhất thứ năm một khi có ít nhất 800000 số trong danh sách, trường hợp xấu nhất sẽ liên quan đến tổng cộng khoảng 30 triệu hoạt động đọc và viết danh sách nhỏ gọn.

Nguồn:

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


15
Tôi không nghĩ rằng bất kỳ giải pháp tốt hơn là có thể (trong trường hợp chúng ta cần phải làm việc với bất kỳ giá trị không thể nén nào). Nhưng điều này có thể được cải thiện một chút. Không cần thiết phải thay đổi các tiêu đề danh sách con giữa các biểu diễn 1 bit và 2 bit. Thay vào đó, bạn có thể sử dụng mã hóa số học , giúp đơn giản hóa thuật toán và cũng giảm số bit trong trường hợp xấu nhất trên mỗi tiêu đề từ 1,67 xuống 1,58. Và bạn không cần phải di chuyển danh sách nhỏ gọn trong bộ nhớ; thay vào đó sử dụng bộ đệm tròn và chỉ thay đổi con trỏ.
Evgeny Kluev

5
Vì vậy, cuối cùng, đó có phải là một câu hỏi phỏng vấn?
mlvljr

2
Cải tiến khác có thể là sử dụng danh sách phụ 100 phần tử thay vì danh sách phụ 128 phần tử (vì chúng tôi có đại diện nhỏ gọn nhất khi số lượng danh sách phụ bằng với số phần tử trong tập dữ liệu). Mỗi giá trị của danh sách con được mã hóa bằng mã hóa số học (với tần số bằng 1/100 cho mỗi giá trị). Điều này có thể tiết kiệm khoảng 10000 bit, ít hơn nhiều so với nén các tiêu đề danh sách phụ.
Evgeny Kluev

Đối với trường hợp C, bạn nói "Các mục được lưu theo thứ tự không giảm, ngoại trừ mục cuối cùng nhỏ hơn hoặc bằng mục đầu tiên". Làm thế nào sau đó bạn sẽ mã hóa 2,2,2,3,5? {2,2,3,5,2} sẽ trông giống như 2,2
Rollie

1
Có thể có một giải pháp đơn giản hơn về mã hóa tiêu đề danh sách phụ với cùng tỷ lệ nén 1,67 bit cho mỗi tiêu đề phụ mà không cần chuyển đổi ánh xạ phức tạp. Bạn có thể kết hợp mỗi 3 tiêu đề phụ liên tiếp với nhau, có thể dễ dàng mã hóa thành 5 bit bởi vì 3 * 3 * 3 = 27 < 32. Bạn kết hợp chúng combined_subheader = subheader1 + 3 * subheader2 + 9 * subheader3.
hynekcer

57

Câu trả lời của Gilmanov là rất sai trong các giả định của nó. Nó bắt đầu đầu cơ dựa trên một thước đo vô nghĩa của một triệu số nguyên liên tiếp . Điều đó có nghĩa là không có khoảng trống. Những khoảng trống ngẫu nhiên đó, tuy nhỏ nhưng thực sự khiến nó trở thành một ý tưởng tồi.

Hãy thử nó. Nhận 1 triệu số nguyên 27 bit ngẫu nhiên, sắp xếp chúng, nén với 7-Zip , xz, bất cứ LZMA nào bạn muốn. Kết quả là hơn 1,5 MB. Tiền đề trên cùng là nén các số liên tiếp. Ngay cả mã hóa delta của điều đó là hơn 1,1 MB . Và đừng bận tâm điều này là sử dụng hơn 100 MB RAM để nén. Vì vậy, ngay cả các số nguyên nén cũng không phù hợp với vấn đề và không bao giờ bận tâm đến việc sử dụng RAM thời gian .

Thật buồn cho tôi khi mọi người chỉ nâng cao đồ họa đẹp và hợp lý hóa.

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

}

Bây giờ nén ints.bin bằng 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
bất kỳ thuật toán liên quan đến từ điển nén dựa chỉ là ngoài chậm phát triển, tôi đã mã hóa một vài những tùy chỉnh và tất cả họ mất khá một chút bộ nhớ chỉ để đặt bảng băm riêng của họ (và không có HashMap trong java như nó thêm đói về tài nguyên). Giải pháp gần nhất sẽ là mã hóa delta với độ dài bit thay đổi và trả lại các gói TCP bạn không thích. Các đồng nghiệp sẽ truyền lại, vẫn còn tốt nhất.
bestsss

@bestsss vâng! kiểm tra câu trả lời đang thực hiện cuối cùng của tôi Tôi nghĩ rằng nó thể có thể.
alecco

3
Xin lỗi nhưng điều này dường như không trả lời câu hỏi , thực sự.
n611x007

@naxa có nó trả lời: nó không thể được thực hiện trong các tham số của câu hỏi ban đầu. Nó chỉ có thể được thực hiện nếu phân phối các số có entropy rất thấp.
alecco

1
Tất cả câu trả lời này cho thấy rằng các thói quen nén tiêu chuẩn gặp khó khăn khi nén dữ liệu dưới 1MB. Có thể có hoặc không có sơ đồ mã hóa có thể nén dữ liệu yêu cầu ít hơn 1MB, nhưng câu trả lời này không chứng minh rằng không có sơ đồ mã hóa nào sẽ nén dữ liệu này nhiều như vậy.
Itsme2003

41

Tôi nghĩ một cách để nghĩ về điều này là từ quan điểm tổ hợp: có bao nhiêu kết hợp có thể sắp xếp thứ tự số được sắp xếp? Nếu chúng tôi cung cấp kết hợp 0,0,0, ...., 0 mã 0 và 0,0,0, ..., 1 mã 1 và 99999999, 99999999, ... 99999999 mã N, N là gì Nói cách khác, không gian kết quả lớn như thế nào?

Chà, một cách để nghĩ về điều này là nhận thấy rằng đây là một vấn đề của việc tìm kiếm số lượng đường dẫn đơn điệu trong lưới N x M, trong đó N = 1.000.000 và M = 100.000.000. Nói cách khác, nếu bạn có một lưới rộng 1.000.000 và cao 100.000.000, thì có bao nhiêu con đường ngắn nhất từ ​​dưới cùng bên trái lên trên cùng bên phải? Tất nhiên, những con đường ngắn nhất chỉ yêu cầu bạn phải di chuyển sang phải hoặc lên (nếu bạn di chuyển xuống hoặc sang trái, bạn sẽ hoàn tác tiến trình đã hoàn thành trước đó). Để xem đây là một vấn đề khó khăn trong việc sắp xếp số của chúng tôi, hãy tuân thủ các điều sau:

Bạn có thể tưởng tượng bất kỳ chân ngang nào trong đường dẫn của chúng tôi là một số trong thứ tự của chúng tôi, trong đó vị trí Y của chân đại diện cho giá trị.

nhập mô tả hình ảnh ở đây

Vì vậy, nếu đường dẫn chỉ đơn giản là di chuyển sang phải cho đến hết, sau đó nhảy hết cỡ lên đỉnh, tương đương với thứ tự 0,0,0, ..., 0. thay vào đó, nếu nó bắt đầu bằng cách nhảy lên đỉnh và sau đó di chuyển sang phải 1.000.000 lần, tương đương với 99999999,99999999, ..., 99999999. Một con đường nơi nó di chuyển đúng một lần, sau đó lên một lần, sau đó rẽ phải , sau đó lên một lần, v.v ... đến cuối cùng (sau đó nhất thiết phải nhảy từ đầu đến đỉnh), tương đương với 0,1,2,3, ..., 999999.

May mắn thay cho chúng tôi vấn đề này đã được giải quyết, một lưới như vậy có các đường dẫn (N + M) Chọn (M):

(1.000.000 + 100.000.000) Chọn (100.000.000) ~ = 2.27 * 10 ^ 2436455

Do đó, N bằng 2,27 * 10 ^ 2436455 và do đó, mã 0 đại diện cho 0,0,0, ..., 0 và mã 2,27 * 10 ^ 2436455 và một số thay đổi đại diện cho 99999999,99999999, ..., 99999999.

Để lưu trữ tất cả các số từ 0 đến 2,27 * 10 ^ 2436455, bạn cần lg2 (2.27 * 10 ^ 2436455) = 8.0937 * 10 ^ 6 bit.

1 megabyte = 8388608 bit> 8093700 bit

Vì vậy, có vẻ như chúng ta ít nhất thực sự có đủ chỗ để lưu trữ kết quả! Tất nhiên, bây giờ bit thú vị đang thực hiện sắp xếp theo dòng số. Không chắc chắn cách tiếp cận tốt nhất cho điều này được đưa ra, chúng tôi còn lại 294908 bit. Tôi tưởng tượng một kỹ thuật thú vị ở mỗi điểm sẽ cho rằng đó là toàn bộ đơn hàng, tìm mã cho đơn đặt hàng đó và sau đó khi bạn nhận được một số mới sẽ quay lại và cập nhật mã trước đó. Tay sóng vẫy tay.


Đây thực sự là rất nhiều vẫy tay. Một mặt, về mặt lý thuyết đây là giải pháp vì chúng ta chỉ có thể viết một cỗ máy nhà nước lớn - nhưng vẫn hữu hạn; mặt khác, kích thước của con trỏ lệnh cho máy trạng thái lớn đó có thể nhiều hơn một megabyte, khiến nó không phải là khởi đầu. Nó thực sự đòi hỏi phải suy nghĩ nhiều hơn một chút so với điều này để thực sự giải quyết vấn đề đã cho. Chúng ta không chỉ đại diện cho tất cả các trạng thái, mà còn tất cả các trạng thái chuyển tiếp cần thiết để tính toán những việc cần làm trên bất kỳ số đầu vào tiếp theo nào.
Daniel Wagner

4
Tôi nghĩ rằng những câu trả lời khác chỉ tinh tế hơn về việc vẫy tay. Cho rằng bây giờ chúng ta biết kích thước của không gian kết quả, chúng ta biết chúng ta thực sự cần bao nhiêu không gian. Không có câu trả lời nào khác có thể lưu trữ mọi câu trả lời có thể có trong bất kỳ thứ gì nhỏ hơn 8093700 bit, vì đó là số lượng trạng thái cuối cùng có thể có. Việc nén (trạng thái cuối cùng) đôi khi có thể làm giảm dung lượng, nhưng sẽ luôn có một số câu trả lời yêu cầu toàn bộ không gian (không có thuật toán nén nào có thể nén mọi đầu vào).
Francisco Ryan Tolmasky I

Một số câu trả lời khác đã đề cập đến giới hạn dưới cứng hơn (ví dụ: câu thứ hai của câu trả lời của người hỏi ban đầu), vì vậy tôi không chắc chắn tôi thấy câu trả lời này được thêm vào cử chỉ.
Daniel Wagner

Bạn đang tham khảo 3,5M để lưu trữ luồng thô? (Nếu không, tôi xin lỗi và bỏ qua phản hồi này). Nếu vậy, thì đó là một giới hạn dưới hoàn toàn không liên quan. Giới hạn dưới của tôi là kết quả sẽ chiếm bao nhiêu dung lượng, giới hạn dưới đó là dung lượng đầu vào sẽ chiếm bao nhiêu nếu cần thiết để lưu trữ chúng - với điều kiện là câu hỏi được đặt ra khi một luồng đến từ kết nối TCP không rõ liệu bạn có thực sự cần hay không, bạn có thể đang đọc từng số một và cập nhật trạng thái của mình, do đó không cần 3,5M - dù sao đi nữa, 3,5 đó là trực giao với phép tính này.
Francisco Ryan Tolmasky I

"Có khoảng 2 đến 8093729,5 cách khác nhau để chọn 1 triệu số có 8 chữ số với các số trùng lặp được phép và đặt hàng không quan trọng" <- từ câu trả lời của người hỏi ban đầu. Không biết làm thế nào để rõ hơn về những gì tôi đang nói về. Tôi đã đề cập khá cụ thể đến câu này trong bình luận cuối cùng của tôi.
Daniel Wagner

20

Đề xuất của tôi ở đây nợ rất nhiều giải pháp của Dan

Trước hết tôi giả sử giải pháp phải xử lý tất cả các danh sách đầu vào có thể. Tôi nghĩ rằng các câu trả lời phổ biến không đưa ra giả định này (mà IMO là một sai lầm rất lớn).

Được biết, không có hình thức nén không mất dữ liệu nào sẽ làm giảm kích thước của tất cả các đầu vào.

Tất cả các câu trả lời phổ biến đều cho rằng họ sẽ có thể áp dụng nén đủ hiệu quả để cho phép họ có thêm không gian. Trong thực tế, một khối không gian thừa đủ lớn để chứa một phần của danh sách đã hoàn thành một phần của họ ở dạng không nén và cho phép họ thực hiện các hoạt động sắp xếp của mình. Đây chỉ là một giả định xấu.

Đối với giải pháp như vậy, bất kỳ ai có kiến ​​thức về cách họ thực hiện nén sẽ có thể thiết kế một số dữ liệu đầu vào không nén tốt cho sơ đồ này và "giải pháp" rất có thể sẽ bị hỏng do hết dung lượng.

Thay vào đó tôi có một cách tiếp cận toán học. Đầu ra có thể của chúng tôi là tất cả các danh sách độ dài LEN bao gồm các yếu tố trong phạm vi 0..MAX. Ở đây LEN là 1.000.000 và MAX của chúng tôi là 100.000.000.

Đối với LEN và MAX tùy ý, số lượng bit cần thiết để mã hóa trạng thái này là:

Log2 (MAX Multichoose LEN)

Vì vậy, đối với các số của chúng tôi, khi chúng tôi đã hoàn thành việc nhận và sắp xếp, chúng tôi sẽ cần ít nhất các bit Log2 (100.000.000 MC 1.000.000) để lưu trữ kết quả của chúng tôi theo cách có thể phân biệt duy nhất tất cả các đầu ra có thể.

Đây là ~ = 988kb . Vì vậy, chúng tôi thực sự có đủ không gian để giữ kết quả của chúng tôi. Từ quan điểm này, nó là có thể.

[Đã xóa lan man vô nghĩa ngay bây giờ khi các ví dụ tốt hơn tồn tại ...]

Câu trả lời tốt nhất là ở đây .

Một câu trả lời hay khác là ở đây và về cơ bản sử dụng sắp xếp chèn làm hàm để mở rộng danh sách theo một yếu tố (đệm một vài yếu tố và sắp xếp trước, để cho phép chèn nhiều hơn một lần, tiết kiệm một chút thời gian). sử dụng một mã hóa trạng thái nhỏ gọn đẹp quá, xô bảy đồng bằng


Luôn luôn vui vẻ để đọc lại câu trả lời của riêng bạn vào ngày hôm sau ... Vì vậy, trong khi câu trả lời hàng đầu là sai, thì một stackoverflow.com/a/12978097/1763801 được chấp nhận là khá tốt. Về cơ bản sử dụng sắp xếp chèn làm hàm để lấy danh sách LEN-1 và trả về LEN. Tận dụng thực tế là nếu bạn đặt trước một bộ nhỏ, bạn có thể chèn tất cả chúng trong một lượt, để tăng hiệu quả. Biểu diễn trạng thái khá nhỏ gọn (xô gồm 7 số bit) tốt hơn so với đề xuất lượn sóng bằng tay của tôi và trực quan hơn. suy nghĩ địa lý comp của tôi là bollocks, xin lỗi về điều đó
davec

1
Tôi nghĩ rằng số học của bạn là một chút. Tôi nhận được lg2 (100999999! / (99999999! * 1000000!)) = 1011718.55
NovaDenizen

Vâng, cảm ơn, đó là 988kb chứ không phải 965. Tôi đã cẩu thả về mặt 1024 so với 1000. Chúng tôi vẫn còn khoảng 35kb để chơi xung quanh. Tôi đã thêm một liên kết để tính toán trong câu trả lời.
davec

18

Giả sử nhiệm vụ này là có thể. Ngay trước khi xuất ra, sẽ có một đại diện trong bộ nhớ của hàng triệu số được sắp xếp. Có bao nhiêu đại diện khác nhau như vậy có? Vì có thể được lặp đi lặp lại số liệu chúng ta không thể sử dụng nCr (chọn), nhưng có một hoạt động gọi là multichoose rằng công trình trên multisets .

  • 2.2e2436455 cách chọn một triệu số trong phạm vi 0,99.999.999.
  • Điều đó đòi hỏi 8.093.730 bit để đại diện cho mọi kết hợp có thể, hoặc 1.011.717 byte.

Vì vậy, về mặt lý thuyết có thể là có thể, nếu bạn có thể đưa ra một đại diện lành mạnh (đủ) của danh sách các số được sắp xếp. Ví dụ: một đại diện điên rồ có thể yêu cầu bảng tra cứu 10 MB hoặc hàng ngàn dòng mã.

Tuy nhiên, nếu "RAM 1M" có nghĩa là một triệu byte, thì rõ ràng không có đủ dung lượng. Thực tế là bộ nhớ nhiều hơn 5% khiến cho về mặt lý thuyết có thể gợi ý cho tôi rằng việc đại diện sẽ phải RẤT hiệu quả và có lẽ không lành mạnh.


Số cách để chọn một triệu số (2.2e2436455) xảy ra gần với (256 ^ (1024 * 988)), đó là (2.0e2436445). Ergo, nếu bạn lấy đi khoảng 32 KB bộ nhớ từ 1M, vấn đề không thể được giải quyết. Ngoài ra, hãy nhớ ít nhất 3 KB bộ nhớ đã được bảo lưu.
johnwbyrd

Điều này tất nhiên giả định dữ liệu là hoàn toàn ngẫu nhiên. Theo như chúng tôi biết, đó là, nhưng tôi chỉ nói :)
Thorarin

Cách thông thường để biểu diễn số lượng trạng thái có thể này là bằng cách lấy cơ sở nhật ký 2 và báo cáo số lượng bit cần thiết để thể hiện chúng.
NovaDenizen

@Thorarin, yup, tôi thấy không có điểm nào trong "giải pháp" chỉ hoạt động đối với một số đầu vào.
Dan

12

(Câu trả lời ban đầu của tôi là sai, xin lỗi vì toán kém, xem bên dưới giờ nghỉ.)

Còn cái này thì sao?

27 bit đầu tiên lưu trữ số thấp nhất bạn đã thấy, sau đó chênh lệch với số tiếp theo được thấy, được mã hóa như sau: 5 bit để lưu trữ số bit được sử dụng để lưu trữ chênh lệch, sau đó là chênh lệch. Sử dụng 00000 để chỉ ra rằng bạn đã thấy số đó một lần nữa.

Điều này hoạt động vì khi càng nhiều số được chèn, chênh lệch trung bình giữa các số càng giảm, do đó bạn sử dụng ít bit hơn để lưu trữ chênh lệch khi bạn thêm nhiều số. Tôi tin rằng đây được gọi là một danh sách delta.

Trường hợp xấu nhất tôi có thể nghĩ đến là tất cả các số cách đều nhau (bằng 100), ví dụ Giả sử 0 là số đầu tiên:

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

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

Reddit để giải cứu!

Nếu tất cả những gì bạn phải làm là sắp xếp chúng, vấn đề này sẽ trở nên dễ dàng. Phải mất 122k (1 triệu bitcoin) để lưu trữ những con số bạn đã thấy (bit thứ 0 nếu nhìn thấy 0, bit thứ 2300 nếu nhìn thấy 2300, v.v.

Bạn đọc các số, lưu trữ chúng trong trường bit và sau đó chuyển các bit ra trong khi vẫn giữ số đếm.

NHƯNG, bạn phải nhớ bao nhiêu bạn đã thấy. Tôi đã được truyền cảm hứng từ câu trả lời của danh sách phụ ở trên để đưa ra kế hoạch này:

Thay vì sử dụng một bit, hãy sử dụng 2 hoặc 27 bit:

  • 00 có nghĩa là bạn đã không nhìn thấy số.
  • 01 có nghĩa là bạn đã nhìn thấy nó một lần
  • 1 có nghĩa là bạn đã thấy nó và 26 bit tiếp theo là số lần bao nhiêu lần.

Tôi nghĩ rằng điều này hoạt động: nếu không có bản sao, bạn có một danh sách 244k. Trong trường hợp xấu nhất bạn thấy mỗi số hai lần (nếu bạn thấy một số ba lần, nó sẽ rút ngắn phần còn lại của danh sách cho bạn), điều đó có nghĩa là bạn đã thấy 50.000 nhiều hơn một lần và bạn đã thấy 950.000 mục 0 hoặc 1 lần.

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

Bạn có thể cải thiện thêm nếu bạn sử dụng mã hóa sau:

0 có nghĩa là bạn không nhìn thấy số 10 có nghĩa là bạn đã nhìn thấy nó một lần 11 là cách bạn tiếp tục đếm

Trung bình sẽ mang lại 280,7k dung lượng lưu trữ.

EDIT: toán sáng chủ nhật của tôi đã sai.

Trường hợp xấu nhất là chúng ta thấy 500.000 số hai lần, vì vậy toán học trở thành:

500.000 * 27 + 500.000 * 2 = 1.77M

Kết quả mã hóa thay thế trong một bộ lưu trữ trung bình của

500.000 * 27 + 500.000 = 1,70M

: (


1
Chà, không, vì số thứ hai sẽ là 500000.
jfernand

Có thể thêm một số trung gian, như trong đó 11 có nghĩa là bạn đã thấy số lượng lên tới 64 lần (sử dụng 6 bit tiếp theo) và 11000000 có nghĩa là sử dụng 32 bit khác để lưu trữ số lần bạn đã thấy.
τεκ

10
Bạn lấy số "1 triệu bit" ở đâu? Bạn nói rằng bit thứ 2300 đại diện cho dù 2300 đã được nhìn thấy. (Tôi nghĩ rằng bạn thực sự có nghĩa là 2301.) Bit nào đại diện cho dù 99.999.999 đã được nhìn thấy (số có 8 chữ số lớn nhất)? Có lẽ, nó sẽ là bit thứ 100 triệu.
dùng94559

Bạn đã nhận được một triệu và hàng trăm triệu của bạn ngược lại. Số lần giá trị có thể xảy ra nhiều nhất là 1 triệu và bạn chỉ cần 20 bit để biểu thị số lần xuất hiện của một giá trị. Tương tự, bạn cần các trường 100.000.000 bit (không phải 1 triệu), mỗi trường cho một giá trị có thể.
Tim R.

Uh, 27 + 1000000 * (5 + 7) = 12000027 bit = 1,43M, không phải là 427K.
Daniel Wagner

10

Có một giải pháp cho vấn đề này trên tất cả các đầu vào có thể. Lừa đảo.

  1. Đọc giá trị m qua TCP, trong đó m gần mức tối đa có thể được sắp xếp trong bộ nhớ, có thể là n / 4.
  2. Sắp xếp các số 250.000 (hoặc hơn) và xuất chúng.
  3. Lặp lại trong 3 quý còn lại.
  4. Để người nhận hợp nhất 4 danh sách các số mà nó đã nhận khi xử lý chúng. (Nó không chậm hơn nhiều so với sử dụng một danh sách.)

7

Tôi sẽ thử một cây Radix . Nếu bạn có thể lưu trữ dữ liệu trong một cây, thì bạn có thể thực hiện một thao tác theo thứ tự để truyền dữ liệu.

Tôi không chắc bạn có thể phù hợp với điều này thành 1MB, nhưng tôi nghĩ nó đáng để thử.


7

Bạn đang sử dụng loại máy tính nào? Nó có thể không có bất kỳ bộ nhớ cục bộ "bình thường" nào khác, nhưng nó có RAM video không, chẳng hạn? 1 megapixel x 32 bit cho mỗi pixel (giả sử) khá gần với kích thước nhập dữ liệu cần thiết của bạn.

(Tôi chủ yếu hỏi trong bộ nhớ của PC Acorn RISC cũ , có thể 'mượn' VRAM để mở rộng RAM hệ thống khả dụng, nếu bạn chọn chế độ màn hình độ phân giải thấp hoặc độ sâu màu thấp!). Điều này khá hữu ích trên một máy chỉ có vài MB RAM bình thường.


1
Quan tâm để bình luận, downvoter? - Tôi chỉ đang cố gắng kéo dài những mâu thuẫn rõ ràng của câu hỏi (tức là gian lận một cách sáng tạo ;-)
DNA

Có thể không có máy tính nào cả, vì chủ đề liên quan trên tin tức hacker đề cập đây từng là một câu hỏi phỏng vấn của Google.
mlvljr

1
Có - Tôi đã trả lời trước khi câu hỏi được chỉnh sửa để chỉ ra rằng đó là một câu hỏi phỏng vấn!
DNA

6

Một đại diện của cây cơ số sẽ đến gần để xử lý vấn đề này, vì cây cơ số tận dụng "nén tiền tố". Nhưng thật khó để hình dung một biểu diễn cây cơ số có thể biểu thị một nút trong một byte - hai có lẽ là về giới hạn.

Nhưng, bất kể dữ liệu được biểu diễn như thế nào, một khi được sắp xếp, nó có thể được lưu trữ ở dạng nén tiền tố, trong đó các số 10, 11 và 12 sẽ được biểu thị bằng, ví dụ 001b, 001b, 001b, biểu thị mức tăng 1 từ số trước. Có lẽ, sau đó, 10101b sẽ đại diện cho mức tăng 5, 1101001b tăng 9, v.v.


6

Có 10 ^ 6 giá trị trong phạm vi 10 ^ 8, vì vậy trung bình có một giá trị trên một trăm điểm mã. Lưu khoảng cách từ điểm thứ N đến (N + 1) th. Các giá trị trùng lặp có số lần bỏ qua là 0. Điều này có nghĩa là việc bỏ qua cần trung bình chỉ dưới 7 bit để lưu trữ, vì vậy một triệu trong số chúng sẽ vui vẻ phù hợp với 8 triệu bit lưu trữ của chúng tôi.

Những lần bỏ qua này cần được mã hóa thành một dòng bit, theo mã hóa Huffman. Chèn là bằng cách lặp qua dòng bit và viết lại sau giá trị mới. Đầu ra bằng cách lặp qua và viết ra các giá trị ngụ ý. Về tính thực tiễn, có lẽ nó muốn được thực hiện như, giả sử, 10 ^ 4 danh sách bao gồm 10 ^ 4 điểm mã (và trung bình 100 giá trị) mỗi điểm.

Một cây Huffman tốt cho dữ liệu ngẫu nhiên có thể được xây dựng một ưu tiên bằng cách giả sử phân phối Poisson (mean = variance = 100) trên chiều dài của các lần bỏ qua, nhưng số liệu thống kê thực có thể được giữ trên đầu vào và được sử dụng để tạo ra một cây tối ưu để đối phó trường hợp bệnh lý.


5

Tôi có một máy tính có 1M RAM và không có bộ nhớ cục bộ nào khác

Một cách khác để gian lận: thay vào đó, bạn có thể sử dụng bộ nhớ không phải cục bộ (nối mạng) (câu hỏi của bạn không loại trừ điều này) và gọi một dịch vụ được nối mạng có thể sử dụng tính năng hợp nhất dựa trên đĩa đơn giản (hoặc chỉ đủ RAM để sắp xếp trong bộ nhớ, vì bạn chỉ cần chấp nhận số 1M), mà không cần các giải pháp (thừa nhận cực kỳ khéo léo) đã được đưa ra.

Điều này có thể là gian lận, nhưng không rõ liệu bạn đang tìm kiếm một giải pháp cho một vấn đề trong thế giới thực hay một câu đố mời bẻ cong các quy tắc ... nếu sau đó, một mánh gian lận đơn giản có thể có kết quả tốt hơn một phức tạp nhưng giải pháp "chính hãng" (như những người khác đã chỉ ra, chỉ có thể hoạt động cho các đầu vào có thể nén).


5

Tôi nghĩ giải pháp là kết hợp các kỹ thuật từ mã hóa video, cụ thể là chuyển đổi cosine rời rạc. Trong video kỹ thuật số, thay vì ghi lại độ thay đổi độ sáng hoặc màu của video dưới dạng các giá trị thông thường, chẳng hạn như 110 112 115 116, mỗi giá trị được trừ đi so với giá trị cuối cùng (tương tự như mã hóa độ dài chạy). 110 112 115 116 trở thành 110 2 3 1. Các giá trị, 2 3 1 yêu cầu ít bit hơn bản gốc.

Vì vậy, giả sử chúng ta tạo một danh sách các giá trị đầu vào khi chúng đến trên ổ cắm. Chúng tôi đang lưu trữ trong mỗi phần tử, không phải giá trị, mà là phần bù của phần tử trước nó. Chúng tôi sắp xếp khi chúng tôi đi, vì vậy sự bù đắp sẽ chỉ là tích cực. Nhưng phần bù có thể rộng 8 chữ số thập phân, phần này khớp với 3 byte. Mỗi phần tử không thể là 3 byte, vì vậy chúng ta cần phải đóng gói chúng. Chúng ta có thể sử dụng bit trên cùng của mỗi byte làm "bit tiếp tục", chỉ ra rằng byte tiếp theo là một phần của số và 7 bit thấp hơn của mỗi byte cần được kết hợp. không có giá trị cho các bản sao.

Khi danh sách đầy lên, các số sẽ gần nhau hơn, nghĩa là trung bình chỉ có 1 byte được sử dụng để xác định khoảng cách đến giá trị tiếp theo. 7 bit giá trị và 1 bit bù nếu thuận tiện, nhưng có thể có một điểm ngọt yêu cầu ít hơn 8 bit cho giá trị "tiếp tục".

Dù sao, tôi đã làm một số thí nghiệm. Tôi sử dụng một trình tạo số ngẫu nhiên và tôi có thể ghép một triệu số thập phân 8 chữ số được sắp xếp vào khoảng 1279000 byte. Không gian trung bình giữa mỗi số là 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

Chúng ta có thể chơi với ngăn xếp mạng để gửi các số theo thứ tự được sắp xếp trước khi chúng ta có tất cả các số. Nếu bạn gửi 1M dữ liệu, TCP / IP sẽ chia nó thành các gói 1500 byte và truyền chúng theo mục tiêu. Mỗi gói sẽ được cung cấp một số thứ tự.

Chúng ta có thể làm điều này bằng tay. Ngay trước khi chúng tôi lấp đầy RAM, chúng tôi có thể sắp xếp những gì chúng tôi có và gửi danh sách đến mục tiêu của chúng tôi nhưng để lại các lỗ hổng trong chuỗi của chúng tôi xung quanh mỗi số. Sau đó xử lý 1/2 số thứ tự theo cách tương tự bằng cách sử dụng các lỗ đó trong chuỗi.

Ngăn xếp mạng ở đầu xa sẽ lắp ráp luồng dữ liệu kết quả theo thứ tự trước khi đưa nó cho ứng dụng.

Đó là sử dụng mạng để thực hiện sắp xếp hợp nhất. Đây là một hack hoàn toàn, nhưng tôi đã được truyền cảm hứng từ các hack mạng khác được liệt kê trước đây.


4

Cách tiếp cận (xấu) của Google , từ chủ đề HN. Lưu trữ số lượng theo kiểu RLE.

Cấu trúc dữ liệu ban đầu của bạn là '99999999: 0' (tất cả các số 0, chưa thấy bất kỳ số nào) và sau đó giả sử bạn thấy số 3,866,344 để cấu trúc dữ liệu của bạn trở thành '3866343: 0,1: 1,96133654: 0' như bạn có thể thấy các số sẽ luôn xen kẽ giữa số bit 0 và số bit '1' để bạn có thể giả sử các số lẻ đại diện cho 0 bit và số chẵn 1 bit. Điều này trở thành (3866343,1,96133654)

Vấn đề của họ dường như không bao gồm các bản sao, nhưng giả sử họ sử dụng "0: 1" cho các bản sao.

Vấn đề lớn # 1: việc chèn vào số nguyên 1M sẽ mất nhiều thời gian .

Vấn đề lớn thứ 2: giống như tất cả các giải pháp mã hóa delta đơn giản, một số phân phối không thể được đề cập theo cách này. Ví dụ: số nguyên 1m có khoảng cách 0:99 (ví dụ +99 mỗi số). Bây giờ cũng nghĩ như vậy nhưng với khoảng cách ngẫu nhiên trong phạm vi 0h99 . (Lưu ý: 99999999/1000000 = 99,99)

Cách tiếp cận của Google là không xứng đáng (chậm) và không chính xác. Nhưng để bảo vệ họ, vấn đề của họ có thể hơi khác một chút.


3

Để biểu diễn mảng đã sắp xếp, người ta chỉ cần lưu trữ phần tử đầu tiên và sự khác biệt giữa các phần tử liền kề. Theo cách này, chúng tôi quan tâm đến việc mã hóa 10 ^ 6 phần tử có thể tổng hợp tối đa 10 ^ 8. Hãy gọi đây là D . Để mã hóa các phần tử của D, người ta có thể sử dụng mã Huffman . Từ điển cho mã Huffman có thể được tạo khi đang di chuyển và mảng được cập nhật mỗi khi một mục mới được chèn vào mảng được sắp xếp (sắp xếp chèn). Lưu ý rằng khi từ điển thay đổi vì một mục mới, toàn bộ mảng phải được cập nhật để phù hợp với mã hóa mới.

Số bit trung bình để mã hóa từng phần tử của D được tối đa hóa nếu chúng ta có số lượng bằng nhau của mỗi phần tử duy nhất. Nói các phần tử d1 , d2 , ..., dN trong D mỗi lần xuất hiện F lần. Trong trường hợp đó (trong trường hợp xấu nhất, chúng tôi có cả 0 và 10 ^ 8 trong chuỗi đầu vào), chúng tôi có

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

Ở đâu

tổng (1 <= i <= N ) F = 10 ^ 6 hoặc F = 10 ^ 6 / N và tần số chuẩn hóa sẽ là p = F / 10 ^ = 1 / N

Số bit trung bình sẽ là -log2 (1 / P ) = log2 ( N ). Trong những trường hợp chúng ta nên tìm một trường hợp nhằm tối đa hóa N . Điều này xảy ra nếu chúng ta có các số liên tiếp cho di bắt đầu từ 0, hoặc, di = i -1, do đó

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

I E

N <= 201. Và trong trường hợp này, số bit trung bình là log2 (201) = 7.6511, có nghĩa là chúng ta sẽ cần khoảng 1 byte cho mỗi phần tử đầu vào để lưu mảng đã sắp xếp. Lưu ý rằng điều này không có nghĩa là D nói chung không thể có nhiều hơn 201 phần tử. Nó chỉ gieo rằng nếu các phần tử của D được phân phối đồng đều, nó không thể có nhiều hơn 201 giá trị duy nhất.


1
Tôi nghĩ rằng bạn đã quên số đó có thể được nhân đôi.
bestsss

Đối với các số trùng lặp, sự khác biệt giữa các số liền kề sẽ bằng không. Không tạo ra bất kỳ vấn đề. Mã Huffman không yêu cầu giá trị khác không.
Mohsen Nosratinia

3

Tôi sẽ khai thác hành vi truyền lại của TCP.

  1. Làm cho thành phần TCP tạo một cửa sổ nhận lớn.
  2. Nhận một số lượng gói mà không gửi ACK cho chúng.
    • Xử lý những người trong pass tạo một số cấu trúc dữ liệu nén (tiền tố)
    • Gửi ack trùng lặp cho gói cuối cùng không cần thiết nữa / chờ hết thời gian truyền lại
    • Đi 2
  3. Tất cả các gói đã được chấp nhận

Điều này giả định một số loại lợi ích của xô hoặc nhiều lượt.

Có lẽ bằng cách sắp xếp các lô / thùng và hợp nhất chúng. -> cây cơ

Sử dụng kỹ thuật này để chấp nhận và sắp xếp 80% đầu tiên sau đó đọc 20% cuối cùng, xác minh rằng 20% ​​cuối cùng không chứa các số sẽ hạ cánh trong 20% ​​đầu tiên của các số thấp nhất. Sau đó gửi 20% số thấp nhất, xóa khỏi bộ nhớ, chấp nhận 20% số còn lại mới và hợp nhất. **


3

Đây là một giải pháp tổng quát cho loại vấn đề này:

Thủ tục chung

Cách tiếp cận được thực hiện như sau. Thuật toán hoạt động trên một bộ đệm gồm các từ 32 bit. Nó thực hiện các thủ tục sau trong một vòng lặp:

  • Chúng tôi bắt đầu với một bộ đệm chứa đầy dữ liệu nén từ lần lặp cuối cùng. Bộ đệm trông như thế này

    |compressed sorted|empty|

  • Tính số lượng tối đa của các số có thể được lưu trữ trong bộ đệm này, cả nén và không nén. Chia bộ đệm thành hai phần này, bắt đầu bằng khoảng trống cho dữ liệu nén, kết thúc bằng dữ liệu không nén. Bộ đệm trông giống như

    |compressed sorted|empty|empty|

  • Điền vào phần không nén với các số được sắp xếp. Bộ đệm trông giống như

    |compressed sorted|empty|uncompressed unsorted|

  • Sắp xếp các số mới với một sắp xếp tại chỗ. Bộ đệm trông giống như

    |compressed sorted|empty|uncompressed sorted|

  • Căn phải mọi dữ liệu đã được nén từ lần lặp trước trong phần được nén. Tại thời điểm này, bộ đệm được phân vùng

    |empty|compressed sorted|uncompressed sorted|

  • Thực hiện giải nén trực tuyến - giải nén trên phần được nén, hợp nhất trong dữ liệu được sắp xếp trong phần không nén. Phần nén cũ được tiêu thụ khi phần nén mới phát triển. Bộ đệm trông giống như

    |compressed sorted|empty|

Thủ tục này được thực hiện cho đến khi tất cả các số đã được sắp xếp.

Nén

Thuật toán này tất nhiên chỉ hoạt động khi có thể tính kích thước nén cuối cùng của bộ đệm sắp xếp mới trước khi thực sự biết cái gì sẽ thực sự được nén. Bên cạnh đó, thuật toán nén cần phải đủ tốt để giải quyết vấn đề thực tế.

Cách tiếp cận được sử dụng sử dụng ba bước. Đầu tiên, thuật toán sẽ luôn lưu trữ các chuỗi đã được sắp xếp, do đó chúng ta có thể lưu trữ hoàn toàn sự khác biệt giữa các mục liên tiếp. Mỗi sự khác biệt nằm trong phạm vi [0, 99999999].

Những khác biệt này sau đó được mã hóa dưới dạng một dòng bit đơn nhất. A 1 trong luồng này có nghĩa là "Thêm 1 vào bộ tích lũy, A 0 có nghĩa là" Phát ra bộ tích lũy dưới dạng mục nhập và đặt lại ". ​​Vì vậy, sự khác biệt N sẽ được biểu thị bằng N 1 và một 0.

Tổng của tất cả các khác biệt sẽ tiếp cận giá trị tối đa mà thuật toán hỗ trợ và tổng số các khác biệt sẽ tiếp cận với số lượng giá trị được chèn trong thuật toán. Điều này có nghĩa là chúng tôi hy vọng luồng cuối cùng sẽ chứa giá trị tối đa 1 và đếm 0. Điều này cho phép chúng tôi tính xác suất dự kiến ​​là 0 và 1 trong luồng. Cụ thể, xác suất của 0 là count/(count+maxval)và xác suất của 1 làmaxval/(count+maxval) .

Chúng tôi sử dụng các xác suất này để xác định một mô hình mã hóa số học trên dòng bit này. Mã số học này sẽ mã hóa chính xác số tiền 1 và 0 này trong không gian tối ưu. Chúng ta có thể tính toán không gian được sử dụng bởi mô hình này cho bất kỳ dòng bit trung gian nào như : bits = encoded * log2(1 + amount / maxval) + maxval * log2(1 + maxval / amount). Để tính tổng không gian cần thiết cho thuật toán, hãy đặt encodedbằng số tiền.

Để không yêu cầu số lần lặp vô lý, một chi phí nhỏ có thể được thêm vào bộ đệm. Điều này sẽ đảm bảo rằng thuật toán ít nhất sẽ hoạt động dựa trên số lượng phù hợp với chi phí này, vì cho đến nay, chi phí thời gian lớn nhất của thuật toán là nén và giải nén mã hóa số học mỗi chu kỳ.

Bên cạnh đó, một số chi phí cần thiết để lưu trữ dữ liệu sổ sách và xử lý các điểm không chính xác trong phép tính gần đúng điểm cố định của thuật toán mã hóa số học, nhưng tổng cộng thuật toán có thể chứa được 1MiB không gian ngay cả khi có thêm bộ đệm có thể chứa 8000 số, với tổng số 1043916 byte không gian.

Sự tối ưu

Ngoài việc giảm chi phí (nhỏ) của thuật toán, về mặt lý thuyết là không thể có được kết quả nhỏ hơn. Để chỉ chứa entropy của kết quả cuối cùng, 1011717 byte là cần thiết. Nếu chúng ta trừ đi bộ đệm bổ sung được thêm vào để đạt hiệu quả, thuật toán này đã sử dụng 1011916 byte để lưu trữ kết quả cuối cùng + phí.


2

Nếu luồng đầu vào có thể được nhận vài lần thì điều này sẽ dễ dàng hơn nhiều (không có thông tin về vấn đề đó, vấn đề về ý tưởng và hiệu suất thời gian).

Sau đó, chúng ta có thể đếm các giá trị thập phân. Với các giá trị được tính, sẽ dễ dàng tạo luồng đầu ra. Nén bằng cách đếm các giá trị. Nó phụ thuộc vào những gì sẽ có trong luồng đầu vào.


1

Nếu luồng đầu vào có thể được nhận vài lần thì điều này sẽ dễ dàng hơn nhiều (không có thông tin về vấn đề đó, vấn đề về ý tưởng và hiệu suất thời gian). Sau đó, chúng ta có thể đếm các giá trị thập phân. Với các giá trị được tính, sẽ dễ dàng tạo luồng đầu ra. Nén bằng cách đếm các giá trị. Nó phụ thuộc vào những gì sẽ có trong luồng đầu vào.


1

Sắp xếp là một vấn đề thứ yếu ở đây. Như đã nói, chỉ lưu trữ số nguyên là khó và không thể hoạt động trên tất cả các đầu vào , vì 27 bit cho mỗi số là cần thiết.

Tôi đảm nhận điều này là: chỉ lưu trữ sự khác biệt giữa các số nguyên liên tiếp (được sắp xếp), vì chúng rất có thể sẽ nhỏ. Sau đó, sử dụng sơ đồ nén, ví dụ với 2 bit bổ sung cho mỗi số đầu vào, để mã hóa số lượng bit được lưu trữ trên đó. Cái gì đó như:

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

Có thể lưu trữ một số lượng lớn các danh sách đầu vào có thể trong giới hạn bộ nhớ đã cho. Các phép toán về cách chọn sơ đồ nén để nó hoạt động với số lượng đầu vào tối đa, nằm ngoài tôi.

Tôi hy vọng bạn có thể khai thác kiến thức cụ thể về tên miền của đầu vào của bạn để tìm ra sơ đồ nén số nguyên đủ tốt dựa trên điều này.

Ồ và sau đó, bạn thực hiện sắp xếp chèn vào danh sách được sắp xếp đó khi bạn nhận được dữ liệu.


1

Bây giờ hướng đến một giải pháp thực tế, bao gồm tất cả các trường hợp đầu vào có thể có trong phạm vi 8 chữ số chỉ với 1MB RAM. LƯU Ý: công việc đang tiến hành, ngày mai sẽ tiếp tục. Sử dụng mã hóa số học của deltas của các int được sắp xếp, trường hợp xấu nhất cho các int được sắp xếp 1M sẽ có giá khoảng 7 bit mỗi lần nhập (vì 99999999/1000000 là 99 và log2 (99) là gần 7 bit).

Nhưng bạn cần số nguyên 1m được sắp xếp để có được 7 hoặc 8 bit! Chuỗi ngắn hơn sẽ có đồng bằng lớn hơn, do đó nhiều bit hơn cho mỗi phần tử.

Tôi đang làm việc để lấy càng nhiều càng tốt và nén (gần như) tại chỗ. Lô đầu tiên gần 250K int sẽ cần khoảng 9 bit mỗi bit. Vì vậy, kết quả sẽ mất khoảng 275KB. Lặp lại với bộ nhớ còn lại một vài lần. Sau đó giải nén-merge-in-place-nén những khối đã nén đó. Điều này khá khó , nhưng có thể. Tôi nghĩ.

Các danh sách được hợp nhất sẽ ngày càng gần hơn với mục tiêu 7 bit cho mỗi số nguyên. Nhưng tôi không biết có bao nhiêu lần lặp trong vòng lặp hợp nhất. Có lẽ 3.

Nhưng sự thiếu chính xác của việc triển khai mã hóa số học có thể làm cho nó không thể. Nếu vấn đề này hoàn toàn có thể xảy ra, nó sẽ cực kỳ chặt chẽ.

Một số tình nguyện viên?


Mã hóa số học là hoàn toàn khả thi. Nó có thể giúp nhận thấy rằng mỗi delta liên tiếp được rút ra từ phân phối nhị thức âm.
đông đúc vào

1

Bạn chỉ cần lưu trữ sự khác biệt giữa các số theo thứ tự và sử dụng mã hóa để nén các số thứ tự này. Chúng tôi có 2 ^ 23 bit. Chúng ta sẽ chia nó thành các khối 6 bit và để bit cuối cùng cho biết liệu số có kéo dài thêm 6 bit hay không (5 bit cộng với phần mở rộng).

Do đó, 000010 là 1 và 000100 là 2. 000001100000 là 128. Bây giờ, chúng tôi xem xét các diễn viên tồi tệ nhất trong việc thể hiện sự khác biệt trong chuỗi số lên tới 10.000.000. Có thể có 10.000.000 / 2 ^ 5 chênh lệch lớn hơn 2 ^ 5, 10.000.000 / 2 ^ 10 chênh lệch lớn hơn 2 ^ 10 và 10.000.000 / 2 ^ 15 chênh lệch lớn hơn 2 ^ 15, v.v.

Vì vậy, chúng tôi thêm bao nhiêu bit sẽ mất để đại diện cho chuỗi của chúng tôi. Chúng tôi có 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. Kể từ 8388608> 7935479, chúng ta sẽ dễ dàng có đủ bộ nhớ. Chúng ta có thể sẽ cần thêm một chút bộ nhớ để lưu trữ tổng của vị trí khi chúng ta chèn số mới. Sau đó chúng tôi đi qua chuỗi và tìm nơi để chèn số mới của chúng tôi, giảm sự khác biệt tiếp theo nếu cần thiết và thay đổi mọi thứ sau khi nó đúng.


Tôi tin rằng phân tích của tôi ở đây cho thấy sơ đồ này không hoạt động (và thậm chí không thể nếu chúng ta chọn kích thước khác hơn năm bit).
Daniel Wagner

@Daniel Wagner - Bạn không phải sử dụng số bit đồng nhất trên mỗi khối và thậm chí bạn không phải sử dụng số bit nguyên cho mỗi khối.
đông đúc vào

@crowding Nếu bạn có một đề xuất cụ thể, tôi muốn nghe nó. =)
Daniel Wagner

@crowding Làm toán trên bao nhiêu mã hóa số học không gian sẽ mất. Khóc một chút. Rồi suy nghĩ kỹ hơn.
Daniel Wagner

Tìm hiểu thêm. Một phân phối có điều kiện đầy đủ các ký hiệu trong biểu diễn trung gian bên phải (Francisco có biểu diễn trung gian đơn giản nhất, cũng như Strilanc) rất dễ tính toán. Do đó, mô hình mã hóa có thể hoàn hảo theo nghĩa đen và có thể đến trong một chút giới hạn entropic. Số học chính xác hữu hạn có thể thêm một vài bit.
đông đúc

1

Nếu chúng tôi không biết gì về những con số đó, chúng tôi sẽ bị giới hạn bởi các ràng buộc sau:

  • chúng ta cần tải tất cả các số trước khi có thể sắp xếp chúng,
  • bộ số không nén được.

Nếu các giả định này được giữ, không có cách nào để thực hiện nhiệm vụ của bạn, vì bạn sẽ cần ít nhất 26.575.425 bit lưu trữ (3.321.929 byte).

Bạn có thể cho chúng tôi biết gì về dữ liệu của bạn?


1
Bạn đọc chúng và sắp xếp chúng khi bạn đi. Về mặt lý thuyết, nó yêu cầu các bit lg2 (100999999! / (99999999! * 1000000!)) Để lưu trữ các mục không thể phân biệt 1M trong các hộp phân biệt 100M, hoạt động tới 96,4% của 1MB.
NovaDenizen

1

Thủ thuật là biểu diễn trạng thái thuật toán, là một tập hợp số nguyên, dưới dạng luồng nén của "bộ đếm tăng" = "+" và "bộ đếm đầu ra" = "!" nhân vật. Ví dụ: tập {0,3,3,4} sẽ được biểu diễn dưới dạng "! +++ !! +!", Theo sau là bất kỳ số lượng ký tự "+" nào. Để sửa đổi đa bộ, bạn phát trực tiếp các ký tự, chỉ giữ một lượng không đổi được giải nén tại một thời điểm và thực hiện thay đổi tại chỗ trước khi truyền lại chúng ở dạng nén.

Chi tiết

Chúng tôi biết có chính xác 10 ^ 6 số trong tập cuối cùng, vì vậy có nhiều nhất là 10 ^ 6 "!" nhân vật. Chúng tôi cũng biết rằng phạm vi của chúng tôi có kích thước 10 ^ 8, có nghĩa là có nhiều nhất 10 ^ 8 "+" ký tự. Số cách chúng ta có thể sắp xếp 10 ^ 6 "!" Trong số 10 ^ 8 "+" là (10^8 + 10^6) choose 10^6, và vì vậy việc chỉ định một số sắp xếp cụ thể cần ~ 0,965 MiB `dữ liệu. Đó sẽ là một sự phù hợp chặt chẽ.

Chúng tôi có thể coi mỗi nhân vật là độc lập mà không vượt quá hạn ngạch của chúng tôi. Có chính xác hơn 100 lần ký tự "+" so với "!" các ký tự, đơn giản hóa tỷ lệ cược 100: 1 của mỗi ký tự là "+" nếu chúng ta quên rằng chúng phụ thuộc. Tỷ lệ 100: 101 tương ứng với ~ 0,08 bit cho mỗi ký tự , với tổng số gần như ~ 0,965 MiB (bỏ qua sự phụ thuộc có chi phí chỉ ~ 12 bit trong trường hợp này!).

Kỹ thuật đơn giản nhất để lưu trữ các ký tự độc lập với xác suất được biết trước là mã hóa Huffman . Lưu ý rằng chúng ta cần một cây lớn không chính thức (Cây huffman cho các khối 10 ký tự có chi phí trung bình cho mỗi khối khoảng 2,4 bit, với tổng số ~ 2,9 Mib. Cây huffman cho các khối 20 ký tự có chi phí trung bình cho mỗi khối có khoảng 3 bit, tổng cộng ~ 1,8 MiB. Có lẽ chúng ta sẽ cần một khối kích thước theo thứ tự một trăm, ngụ ý nhiều nút trong cây của chúng ta hơn tất cả các thiết bị máy tính từng tồn tại có thể lưu trữ. ). Tuy nhiên, ROM về mặt kỹ thuật là "miễn phí" theo vấn đề và các giải pháp thực tế tận dụng sự đều đặn trong cây sẽ trông giống nhau.

Mã giả

  • Có một cây huffman đủ lớn (hoặc dữ liệu nén theo từng khối tương tự) được lưu trữ trong ROM
  • Bắt đầu với một chuỗi nén gồm 10 ^ 8 "+" ký tự.
  • Để chèn số N, hãy truyền ra chuỗi nén cho đến khi các ký tự N "+" đi qua rồi chèn "!". Truyền trực tiếp chuỗi được nén lại qua chuỗi trước khi bạn đi, giữ một số lượng khối đệm không đổi để tránh tình trạng chạy quá mức / dưới mức.
  • Lặp lại một triệu lần: [đầu vào, giải nén luồng> chèn> nén], sau đó giải nén thành đầu ra

1
Cho đến nay, đây là câu trả lời duy nhất tôi thấy thực sự trả lời vấn đề! Tôi nghĩ rằng mã hóa số học là một sự phù hợp đơn giản hơn so với mã hóa Huffman, vì nó làm giảm nhu cầu lưu trữ một cuốn sách mã và lo lắng về ranh giới biểu tượng. Bạn có thể tính đến sự phụ thuộc quá.
đông đúc

Các số nguyên đầu vào KHÔNG được sắp xếp. Bạn cần sắp xếp trước.
alecco

1
@alecco Thuật toán sắp xếp chúng khi nó tiến triển. Chúng không bao giờ được lưu trữ chưa được sắp xếp.
Craig Gidney

1

Chúng tôi có RAM 1 MB - 3 KB = 2 ^ 23 - 3 * 2 ^ 13 bit = 8388608 - 24576 = 8364032 bit khả dụng.

Chúng tôi được cung cấp 10 ^ 6 số trong phạm vi 10 ^ 8. Điều này cho khoảng cách trung bình ~ 100 <2 ^ 7 = 128

Trước tiên chúng ta hãy xem xét vấn đề đơn giản hơn về các số cách đều nhau khi tất cả các khoảng trống đều <128. Điều này thật dễ dàng. Chỉ cần lưu trữ số đầu tiên và các khoảng trống 7 bit:

(27 bit) + 10 ^ 6 Số khoảng cách 7 bit = 7000027 bit yêu cầu

Lưu ý số lặp lại có khoảng cách bằng 0.

Nhưng nếu chúng ta có khoảng trống lớn hơn 127 thì sao?

OK, giả sử kích thước khoảng cách <127 được biểu thị trực tiếp, nhưng kích thước khoảng cách 127 được theo sau bởi mã hóa 8 bit liên tục cho độ dài khoảng cách thực tế:

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

Vân vân.

Lưu ý biểu diễn số này mô tả độ dài của chính nó để chúng tôi biết khi nào số khoảng cách tiếp theo bắt đầu.

Chỉ với những khoảng trống nhỏ <127, điều này vẫn cần 7000027 bit.

Có thể có tối đa (10 ^ 8) / (2 ^ 7) = 781250 số khoảng cách 23 bit, yêu cầu thêm 16 * 781.250 = 12.500.000 bit là quá nhiều. Chúng ta cần một đại diện nhỏ hơn và tăng dần các khoảng trống.

Kích thước khoảng cách trung bình là 100 vì vậy nếu chúng tôi sắp xếp lại chúng là [100, 99, 101, 98, 102, ..., 2, 198, 1, 199, 0, 200, 201, 202, ...] và lập chỉ mục này với mã hóa cơ sở Fibre nhị phân dày đặc không có cặp số không (ví dụ: 11011 = 8 + 5 + 2 + 1 = 16) với các số được phân tách bằng '00' thì tôi nghĩ rằng chúng ta có thể giữ cho biểu diễn khoảng cách đủ ngắn, nhưng nó cần phân tích nhiều hơn.


0

Trong khi nhận được luồng làm các bước này.

1 bộ kích thước chunk hợp lý

Ý tưởng mã giả:

  1. Bước đầu tiên sẽ là tìm tất cả các bản sao và dán chúng vào một từ điển với số lượng của nó và loại bỏ chúng.
  2. Bước thứ ba sẽ là đặt số tồn tại theo thứ tự các bước thuật toán của chúng và đặt chúng vào các bộ từ điển đặc biệt với số thứ nhất và bước của chúng như n, n + 1 ..., n + 2, 2n, 2n + 1, 2n + 2 ...
  3. Bắt đầu nén theo từng khối một số phạm vi số hợp lý như cứ sau 1000 hoặc bao giờ 10000 số còn lại xuất hiện ít thường xuyên hơn để lặp lại.
  4. Giải nén phạm vi đó nếu một số được tìm thấy và thêm nó vào phạm vi và để nó không bị nén trong một thời gian nữa.
  5. Mặt khác, chỉ cần thêm số đó vào một byte [chunkSize]

Tiếp tục 4 bước đầu tiên trong khi nhận luồng. Bước cuối cùng sẽ là thất bại nếu bạn vượt quá bộ nhớ hoặc bắt đầu xuất kết quả một khi tất cả dữ liệu được thu thập bằng cách bắt đầu sắp xếp các phạm vi và nhổ các kết quả theo thứ tự và giải nén chúng theo thứ tự cần giải nén và sắp xếp chúng khi bạn nhận được chúng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.