RAM이 1MB이고 다른 로컬 저장소가없는 컴퓨터가 있습니다. TCP 연결을 통해 백만 개의 8 자리 10 진수를 받아들이고 정렬 한 다음 정렬 된 목록을 다른 TCP 연결을 통해 보내야합니다.
숫자 목록에 중복이 포함될 수 있으므로 삭제해서는 안됩니다. 코드는 ROM에 배치되므로 1MB에서 코드 크기를 뺄 필요가 없습니다. 이미 이더넷 포트를 구동하고 TCP / IP 연결을 처리하는 코드가 있으며 코드를 통해 데이터를 읽고 쓰는 데 필요한 1KB 버퍼를 포함하여 상태 데이터에 2KB가 필요합니다. 이 문제에 대한 해결책이 있습니까?
질문과 답변의 출처 :
답변
여기까지 언급되지 않은 다소 교묘 한 속임수가 있습니다. 데이터를 저장할 수있는 추가 방법이 없다고 가정하지만 이는 사실이 아닙니다.
문제를 해결하는 한 가지 방법은 다음과 같은 끔찍한 일을하는 것입니다. 어떤 상황에서도 다른 사람이 시도해서는 안됩니다. 네트워크 트래픽을 사용하여 데이터를 저장하십시오. 그리고 아니요, NAS를 의미하지 않습니다.
다음과 같은 방법으로 몇 바이트의 RAM만으로 숫자를 정렬 할 수 있습니다.
- 먼저 두 변수를 가지고 :
COUNTER
와VALUE
. - 먼저 모든 레지스터를
0
; - 정수를받을 때마다
I
증분COUNTER
하고로 설정VALUE
하십시오max(VALUE, I)
. - 그런 다음 데이터가 설정된 ICMP 에코 요청 패킷을
I
라우터로 보냅니다 . 지우고I
반복하십시오. - 반환 된 ICMP 패킷을받을 때마다 정수를 추출하여 다른 에코 요청으로 다시 보냅니다. 이것은 정수를 포함하여 앞뒤로 많은 ICMP 요청을 생성합니다.
에 COUNTER
도달하면 1000000
ICMP 요청의 연속 스트림에 모든 값이 저장되고 VALUE
이제 최대 정수가 포함됩니다. 일부를 선택하십시오 threshold T >> 1000000
. COUNTER
0으로 설정하십시오 . ICMP 패킷을 수신 할 때마다 COUNTER
포함 된 정수를 증가 시키고 I
다른 에코 요청으로 다시 보내십시오 I=VALUE
.이 경우 정렬 된 정수의 대상으로 전송 하지 않습니다 . 에 의해 COUNTER=T
감소한 후 0으로 재설정 하고 반복하십시오. 한번VALUE
1
COUNTER
VALUE
0에 도달하면 모든 정수를 가장 큰 것부터 가장 작은 것까지 순서대로 전송해야하며, 두 개의 영구 변수 (및 임시 값에 필요한 소량)에 약 47 비트의 RAM 만 사용했습니다.
나는 이것이 끔찍하다는 것을 알고 있으며, 모든 종류의 실질적인 문제가있을 수 있다는 것을 알고 있지만, 그것은 그것이 당신에게 웃음을 주거나 적어도 당신을 놀라게 할 것이라고 생각했습니다.
답변
다음은 작동하는 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
...
이 두 어레이는 함께 1045000 바이트의 스토리지를 사용합니다. 나머지 변수와 스택 공간에 대해서는 1048576-1045000-2 × 1024 = 1528 바이트가 남습니다.
내 Xeon W3520에서 약 23 초 안에 실행됩니다. 프로그램 이름이이라고 가정하면 다음 Python 스크립트를 사용하여 프로그램이 작동하는지 확인할 수 있습니다 sort1mb.exe
.
from subprocess import *
import random
sequence = [random.randint(0, 99999999) for i in xrange(1000000)]
sorter = Popen('sort1mb.exe', stdin=PIPE, stdout=PIPE)
for value in sequence:
sorter.stdin.write('%08d\n' % value)
sorter.stdin.close()
result = [int(line) for line in sorter.stdout]
print('OK!' if result == sorted(sequence) else 'Error!')
알고리즘에 대한 자세한 설명은 다음 일련의 게시물에서 찾을 수 있습니다.
답변
참조하십시오 최초의 정답 또는 산술 인코딩 나중에 답을 .아래는 재미 있지만 100 % 방탄 솔루션은 아닙니다.
이것은 매우 흥미로운 작업이며 여기에 또 다른 해결책이 있습니다. 누군가가 결과가 유용하다고 생각하기를 바랍니다.
1 단계 : 초기 데이터 구조, 대략적인 압축 방식, 기본 결과
간단한 계산을 해보자. 10 ^ 6 8 자리 10 진수를 저장할 수있는 1M (1048576 바이트)의 RAM이 처음에있다. [0; 99999999]. 따라서 하나의 숫자를 저장하려면 27 비트가 필요합니다 (부호없는 숫자가 사용된다는 가정을 취하십시오). 따라서 원시 스트림을 저장하려면 ~ 3.5M의 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를 준비했지만 여전히 강조해야 할 세부 사항이 있습니다.
- 메모리가 제한되어 있으므로 숫자를 미리 정렬하고 압축 된 버킷 (동적 크기)을 임시 저장소로 사용하는 것이 좋습니다.
- 미리 정렬 된 데이터로 더 나은 압축 계수를 달성하는 것이 더 쉬우므로 각 버킷에 정적 버퍼가 있습니다 (버퍼의 숫자는 LZMA 전에 정렬되어야 함)
- 각 버킷에는 특정 범위가 있으므로 각 버킷에 대해 최종 정렬을 개별적으로 수행 할 수 있습니다.
- 버킷 크기를 올바르게 설정할 수 있으므로 저장된 데이터의 압축을 풀고 각 버킷에 대해 최종 정렬을 수행하기에 충분한 메모리가 있습니다.
첨부 코드는 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
편집하다
결론:
- 자연 을 속이려하지 마십시오
- 더 적은 메모리 공간으로 더 간단한 압축 사용
- 몇 가지 추가 단서가 실제로 필요합니다. 일반적인 방탄 솔루션은 실현 가능하지 않은 것 같습니다.
2 단계 : 압축 향상, 최종 결론
이전 섹션에서 이미 언급했듯이 적절한 압축 기술을 사용할 수 있습니다. 간단하고 더 나은 (가능한 경우) 접근 방식을 위해 LZMA를 제거합시다. 산술 코딩 , 기수 트리 등 좋은 솔루션이 많이 있습니다 .
어쨌든 간단하지만 유용한 인코딩 체계는 다른 외부 라이브러리보다 더 잘 설명되어 멋진 알고리즘을 제공합니다. 실제 솔루션은 매우 간단합니다. 데이터가 부분적으로 정렬 된 버킷이 있기 때문에 숫자 대신 델타를 사용할 수 있습니다.
무작위 입력 테스트는 약간 더 나은 결과를 보여줍니다.
bucket[0] contains: 10103 numbers, compressed size: 13683 compression factor: 0.34
bucket[1] contains: 9885 numbers, compressed size: 13479 compression factor: 0.34
...
bucket[98] contains: 10026 numbers, compressed size: 13612 compression factor: 0.34
bucket[99] contains: 10058 numbers, compressed size: 13701 compression factor: 0.34
Data size: 3.85M compressed 1.31M compression factor 0.34
샘플 코드
public static void encode(int[] buffer, int length, BinaryOut output) {
short size = (short)(length & 0x7FFF);
output.write(size);
output.write(buffer[0]);
for(int i=1; i< size; i++) {
int next = buffer[i] - buffer[i-1];
int bits = getBinarySize(next);
int len = bits;
if(bits > 24) {
output.write(3, 2);
len = bits - 24;
}else if(bits > 16) {
output.write(2, 2);
len = bits-16;
}else if(bits > 8) {
output.write(1, 2);
len = bits - 8;
}else{
output.write(0, 2);
}
if (len > 0) {
if ((len % 2) > 0) {
len = len / 2;
output.write(len, 2);
output.write(false);
} else {
len = len / 2 - 1;
output.write(len, 2);
}
output.write(next, bits);
}
}
}
public static short decode(BinaryIn input, int[] buffer, int offset) {
short length = input.readShort();
int value = input.readInt();
buffer[offset] = value;
for (int i = 1; i < length; i++) {
int flag = input.readInt(2);
int bits;
int next = 0;
switch (flag) {
case 0:
bits = 2 * input.readInt(2) + 2;
next = input.readInt(bits);
break;
case 1:
bits = 8 + 2 * input.readInt(2) +2;
next = input.readInt(bits);
break;
case 2:
bits = 16 + 2 * input.readInt(2) +2;
next = input.readInt(bits);
break;
case 3:
bits = 24 + 2 * input.readInt(2) +2;
next = input.readInt(bits);
break;
}
buffer[offset + i] = buffer[offset + i - 1] + next;
}
return length;
}
이 접근 방식은 다음과 같습니다.
- 많은 메모리를 소비하지 않습니다
- 스트림과 함께 작동
- 그렇게 나쁘지 않은 결과를 제공합니다
전체 코드는 here , BinaryInput 및 BinaryOutput 구현은 여기 에서 찾을 수 있습니다.
최종 결론
마지막 결론은 없습니다 🙂 때로는 한 수준 위로 올라가 메타 수준의 관점 에서 작업을 검토하는 것이 좋습니다 .
이 작업에 시간을 보내는 것이 재미있었습니다. BTW, 아래에 흥미로운 답변이 많이 있습니다. 당신의 관심과 행복한 코딩에 감사드립니다.
답변
해결책은 1MB와 100 만 바이트의 차이로 인해 가능합니다. 8093729.5에 대해 중복이 허용 된 백만 개의 8 자리 숫자를 선택하고 중요하지 않은 순서로 약 2 개의 방법이 있으므로, 백만 바이트의 RAM이있는 머신에는 모든 가능성을 나타내는 데 충분한 상태가 없습니다. 그러나 1M (TCP / IP의 경우 2k 미만)은 1022 * 1024 * 8 = 8372224 비트이므로 솔루션이 가능합니다.
1 부, 초기 솔루션
이 방법은 1M보다 조금 더 필요합니다. 나중에 1M에 맞도록 수정하겠습니다.
7 비트 숫자의 하위 목록 시퀀스로 0에서 99999999 범위의 간단한 정렬 된 숫자 목록을 저장합니다. 첫 번째 서브리스트는 0에서 127까지의 숫자를 보유하고, 두 번째 서브리스트는 128에서 255까지의 숫자를 보유합니다. 100000000/128은 정확히 781250이므로 781250의 이러한 서브리스트가 필요합니다.
각 하위 목록은 2 비트 하위 목록 헤더와 하위 목록 본문으로 구성됩니다. 서브리스트 본문은 서브리스트 항목 당 7 비트를 차지합니다. 하위 목록은 모두 함께 연결되며 형식을 사용하면 한 하위 목록이 끝나고 다음이 시작되는 위치를 알 수 있습니다. 완전히 채워진 목록에 필요한 총 스토리지는 2 * 781250 + 7 * 1000000 = 8562500 비트이며 이는 약 1.021MB입니다.
4 가지 가능한 서브리스트 헤더 값은 다음과 같습니다.
00 빈 서브리스트, 다음은 아무것도 없습니다.
01 싱글 톤, 서브리스트에는 단 하나의 엔트리 만이 있고 다음 7 비트는 그것을 유지합니다.
10 하위 목록에는 최소한 2 개의 고유 숫자가 있습니다. 마지막 항목이 첫 번째 항목보다 작거나 같은 것을 제외하고 항목은 감소하지 않는 순서로 저장됩니다. 이를 통해 서브리스트의 끝을 식별 할 수 있습니다. 예를 들어 숫자 2,4,6은 (4,6,2)로 저장됩니다. 숫자 2,2,3,4,4는 (2,3,4,4,2)로 저장됩니다.
11 서브리스트는 단일 숫자의 반복을 두 번 이상 보유합니다. 다음 7 비트는 숫자를 나타냅니다. 그런 다음 값이 1 인 7 비트 항목이 0 개 이상이고 값이 0 인 7 비트 항목이옵니다. 하위 목록 본문의 길이는 반복 횟수를 나타냅니다. 예를 들어 숫자 12,12는 (12,0)으로, 숫자 12,12,12는 (12,1,0)으로, 12,12,12,12는 (12,1)로 저장됩니다. , 1,0) 등입니다.
빈 목록으로 시작하여 많은 수의 숫자를 읽고 32 비트 정수로 저장하고 새 숫자를 제자리에 정렬하고 (아마도 힙 정렬을 사용하여) 새로운 소형 정렬 목록으로 병합합니다. 읽을 숫자가 더 이상 없을 때까지 반복 한 다음 요약 목록을 한 번 더 걸어 출력을 생성하십시오.
아래 줄은 목록 병합 작업이 시작되기 직전에 메모리를 나타냅니다. “O”는 정렬 된 32 비트 정수를 보유하는 영역입니다. “X”는 이전 컴팩트 목록을 보유한 영역입니다. “=”부호는 콤팩트 목록의 확장 공간이며 “O”의 각 정수마다 7 비트입니다. “Z”는 다른 임의의 오버 헤드입니다.
ZZZOOOOOOOOOOOOOOOOOOOOOOOOOO==========XXXXXXXXXXXXXXXXXXXXXXXXXX
병합 루틴은 맨 왼쪽 “O”및 맨 왼쪽 “X”에서 읽기 시작하고 맨 왼쪽 “=”에서 쓰기 시작합니다. 쓰기 포인터는 모든 새로운 정수가 병합 될 때까지 컴팩트 목록 읽기 포인터를 포착하지 못합니다. 두 포인터는 각각의 하위 목록에 대해 2 비트 씩, 이전 컴팩트 목록에있는 각 항목에 대해 7 비트 씩 앞당겨지기 때문입니다. 새 숫자에 대한 7 비트 항목
Part 2, 1M으로 짜다
위의 솔루션을 1M로 짜려면 압축 목록 형식을 좀 더 압축해야합니다. 하위 목록 유형 중 하나를 제거하여 가능한 3 개의 다른 하위 목록 헤더 값이 있습니다. 그런 다음 “00”, “01”및 “1”을 하위 목록 헤더 값으로 사용하고 몇 비트를 저장할 수 있습니다. 하위 목록 유형은 다음과 같습니다.
빈 하위 목록, 다음은 아무것도 없습니다.
B 싱글 톤의 경우, 서브리스트에 단 하나의 항목 만 있고 다음 7 비트가이를 보유합니다.
C 하위 목록에는 최소 2 개의 고유 숫자가 있습니다. 마지막 항목이 첫 번째 항목보다 작거나 같은 것을 제외하고 항목은 감소하지 않는 순서로 저장됩니다. 이를 통해 서브리스트의 끝을 식별 할 수 있습니다. 예를 들어 숫자 2,4,6은 (4,6,2)로 저장됩니다. 숫자 2,2,3,4,4는 (2,3,4,4,2)로 저장됩니다.
D 서브리스트는 단일 숫자의 두 번 이상의 반복으로 구성됩니다.
내 3 개의 서브리스트 헤더 값은 “A”, “B”및 “C”가되므로 D- 타입 서브리스트를 나타내는 방법이 필요합니다.
“C [17] [101] [58]”과 같이 C- 타입 서브리스트 헤더 뒤에 3 개의 항목이 있다고 가정합니다. 세 번째 항목은 두 번째 항목보다 작지만 첫 번째 항목보다 많기 때문에 위에서 설명한대로 유효한 C 유형 하위 목록에 속할 수 없습니다. 이 유형의 구성을 사용하여 D 유형 하위 목록을 나타낼 수 있습니다. 비트 용어로, “C {00 ?????} {1 ??????} {01 ?????}”는 불가능한 C- 타입 서브리스트입니다. 이것을 사용하여 단일 숫자의 3 회 이상의 반복으로 구성된 하위 목록을 나타냅니다. 처음 2 개의 7 비트 단어는 숫자 (아래 “N”비트)를 인코딩하고 0 개 이상의 {0100001} 단어 다음에 {0100000} 단어가옵니다.
For example, 3 repetitions: "C{00NNNNN}{1NN0000}{0100000}", 4 repetitions: "C{00NNNNN}{1NN0000}{0100001}{0100000}", and so on.
단 하나의 숫자를 정확히 2 번 반복하는 목록 만 남습니다. 또 다른 불가능한 C- 타입 서브리스트 패턴 인 “C {0 ??????} {11 ?????} {10 ?????}”를 표현하겠습니다. 처음 두 단어에는 숫자의 7 비트를위한 충분한 공간이 있지만이 패턴은 그것이 나타내는 하위 목록보다 길기 때문에 조금 더 복잡합니다. 끝에있는 5 개의 물음표는 패턴의 일부가 아닌 것으로 간주 될 수 있으므로 “C {0NNNNNN} {11N ????} 10″을 패턴으로 사용하고 반복되는 숫자는 “N에 저장됩니다. “에스. 2 비트가 너무 깁니다.
이 패턴에서 2 비트를 빌려서 사용하지 않은 4 비트에서 갚아야합니다. 읽을 때 “C {0NNNNNN} {11N00AB} 10″이 발생하면 “N”에있는 숫자의 인스턴스 2 개를 출력하고 끝에 A와 B로 “10”을 덮어 쓰고 읽기 포인터를 2만큼 되감습니다. 비트. 각 컴팩트 목록이 한 번만 걷기 때문에이 알고리즘에 대한 파괴적인 읽기가 가능합니다.
단일 숫자의 두 번 반복되는 서브리스트를 작성할 때 “C {0NNNNNN} 11N00″을 쓰고 빌린 비트 카운터를 2로 설정하십시오. 빌린 비트 카운터가 0이 아닌 모든 쓰기에서 기록 된 각 비트에 대해 감소합니다. 카운터가 0에 도달하면 “10”이 기록됩니다. 따라서 다음 2 비트는 슬롯 A와 B로 들어가고 “10”은 끝으로 떨어집니다.
“00”, “01”및 “1”로 표시되는 3 개의 하위 목록 헤더 값을 사용하여 가장 인기있는 하위 목록 유형에 “1”을 할당 할 수 있습니다. 하위 목록 헤더 값을 하위 목록 유형에 매핑하려면 작은 테이블이 필요하며 최상의 하위 목록 헤더 매핑이 무엇인지 알 수 있도록 각 하위 목록 유형마다 발생 카운터가 필요합니다.
모든 서브리스트 유형이 동일하게 인기가있을 때 완전히 채워진 컴팩트 목록의 최악의 경우 최소 표시가 발생합니다. 이 경우 3 개의 하위 목록 헤더마다 1 비트를 저장하므로 목록 크기는 2 * 781250 + 7 * 1000000-781250/3 = 8302083.3 비트입니다. 32 비트 워드 경계, 즉 8302112 비트 또는 1037764 바이트로 반올림합니다.
1M에서 TCP / IP 상태 및 버퍼의 2k를 뺀 값은 1022 * 1024 = 1046528 바이트이므로 8764 바이트를 사용할 수 있습니다.
그러나 서브리스트 헤더 매핑을 변경하는 프로세스는 어떻습니까? 아래 메모리 맵에서 “Z”는 임의의 오버 헤드이고 “=”는 여유 공간이며 “X”는 간단한 목록입니다.
ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
가장 왼쪽 “X”에서 읽기 시작하고 가장 왼쪽 “=”에서 쓰기 시작하고 오른쪽으로 작업하십시오. 완료되면 압축 목록이 약간 짧아지고 메모리의 잘못된 끝에있을 것입니다.
ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======
그런 다음 오른쪽으로 분류해야합니다.
ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
헤더 맵핑 변경 프로세스에서 서브리스트 헤더의 최대 1/3이 1 비트에서 2 비트로 변경됩니다. 최악의 경우 이들은 모두 목록의 선두에 있기 때문에 시작하기 전에 최소 781250/3 비트의 무료 저장 공간이 필요하므로 이전 목록의 압축 목록의 메모리 요구 사항으로 되돌아갑니다. (
이 문제를 해결하기 위해 781250 하위 목록을 각각 78125 개의 하위 목록으로 구성된 10 개의 하위 목록 그룹으로 나누겠습니다. 각 그룹에는 고유 한 독립적 인 서브리스트 헤더 매핑이 있습니다. 그룹에 문자 A-J 사용 :
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
하위 목록 헤더 매핑 변경 중에 각 하위 목록 그룹이 축소되거나 동일하게 유지됩니다.
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAA=====BBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABB=====CCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCC======DDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDD======EEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEE======FFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFF======GGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGG=======HHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHH=======IJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHI=======JJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ=======
ZZZ=======AAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
매핑 변경 중 하위 목록 그룹의 최악의 임시 확장은 4k 미만에서 78125/3 = 26042 비트입니다. 완전히 채워진 컴팩트 목록에 4k와 1037764 바이트를 허용하면 메모리 맵의 “Z”에 대해 8764-4096 = 4668 바이트가 남습니다.
10 개의 하위 목록 헤더 매핑 테이블, 30 개의 하위 목록 헤더 발생 횟수 및 필요한 다른 카운터, 포인터 및 작은 버퍼 및 함수 호출 반환 주소의 스택 공간 및 지역 변수.
3 부, 얼마나 오래 걸립니까?
빈 컴팩트 목록의 경우 1 비트 목록 헤더가 빈 하위 목록에 사용되고 목록의 시작 크기는 781250 비트입니다. 최악의 경우 목록은 추가 된 각 숫자에 대해 8 비트로 증가하므로 32 비트 숫자 각각이 목록 버퍼의 맨 위에 배치 된 다음 정렬 및 병합하려면 32 + 8 = 40 비트의 여유 공간이 필요합니다. 최악의 경우, 서브리스트 헤더 맵핑을 변경하면 2 * 781250 + 7 * 항목-781250/3 비트의 공간 사용량이 발생합니다.
목록에 최소 800000 개의 숫자가 있으면 5 번의 병합 후마다 서브리스트 헤더 매핑을 변경하는 정책으로 최악의 경우 약 30M의 간단한 목록 읽기 및 쓰기 작업이 필요합니다.
출처:
답변
길마 노프의 대답은 그 가정에서 매우 잘못되었습니다. 그것은 백만 연속 정수 의 무의미한 측정을 기반으로 추측을 시작합니다 . 그것은 격차가 없음을 의미합니다. 이러한 임의의 차이는 작지만 실제로는 나쁜 아이디어입니다.
직접 해보십시오. 원하는 LZMA에 관계없이 백만 개의 임의 27 비트 정수를 가져 와서 정렬하고 7-Zip , xz 로 압축하십시오 . 결과는 1.5MB를 초과합니다. 맨 위의 전제는 순차적 숫자의 압축입니다. 심지어 델타 인코딩 그입니다 이상 1.1 MB . 그리고 이것이 압축을 위해 100MB 이상의 RAM을 사용하고 있다는 것을 신경 쓰지 마십시오. 따라서 압축 된 정수조차도 문제에 맞지 않으며 런타임 RAM 사용에 신경 쓰지 않습니다 .
사람들이 어떻게 예쁜 그래픽과 합리화를지지하는지 슬프다.
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
int32_t ints[1000000]; // Random 27-bit integers
int cmpi32(const void *a, const void *b) {
return ( *(int32_t *)a - *(int32_t *)b );
}
int main() {
int32_t *pi = ints; // Pointer to input ints (REPLACE W/ read from net)
// Fill pseudo-random integers of 27 bits
srand(time(NULL));
for (int i = 0; i < 1000000; i++)
ints[i] = rand() & ((1<<27) - 1); // Random 32 bits masked to 27 bits
qsort(ints, 1000000, sizeof (ints[0]), cmpi32); // Sort 1000000 int32s
// Now delta encode, optional, store differences to previous int
for (int i = 1, prev = ints[0]; i < 1000000; i++) {
ints[i] -= prev;
prev += ints[i];
}
FILE *f = fopen("ints.bin", "w");
fwrite(ints, 4, 1000000, f);
fclose(f);
exit(0);
}
이제 LZMA로 ints.bin을 압축하십시오 …
$ xz -f --keep ints.bin # 100 MB RAM
$ 7z a ints.bin.7z ints.bin # 130 MB RAM
$ ls -lh ints.bin*
3.8M ints.bin
1.1M ints.bin.7z
1.2M ints.bin.xz
답변
나는 이것에 대해 생각할 수있는 한 가지 방법이 조합 론적 관점이라고 생각합니다. 정렬 된 숫자 순서의 가능한 조합은 몇 개입니까? 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 메가 바이트 = 8388608 비트> 8093700 비트
따라서 결과를 저장하기에 실제로 충분한 공간이있는 것으로 보입니다! 물론 흥미로운 점은 숫자가 흐름에 따라 정렬하는 것입니다. 최선의 접근 방법이 확실하지 않다면 294908 비트가 남아 있다는 것입니다. 흥미로운 점은 각 시점에서 그것이 전체 주문이라고 가정하고 해당 주문에 대한 코드를 찾은 다음 새로운 번호를 받고 이전 코드를 업데이트 할 때 흥미로운 기술이라고 생각합니다. 손 파 손 파.
답변
여기에 내 제안은 Dan의 솔루션에 많은 빚을지고 있습니다.
먼저 솔루션이 가능한 모든 입력 목록을 처리해야한다고 가정 합니다. 나는 대중적인 대답 이이 가정을하지 않는다고 생각한다 (IMO는 큰 실수이다)
무손실 압축 형태는 모든 입력의 크기를 줄이지 않는 것으로 알려져 있습니다.
모든 인기있는 답변은 추가 공간을 확보 할 수있을만큼 효과적인 압축을 적용 할 수 있다고 가정합니다. 실제로, 부분적으로 완성 된 목록의 일부를 압축되지 않은 형태로 유지하고 정렬 작업을 수행 할 수있을 정도로 큰 추가 공간 청크. 이것은 단지 나쁜 가정입니다.
이러한 솔루션의 경우 압축 방식에 대한 지식이있는 사람은이 구성표에 대해 잘 압축되지 않는 일부 입력 데이터를 설계 할 수 있으며 공간 부족으로 인해 “솔루션”이 중단 될 가능성이 높습니다.
대신 나는 수학적 접근법을 취합니다. 가능한 출력은 0..MAX 범위의 요소로 구성된 길이 LEN의 모든 목록입니다. 여기서 LEN은 1,000,000이고 MAX는 100,000,000입니다.
임의의 LEN 및 MAX의 경우이 상태를 인코딩하는 데 필요한 비트 수는 다음과 같습니다.
Log2 (MAX 멀티 초이스 LEN)
따라서 숫자의 경우 수신 및 정렬이 완료되면 가능한 모든 출력을 고유하게 구별 할 수있는 방식으로 결과를 저장하려면 최소한 Log2 (100,000,000 MC 1,000,000) 비트가 필요합니다.
~ = 988kb 입니다. 따라서 실제로 결과를 저장할 수있는 충분한 공간이 있습니다. 이 관점에서 가능합니다.
[더 나은 예제가 존재하기 때문에 무의식적으로 삭제되었습니다 …]
가장 좋은 대답 은 여기 입니다.
또 다른 좋은 대답 은 여기에 있으며 기본적으로 삽입 정렬을 하나의 요소로 확장하는 함수로 사용합니다 (한 번에 여러 요소를 삽입 할 수 있도록 몇 가지 요소와 사전 정렬 버퍼링, 약간의 시간 절약). 7 비트 델타의 버킷 인 멋진 컴팩트 상태 인코딩도 사용합니다.