การเรียงลำดับตัวเลข 1 หลัก 8 หลัก 8 หลักพร้อม RAM 1 MB


726

ฉันมีคอมพิวเตอร์ที่มี RAM ขนาด 1 MB และไม่มีที่เก็บในตัวเครื่องอื่น ฉันต้องใช้มันเพื่อรับ 1 ล้าน 8 หลักเลขทศนิยมผ่านการเชื่อมต่อ TCP เรียงลำดับแล้วส่งรายการที่เรียงลำดับออกไปผ่านการเชื่อมต่อ TCP อื่น

รายการตัวเลขอาจมีซ้ำซึ่งฉันต้องไม่ทิ้ง รหัสจะถูกวางใน ROM ดังนั้นฉันไม่จำเป็นต้องลบขนาดของรหัสของฉันจาก 1 MB ฉันมีรหัสเพื่อขับเคลื่อนพอร์ต Ethernet และจัดการการเชื่อมต่อ TCP / IP และต้องใช้ 2 KB สำหรับข้อมูลสถานะรวมถึงบัฟเฟอร์ 1 KB ผ่านที่รหัสจะอ่านและเขียนข้อมูล มีวิธีแก้ไขปัญหานี้หรือไม่?

แหล่งที่มาของคำถามและคำตอบ:

slashdot.org

cleaton.net


45
เอ๊ะ, ล้านครั้งเลขทศนิยม 8 หลัก (ไบนารีเลขจำนวนเต็มขั้นต่ำ 27 บิต)> ram 1MB
Mr47

15
1M of RAM หมายถึง 2 ^ 20 bytes? และมีกี่บิตในหนึ่งไบต์บนสถาปัตยกรรมนี้? และ "ล้าน" ใน "1 ล้าน 8 หลักทศนิยม" หนึ่งล้าน SI (10 ^ 6) คืออะไร? เลขฐานสิบ 8 หลักคืออะไรจำนวนธรรมชาติ <10 ^ 8 จำนวนตรรกยะที่มีทศนิยมแทน 8 หลักไม่รวมจุดทศนิยมหรืออย่างอื่น?

13
1 ล้าน 8 หลักทศนิยมหรือ 1 ล้าน 8 บิต?
Patrick White

13
มันทำให้ฉันนึกถึงบทความใน "Dr Dobb's Journal" (บางแห่งระหว่างปี 1998-2001) ซึ่งผู้เขียนใช้การเรียงลำดับการเรียงเพื่อเรียงหมายเลขโทรศัพท์เมื่อเขาอ่าน: นั่นเป็นครั้งแรกที่ฉันรู้ว่าบางครั้งช้าลง ขั้นตอนวิธีการอาจจะได้เร็วขึ้น ...
Adrien Plisson

103
มีวิธีแก้ไขปัญหาอื่นที่ยังไม่ได้กล่าวถึง: ซื้อฮาร์ดแวร์ด้วย RAM 2MB มันไม่ควรจะมีราคาแพงมากและมันจะทำให้ปัญหามากมากง่ายต่อการแก้ปัญหา
Daniel Wagner

คำตอบ:


716

มีเคล็ดลับลับ ๆ ล่อๆอย่างหนึ่งที่ไม่ได้กล่าวถึงที่นี่ เราคิดว่าคุณไม่มีวิธีพิเศษในการจัดเก็บข้อมูล แต่นั่นไม่เป็นความจริงอย่างเด็ดขาด

วิธีหนึ่งในการแก้ไขปัญหาของคุณคือทำสิ่งที่น่ากลัวต่อไปนี้ซึ่งไม่ควรมีใครพยายามทำในทุกกรณี: ใช้ปริมาณข้อมูลเครือข่ายเพื่อจัดเก็บข้อมูล และไม่ฉันไม่ได้หมายถึง NAS

คุณสามารถเรียงลำดับตัวเลขด้วย RAM เพียงไม่กี่ไบต์ในวิธีต่อไปนี้:

  • ครั้งแรกใช้เวลา 2 ตัวแปรและCOUNTERVALUE
  • ก่อนอื่นตั้งค่าการลงทะเบียนทั้งหมดเป็น0;
  • ทุกครั้งที่คุณได้รับเป็นจำนวนเต็มI, ที่เพิ่มขึ้นCOUNTERและการตั้งค่าVALUEไปmax(VALUE, I);
  • จากนั้นส่งแพ็กเก็ตคำขอ ICMP echo พร้อมชุดข้อมูลไปIยังเราเตอร์ ลบIและทำซ้ำ
  • ทุกครั้งที่คุณได้รับแพ็คเก็ต ICMP ที่ส่งคืนคุณเพียงดึงจำนวนเต็มและส่งกลับมาอีกครั้งในคำขอ echo อื่น สิ่งนี้สร้างคำขอ ICMP จำนวนมากที่วิ่งกลับไปข้างหน้าและข้างหลังที่มีจำนวนเต็ม

เมื่อCOUNTERถึง1000000แล้วคุณจะมีค่าทั้งหมดเก็บไว้ในสตรีมต่อเนื่องของคำขอ ICMP และVALUEตอนนี้มีจำนวนเต็มสูงสุด threshold T >> 1000000เลือกบาง ตั้งค่าCOUNTERเป็นศูนย์ ทุกครั้งที่คุณได้รับแพ็คเก็ต ICMP ให้เพิ่มCOUNTERและส่งจำนวนเต็มที่มีอยู่Iออกไปในคำขอ echo อื่นยกเว้นI=VALUEในกรณีที่ส่งผ่านไปยังปลายทางสำหรับจำนวนเต็มเรียง หนึ่งครั้งCOUNTER=Tลดค่าลงVALUEโดย1รีเซ็ตCOUNTERเป็นศูนย์และทำซ้ำ เมื่อVALUEถึงศูนย์คุณควรส่งจำนวนเต็มทั้งหมดตามลำดับจากมากไปน้อยที่สุดไปยังปลายทางและใช้ RAM เพียง 47 บิตสำหรับตัวแปรถาวรสองตัว (และจำนวนเล็กน้อยที่คุณต้องการสำหรับค่าชั่วคราว)

ฉันรู้ว่ามันน่ากลัวและฉันรู้ว่าอาจมีปัญหาในทางปฏิบัติได้ทุกประเภท แต่ฉันคิดว่ามันอาจทำให้คุณหัวเราะหรืออย่างน้อยก็ทำให้คุณตกใจ


27
ดังนั้นโดยทั่วไปคุณใช้ประโยชน์จากความหน่วงของเครือข่ายและเปลี่ยนเราเตอร์ของคุณให้กลายเป็นคิว?
Eric R.

335
โซลูชันนี้ไม่เพียง แต่อยู่นอกกรอบ ดูเหมือนว่าจะลืมกล่องไว้ที่บ้าน: D
Vladislav Zorov

28
คำตอบที่ยอดเยี่ยม ... ฉันชอบคำตอบเหล่านี้เพราะพวกเขาเปิดเผยว่าการแก้ปัญหาที่หลากหลายสามารถแก้ไขปัญหาได้อย่างไร
StackOverflow

33
ICMP ไม่น่าเชื่อถือ
sleeplessnerd

13
@MDMarra: คุณจะสังเกตเห็นที่ด้านบนฉันพูดว่า "วิธีหนึ่งในการแก้ไขปัญหาของคุณคือการทำสิ่งที่น่ากลัวต่อไปนี้ซึ่งไม่ควรมีใครพยายามในทุกสถานการณ์" มีเหตุผลที่ฉันพูดแบบนี้
Joe Fitzsimons

423

นี่คือโค้ด C ++ที่ใช้งานได้ซึ่งแก้ปัญหาได้

พิสูจน์ว่าข้อ จำกัด ของหน่วยความจำมีความพึงพอใจ:

บรรณาธิการ:ไม่มีข้อพิสูจน์ความต้องการหน่วยความจำสูงสุดที่ผู้เขียนเสนอในโพสต์นี้หรือในบล็อกของเขา เนื่องจากจำนวนบิตที่จำเป็นในการเข้ารหัสค่าขึ้นอยู่กับค่าที่เข้ารหัสก่อนหน้านี้การพิสูจน์ดังกล่าวจึงไม่น่ารำคาญ ผู้เขียนตั้งข้อสังเกตว่าขนาดที่เข้ารหัสที่ใหญ่ที่สุดที่เขาสามารถสะดุดเมื่อสังเกตุคือ1011732และเลือกขนาดบัฟเฟอร์1013000โดยพลการ

typedef unsigned int u32;

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

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

    ...

ทั้งสองอาร์เรย์ใช้พื้นที่เก็บข้อมูล 1,045,000 ไบต์ นั่นจะเหลือ 1048576 - 1045000 - 2 × 1024 = 1528 ไบต์สำหรับตัวแปรที่เหลือและพื้นที่สแต็ก

มันทำงานในประมาณ 23 วินาทีใน Xeon W3520 ของฉัน คุณสามารถตรวจสอบว่าการทำงานของโปรแกรมโดยใช้สคริปต์ Python sort1mb.exeต่อไปนี้สมมติว่าชื่อโปรแกรมของ

from subprocess import *
import random

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

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

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

คำอธิบายโดยละเอียดของอัลกอริทึมสามารถพบได้ในโพสต์ชุดต่อไปนี้:


8
@preshing ใช่เราต้องการคำอธิบายโดยละเอียด
T Suds

25
ฉันคิดว่าการสังเกตที่สำคัญคือตัวเลข 8 หลักมีข้อมูลประมาณ 26.6 บิตและหนึ่งล้านคือ 19.9 บิต หากคุณเดลต้าบีบอัดรายการ (เก็บความแตกต่างของค่าที่อยู่ติดกัน) ความแตกต่างอยู่ในช่วงตั้งแต่ 0 (0 บิต) ถึง 99999999 (26.6 บิต) แต่คุณไม่สามารถมีเดลต้าสูงสุดระหว่างทุกคู่ กรณีที่เลวร้ายที่สุดควรจะมีค่าการกระจายเท่ากันหนึ่งล้านค่าซึ่งต้องใช้ delta ของ (26.6-19.9) หรือประมาณ 6.7 bits ต่อ delta การจัดเก็บหนึ่งล้านค่า 6.7 บิตสามารถเข้ากันได้ดีใน 1M การบีบอัดเดลต้าต้องการการจัดเรียงผสานอย่างต่อเนื่องเพื่อให้คุณเกือบจะได้รับฟรี
เบ็คแจ็คสัน

4
วิธีแก้ปัญหาหวาน คุณควรเยี่ยมชมบล็อกของเขาสำหรับคำอธิบายที่ preshing.com/20121025/…
davec

9
@BenJackson: มีข้อผิดพลาดบางอย่างในคณิตศาสตร์ของคุณ มีเอาต์พุตที่เป็นไปได้ที่ไม่ซ้ำกัน 2.265 x 10 ^ 2436455 (ชุดเซต 10 ^ 6 จำนวนเต็ม 8 หลัก) ซึ่งใช้เวลา 8.094 x 10 ^ 6 บิตในการจัดเก็บ (เช่นผมใต้เมกะไบต์) ไม่มีโครงร่างที่ชาญฉลาดใดที่สามารถบีบอัดข้อมูลได้เกินกว่าที่ จำกัด ทางทฤษฎีนี้ คำอธิบายของคุณบ่งบอกว่าคุณต้องการพื้นที่น้อยกว่ามากและเป็นสิ่งที่ผิด อันที่จริง "วงกลม" ในการแก้ปัญหาข้างต้นมีขนาดใหญ่พอที่จะเก็บข้อมูลที่จำเป็นดังนั้นการ preshing ดูเหมือนจะนำสิ่งนี้มาพิจารณา แต่คุณหายไป
Joe Fitzsimons

5
@ JoeFitzsimons: ฉันไม่ได้ทำการเรียกซ้ำ (ชุดเรียงลำดับที่ไม่ซ้ำกันจำนวน n จาก 0..m คือ(n+m)!/(n!m!)) ดังนั้นคุณจะต้องถูกต้อง อาจเป็นจากการประมาณของฉันว่าเดลต้าของ b บิตใช้ b บิตในการจัดเก็บ - เดลต้าที่ชัดเจนของ 0 ไม่ได้ใช้ 0 บิตในการจัดเก็บ
เบ็คแจ็คสัน

371

โปรดดูคำตอบที่ถูกต้องก่อนหรือคำตอบในภายหลังด้วยการเข้ารหัสเลขคณิต ด้านล่างคุณอาจพบว่าสนุก แต่ไม่ใช่วิธีพิสูจน์ด้วยกระสุน 100%

นี่เป็นงานที่น่าสนใจและนี่เป็นวิธีแก้ปัญหาอื่น ฉันหวังว่าบางคนจะพบว่าผลลัพธ์มีประโยชน์ (หรืออย่างน้อยก็น่าสนใจ)

ขั้นตอนที่ 1: โครงสร้างข้อมูลเริ่มต้นวิธีการบีบอัดอย่างหยาบผลลัพธ์พื้นฐาน

ลองทำคณิตศาสตร์ง่ายๆ: เรามี RAM 1M (1048576 bytes) ที่พร้อมใช้งานสำหรับเก็บเลขทศนิยม 10 หลัก 6 8 หลัก [0; 99999999] ดังนั้นในการจัดเก็บหนึ่งหมายเลข 27 บิตจำเป็นต้องมี (การสมมติว่าจะใช้ตัวเลขที่ไม่ได้ลงชื่อ) ดังนั้นในการจัดเก็บ raw stream ~ 3.5M of RAM จะต้อง ใครบางคนบอกว่ามันดูเหมือนจะไม่เป็นไปได้ แต่ฉันจะบอกว่างานสามารถแก้ไขได้หากอินพุตเป็น "ดีพอ" โดยพื้นฐานแล้วแนวคิดคือการบีบอัดข้อมูลอินพุตด้วยอัตราการบีบอัด 0.29 หรือสูงกว่าและทำการเรียงลำดับอย่างเหมาะสม

ลองแก้ปัญหาการบีบอัดก่อน มีการทดสอบที่เกี่ยวข้องอยู่แล้ว:

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

"ฉันทดสอบเพื่อบีบอัดจำนวนเต็มหนึ่งล้านต่อเนื่องโดยใช้การบีบอัดในรูปแบบต่าง ๆ ผลลัพธ์มีดังนี้:"

None     4000027
Deflate  2006803
Filtered 1391833
BZip2    427067
Lzma     255040

ดูเหมือนว่า LZMA ( อัลกอริทึมลูกโซ่ Lempel – Ziv – Markov ) เป็นตัวเลือกที่ดีในการดำเนินการต่อ ฉันได้เตรียม PoC ง่าย ๆ แต่ยังมีรายละเอียดบางส่วนที่จะเน้น:

  1. หน่วยความจำมี จำกัด ดังนั้นแนวคิดก็คือการจัดเตรียมหมายเลขไว้ล่วงหน้าและใช้ที่เก็บข้อมูลที่บีบอัด (ขนาดไดนามิก) เป็นที่เก็บข้อมูลชั่วคราว
  2. มันง่ายกว่าที่จะบรรลุถึงปัจจัยการบีบอัดที่ดีกว่าด้วยข้อมูลที่ถูกจัดเรียงไว้ล่วงหน้าดังนั้นจึงมีบัฟเฟอร์แบบคงที่สำหรับที่เก็บข้อมูลแต่ละชุด (หมายเลขจากบัฟเฟอร์จะถูกจัดเรียงก่อน LZMA)
  3. ที่เก็บข้อมูลแต่ละอันมีช่วงที่เฉพาะเจาะจงดังนั้นการเรียงลำดับสุดท้ายสามารถทำได้สำหรับที่เก็บแต่ละชุดแยกกัน
  4. สามารถตั้งค่าขนาดของ Bucket ได้อย่างเหมาะสมดังนั้นจะมีหน่วยความจำเพียงพอที่จะแตกข้อมูลที่เก็บไว้และทำการเรียงลำดับสุดท้ายสำหรับที่เก็บข้อมูลแต่ละอันแยกกัน

การเรียงลำดับในหน่วยความจำ

โปรดทราบว่ารหัสที่แนบมาเป็นPOCมันไม่สามารถใช้เป็นวิธีแก้ปัญหาขั้นสุดท้ายได้เพียงแสดงให้เห็นถึงแนวคิดของการใช้บัฟเฟอร์ขนาดเล็กจำนวนมากเพื่อเก็บหมายเลขที่ถูกจัดเรียงไว้ล่วงหน้าด้วยวิธีที่เหมาะสมที่สุด LZMA ไม่ได้เสนอให้เป็นทางออกสุดท้าย มันถูกใช้เป็นวิธีที่เร็วที่สุดในการแนะนำการบีบอัดให้กับ PoC นี้

ดูรหัส PoC ด้านล่าง (โปรดทราบว่ามันเป็นเพียงการสาธิตเพื่อรวบรวมLZMA-Javaจะต้องใช้):

public class MemorySortDemo {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return encoderPrperties.toByteArray();
    }

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

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

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

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

        return array;
    }
}

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

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

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

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

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

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

        printStatistics();

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

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

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

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

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

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

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

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

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

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

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

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

    sorter.doSort(p, c);
}
}

ด้วยตัวเลขสุ่มมันสร้างดังต่อไปนี้:

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

สำหรับการเรียงลำดับจากน้อยไปมากอย่างง่าย (ใช้ที่ฝากข้อมูลหนึ่งอัน) จะสร้าง:

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

แก้ไข

สรุป:

  1. อย่าพยายามหลอกธรรมชาติ
  2. ใช้การบีบอัดที่เรียบง่ายขึ้นพร้อมกับลดพื้นที่หน่วยความจำ
  3. จำเป็นต้องใช้ข้อมูลเพิ่มเติมบางอย่าง วิธีแก้ปัญหากระสุนกันทั่วไปดูเหมือนจะเป็นไปไม่ได้

ขั้นตอนที่ 2: การบีบอัดขั้นสูงข้อสรุปสุดท้าย

ดังที่ได้กล่าวไว้แล้วในส่วนก่อนหน้านี้เทคนิคการบีบอัดใด ๆ ที่เหมาะสมสามารถใช้ งั้นลองกำจัด LZMA เพื่อแนวทางที่ง่ายและดีกว่า (ถ้าเป็นไปได้) มีวิธีแก้ปัญหาที่ดีมากมายเช่นการเข้ารหัสเลขคณิต , Radix treeเป็นต้น

อย่างไรก็ตามรูปแบบการเข้ารหัสที่เรียบง่าย แต่มีประโยชน์จะเป็นตัวอย่างมากกว่าห้องสมุดภายนอกอื่นที่ให้อัลกอริทึมที่ดี ทางออกที่แท้จริงนั้นค่อนข้างตรงไปตรงมา: เนื่องจากมีที่เก็บข้อมูลที่มีการเรียงบางส่วนจึงสามารถใช้ delta แทนตัวเลขได้

รูปแบบการเข้ารหัส

การทดสอบอินพุตแบบสุ่มแสดงผลลัพธ์ที่ดีขึ้นเล็กน้อย:

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

โค้ดตัวอย่าง

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

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

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

        int len = bits;

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

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

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

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

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

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

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

   return length;
}

โปรดทราบว่าวิธีการนี้:

  1. ไม่ใช้หน่วยความจำมาก
  2. ทำงานร่วมกับกระแส
  3. ให้ผลลัพธ์ที่ไม่เลว

รหัสเต็มสามารถพบได้ที่นี่การใช้งาน BinaryInput และ BinaryOutput สามารถดูได้ที่นี่

ข้อสรุปสุดท้าย

ไม่มีข้อสรุปสุดท้าย :) บางครั้งมันเป็นความคิดที่ดีจริงๆที่จะเลื่อนระดับหนึ่งขึ้นไปและทบทวนงานจากมุมมองระดับเมตา

มันสนุกมากที่ได้ใช้เวลากับงานนี้ BTW มีคำตอบที่น่าสนใจมากมายด้านล่าง ขอขอบคุณสำหรับความสนใจและความสุขของคุณ


17
ผมใช้Inkscape เครื่องมือที่ยอดเยี่ยม คุณสามารถใช้แหล่งแผนภาพนี้เป็นตัวอย่าง
Renat Gilmanov

21
LZMA ต้องการหน่วยความจำมากเกินไปที่จะเป็นประโยชน์ในกรณีนี้หรือไม่? ในฐานะที่เป็นอัลกอริทึมมันหมายถึงการลดปริมาณข้อมูลที่จะต้องจัดเก็บหรือส่งผ่านแทนที่จะมีประสิทธิภาพในหน่วยความจำ
Mjiig

67
นี่เป็นเรื่องไร้สาระ ... รับ 1 ล้านสุ่ม 27 บิตจำนวนเต็มเรียงลำดับบีบอัดด้วย 7zip, xz ไม่ว่า LZMA ที่คุณต้องการ ผลลัพธ์เกิน 1MB สถานที่ตั้งอยู่ด้านบนคือการบีบอัดของหมายเลขลำดับ การเข้ารหัส Delta ที่มี 0 บิตจะเป็นเพียงตัวเลขเช่น 1000000 (พูดเป็น 4 ไบต์) ด้วยลำดับและรายการซ้ำ (ไม่มีช่องว่าง) จำนวน 1000000 และ 1000000 บิต = 128KB โดยมี 0 สำหรับหมายเลขซ้ำกันและ 1 เพื่อทำเครื่องหมายถัดไป เมื่อคุณมีช่องว่างแบบสุ่มแม้แต่น้อย LZMA ก็ไร้สาระ มันไม่ได้ออกแบบมาสำหรับสิ่งนี้
alecco

30
สิ่งนี้ใช้ไม่ได้จริง ฉันรันการจำลองและในขณะที่ข้อมูลที่บีบอัดมีมากกว่า 1MB (ประมาณ 1.5MB) แต่ก็ยังคงใช้ RAM มากกว่า 100MB เพื่อบีบอัดข้อมูล ดังนั้นแม้แต่จำนวนเต็มที่ถูกบีบอัดก็ยังไม่พอดีกับปัญหาและไม่ต้องพูดถึงการใช้ RAM แบบรันไทม์ การมอบรางวัลให้กับคุณคือข้อผิดพลาดที่ใหญ่ที่สุดของฉันใน stackoverflow
Onwuemene ที่ชื่นชอบ

10
คำตอบนี้ได้รับการอัปเดตอย่างมากเนื่องจากโปรแกรมเมอร์จำนวนมากชอบแนวคิดที่เป็นประกายมากกว่าใช้รหัสที่พิสูจน์แล้ว หากความคิดนี้ใช้ได้ผลคุณจะเห็นอัลกอริธึมการบีบอัดที่เลือกและพิสูจน์แล้วจริง ๆ มากกว่าการยืนยันเพียงอย่างเดียวว่ามีอย่างใดอย่างหนึ่งที่สามารถทำได้ ... เมื่อเป็นไปได้ค่อนข้างเป็นไปได้ที่จะไม่มี .
โอเลเท

185

การแก้ปัญหาเป็นไปได้เพียงเพราะความแตกต่างระหว่าง 1 เมกะไบต์และ 1 ล้านไบต์ มีประมาณ 2 ถึงกำลัง 8093729.5 วิธีที่แตกต่างในการเลือก 1 ล้าน 8 หลักตัวเลขด้วยซ้ำได้รับอนุญาตและการสั่งซื้อที่ไม่สำคัญดังนั้นเครื่องที่มี RAM เพียง 1 ล้านไบต์ไม่มีสถานะเพียงพอที่จะแสดงถึงความเป็นไปได้ทั้งหมด แต่ 1M (น้อยกว่า 2k สำหรับ TCP / IP) คือ 1022 * 1024 * 8 = 8372224 บิตดังนั้นการแก้ปัญหาจึงเป็นไปได้

ส่วนที่ 1 การแก้ปัญหาเบื้องต้น

วิธีนี้ต้องการมากกว่า 1M เล็กน้อยฉันจะปรับแต่งให้พอดีกับ 1M ในภายหลัง

ฉันจะเก็บรายการที่เรียงลำดับขนาดกะทัดรัดของตัวเลขในช่วง 0 ถึง 99999999 เป็นลำดับของรายการย่อยของตัวเลข 7 บิต รายการย่อยแรกเก็บตัวเลขตั้งแต่ 0 ถึง 127 รายการย่อยที่สองเก็บหมายเลขได้ตั้งแต่ 128 ถึง 255 และ 100000000/128 นั้นมีค่าเท่ากับ 781250 ดังนั้น 781250 จึงจำเป็นต้องมีรายการย่อยดังกล่าว

แต่ละรายการย่อยประกอบด้วยส่วนหัวของรายการย่อยแบบ 2 บิตตามด้วยเนื้อหาของรายการย่อย เนื้อหาย่อยรายการใช้เวลา 7 บิตต่อรายการรายการย่อย รายการย่อยทั้งหมดจะถูกรวมเข้าด้วยกันและรูปแบบทำให้สามารถบอกได้ว่ารายการย่อยหนึ่งจบลงที่ใด พื้นที่เก็บข้อมูลทั้งหมดที่จำเป็นสำหรับรายการที่มีประชากรเต็มรูปแบบคือ 2 * 781250 + 7 * 1000000 = 8562500 บิตซึ่งประมาณ 1.021 M- ไบต์

ค่าส่วนหัวของรายการย่อย 4 ที่เป็นไปได้คือ:

00รายการย่อยว่างเปล่าไม่มีอะไรติดตาม

01 Singleton มีเพียงหนึ่งรายการในรายการย่อยและอีก 7 บิตจะเก็บไว้

10รายการย่อยมีตัวเลขที่แตกต่างกันอย่างน้อย 2 ตัว รายการจะถูกเก็บไว้ในลำดับที่ไม่ลดลงยกเว้นว่ารายการสุดท้ายมีค่าน้อยกว่าหรือเท่ากับรายการแรก สิ่งนี้ยอมให้มีการระบุจุดสิ้นสุดของรายการย่อย ตัวอย่างเช่นตัวเลข 2,4,6 จะถูกเก็บไว้เป็น (4,6,2) ตัวเลข 2,2,3,4,4 จะถูกจัดเก็บเป็น (2,3,4,4,2)

11รายการย่อยมีการซ้ำซ้อน 2 ครั้งขึ้นไปของหมายเลขเดียว 7 บิตถัดไปให้หมายเลข จากนั้นมารายการ 7 บิตที่เป็นศูนย์หรือมากกว่าด้วยค่า 1 ตามด้วยรายการ 7 บิตที่มีค่า 0 ความยาวของเนื้อหาย่อยจะกำหนดจำนวนการทำซ้ำ ตัวอย่างเช่นตัวเลข 12,12 จะถูกจัดเก็บเป็น (12,0), ตัวเลข 12,12,12 จะถูกเก็บไว้เป็น (12,1,0), 12,12,12,12 จะเป็น (12,1) , 1,0) และอื่น ๆ

ฉันเริ่มด้วยรายการที่ว่างอ่านตัวเลขและเก็บไว้เป็นจำนวนเต็ม 32 บิตเรียงลำดับหมายเลขใหม่เข้าที่ (ใช้ heapsort, อาจ) จากนั้นรวมเข้าไว้ในรายการเรียงลำดับขนาดกะทัดรัดใหม่ ทำซ้ำจนกว่าจะไม่มีตัวเลขให้อ่านแล้วเดินรายการขนาดกะทัดรัดอีกครั้งเพื่อสร้างผลลัพธ์

บรรทัดด้านล่างแสดงถึงหน่วยความจำก่อนเริ่มการดำเนินการผสานรายการ "O" คือพื้นที่ที่เก็บจำนวนเต็ม 32- บิตที่เรียงลำดับไว้ "X" เป็นพื้นที่ที่มีรายการขนาดเล็กแบบเก่า เครื่องหมาย "=" เป็นห้องส่วนขยายสำหรับรายการขนาดกะทัดรัด 7 บิตสำหรับแต่ละจำนวนเต็มใน "O" "Z" เป็นค่าใช้จ่ายแบบสุ่มอื่น ๆ

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

รูทีนการผสานเริ่มอ่านที่ "O" ซ้ายสุดและที่ซ้ายสุด "X" และเริ่มเขียนที่ซ้ายสุด "=" ตัวชี้การเขียนไม่จับตัวชี้การอ่านรายการแบบกะทัดรัดจนกว่าจำนวนเต็มใหม่ทั้งหมดจะถูกรวมกันเนื่องจากตัวชี้ทั้งสองเลื่อนไปข้างหน้า 2 บิตสำหรับแต่ละรายการย่อยและ 7 บิตสำหรับแต่ละรายการในรายการขนาดกะทัดรัดเก่าและมีพื้นที่เหลือเพียงพอสำหรับ รายการ 7 บิตสำหรับหมายเลขใหม่

ตอนที่ 2 ยัดเข้าไปใน 1M

ในการบีบโซลูชันข้างต้นเป็น 1M ฉันต้องทำให้รูปแบบรายการแบบกะทัดรัดมีขนาดกะทัดรัดขึ้นเล็กน้อย ฉันจะกำจัดหนึ่งในประเภทรายการย่อยเพื่อที่จะมีค่าส่วนหัวของรายการย่อยที่แตกต่างกันเพียง 3 ค่า จากนั้นฉันสามารถใช้ "00", "01" และ "1" เป็นค่าส่วนหัวของรายการย่อยและบันทึกไม่กี่บิต ประเภทรายการย่อยคือ:

รายการย่อยว่างเปล่าไม่มีอะไรตามมา

B Singleton มีเพียงหนึ่งรายการในรายการย่อยและ 7 บิตถัดไปถือไว้

C รายการย่อยมีตัวเลขที่แตกต่างกันอย่างน้อย 2 ตัว รายการจะถูกเก็บไว้ในลำดับที่ไม่ลดลงยกเว้นว่ารายการสุดท้ายมีค่าน้อยกว่าหรือเท่ากับรายการแรก สิ่งนี้ยอมให้มีการระบุจุดสิ้นสุดของรายการย่อย ตัวอย่างเช่นตัวเลข 2,4,6 จะถูกเก็บไว้เป็น (4,6,2) ตัวเลข 2,2,3,4,4 จะถูกจัดเก็บเป็น (2,3,4,4,2)

D รายการย่อยประกอบด้วยการทำซ้ำ 2 ครั้งขึ้นไปของหมายเลขเดียว

ค่าส่วนหัวของรายการย่อย 3 ของฉันจะเป็น "A", "B" และ "C" ดังนั้นฉันต้องการวิธีในการแสดงรายการย่อยของ D-type

สมมติว่าฉันมีส่วนหัวของรายการย่อยประเภท C ตามด้วย 3 รายการเช่น "C [17] [101] [58]" นี่ไม่สามารถเป็นส่วนหนึ่งของรายการย่อยประเภท C ที่ถูกต้องตามที่อธิบายไว้ข้างต้นเนื่องจากรายการที่สามน้อยกว่ารายการที่สอง แต่มากกว่ารายการแรก ฉันสามารถใช้โครงสร้างประเภทนี้เพื่อเป็นตัวแทนรายการย่อย D-type กล่าวโดยนัยคือทุกที่ที่ฉันมี "C {00 ?????} {1 ??????} {01 ?????}" เป็นรายการย่อยประเภท C ที่เป็นไปไม่ได้ ฉันจะใช้สิ่งนี้เพื่อแสดงรายการย่อยที่ประกอบด้วยการซ้ำ 3 ครั้งขึ้นไปของหมายเลขเดียว คำ 7 บิตสองคำแรกเข้ารหัสหมายเลข (บิต "N" ด้านล่าง) และตามด้วยศูนย์ {0100001} คำหรือมากกว่านั้นตามด้วยคำ {0100000}

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

ที่เพิ่งออกจากรายการที่ถือซ้ำ 2 หมายเลขเดียว ฉันจะเป็นตัวแทนของรูปแบบรายการย่อยประเภท C ที่เป็นไปไม่ได้อีก: "C {0 ??????} {11 ?????} {10 ?????}" มีพื้นที่เหลือเฟือสำหรับจำนวน 7 บิตใน 2 คำแรก แต่รูปแบบนี้ยาวกว่ารายการย่อยที่แสดงถึงซึ่งทำให้สิ่งต่าง ๆ ซับซ้อนขึ้นเล็กน้อย เครื่องหมายคำถามห้าข้อสุดท้ายถือได้ว่าไม่ได้เป็นส่วนหนึ่งของรูปแบบดังนั้นฉันจึงมี: "C {0NNNNNN} {11N ????} 10" เป็นรูปแบบของฉันพร้อมหมายเลขที่จะเก็บซ้ำใน "N "s นั่นยาวเกินไป 2 บิต

ฉันจะต้องยืม 2 บิตและจ่ายคืนจาก 4 บิตที่ไม่ได้ใช้ในรูปแบบนี้ เมื่ออ่านเมื่อพบ "C {0NNNNNN} {11N00AB} 10" จะแสดงผลลัพธ์ 2 หมายเลขของอินสแตนซ์ใน "N" s เขียนทับ "10" ที่ส่วนท้ายด้วยบิต A และ B และย้อนตัวชี้การอ่าน 2 เกร็ด การอ่านแบบทำลายนั้นใช้ได้สำหรับอัลกอริทึมนี้เนื่องจากแต่ละรายการขนาดกะทัดรัดจะได้รับการเดินเพียงครั้งเดียว

เมื่อเขียนรายการย่อยของการซ้ำ 2 ครั้งของตัวเลขเดียวให้เขียน "C {0NNNNNN} 11N00" และตั้งค่าตัวนับบิตที่ยืมเป็น 2 ที่การเขียนทุกครั้งที่ตัวนับบิตที่ยืมมานั้นไม่ใช่ศูนย์จะลดลงสำหรับแต่ละบิตที่เขียนและ "10" ถูกเขียนเมื่อตัวนับจำนวนเยี่ยมชมเป็นศูนย์ ดังนั้น 2 บิตถัดไปที่เขียนจะเข้าไปในช่อง A และ B แล้ว "10" จะถูกส่งไปยังจุดสิ้นสุด

ด้วยค่าส่วนหัว 3 รายการย่อยที่แสดงด้วย "00", "01" และ "1" ฉันสามารถกำหนด "1" ให้กับประเภทรายการย่อยที่ได้รับความนิยมมากที่สุด ฉันจะต้องใช้ตารางเล็ก ๆ ในการแมปค่าส่วนหัวของรายการย่อยกับประเภทรายการย่อยและฉันต้องการตัวนับเหตุการณ์สำหรับแต่ละรายการย่อยเพื่อให้ฉันรู้ว่าการแมปส่วนหัวของรายการย่อยที่ดีที่สุดคืออะไร

กรณีที่เลวร้ายที่สุดการแสดงรายการขนาดกะทัดรัดที่มีประชากรน้อยที่สุดเกิดขึ้นน้อยที่สุดเมื่อประเภทรายการย่อยทั้งหมดได้รับความนิยมเท่ากัน ในกรณีนี้ฉันบันทึก 1 บิตสำหรับทุก ๆ 3 หัวข้อย่อยดังนั้นขนาดรายการคือ 2 * 781250 + 7 * 1000000 - 781250/3 = 8302083.3 บิต การปัดเศษขึ้นเป็นขอบเขตคำ 32 บิตนั่นคือ 8302112 บิตหรือ 1037764 ไบต์

1M ลบ 2k สำหรับสถานะ TCP / IP และบัฟเฟอร์คือ 1022 * 1024 = 1046528 ไบต์ปล่อยให้ฉัน 8764 ไบต์เล่น

แต่กระบวนการเปลี่ยนการแมปส่วนหัวของรายการย่อยนั้นเป็นอย่างไร ในแผนที่หน่วยความจำด้านล่าง "Z" เป็นค่าใช้จ่ายแบบสุ่ม "=" เป็นพื้นที่ว่าง "X" เป็นรายการขนาดกะทัดรัด

ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

เริ่มอ่านที่ "X" ซ้ายสุดและเริ่มเขียนที่ซ้ายสุด "=" และทำงานได้ทันที เมื่อเสร็จสิ้นรายการขนาดกะทัดรัดจะสั้นกว่าเล็กน้อยและจะอยู่ในตำแหน่งที่ไม่ถูกต้องของหน่วยความจำ:

ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======

ดังนั้นฉันจะต้องปัดมันไปทางขวา:

ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ในกระบวนการเปลี่ยนแปลงการจับคู่ส่วนหัวส่วนหัวย่อยของรายการย่อยมากถึง 1/3 จะเปลี่ยนจาก 1 บิตเป็น 2 บิต ในกรณีที่เลวร้ายที่สุดสิ่งเหล่านี้จะอยู่ที่ส่วนหัวของรายการดังนั้นฉันจะต้องมีพื้นที่เก็บข้อมูลฟรีอย่างน้อย 781250/3 บิตก่อนที่ฉันจะเริ่มต้นซึ่งจะนำฉันกลับไปที่ข้อกำหนดหน่วยความจำของรายการขนาดกะทัดรัดรุ่นก่อนหน้า: (

ในการหลีกเลี่ยงปัญหานั้นฉันจะแบ่งรายการย่อย 781250 ออกเป็น 10 กลุ่มย่อยของรายการย่อย 78125 รายการ แต่ละกลุ่มมีการแมปส่วนหัวของรายการย่อยอิสระ การใช้ตัวอักษร A ถึง J สำหรับกลุ่ม:

ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ

แต่ละกลุ่มรายการย่อยย่อขนาดหรือลดขนาดเดิมในระหว่างการเปลี่ยนการจับคู่ส่วนหัวของรายการย่อย:

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

การขยายชั่วคราวกรณีที่เลวร้ายที่สุดของกลุ่มรายการย่อยระหว่างการเปลี่ยนแปลงการแมปคือ 78125/3 = 26042 บิตภายใต้ 4k ถ้าฉันอนุญาต 4k บวก 1037764 bytes สำหรับรายการคอมแพคที่เติมเต็มนั่นทำให้ฉัน 8764 - 4096 = 4668 bytes สำหรับ "Z" ในแผนที่หน่วยความจำ

ควรมีมากพอสำหรับตารางการแมปหัวข้อย่อย 10 รายการการนับส่วนหัวรายการย่อย 30 รายการและอีกไม่กี่ตัวนับพอยน์เตอร์และบัฟเฟอร์เล็ก ๆ ที่ฉันต้องการและพื้นที่ที่ฉันใช้โดยไม่สังเกตเช่นพื้นที่สแต็คสำหรับที่อยู่สำหรับเรียกคืน ตัวแปรท้องถิ่น

ส่วนที่ 3 ใช้เวลานานเท่าไหร่ในการรัน

ด้วยรายการขนาดกะทัดรัดที่ว่างเปล่าส่วนหัวรายการ 1 บิตจะถูกใช้สำหรับรายการย่อยที่ว่างเปล่าและขนาดเริ่มต้นของรายการจะเป็น 781250 บิต ในกรณีที่เลวร้ายที่สุดรายการจะเติบโต 8 บิตสำหรับแต่ละหมายเลขที่เพิ่มดังนั้นจำเป็นต้องมีพื้นที่ว่าง 32 + 8 = 40 บิตสำหรับแต่ละหมายเลข 32 บิตที่จะวางไว้ที่ด้านบนสุดของบัฟเฟอร์รายการจากนั้นเรียงลำดับและผสาน ในกรณีที่เลวร้ายที่สุดการเปลี่ยนการแมปส่วนหัวของรายการย่อยในการใช้พื้นที่ 2 รายการ * 781250 + 7 * - 781250/3 บิต

ด้วยนโยบายการเปลี่ยนการแมปส่วนหัวของรายการย่อยหลังจากการผสานที่ห้าทุกครั้งที่มีอย่างน้อย 800,000 หมายเลขในรายการการเรียกใช้กรณีที่แย่ที่สุดจะเกี่ยวข้องกับกิจกรรมการอ่านและเขียนรายการขนาดกะทัดรัดประมาณ 30 ล้านรายการ

ที่มา:

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


15
ฉันไม่คิดว่าจะมีวิธีแก้ปัญหาที่ดีกว่านี้ได้ (ในกรณีที่เราจำเป็นต้องทำงานกับค่าที่ไม่สามารถบีบอัดได้) แต่อันนี้อาจจะปรับปรุงเล็กน้อย ไม่จำเป็นต้องเปลี่ยนส่วนหัวของรายการย่อยระหว่างการแสดงแบบ 1 บิตและ 2 บิต แต่คุณสามารถใช้การเข้ารหัสทางคณิตศาสตร์ซึ่งลดความซับซ้อนของอัลกอริทึมและลดจำนวนบิตต่อกรณีที่เลวร้ายที่สุดจาก 1.67 เป็น 1.58 และคุณไม่จำเป็นต้องย้ายรายการขนาดเล็กในหน่วยความจำ ใช้บัฟเฟอร์แบบวงกลมแทนและเปลี่ยนเฉพาะพอยน์เตอร์
Evgeny Kluev

5
ดังนั้นในที่สุดคำถามสัมภาษณ์นั้นคืออะไร?
mlvljr

2
การปรับปรุงอื่น ๆ ที่เป็นไปได้คือการใช้รายการย่อย 100 องค์ประกอบแทนรายการย่อย 128 องค์ประกอบ (เพราะเราได้รับการแสดงตัวแบบย่อส่วนใหญ่เมื่อจำนวนรายการย่อยเท่ากับจำนวนองค์ประกอบในชุดข้อมูล) แต่ละค่าของรายการย่อยที่จะเข้ารหัสด้วยการเข้ารหัสเลขคณิต (ด้วยความถี่ที่เท่ากัน 1/100 สำหรับแต่ละค่า) สิ่งนี้สามารถบันทึกได้ประมาณ 10,000 บิตซึ่งน้อยกว่าการบีบอัดส่วนหัวของรายการย่อย
Evgeny Kluev

สำหรับกรณี C คุณพูดว่า "รายการจะถูกเก็บไว้ในลำดับที่ไม่ลดลงยกเว้นรายการสุดท้ายจะน้อยกว่าหรือเท่ากับรายการแรก" แล้วคุณจะเข้ารหัส 2,2,2,3,5 อย่างไร {2,2,3,5,2} ดูเหมือน 2,2
Rollie

1
วิธีแก้ปัญหาที่ง่ายกว่าของการเข้ารหัสส่วนหัวของรายการย่อยสามารถทำได้ด้วยอัตราส่วนการบีบอัดที่เท่ากัน 1.67 บิตต่อเฮดเดอร์ย่อยโดยไม่ต้องสลับการแมปที่ซับซ้อน คุณสามารถรวมทุก 3 subheaders ติดต่อกันด้วยกันซึ่งสามารถเข้ารหัสง่ายเป็น 5 3 * 3 * 3 = 27 < 32บิตเพราะ combined_subheader = subheader1 + 3 * subheader2 + 9 * subheader3คุณรวมพวกเขา
hynekcer

57

คำตอบของกิลมานอฟผิดไปมากในการตั้งสมมติฐาน มันเริ่มเก็งกำไรโดยใช้การวัดแบบไม่มีจุดหมายของจำนวนเต็มต่อเนื่องนับล้าน นั่นหมายความว่าไม่มีช่องว่าง ช่องว่างแบบสุ่มเหล่านั้นเล็ก แต่จริงๆแล้วทำให้มันเป็นความคิดที่ดี

ลองด้วยตัวคุณเอง รับ 1 ล้านสุ่ม 27 บิตจำนวนเต็มเรียงลำดับบีบอัดด้วย7-Zip , xz ไม่ว่าคุณต้องการ LZMA ใด ผลลัพธ์มีขนาดเกิน 1.5 MB สถานที่ตั้งอยู่ด้านบนคือการบีบอัดของหมายเลขลำดับ แม้การเข้ารหัสเดลต้าที่เป็นมากกว่า 1.1 MB และไม่เป็นไรที่จะใช้การบีบอัด RAM มากกว่า 100 MB ดังนั้นแม้จำนวนเต็มบีบอัดไม่พอดีกับปัญหาที่เกิดขึ้นและไม่เคยคิดใช้ RAM เวลาทำงาน

มันทำให้ฉันเศร้าใจว่าทำไมผู้คนถึงลงกราฟิกสวย ๆ และหาเหตุผลเข้าข้างตนเอง

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

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

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

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

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

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

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

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

}

ตอนนี้บีบอัด ints.bin ด้วย 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
อัลกอริทึมใด ๆ ที่เกี่ยวข้องกับการบีบอัดข้อมูลจากพจนานุกรมนั้นเกินกว่าจะหน่วงเหนี่ยวฉันได้เขียนโค้ดกำหนดเองสองสามแบบและสิ่งที่พวกเขาใช้หน่วยความจำค่อนข้างน้อยเพื่อวางตารางแฮชของตัวเอง (และไม่มี HashMap ใน java ทางออกที่ใกล้เคียงที่สุดคือการเข้ารหัสเดลต้าที่มีความยาวบิตแปรผันและตีกลับแพ็กเก็ต TCP ที่คุณไม่ชอบ เพียร์จะส่งสัญญาณอีกครั้งยังคงดีที่สุด
bestsss

@ bests yeah! ลองดูคำตอบที่กำลังดำเนินการล่าสุดของฉัน ฉันคิดว่ามันอาจเป็นไปได้
alecco

3
ขออภัยดูเหมือนจะไม่ตอบคำถามเช่นกัน
n611x007

@naxa ใช่คำตอบ: ไม่สามารถทำได้ภายในพารามิเตอร์ของคำถามเดิม สามารถทำได้ก็ต่อเมื่อการกระจายของตัวเลขมีค่าเอนโทรปีต่ำมาก
alecco

1
คำตอบทั้งหมดนี้แสดงให้เห็นว่ารูทีนการบีบอัดมาตรฐานมีปัญหาในการบีบอัดข้อมูลที่ต่ำกว่า 1MB อาจมีหรือไม่มีรูปแบบการเข้ารหัสที่สามารถบีบอัดข้อมูลเพื่อต้องการน้อยกว่า 1MB แต่คำตอบนี้ไม่ได้พิสูจน์ว่าไม่มีรูปแบบการเข้ารหัสที่จะบีบอัดข้อมูลมากขนาดนี้
Itsme2003

41

ฉันคิดว่าวิธีหนึ่งที่จะคิดเกี่ยวกับสิ่งนี้คือจากมุมมอง combinatorics: มีการเรียงลำดับหมายเลขเรียงลำดับที่เป็นไปได้จำนวนเท่าใด ถ้าเราให้ชุดค่าผสม 0,0,0, .... , 0 รหัส 0, และ 0,0,0, ... , 1 รหัส 1, และ 99999999, 99999999, ... 99999999 รหัส N, N คืออะไร กล่าวอีกนัยหนึ่งว่าพื้นที่ผลลัพธ์มีขนาดเท่าใด

วิธีหนึ่งที่จะคิดเกี่ยวกับสิ่งนี้คือการสังเกตว่านี่เป็นการอ้างถึงปัญหาของการหาจำนวนเส้นทางแบบโมโนโทนิกในตาราง N x M ซึ่ง N = 1,000,000 และ M = 100,000,000 กล่าวอีกนัยหนึ่งถ้าคุณมีตารางที่มีความกว้าง 1,000,000 และสูง 100,000,000 มีเส้นทางที่สั้นที่สุดจากด้านล่างซ้ายไปขวาบนมีกี่เส้นทาง เส้นทางที่สั้นที่สุดของหลักสูตรต้องการให้คุณย้ายไปทางขวาหรือขึ้น (ถ้าคุณเลื่อนลงหรือไปทางซ้ายคุณจะยกเลิกความคืบหน้าก่อนหน้านี้) หากต้องการดูว่านี่เป็นปัญหาของการเรียงลำดับหมายเลขของเราอย่างไรให้สังเกตสิ่งต่อไปนี้:

คุณสามารถจินตนาการว่าขาในแนวนอนของเราเป็นตัวเลขในการสั่งซื้อของเราโดยที่ตำแหน่ง Y ของขาแสดงถึงค่า

ป้อนคำอธิบายรูปภาพที่นี่

ดังนั้นหากเส้นทางนั้นเลื่อนไปทางขวาจนสุดจนสุดแล้วก็กระโดดไปทางด้านบนนั่นก็เท่ากับการเรียงลำดับ 0,0,0, ... , 0 ถ้ามันเริ่มต้นด้วยการกระโดดไปทางด้านบนสุดแล้วเลื่อนไปทางขวา 1,000,000 ครั้งนั่นเท่ากับ 99999999,99999999, ... , 99999999 เส้นทางที่มันเคลื่อนที่ทันทีหนึ่งจากนั้นขึ้นหนึ่งครั้ง จากนั้นขึ้นหนึ่งครั้ง ฯลฯ ไปจนสุด (จากนั้นก็ต้องกระโดดไปจนถึงด้านบน) เทียบเท่ากับ 0,1,2,3, ... , 999999

โชคดีสำหรับเราที่ปัญหานี้ได้รับการแก้ไขแล้วตารางดังกล่าวมีเส้นทาง (N + M) เลือก (M):

(1,000,000 + 100,000,000) เลือก (100,000,000) ~ = 2.27 * 10 ^ 2436455

N จึงเท่ากับ 2.27 * 10 ^ 2436455 และรหัส 0 แทน 0,0,0, ... , 0 และรหัส 2.27 * 10 ^ 2436455 และการเปลี่ยนแปลงบางอย่างแทน 99999999,99999999, ... , 99999999

ในการจัดเก็บตัวเลขทั้งหมดตั้งแต่ 0 ถึง 2.27 * 10 ^ 2436455 คุณต้องมี lg2 (2.27 * 10 ^ 2436455) = 8.0937 * 10 ^ 6 บิต

1 megabyte = 8388608 บิต> 8093700 บิต

ดังนั้นอย่างน้อยเราก็มีพื้นที่เพียงพอที่จะเก็บผล! ตอนนี้แน่นอนว่าบิตที่น่าสนใจกำลังทำการเรียงลำดับตามตัวเลขที่ส่งเข้ามาไม่แน่ใจว่าวิธีที่ดีที่สุดในการนี้คือเมื่อเรามีเหลือ 294908 บิต ฉันคิดว่าเทคนิคที่น่าสนใจในแต่ละจุดจะถือว่านั่นคือการสั่งซื้อทั้งหมดการค้นหารหัสสำหรับการสั่งซื้อนั้นและเมื่อคุณได้รับหมายเลขใหม่กลับไปและอัปเดตรหัสก่อนหน้า คลื่นมือคลื่นมือ


นี่มันโบกมือจริง ๆ ในทางทฤษฎีแล้วนี่เป็นวิธีแก้ปัญหาเพราะเราสามารถเขียนเครื่องจักรขนาดใหญ่ - แต่ยัง จำกัด - รัฐ; ในทางกลับกันขนาดของตัวชี้คำสั่งสำหรับเครื่องสถานะขนาดใหญ่นั้นอาจมากกว่าหนึ่งเมกะไบต์แสดงผลนี้ไม่ใช่แบบเริ่มต้น มันต้องใช้ความคิดมากกว่านี้เล็กน้อยเพื่อแก้ปัญหาที่กำหนด เราไม่เพียง แต่จะเป็นตัวแทนของรัฐทั้งหมด แต่ยังรวมถึงรัฐในช่วงเปลี่ยนผ่านทั้งหมดที่จำเป็นในการคำนวณว่าจะทำอย่างไรกับหมายเลขอินพุตต่อไปที่ได้รับ
Daniel Wagner

4
ฉันคิดว่าคำตอบอื่น ๆ นั้นลึกซึ้งยิ่งขึ้นเกี่ยวกับการโบกมือของพวกเขา เนื่องจากตอนนี้เรารู้ขนาดของพื้นที่ผลลัพธ์แล้วเราจึงรู้ว่าต้องใช้พื้นที่เท่าใด ไม่มีคำตอบอื่นใดที่สามารถเก็บทุกคำตอบที่เป็นไปได้ในสิ่งที่เล็กกว่า 8093700 บิตเนื่องจากนั่นคือจำนวนขั้นสุดท้ายที่สามารถมีได้ การบีบอัด (สถานะขั้นสุดท้าย) อย่างดีที่สุดบางครั้งอาจลดพื้นที่ได้ แต่จะมีคำตอบบางคำตอบที่ต้องการพื้นที่เต็มรูปแบบเสมอ (ไม่มีอัลกอริทึมการบีบอัดใด ๆ
Francisco Ryan Tolmasky I

คำตอบอื่น ๆ อีกหลายข้อได้กล่าวถึงขอบเขตล่างที่ยากอยู่แล้ว (เช่นประโยคที่สองของคำตอบของผู้ถามคำถามเดิม) ดังนั้นฉันไม่แน่ใจว่าฉันจะเห็นว่าคำตอบนี้เพิ่มอะไรใน gestalt
Daniel Wagner

คุณหมายถึง 3.5M เพื่อจัดเก็บสตรีมดิบหรือไม่ (ถ้าไม่ขอโทษและไม่สนใจคำตอบนี้) ถ้าเป็นเช่นนั้นนั่นคือขอบเขตล่างที่ไม่เกี่ยวข้องอย่างสมบูรณ์ ขอบเขตล่างของฉันคือพื้นที่ผลลัพธ์ที่จะเกิดขึ้นเท่าไหร่ขอบเขตล่างคือจำนวนพื้นที่อินพุตที่จะใช้หากจำเป็นต้องเก็บไว้ - เนื่องจากคำถามถูกใช้เป็นกระแสในการเชื่อมต่อ TCP ไม่ชัดเจนว่าคุณจำเป็นต้องทำในความเป็นจริงคุณอาจกำลังอ่านหมายเลขหนึ่งครั้งและอัปเดตสถานะของคุณดังนั้นจึงไม่จำเป็นต้องใช้ 3.5M - ไม่ว่าจะด้วยวิธีใด 3.5 นั้นเป็นมุมฉากของการคำนวณนี้
Francisco Ryan Tolmasky ฉัน

"มีประมาณ 2 ถึงกำลัง 8093729.5 วิธีที่แตกต่างในการเลือก 1 ล้าน 8 หลักโดยใช้ตัวเลขซ้ำและสั่งซ้ำ" <- จากคำตอบของผู้ถามคำถามเดิม ไม่ทราบว่าจะชัดเจนเกี่ยวกับสิ่งที่ผูกพันฉันกำลังพูดถึง ฉันพูดถึงประโยคนี้โดยเฉพาะในความคิดเห็นล่าสุดของฉัน
Daniel Wagner

20

คำแนะนำของฉันที่นี่เป็นหนี้จำนวนมากสำหรับโซลูชันของ Dan

ก่อนอื่นฉันคิดว่าโซลูชันจะต้องจัดการกับรายการอินพุตที่เป็นไปได้ทั้งหมด ฉันคิดว่าคำตอบที่ได้รับความนิยมไม่ได้ทำให้สมมติฐานนี้

เป็นที่ทราบกันดีว่ารูปแบบการบีบอัดแบบไม่สูญเสียจะลดขนาดของอินพุตทั้งหมด

คำตอบยอดนิยมทั้งหมดสมมติว่าพวกเขาจะสามารถใช้การบีบอัดที่มีประสิทธิภาพเพียงพอที่จะให้พื้นที่เพิ่มเติม ในความเป็นจริงกลุ่มของพื้นที่พิเศษขนาดใหญ่พอที่จะเก็บบางส่วนของรายการที่เสร็จสมบูรณ์บางส่วนของพวกเขาในรูปแบบที่ไม่มีการบีบอัดและช่วยให้พวกเขาดำเนินการเรียงลำดับของพวกเขา นี่เป็นเพียงสมมติฐานที่ไม่ดี

สำหรับโซลูชันดังกล่าวใครก็ตามที่มีความรู้ว่าพวกเขาบีบอัดข้อมูลนั้นจะสามารถออกแบบข้อมูลอินพุตบางตัวที่ไม่บีบอัดได้ดีสำหรับโครงร่างนี้และ "ทางออก" จะมีแนวโน้มที่จะแตกเนื่องจากพื้นที่ไม่เพียงพอ

แต่ฉันใช้วิธีการทางคณิตศาสตร์ ผลลัพธ์ที่เป็นไปได้ของเราคือรายการทั้งหมดของความยาว LEN ที่ประกอบด้วยองค์ประกอบในช่วง 0..MAX ที่นี่ LEN คือ 1,000,000 และ MAX ของเราคือ 100,000,000

สำหรับ LEN และ MAX ตามอำเภอใจจำนวนบิตที่จำเป็นในการเข้ารหัสสถานะนี้คือ:

Log2 (MAX Multichoose LEN)

ดังนั้นสำหรับตัวเลขของเราเมื่อเราได้รับและจัดเรียงเสร็จแล้วเราจะต้องบิตอย่างน้อย Log2 (100,000,000 MC 1,000,000) บิตเพื่อเก็บผลลัพธ์ของเราในวิธีที่สามารถแยกความแตกต่างของผลลัพธ์ที่เป็นไปได้ทั้งหมด

นี่คือ ~ = 988kb ดังนั้นเราจึงมีพื้นที่เพียงพอที่จะเก็บผลของเรา จากมุมมองนี้มันเป็นไปได้

[ลบการท่องเที่ยวแบบไม่มีจุดหมายแล้วตอนนี้มีตัวอย่างที่ดีกว่าอยู่ ... ]

คำตอบที่ดีที่สุดอยู่ที่นี่

คำตอบที่ดีอีกข้ออยู่ที่นี่และโดยทั่วไปใช้การเรียงลำดับการแทรกเป็นฟังก์ชันเพื่อขยายรายการโดยองค์ประกอบหนึ่ง (บัฟเฟอร์องค์ประกอบบางอย่างและเรียงลำดับล่วงหน้าเพื่อให้การแทรกมากกว่าหนึ่งครั้งช่วยประหยัดเวลา) ใช้การเข้ารหัสสถานะแบบกระชับที่ดีเช่นกันที่ฝากข้อมูลของเจ็ดบิต


สนุกกับการอ่านคำตอบของคุณเองอีกครั้งในวันถัดไป ... ดังนั้นในขณะที่คำตอบยอดนิยมนั้นไม่ถูกต้องหนึ่งstackoverflow.com/a/12978097/1763801 ที่ยอมรับนั้นค่อนข้างดี โดยทั่วไปใช้การเรียงลำดับการแทรกเป็นฟังก์ชั่นในการใช้รายการ LEN-1 และส่งคืน LEN ใช้ประโยชน์จากความจริงที่ว่าถ้าคุณเตรียมชุดเล็กไว้ล่วงหน้าคุณสามารถใส่ชุดทั้งหมดในครั้งเดียวเพื่อเพิ่มประสิทธิภาพ การเป็นตัวแทนของรัฐนั้นค่อนข้างกะทัดรัด (ถังจำนวน 7 บิต) ดีกว่าข้อเสนอแนะที่เป็นคลื่นมือของฉันและใช้งานง่ายขึ้น คอมพ์ทางภูมิศาสตร์ของฉันคิดว่าเป็น bollocks ขอโทษด้วย
davec

1
ฉันคิดว่าเลขคณิตของคุณค่อนข้างน้อย ฉันได้รับ lg2 (100999999! / (99999999! * 1000000!)) = 1011718.55
NovaDenizen

ใช่ขอบคุณมันคือ 988kb ไม่ใช่ 965 ฉันรู้สึกเลอะเทอะในแง่ของ 1024 และ 1,000 เรายังเหลือประมาณ 35kb เพื่อเล่นกับ ฉันเพิ่มลิงก์ไปยังการคำนวณทางคณิตศาสตร์ในคำตอบ
davec

18

สมมติว่างานนี้เป็นไปได้ ก่อนที่จะส่งออกจะมีการแสดงในหน่วยความจำของตัวเลขเรียงล้าน มีตัวแทนที่แตกต่างกันจำนวนเท่าไหร่? เนื่องจากอาจมีตัวเลขซ้ำเราจึงไม่สามารถใช้ nCr (เลือก) ได้ แต่มีการดำเนินการที่เรียกว่ามัลติคอลที่ใช้งานได้กับหลายชุด

  • มี2.2e2436455วิธีในการเลือกตัวเลขหนึ่งล้านตัวในช่วง 0..99,999,999
  • ต้องใช้8,093,730บิตเพื่อแสดงชุดค่าผสมที่เป็นไปได้ทั้งหมดหรือ 1,011,717 ไบต์

ในทางทฤษฎีแล้วมันอาจเป็นไปได้ถ้าคุณสามารถหาตัวแทนที่มีสติ (พอ) ของรายการที่เรียงลำดับตัวเลข ตัวอย่างเช่นการแทนค่าวิกลจริตอาจต้องใช้ตารางการค้นหา 10MB หรือโค้ดหลายพันบรรทัด

อย่างไรก็ตามหาก "1M RAM" หมายถึงหนึ่งล้านไบต์แสดงว่ามีพื้นที่ไม่เพียงพอ ความจริงที่ว่าหน่วยความจำเพิ่มขึ้น 5% ทำให้เป็นไปได้ในทางทฤษฎีแนะนำให้ฉันว่าการเป็นตัวแทนจะต้องมีประสิทธิภาพมากและอาจไม่ได้สติ


จำนวนวิธีในการเลือกตัวเลขหนึ่งล้านตัว (2.2e2436455) ใกล้เคียงกับ (256 ^ (1024 * 988)) ซึ่งคือ (2.0e2436445) ดังนั้นถ้าคุณนำหน่วยความจำขนาด 32 KB ออกไปจาก 1M ปัญหาจะไม่สามารถแก้ไขได้ โปรดจำไว้ว่าสงวนอย่างน้อย 3 KB
johnwbyrd

แน่นอนว่าข้อมูลนี้สุ่มโดยสมบูรณ์ เท่าที่เรารู้ว่ามันเป็น แต่ฉันแค่บอก :)
Thorarin

วิธีทั่วไปในการแสดงจำนวนสถานะที่เป็นไปได้นี้คือการบันทึกฐาน 2 และรายงานจำนวนบิตที่ต้องการเพื่อแสดง
NovaDenizen

@Thorarin, yup, ฉันไม่เห็นจุดใดใน "ทางออก" ที่ใช้ได้กับอินพุตบางตัวเท่านั้น
ด่าน

12

(คำตอบเดิมของฉันผิดขอโทษสำหรับคณิตศาสตร์ที่ไม่ดีดูด้านล่างตัวแบ่ง)

แล้วเรื่องนี้ล่ะ

27 บิตแรกจัดเก็บหมายเลขต่ำสุดที่คุณเห็นจากนั้นความแตกต่างกับหมายเลขถัดไปที่เห็นเข้ารหัสดังนี้: 5 บิตเพื่อเก็บจำนวนบิตที่ใช้ในการจัดเก็บความแตกต่างแล้วความแตกต่าง ใช้ 00000 เพื่อระบุว่าคุณเห็นหมายเลขนั้นอีกครั้ง

วิธีนี้ใช้งานได้เนื่องจากเมื่อมีการใส่ตัวเลขมากขึ้นความแตกต่างเฉลี่ยระหว่างตัวเลขจึงลดลงดังนั้นคุณจึงใช้บิตน้อยลงในการจัดเก็บผลต่างเมื่อคุณเพิ่มตัวเลขมากขึ้น ฉันเชื่อว่านี่เรียกว่ารายการเดลต้า

กรณีที่เลวร้ายที่สุดที่ฉันนึกได้คือตัวเลขทั้งหมดเว้นระยะเท่า ๆ กัน (โดย 100) เช่นสมมติว่า 0 คือหมายเลขแรก:

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

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

Reddit เพื่อช่วยเหลือ!

หากสิ่งที่คุณต้องทำคือจัดเรียงปัญหานี้จะง่าย ใช้เวลา 122k (1 ล้านบิต) ในการจัดเก็บตัวเลขที่คุณเห็น (0 บิตบนถ้าเห็น 0, 2300 บิตบนถ้าเห็น 2300 เป็นต้น

คุณอ่านตัวเลขเก็บไว้ในเขตบิตแล้วเลื่อนบิตออกไปขณะที่ยังคงนับ

แต่คุณต้องจำไว้ว่ามีกี่คนที่ได้เห็น ฉันได้รับแรงบันดาลใจจากคำตอบของรายการย่อยด้านบนเพื่อให้ได้รูปแบบนี้:

แทนที่จะใช้หนึ่งบิตให้ใช้ 2 หรือ 27 บิต:

  • 00 หมายความว่าคุณไม่เห็นหมายเลข
  • 01 หมายความว่าคุณเห็นมันครั้งเดียว
  • 1 หมายความว่าคุณเห็นมันและ 26 บิตถัดไปคือการนับจำนวนครั้ง

ฉันคิดว่ามันใช้งานได้: หากไม่มีรายการซ้ำคุณมีรายชื่อ 244k ในกรณีที่เลวร้ายที่สุดที่คุณเห็นแต่ละหมายเลขสองครั้ง (ถ้าคุณเห็นหนึ่งหมายเลขสามครั้งจะทำให้รายการที่เหลือสั้นลงสำหรับคุณ) นั่นหมายความว่าคุณเห็น 50,000 รายการมากกว่าหนึ่งครั้งและคุณเห็นรายการ 950,000 0 หรือ 1 ครั้ง

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

คุณสามารถทำการปรับปรุงเพิ่มเติมหากคุณใช้การเข้ารหัสต่อไปนี้:

0 หมายความว่าคุณไม่เห็นหมายเลข 10 หมายความว่าคุณเห็นครั้งเดียว 11 เป็นวิธีการนับของคุณ

ซึ่งโดยเฉลี่ยจะส่งผลให้มีพื้นที่จัดเก็บ 280.7k

แก้ไข: คณิตศาสตร์เช้าวันอาทิตย์ของฉันผิด

กรณีที่เลวร้ายที่สุดคือเราเห็นตัวเลข 500,000 ครั้งสองครั้งดังนั้นคณิตศาสตร์จึงกลายเป็น:

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

การเข้ารหัสทางเลือกส่งผลให้หน่วยเก็บข้อมูลเฉลี่ยของ

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

: (


1
ดีไม่ตั้งแต่ตัวเลขที่สองจะเป็น
500,000

อาจเพิ่มบางสื่อกลางเช่นเดียวกับที่ 11 หมายถึงคุณเห็นจำนวนมากถึง 64 ครั้ง (ใช้ 6 บิตถัดไป) และ 11000000 หมายถึงใช้อีก 32 บิตเพื่อเก็บจำนวนครั้งที่คุณเห็น
τεκ

10
คุณได้หมายเลข "1 ล้านบิต" มาจากไหน คุณบอกว่า 2300 บิตแสดงว่าเห็น 2300 หรือไม่ (ฉันคิดว่าคุณหมายถึง 2301st) บิตใดที่แสดงให้เห็นว่า 99,999,999 ถูกมองเห็น (ตัวเลข 8 หลักที่ใหญ่ที่สุด)? น่าจะเป็นบิตที่ 100 ล้าน
user94559

คุณได้หนึ่งล้านและร้อยล้านย้อนหลัง เวลาที่ค่าส่วนใหญ่สามารถเกิดขึ้นได้คือ 1 ล้านและคุณต้องการเพียง 20 บิตเพื่อแสดงจำนวนค่าที่เกิดขึ้น ในทำนองเดียวกันคุณต้องการฟิลด์ 100,000,000 บิต (ไม่ใช่ 1 ล้านบิต) สำหรับแต่ละค่าที่เป็นไปได้
ทิมอาร์

เอ่อ 27 + 1000000 * (5 + 7) = 12000027 บิต = 1.43M ไม่ใช่ 427K
Daniel Wagner

10

มีวิธีแก้ไขปัญหานี้สำหรับอินพุตที่เป็นไปได้ทั้งหมด โกง.

  1. อ่านค่า m ผ่าน TCP โดยที่ m ใกล้กับค่าสูงสุดที่สามารถเรียงลำดับในหน่วยความจำได้อาจเป็น n / 4
  2. เรียงลำดับหมายเลข 250,000 (หรือมากกว่านั้น) แล้วส่งออก
  3. ทำซ้ำอีก 3 ไตรมาส
  4. ปล่อยให้ผู้รับรวม 4 รายการของตัวเลขที่ได้รับเมื่อดำเนินการ (มันไม่ช้ากว่าการใช้รายการเดียว)

7

ฉันจะลองต้นไม้ Radix หากคุณสามารถเก็บข้อมูลไว้ในทรีคุณสามารถทำการสำรวจตามลำดับเพื่อส่งข้อมูล

ฉันไม่แน่ใจว่าคุณสามารถปรับขนาดให้เป็น 1MB ได้ แต่ฉันคิดว่ามันน่าลอง


7

คุณใช้คอมพิวเตอร์ประเภทใด อาจไม่มีที่เก็บข้อมูลภายใน "ปกติ" อื่น ๆ แต่มีหน่วยความจำวิดีโอหรือไม่ 1 ล้านพิกเซล x 32 บิตต่อพิกเซล (พูด) ใกล้เคียงกับขนาดอินพุตข้อมูลที่คุณต้องการ

(ฉันส่วนใหญ่ถามในหน่วยความจำของพีซี Acorn RISCเก่าซึ่งสามารถ 'ยืม' VRAM เพื่อขยาย RAM ระบบที่มีอยู่หากคุณเลือกโหมดหน้าจอความละเอียดต่ำหรือสีต่ำ!) สิ่งนี้ค่อนข้างมีประโยชน์บนเครื่องที่มี RAM ปกติเพียงไม่กี่ MB


1
สนใจที่จะแสดงความคิดเห็น downvoter? - ฉันแค่พยายามยืดความชัดเจนของคำถาม (เช่นโกงอย่างสร้างสรรค์ ;-)
DNA

อาจไม่มีคอมพิวเตอร์เลยเนื่องจากหัวข้อที่เกี่ยวข้องกับข่าวแฮ็กเกอร์กล่าวถึงครั้งนี้ว่าเป็นคำถามสัมภาษณ์ของ Google
mlvljr

1
ใช่ - ฉันตอบก่อนคำถามถูกแก้ไขเพื่อระบุว่าเป็นคำถามสัมภาษณ์!
DNA

6

การแสดงแผนผัง Radix จะเข้ามาใกล้กับการจัดการปัญหานี้เนื่องจากต้นไม้ Radix ใช้ประโยชน์จาก "การบีบอัดคำนำหน้า" แต่มันยากที่จะเข้าใจถึงการแทนทรี radix ที่สามารถแทนโหนดเดี่ยวในหนึ่งไบต์ - สองอาจเป็นเรื่องเกี่ยวกับขีด จำกัด

แต่ไม่ว่าข้อมูลจะถูกแสดงอย่างไรเมื่อมีการจัดเรียงข้อมูลก็สามารถเก็บไว้ในรูปแบบที่มีการบีบอัดคำนำหน้าโดยที่ตัวเลข 10, 11 และ 12 จะแสดงโดยใช้คำว่า 001b, 001b, 001b แสดงถึงการเพิ่มขึ้น 1 จากหมายเลขก่อนหน้า บางที 10101b อาจแสดงถึงการเพิ่ม 5, 1101001b เพิ่มขึ้น 9, เป็นต้น


6

มีค่า 10 ^ 6 อยู่ในช่วงของ 10 ^ 8 ดังนั้นจึงมีหนึ่งค่าต่อคะแนนโค้ดหนึ่งร้อยโดยเฉลี่ย เก็บระยะทางจากจุดที่ N ไปที่ (N + 1) th ค่าที่ซ้ำกันมีการข้ามเป็น 0 ซึ่งหมายความว่าการข้ามนั้นต้องการค่าเฉลี่ยต่ำกว่า 7 บิตในการจัดเก็บดังนั้นหนึ่งล้านคนจะมีความสุขพอดีกับที่เก็บข้อมูล 8 ล้านบิตของเรา

การข้ามเหล่านี้จำเป็นต้องเข้ารหัสเป็นบิตสตรีมโดยการเข้ารหัสของ Huffman การแทรกคือการวนซ้ำผ่านบิตสตรีมและเขียนใหม่หลังจากค่าใหม่ เอาท์พุทโดยวนซ้ำและเขียนค่าโดยนัย สำหรับการปฏิบัติจริงมันอาจจะต้องการที่จะทำตามพูด 10 ^ 4 รายการครอบคลุม 10 คะแนนรหัส 4 ^ 4 (และค่าเฉลี่ย 100 ค่า) แต่ละ

ต้นไม้ Huffman ที่ดีสำหรับการสุ่มข้อมูลสามารถสร้างการนิรนัยโดยการสมมติการกระจายแบบปัวซง (ค่าเฉลี่ย = ความแปรปรวน = 100) บนความยาวของการข้าม แต่สถิติจริงสามารถเก็บไว้ในอินพุตและใช้เพื่อสร้างทรีที่เหมาะสม กรณีทางพยาธิวิทยา


5

ฉันมีคอมพิวเตอร์ที่มี RAM 1M และไม่มีที่จัดเก็บในตัวเครื่องอื่น

อีกวิธีหนึ่งในการโกง: คุณสามารถใช้ที่เก็บข้อมูลนอกเครือข่าย (เครือข่าย) แทน (คำถามของคุณไม่ได้ห้ามสิ่งนี้) และเรียกบริการเครือข่ายที่สามารถใช้การรวมที่ใช้ดิสก์ได้โดยตรง (หรือ RAM เพียงพอที่จะเรียงลำดับในหน่วยความจำเนื่องจากคุณ เพียงแค่ต้องยอมรับหมายเลข 1M) โดยไม่จำเป็นต้องแก้ปัญหา

นี่อาจเป็นการโกง แต่ก็ไม่ชัดเจนว่าคุณกำลังมองหาวิธีแก้ปัญหาในโลกแห่งความจริงหรือเป็นปริศนาที่เชิญก้มกฏ ... ถ้าต่อมาการโกงง่ายๆอาจได้ผลลัพธ์ที่ดีกว่าคอมเพล็กซ์ แต่วิธีการแก้ปัญหา "ของแท้" (ซึ่งเป็นที่คนอื่นได้ชี้ให้เห็นจะสามารถทำงานได้กับอินพุตที่บีบอัดได้)


5

ฉันคิดว่าวิธีแก้ปัญหาคือการรวมเทคนิคต่าง ๆ จากการเข้ารหัสวิดีโอนั่นคือการแปลงโคไซน์ไม่ต่อเนื่อง ในวิดีโอดิจิทัลแทนที่จะบันทึกการเปลี่ยนแปลงความสว่างหรือสีของวิดีโอเป็นค่าปกติเช่น 110 112 115 116 แต่ละค่าจะถูกลบออกจากครั้งสุดท้าย (คล้ายกับการเข้ารหัสความยาวรัน) 110 112 115 116 กลายเป็น 110 2 3 1. ค่า 2 2 1 ต้องการบิตน้อยกว่าต้นฉบับ

ดังนั้นสมมติว่าเราสร้างรายการค่าอินพุตเมื่อมาถึงซ็อกเก็ต เรากำลังจัดเก็บในแต่ละองค์ประกอบไม่ใช่ค่า แต่เป็นการชดเชยออฟก่อนหน้า เราจัดเรียงตามที่เราไปดังนั้นค่าออฟเซ็ตจะเป็นค่าบวกเท่านั้น แต่อ็อฟเซ็ตอาจมีความกว้าง 8 หลักทศนิยมซึ่งพอดีใน 3 ไบต์ แต่ละองค์ประกอบต้องมีขนาดไม่เกิน 3 ไบต์ดังนั้นเราต้องแพ็คสิ่งเหล่านี้ เราสามารถใช้บิตบนสุดของแต่ละไบต์เป็น "บิตต่อเนื่อง" ซึ่งแสดงว่าไบต์ถัดไปเป็นส่วนหนึ่งของตัวเลขและ 7 บิตล่างของแต่ละไบต์จะต้องรวมกัน ศูนย์ใช้ได้สำหรับการทำซ้ำ

เมื่อรายการเติมเต็มตัวเลขควรจะเข้าใกล้กันมากขึ้นหมายถึงค่าเฉลี่ยเพียง 1 ไบต์เท่านั้นที่ใช้เพื่อกำหนดระยะทางไปยังค่าถัดไป ค่า 7 บิตและออฟเซ็ต 1 บิตหากสะดวก แต่อาจมีจุดที่น่าสนใจที่ต้องน้อยกว่า 8 บิตสำหรับค่า "ดำเนินการต่อ"

อย่างไรก็ตามฉันทำการทดลองบางอย่าง ฉันใช้ตัวสร้างตัวเลขแบบสุ่มและฉันสามารถใส่ตัวเลขทศนิยม 8 หลักเรียงกันเป็นล้านหลักให้เป็นประมาณ 1279000 ไบต์ ช่องว่างเฉลี่ยระหว่างแต่ละหมายเลขมีความสม่ำเสมอ 99 ...

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

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

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

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

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

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

4

เราสามารถเล่นกับเครือข่ายสแต็คเพื่อส่งตัวเลขตามลำดับก่อนที่เราจะมีตัวเลขทั้งหมด หากคุณส่งข้อมูล 1M, TCP / IP จะแบ่งออกเป็น 1500 ไบต์แพ็คเก็ตและสตรีมเพื่อให้เป้าหมาย แต่ละแพ็คเก็ตจะได้รับหมายเลขลำดับ

เราสามารถทำได้ด้วยมือ ก่อนที่เราจะเติม RAM ของเราเราสามารถเรียงลำดับสิ่งที่เรามีและส่งรายการไปยังเป้าหมายของเรา แต่ปล่อยให้รูอยู่ในลำดับของเรารอบแต่ละหมายเลข จากนั้นประมวลผล 1/2 ของตัวเลขในลักษณะเดียวกันโดยใช้รูเหล่านั้นตามลำดับ

สแต็กเครือข่ายที่ปลายสุดจะรวบรวมสตรีมข้อมูลที่ได้ตามลำดับก่อนที่จะส่งมอบให้กับแอปพลิเคชัน

ใช้เครือข่ายเพื่อทำการเรียงลำดับการผสาน นี่คือแฮ็คทั้งหมด แต่ฉันได้รับแรงบันดาลใจจากแฮ็คเครือข่ายอื่น ๆ ที่ระบุไว้ก่อนหน้านี้


4

แนวทางของ Google (ไม่ดี) จากเธรด HN เก็บจำนวนสไตล์ RLE

โครงสร้างข้อมูลเริ่มต้นของคุณคือ '99999999: 0' (ศูนย์ทั้งหมดไม่เห็นตัวเลขใด ๆ ) จากนั้นสมมติว่าคุณเห็นหมายเลข 3,866,344 ดังนั้นโครงสร้างข้อมูลของคุณจะกลายเป็น '3866343: 0,1: 1,96133654: 0' สามารถดูว่าตัวเลขจะสลับกันระหว่างจำนวนศูนย์บิตและจำนวนบิต '1' เสมอดังนั้นคุณสามารถสมมติว่าเลขคี่แทน 0 บิตและเลขคู่ 1 บิต สิ่งนี้กลายเป็น (3866343,1,96133654)

ดูเหมือนว่าปัญหาของพวกเขาจะไม่ครอบคลุมรายการที่ซ้ำกัน แต่สมมติว่าพวกเขาใช้ "0: 1" สำหรับรายการที่ซ้ำกัน

ปัญหาใหญ่ # 1: แทรกสำหรับจำนวนเต็ม 1M จะใช้เวลานาน

ปัญหาใหญ่ # 2: เช่นเดียวกับโซลูชั่นการเข้ารหัสเดลต้าล้วนการแจกแจงบางอย่างไม่สามารถครอบคลุมได้ด้วยวิธีนี้ ตัวอย่างเช่นจำนวนเต็ม 1m ที่มีระยะทาง 0:99 (เช่น +99 แต่ละรายการ) ตอนนี้คิดว่าเหมือนกัน แต่ด้วยระยะทางที่สุ่มในช่วงของ 0:99 (หมายเหตุ: 99999999/1000000 = 99.99)

วิธีการของ Google มีทั้งแบบไม่คู่ควร (ช้า) และไม่ถูกต้อง แต่เพื่อป้องกันปัญหาของพวกเขาอาจแตกต่างกันเล็กน้อย


3

ในการแทนอาร์เรย์ที่เรียงลำดับคุณสามารถเก็บองค์ประกอบแรกและความแตกต่างระหว่างองค์ประกอบที่อยู่ติดกัน ด้วยวิธีนี้เรามีความกังวลเกี่ยวกับการเข้ารหัสองค์ประกอบ 10 ^ 6 ที่สามารถสรุปได้มากถึง 10 ^ 8 ขอเรียกนี้D การเข้ารหัสองค์ประกอบของDหนึ่งสามารถใช้รหัส Huffman พจนานุกรมสำหรับรหัส Huffman สามารถสร้างได้ในระหว่างการเดินทางและอาร์เรย์จะอัปเดตทุกครั้งที่มีการแทรกรายการใหม่ในอาร์เรย์ที่เรียงลำดับ (เรียงลำดับการแทรก) โปรดทราบว่าเมื่อพจนานุกรมเปลี่ยนแปลงเนื่องจากรายการใหม่อาร์เรย์ทั้งหมดควรได้รับการอัปเดตเพื่อให้ตรงกับการเข้ารหัสใหม่

จำนวนบิตเฉลี่ยสำหรับการเข้ารหัสแต่ละองค์ประกอบของDจะถูกขยายให้ใหญ่สุดถ้าเรามีจำนวนเฉพาะของแต่ละองค์ประกอบที่ไม่ซ้ำกัน กล่าวว่าองค์ประกอบd1 , d2 , ... , dNในDแต่ละครั้งปรากฏFครั้ง ในกรณีนั้น (ในกรณีที่แย่ที่สุดเรามีทั้ง 0 และ 10 ^ 8 ในลำดับอินพุต) เรามี

ผลรวม (1 <= ฉัน <= N ) F di = 10 ^ 8

ที่ไหน

ผลรวม (1 <= i <= N ) F = 10 ^ 6 หรือF = 10 ^ 6 / Nและความถี่ปกติจะเป็นp = F / 10 ^ = 1 / N

จำนวนเฉลี่ยของบิตจะเป็น -log2 (1 / P ) = log2 ( N ) ภายใต้สถานการณ์เหล่านี้เราควรจะหากรณีที่เพิ่มN สิ่งนี้จะเกิดขึ้นหากเรามีตัวเลขติดต่อกันสำหรับdiเริ่มจาก 0 หรือdi = i -1 ดังนั้น

10 ^ 8 = ผลรวม (1 <= ฉัน <= N ) F di = sum (1 <= i <= N ) (10 ^ 6 / N ) (i-1) = (10 ^ 6 / N ) N ( N -1) / 2

กล่าวคือ

N <= 201 และสำหรับกรณีนี้จำนวนบิตเฉลี่ยคือ log2 (201) = 7.6511 ซึ่งหมายความว่าเราจะต้องมีประมาณ 1 ไบต์ต่อองค์ประกอบอินพุตสำหรับบันทึกอาร์เรย์ที่เรียงลำดับ โปรดทราบว่านี่ไม่ได้หมายความว่าDโดยทั่วไปไม่สามารถมีองค์ประกอบได้มากกว่า 201 รายการ เพียงแค่แสดงให้เห็นว่าหากองค์ประกอบของDกระจายอย่างสม่ำเสมอมันจะต้องมีค่าที่ไม่ซ้ำกันมากกว่า 201 ค่า


1
ฉันคิดว่าคุณลืมหมายเลขที่สามารถทำซ้ำได้
bestsss

สำหรับตัวเลขที่ซ้ำกันความแตกต่างระหว่างตัวเลขที่อยู่ติดกันจะเป็นศูนย์ ไม่ได้สร้างปัญหาใด ๆ รหัส Huffman ไม่ต้องการค่าที่ไม่ใช่ศูนย์
Mohsen Nosratinia

3

ฉันจะใช้ประโยชน์จากพฤติกรรมการส่งสัญญาณซ้ำของ TCP

  1. ทำให้องค์ประกอบ TCP สร้างหน้าต่างรับขนาดใหญ่
  2. รับแพ็กเก็ตจำนวนหนึ่งโดยไม่ส่ง ACK ให้กับพวกเขา
    • ประมวลผลข้อมูลที่ผ่านการสร้างโครงสร้างข้อมูลที่ถูกบีบอัด (คำนำหน้า)
    • ส่ง ack ที่ซ้ำกันสำหรับแพ็กเก็ตสุดท้ายที่ไม่ต้องการอีกต่อไป / รอการหมดเวลาการส่งใหม่
    • ไปที่ 2
  3. แพ็คเก็ตทั้งหมดได้รับการยอมรับ

สิ่งนี้ถือว่าประโยชน์บางอย่างของที่เก็บถังหรือการใช้หลายครั้ง

อาจเป็นไปได้โดยการเรียงลำดับชุด / ถังและการรวมพวกเขา -> ต้นไม้ radix

ใช้เทคนิคนี้เพื่อยอมรับและเรียงลำดับ 80% แรกจากนั้นอ่าน 20% ล่าสุดตรวจสอบว่า 20% ล่าสุดไม่มีตัวเลขที่จะลงจอดใน 20% แรกของตัวเลขต่ำสุด จากนั้นส่งตัวเลขต่ำสุด 20% นำออกจากหน่วยความจำยอมรับส่วนที่เหลืออีก 20% ของตัวเลขใหม่และรวม **


3

นี่คือวิธีการทั่วไปในการแก้ไขปัญหาประเภทนี้:

ขั้นตอนทั่วไป

วิธีการดำเนินการดังต่อไปนี้ อัลกอริทึมทำงานกับบัฟเฟอร์เดี่ยวของคำ 32 บิต มันทำตามขั้นตอนต่อไปนี้ในวง:

  • เราเริ่มต้นด้วยบัฟเฟอร์ที่เต็มไปด้วยข้อมูลที่ถูกบีบอัดจากการวนซ้ำครั้งล่าสุด บัฟเฟอร์มีลักษณะเช่นนี้

    |compressed sorted|empty|

  • คำนวณจำนวนสูงสุดของจำนวนที่สามารถเก็บไว้ในบัฟเฟอร์นี้ได้ทั้งแบบบีบอัดและไม่บีบอัด แบ่งบัฟเฟอร์ออกเป็นสองส่วนเริ่มต้นด้วยพื้นที่สำหรับบีบอัดข้อมูลลงท้ายด้วยข้อมูลที่ไม่บีบอัด บัฟเฟอร์ดูเหมือนว่า

    |compressed sorted|empty|empty|

  • กรอกส่วนที่ไม่มีการบีบอัดด้วยตัวเลขที่จะเรียงลำดับ บัฟเฟอร์ดูเหมือนว่า

    |compressed sorted|empty|uncompressed unsorted|

  • เรียงลำดับหมายเลขใหม่ด้วยการเรียงลำดับแบบแทนที่ บัฟเฟอร์ดูเหมือนว่า

    |compressed sorted|empty|uncompressed sorted|

  • จัดตำแหน่งข้อมูลที่ถูกบีบอัดจากขวาก่อนหน้านี้ซ้ำในส่วนที่บีบอัด ณ จุดนี้บัฟเฟอร์ถูกแบ่งพาร์ติชัน

    |empty|compressed sorted|uncompressed sorted|

  • ทำการสตรีมบีบอัด - บีบอัดข้อมูลใหม่ในส่วนที่บีบอัดผสานในข้อมูลที่เรียงลำดับในส่วนที่ไม่บีบอัด ส่วนที่บีบอัดเก่าถูกใช้ไปเมื่อส่วนที่บีบอัดใหม่โตขึ้น บัฟเฟอร์ดูเหมือนว่า

    |compressed sorted|empty|

ขั้นตอนนี้จะดำเนินการจนกว่าตัวเลขทั้งหมดจะถูกจัดเรียง

การอัด

แน่นอนว่าอัลกอริธึมนี้จะใช้งานได้เมื่อเป็นไปได้ในการคำนวณขนาดการบีบอัดสุดท้ายของบัฟเฟอร์การเรียงลำดับใหม่ก่อนที่จะรู้ว่าสิ่งใดที่จะถูกบีบอัดจริง ถัดจากนั้นขั้นตอนวิธีการบีบอัดจะต้องดีพอที่จะแก้ปัญหาที่เกิดขึ้นจริง

วิธีการที่ใช้ใช้สามขั้นตอน ขั้นแรกอัลกอริทึมจะเก็บลำดับที่เรียงไว้เสมอดังนั้นเราสามารถเก็บความแตกต่างระหว่างรายการที่อยู่ติดกันได้ ความแตกต่างแต่ละอย่างอยู่ในช่วง [0, 99999999]

ความแตกต่างเหล่านี้จะถูกเข้ารหัสเป็นบิตสตรีม 1 ในสตรีมนี้หมายถึง "เพิ่ม 1 เข้ากับตัวสะสม A 0 หมายถึง" ปล่อยตัวสะสมเป็นรายการและรีเซ็ต "ดังนั้นความแตกต่าง N จะถูกแทนด้วย N 1 และหนึ่ง 0

ผลรวมของความแตกต่างทั้งหมดจะเข้าใกล้ค่าสูงสุดที่อัลกอริทึมสนับสนุนและการนับความแตกต่างทั้งหมดจะเข้าใกล้จำนวนของค่าที่แทรกในอัลกอริทึม ซึ่งหมายความว่าเราคาดว่าในตอนท้ายจะมีค่าสูงสุด 1 และจำนวน 0 สิ่งนี้ทำให้เราสามารถคำนวณความน่าจะเป็นที่คาดหวังของ 0 และ 1 ในสตรีม คือน่าจะเป็นของ 0 เป็นcount/(count+maxval)และน่าจะเป็นของ 1 maxval/(count+maxval)คือ

เราใช้ความน่าจะเป็นเหล่านี้เพื่อกำหนดรูปแบบการเข้ารหัสทางคณิตศาสตร์เหนือบิตสตรีมนี้ รหัสเลขคณิตนี้จะเข้ารหัสจำนวน 1 และ 0 ของจำนวนนี้ในพื้นที่ที่เหมาะสม เราสามารถคำนวณพื้นที่ที่ใช้โดยรุ่นนี้สำหรับ bitstream กลางใด ๆ bits = encoded * log2(1 + amount / maxval) + maxval * log2(1 + maxval / amount)ที่เป็น: ในการคำนวณพื้นที่ที่ต้องการทั้งหมดสำหรับอัลกอริทึมให้ตั้งค่าencodedเท่ากับจำนวน

เพื่อไม่ต้องการจำนวนการทำซ้ำที่ไร้สาระสามารถเพิ่มค่าใช้จ่ายเล็ก ๆ ลงในบัฟเฟอร์ได้ สิ่งนี้จะช่วยให้มั่นใจได้ว่าอัลกอริทึมจะทำงานอย่างน้อยกับจำนวนของตัวเลขที่พอดีกับค่าใช้จ่ายนี้เนื่องจากค่าใช้จ่ายเวลาที่ใหญ่ที่สุดของอัลกอริทึมคือการบีบอัดการเข้ารหัสทางคณิตศาสตร์และการบีบอัดแต่ละรอบ

ถัดจากนั้นค่าโสหุ้ยบางอย่างจำเป็นต้องเก็บข้อมูลการทำบัญชีและจัดการความไม่ถูกต้องเล็กน้อยในการประมาณค่าคงที่ของอัลกอริธึมการเข้ารหัสทางคณิตศาสตร์ 8000 หมายเลขสำหรับพื้นที่รวม 1043916 ไบต์

optimality

นอกเหนือจากการลดค่าใช้จ่าย (เล็ก) ของอัลกอริทึมแล้วมันก็เป็นไปไม่ได้ในทางทฤษฎีที่จะได้ผลลัพธ์ที่น้อยลง เพื่อให้มีเพียงเอนโทรปีของผลลัพธ์สุดท้าย 1011717 ไบต์ก็จำเป็น หากเราลบบัฟเฟอร์พิเศษที่เพิ่มเข้ามาเพื่อประสิทธิภาพอัลกอริธึมนี้ใช้ 1011916 ไบต์เพื่อเก็บผลลัพธ์สุดท้าย + ค่าใช้จ่าย


2

หากอินพุตสตรีมสามารถรับได้สองสามครั้งสิ่งนี้จะง่ายขึ้นมาก (ไม่มีข้อมูลเกี่ยวกับเรื่องนั้นความคิดและปัญหาด้านประสิทธิภาพเวลา)

จากนั้นเราสามารถนับค่าทศนิยม ด้วยค่าที่นับได้มันจะทำให้การสตรีมเอาต์พุตเป็นเรื่องง่าย บีบอัดโดยการนับค่า มันขึ้นอยู่กับสิ่งที่จะอยู่ในกระแสอินพุต


1

หากสามารถรับสตรีมอินพุตได้สองสามครั้งสิ่งนี้จะง่ายขึ้นมาก (ไม่มีข้อมูลเกี่ยวกับปัญหาความคิดและปัญหาเรื่องประสิทธิภาพเวลา) จากนั้นเราสามารถนับค่าทศนิยม ด้วยค่าที่นับได้มันจะทำให้การสตรีมเอาต์พุตเป็นเรื่องง่าย บีบอัดโดยการนับค่า มันขึ้นอยู่กับสิ่งที่จะอยู่ในกระแสอินพุต


1

การเรียงลำดับเป็นปัญหารองที่นี่ อย่างที่คนอื่นพูดว่าการเก็บจำนวนเต็มนั้นยากและไม่สามารถทำงานกับอินพุตทั้งหมดได้เนื่องจากจำเป็นต้องใช้ 27 บิตต่อหมายเลข

สิ่งที่ฉันใช้ในเรื่องนี้คือ: เก็บเฉพาะความแตกต่างระหว่างจำนวนเต็ม (เรียงลำดับ) เรียงตามเนื่องจากพวกเขาจะมีขนาดเล็กที่สุด จากนั้นใช้รูปแบบการบีบอัดเช่นมี 2 บิตเพิ่มเติมต่อหมายเลขอินพุตเพื่อเข้ารหัสจำนวนบิตที่เก็บหมายเลขไว้ สิ่งที่ต้องการ:

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

มันควรจะเป็นไปได้ที่จะเก็บจำนวนยุติธรรมของรายการอินพุตที่เป็นไปได้ภายในข้อ จำกัด หน่วยความจำที่กำหนด คณิตศาสตร์ของวิธีการเลือกรูปแบบการบีบอัดเพื่อให้มันทำงานกับจำนวนสูงสุดของอินพุตเป็นเกินฉัน

ฉันหวังว่าคุณอาจใช้ประโยชน์จากความรู้เฉพาะโดเมนของข้อมูลของคุณเพื่อค้นหาชุดรูปแบบการบีบอัดจำนวนเต็มที่ดีพอจากสิ่งนี้

โอ้แล้วคุณจะเรียงลำดับการแทรกในรายการเรียงลำดับที่คุณได้รับข้อมูล


1

ตอนนี้มีเป้าหมายที่จะแก้ปัญหาที่เกิดขึ้นจริงครอบคลุมทุกกรณีที่เป็นไปได้ของการป้อนข้อมูลในช่วง 8 หลักที่มี RAM เพียง 1MB หมายเหตุ: กำลังดำเนินการในวันพรุ่งนี้จะดำเนินต่อไป การใช้การเข้ารหัสทางเลขคณิตของ deltas ของ ints ที่เรียงลำดับกรณีที่แย่ที่สุดสำหรับ 1M int ที่เรียงจะเสียค่าใช้จ่ายประมาณ 7 บิตต่อรายการ (ตั้งแต่ 99999999/1000000 คือ 99 และ log2 (99) เกือบ 7 บิต)

แต่คุณต้องมีจำนวนเต็ม 1 ล้านเรียงเพื่อให้ได้ 7 หรือ 8 บิต! ซีรี่ส์ที่สั้นกว่าจะมี delta ที่ใหญ่กว่าดังนั้นบิตต่อองค์ประกอบมากขึ้น

ฉันกำลังพยายามทำมากที่สุดและบีบอัด (เกือบ) ในสถานที่ ชุดแรกที่มีค่าใกล้เคียง 250K ints จะต้องใช้ประมาณ 9 บิตในแต่ละครั้งที่ดีที่สุด ดังนั้นผลลัพธ์จะใช้ประมาณ 275KB ทำซ้ำกับหน่วยความจำว่างที่เหลืออยู่สองสามครั้ง จากนั้นคลายการบีบอัดผสานเข้าแทนที่ตำแหน่งที่บีบอัดก้อนเหล่านั้น มันค่อนข้างยากแต่เป็นไปได้ ฉันคิด.

รายการที่ถูกผสานจะเข้าใกล้ 7 บิตต่อเป้าหมายจำนวนเต็ม แต่ฉันไม่รู้ว่าจะต้องใช้การวนซ้ำหลายครั้งในการวนซ้ำเวียน อาจจะ 3

แต่ความไม่แน่นอนของการนำรหัสมาใช้ทางคณิตศาสตร์อาจทำให้มันเป็นไปไม่ได้ หากปัญหานี้เป็นไปได้ทั้งหมดก็จะแน่นมาก

มีอาสาสมัครคนใดบ้าง?


การเข้ารหัสทางคณิตศาสตร์สามารถใช้การได้ มันอาจช่วยให้สังเกตว่าแต่ละเดลต้าที่ต่อเนื่องมาจากการแจกแจงทวินามลบ
เบียดเสียด

1

คุณเพียงแค่ต้องเก็บความแตกต่างระหว่างตัวเลขในลำดับและใช้การเข้ารหัสเพื่อบีบอัดหมายเลขลำดับเหล่านี้ เรามี 2 ^ 23 บิต เราจะแบ่งมันออกเป็น 6 บิตและให้บิตสุดท้ายระบุว่าจำนวนนั้นขยายไปถึงอีก 6 บิต (5 บิตบวกกับขยายก้อน)

ดังนั้น 000010 คือ 1 และ 000100 คือ 2 000001100000 คือ 128 ทีนี้เราพิจารณานักแสดงที่แย่ที่สุดในการแสดงความแตกต่างตามลำดับของตัวเลขสูงถึง 10,000,000 สามารถมีความแตกต่าง 10,000,000 / 2 ^ 5 มากกว่า 2 ^ 5, 10,000,000 / 2 ^ 10 ความแตกต่างมากกว่า 2 ^ 10 และ 10,000,000 / 2 ^ 15 ความแตกต่างมากกว่า 2 ^ 15 เป็นต้น

ดังนั้นเราจึงเพิ่มจำนวนบิตที่จะแสดงลำดับของเรา เรามี 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 ตั้งแต่ 8388608> 7935479 เราควรมีหน่วยความจำเพียงพอ เราอาจต้องการหน่วยความจำอีกเล็กน้อยเพื่อเก็บผลรวมของตำแหน่งเมื่อเราใส่หมายเลขใหม่ จากนั้นเราจะผ่านลำดับและหาตำแหน่งที่จะแทรกหมายเลขใหม่ของเราลดความแตกต่างต่อไปหากจำเป็นและเปลี่ยนทุกอย่างหลังจากที่ถูกต้อง


ฉันเชื่อว่าการวิเคราะห์ของฉันที่นี่แสดงให้เห็นว่ารูปแบบนี้ใช้งานไม่ได้ (และไม่สามารถแม้ว่าเราจะเลือกขนาดอื่นมากกว่าห้าบิต)
Daniel Wagner

@Daniel Wagner - คุณไม่จำเป็นต้องใช้จำนวนบิตต่อชิ้นเท่ากันและคุณไม่จำเป็นต้องใช้จำนวนเต็มจำนวนบิตต่อชิ้น
เบียดเสียด

@crowding หากคุณมีข้อเสนอที่เป็นรูปธรรมฉันต้องการที่จะได้ยินมัน =)
Daniel Wagner

@crowding ทำคณิตศาสตร์เรื่องจำนวนเนื้อที่ในการเข้ารหัสทางคณิตศาสตร์ที่จะใช้ ร้องไห้นิดหน่อย จากนั้นคิดให้หนักขึ้น
Daniel Wagner

เรียนรู้เพิ่มเติม. การแจกแจงเงื่อนไขแบบสมบูรณ์ของสัญลักษณ์ในการแทนค่าระดับกลางด้านขวา (Francisco มีการแทนค่ากลางที่ง่ายที่สุดเช่นเดียวกับ Strilanc) ง่ายต่อการคำนวณ ดังนั้นรูปแบบการเข้ารหัสจึงสมบูรณ์แบบอย่างแท้จริงและสามารถเข้ามาภายในขีด จำกัด ของบิตได้ การคำนวณทางคณิตศาสตร์ที่มีความแม่นยำแน่นอนอาจเพิ่มสักสองสามบิต
เบียดเสียด

1

หากเราไม่รู้อะไรเกี่ยวกับตัวเลขเหล่านั้นเราจะถูก จำกัด ด้วยข้อ จำกัด ดังต่อไปนี้:

  • เราต้องโหลดตัวเลขทั้งหมดก่อนจึงจะสามารถจัดเรียงมันได้
  • ชุดของตัวเลขไม่สามารถบีบอัดได้

หากสมมติฐานเหล่านี้มีอยู่คุณจะไม่สามารถดำเนินงานของคุณได้เนื่องจากคุณจะต้องมีพื้นที่เก็บข้อมูลอย่างน้อย 26,575,425 บิต (3,321,929 ไบต์)

คุณสามารถบอกอะไรเราเกี่ยวกับข้อมูลของคุณได้บ้าง?


1
คุณอ่านและจัดเรียงตามที่คุณไป ในทางทฤษฎีแล้วต้องใช้ lg2 (100999999! / (99999999! * 1000000!)) เพื่อเก็บรายการที่แยกไม่ได้ 1M ในกล่องที่แตกต่าง 100M ซึ่งทำงานได้ถึง 96.4% ของ 1MB
NovaDenizen

1

เคล็ดลับคือการแสดงสถานะอัลกอริทึมซึ่งเป็นจำนวนเต็มหลายชุดเป็นสตรีมแบบบีบอัดของ "increment counter" = "+" และ "output counter" = "!" ตัวละคร ตัวอย่างเช่นชุด {0,3,3,4} จะแสดงเป็น "! +++ !! +!" ตามด้วยอักขระ "+" จำนวนเท่าใดก็ได้ ในการปรับเปลี่ยนหลายชุดคุณสามารถสตรีมตัวละครโดยเก็บเฉพาะจำนวนคงที่ในแต่ละครั้งและทำการเปลี่ยนแปลงภายในก่อนที่จะสตรีมกลับมาในรูปแบบการบีบอัด

รายละเอียด

เรารู้ว่ามีตัวเลข 10 ^ 6 ในชุดสุดท้ายดังนั้นจึงมีไม่เกิน 10 ^ 6 "!" ตัวละคร นอกจากนี้เรายังรู้ว่าช่วงของเรามีขนาด 10 ^ 8 ซึ่งหมายความว่ามีอักขระได้สูงสุด 10 ^ 8 "+" จำนวนวิธีที่เราสามารถจัดเรียง 10 ^ 6 "!" s ระหว่าง 10 ^ 8 "+" s คือ(10^8 + 10^6) choose 10^6และดังนั้นการระบุการจัดการบางอย่างใช้เวลาประมาณ 0.965 MiB `ของข้อมูล นั่นจะเป็นแบบแน่น

เราสามารถปฏิบัติต่อตัวละครแต่ละตัวอย่างอิสระโดยไม่เกินโควต้าของเรา มีอักขระ "+" มากกว่า "!" 100 เท่า อักขระซึ่งทำให้อัตราต่อรองง่ายขึ้นเป็น 100: 1 ของแต่ละอักขระเป็น "+" ถ้าเราลืมว่ามันขึ้นอยู่กับ อัตราต่อรอง 100: 101 สอดคล้องกับ~ 0.08 บิตต่อตัวอักษรสำหรับรวมเกือบเหมือนกัน~ 0.965 MiB (ไม่สนใจการพึ่งพามีราคาเพียง12 บิตในกรณีนี้!)

เทคนิคที่ง่ายที่สุดสำหรับการจัดเก็บอักขระอิสระที่มีความน่าจะเป็นที่รู้จักกันก่อนเป็นHuffman การเข้ารหัส โปรดทราบว่าเราต้องการต้นไม้ขนาดใหญ่ที่ใช้การไม่ได้ (ต้นไม้ huffman สำหรับบล็อก 10 ตัวอักษรมีค่าใช้จ่ายเฉลี่ยต่อบล็อกประมาณ 2.4 บิตรวมเป็น ~ 2.9 Mib ต้นไม้ huffman สำหรับบล็อก 20 ตัวมีต้นทุนเฉลี่ยต่อบล็อก ประมาณ 3 บิตซึ่งรวมเป็น ~ 1.8 MiB เราอาจจะต้องมีบล็อกขนาดหนึ่งร้อยเรียงซึ่งหมายถึงโหนดในต้นไม้ของเรามากกว่าอุปกรณ์คอมพิวเตอร์ทั้งหมดที่มีอยู่สามารถจัดเก็บได้ ) อย่างไรก็ตาม ROM เป็นเทคนิค "ฟรี" ตามปัญหาและแนวทางแก้ไขที่ใช้ประโยชน์จากความสม่ำเสมอในต้นไม้จะมีลักษณะเหมือนกัน

หลอกรหัส

  • มีต้นไม้ huffman ขนาดใหญ่เพียงพอ (หรือข้อมูลการบีบอัดบล็อกโดยบล็อกที่คล้ายกัน) ที่เก็บไว้ใน ROM
  • เริ่มต้นด้วยสตริงที่บีบอัดที่มีอักขระ 10 ^ 8 "+"
  • ในการแทรกหมายเลข N ให้สตรีมสตริงที่บีบอัดจนอักขระ N "+" ผ่านไปแล้วจึงใส่ "!" สตรีมสตริงที่ถูกบีบอัดกลับไปที่สตริงก่อนหน้าในขณะที่คุณไปรักษาจำนวนบล็อกบัฟเฟอร์ที่คงที่เพื่อหลีกเลี่ยงการวิ่งมากเกินไป / น้อยเกินไป
  • ทำซ้ำหนึ่งล้านครั้ง: [อินพุต, คลายการบีบอัด> แทรก> บีบอัด] จากนั้นคลายการบีบอัดออก

1
จนถึงตอนนี้เป็นคำตอบเดียวที่ฉันเห็นว่าจริงตอบปัญหา! ฉันคิดว่าการเข้ารหัสเลขคณิตเป็นแบบที่ง่ายกว่าการเข้ารหัสแบบ Huffman เนื่องจากมันขัดขวางความต้องการในการจัดเก็บรหัสหนังสือและกังวลเกี่ยวกับขอบเขตของสัญลักษณ์ คุณสามารถบัญชีสำหรับการพึ่งพาเช่นกัน
เบียดเสียด

จำนวนเต็มอินพุทจะไม่ถูกจัดเรียง คุณต้องเรียงลำดับก่อน
alecco

1
@alecco อัลกอริทึมจะเรียงลำดับตามที่ดำเนินการอยู่ พวกเขาไม่เคยเก็บไว้ไม่เรียงลำดับ
Craig Gidney

1

เรามี 1 MB - 3 KB RAM = 2 ^ 23 - 3 * 2 ^ 13 bits = 8388608 - 24576 = 8364032 บิตที่มีอยู่

เราได้รับหมายเลข 10 ^ 6 ในช่วง 10 ^ 8 สิ่งนี้ให้ค่าเฉลี่ยของช่องว่างประมาณ ~ 100 <2 ^ 7 = 128

ก่อนอื่นเรามาลองพิจารณาปัญหาที่ง่ายกว่าของตัวเลขที่เว้นระยะเท่ากันเมื่อช่องว่างทั้งหมด <128 นี่เป็นเรื่องง่าย เพียงเก็บหมายเลขแรกและช่องว่าง 7 บิต:

(27 บิต) + 10 ^ 6 หมายเลขช่องว่าง 7 บิต = 7000027 บิตที่ต้องการ

หมายเหตุหมายเลขซ้ำ ๆ มีช่องว่างเป็น 0

แต่ถ้าเรามีช่องว่างใหญ่กว่า 127

ตกลงสมมติว่าขนาดช่องว่าง <127 ถูกแสดงโดยตรง แต่ขนาดช่องว่าง 127 ตามด้วยการเข้ารหัส 8 บิตอย่างต่อเนื่องสำหรับความยาวของช่องว่างจริง:

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

เป็นต้น

หมายเหตุการแทนค่าตัวเลขนี้จะอธิบายความยาวของมันเองเพื่อให้เราทราบว่าเมื่อใดที่จำนวนช่องว่างเริ่มขึ้น

ด้วยช่องว่างเล็ก ๆ น้อย ๆ <127 นี้ยังคงต้องใช้ 7000027 บิต

อาจมีได้ถึง (10 ^ 8) / (2 ^ 7) = 781250 หมายเลขช่องว่าง 23 บิตต้องใช้ 16 * 781,250 = 12,500,000 บิตพิเศษซึ่งมากเกินไป เราต้องการช่องว่างที่เล็กลงและเพิ่มขึ้นอย่างช้าๆ

ขนาดช่องว่างเฉลี่ยคือ 100 ดังนั้นถ้าเราจัดลำดับใหม่เป็น [100, 99, 101, 98, 102, ... , 2, 198, 1, 199, 0, 200, 201, 202, ... ] และจัดทำดัชนีสิ่งนี้ ด้วยการเข้ารหัสแบบไบนารีฐาน Fibonacci หนาแน่นที่ไม่มีเลขศูนย์ (ตัวอย่างเช่น 11011 = 8 + 5 + 2 + 1 = 16) ด้วยตัวเลขที่คั่นด้วย '00' จากนั้นฉันคิดว่าเราสามารถรักษาการแทนช่องว่างให้สั้นพอ แต่ต้องการ การวิเคราะห์เพิ่มเติม


0

ในขณะที่ได้รับกระแสทำตามขั้นตอนเหล่านี้

ชุดที่ 1 ขนาดก้อนที่เหมาะสม

แนวคิดรหัสหลอก:

  1. ขั้นตอนแรกคือการค้นหารายการที่ซ้ำกันทั้งหมดและติดไว้ในพจนานุกรมด้วยการนับและลบออก
  2. ขั้นตอนที่สามจะวางหมายเลขที่มีอยู่ในลำดับขั้นตอนอัลกอริทึมของพวกเขาและวางไว้ในพจนานุกรมพิเศษเคาน์เตอร์ด้วยหมายเลขแรกและขั้นตอนของพวกเขาเช่น n, n + 1 ... , n + 2, 2n, 2n + 1 2n + 2 ...
  3. เริ่มบีบอัดจำนวนชิ้นช่วงที่เหมาะสมเช่นทุก ๆ 1,000 หรือ 10,000 จำนวนที่เหลือที่ปรากฏขึ้นบ่อยครั้งเพื่อทำซ้ำ
  4. ยกเลิกการบีบอัดช่วงนั้นถ้าพบหมายเลขและเพิ่มลงในช่วงแล้วปล่อยให้ไม่มีการบีบอัดอีกต่อไป
  5. มิฉะนั้นเพียงเพิ่มหมายเลขนั้นลงในไบต์ [chunkSize]

ทำต่อ 4 ขั้นตอนแรกขณะรับสตรีม ขั้นตอนสุดท้ายคือการล้มเหลวหากคุณมีหน่วยความจำเกินหรือเริ่มแสดงผลเมื่อข้อมูลทั้งหมดถูกรวบรวมโดยเริ่มต้นเพื่อจัดเรียงช่วงและแยกผลลัพธ์ออกมาตามลำดับและไม่บีบอัดในลำดับที่จำเป็นต้องบีบอัดและเรียงลำดับเมื่อ คุณได้รับพวกเขา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.