4 ms·
I think there are two difficulties here. 1. How to even represent 1,000,000,000 sorted integers in only 2 megabytes, which means you can use (slightly more tha
by sltkr 5y ago
I think there are two difficulties here.
1. How to even represent 1,000,000,000 sorted integers in only 2 megabytes, which means you can use (slightly more than) 2 bytes per integer. The central observation is that the sum of the deltas is no more than 4294967296. 4294967296 / 1000000 = 4295 which is about 13 bits, so it seems like it should be possible, but it's not easy.
For example, if you use a standard variable length encoding where the highest bit indicates continuation, you would have 7 data bits per byte, but you could have 743,665 times 128 and 256,335 times 16384, which would require 743,665*2 + 256,335*3 = 2,256,335 bytes, which is slightly over 2 megabyte.
If you use the first two bits of the initial byte (so that K bytes encode 8*K - 2 bits). You could have 740,749 times 64 259,251 times 16384, for a total of 259251*3 + 740749*2 = 2,259,251 bytes, slightly worse even.
With 1 continuation bit per nibble the math similarly doesn't work out. So this is starting to look, if not impossible, at least very hard.
2. Imagine that you could represent 1,000,000 sorted integers in 2 megabytes somehow. Then the next problem is to actually create such a sorted sequence from the input.
- Insertion sort works, but O(N^2) time complexity is not great with a million elements.
- Quick sort doesn't work since you'd start with an unsorted array which you cannot represent in memory.
- Heap sort doesn't work because it requires random access to the array, which doesn't work with a variable-length encoding.
- Merge sort works in the beginning, but you need temporary space equal to the size of your input, so towards the end you're in trouble.
I think you could make merge sort work if the requirement is "output the elements in sorted order" rather than actually constructing a sorted representation of the array. In that case, you could create sorted sequences of the first 500,000, 250,000, 125,000 etc. elements, and do a 20-way merge in the end, which is O(N log N) overall.
This is still somewhat tricky because the average delta of e.g. 500,000 elements can be twice as large as for the overall array, so you might need slightly more space to encode it, so we would need a really efficient compression scheme to make this work.
All in all I'm gravitating towards: this problem isn't generally solvable under the given constraints. With 3 megabytes I think the scheme outlined above works.
- raphlinus 5y agoIt's doable. First, you use a Golomb coding for the deltas. Tuning the parameters just right, you can get it to fit inside your budget with a little room to spare. Then, to get reasonably good time complexity, you use that extra space as a scratch buffer - read in a chunk of input, sort in place (heapsort is good for worst-case time complexity), and merge with the compressed stream. It's not simple to implement, but it does work.
- GistNoesis 5y agoCan you explain how you merge a compressed sorted stream with the sorted chunk, to obtain a bigger compressed stream (without extra memory, or quadratic complexity) ? I can see a solution with memory usage : size of( compressed sorted stream ) + size of( compressed sorted chunk ) + size of(compressed stream merge output) This would work but it means you need to fit your compressed sorted stream in less than a single MB instead of 2MB (and I'm ignoring memory fragmentation problems)
- raphlinus 5y agoThere are a bunch of ways to do this. One of the more straightforward is to use a temporary buffer big enough to hold both the new values and the existing stream, and start by shifting the existing stream to be right aligned (using memmove or the equivalent). Then read bits from the old stream while writing bits into the new stream. Using Elias-Fano coding (which I believe is an instance of Golomb coding), the write position will never overtake the read position, so the merge can be done in-place.
- GistNoesis 5y agoThanks. I think I understood it now : https://news.ycombinator.com/item?id=27553076 https://news.ycombinator.com/item?id=27553076 In my solution though when the extra temporary buffer is very small,in the worst case, we need to make sizeof(compressedStream)/sizeof(temporaryBuffer) memmove which can potentially make the algorithm quadratic.
- rjmunro 5y agoOne method is to move the compressed output from the top to the bottom of the memory with each cycle: A = Existing compressed sorted chunk; B = Compressed merge output; C = New sorted chunk Start with: AAAAAAAAAAAAAAAAAAAAAAAAAAA-----CCCCCC Merge into B, start at top of A and working your way down: AAAAAAAAAAAAAAAAAAAAAA-----BBBBBCCCC-- Finish this iteration like: --------BBBBBBBBBBBBBBBBBBBBBBBB------ Then refill C and sort it: --------BBBBBBBBBBBBBBBBBBBBBBBBCCCCCC Merge B back into A, but forwards this time: AAAAAAAAAA------BBBBBBBBBBBBBBBB---CCC AAAAAAAAAAAAAAAAAAAAAAAAAAA-----------
- ot 5y agoEncoding-wise, Elias-Fano requires exactly 2 + ceil(log(2^32 / n)) bits, so it would provably fit in the memory budget. Algorithm-wise, yeah I can only think of quadratic ones. But if in the question the bottleneck is memory, maybe that's fine? An optimization would be to do selection sort, but in each round select the next group of integers that share the top 32 - ceil(log(2^32 / n)) bits, as that is a cluster in the EF representation, and then sort the lower bits, which can be done in-place in O(k log k) where k is the group size. So if your groups are large enough you get some speedup. EDIT: Ah I think that the trick is to always use half of the memory left to read and sort. - Read 256k integers (1MB), sort in-place, construct EF representation (~500kB) - Read 128k, sort, and produce another EF (~280kB) - Keep halving You do a logarithmic number of rounds, so it's O(n log n). At the end you're left with log(n) EF sets, which fit in less than 2MB (in fact there's some space left, and you could save a few rounds) and you can merge them with a log(n)-way heap into a stream.
- noctune 5y agoThe entropy of 1e6 sorted 32 bit integers is log2((2*32+1e6-1) choose 1e6) bits or about 1.61 MiB, so it should be possible. Really difficult, obviously, but possible.