Рішення можливе лише через різницю між 1 мегабайт і 1 мільйон байт. Є близько 2 потужностей 8093729.5 різних способів вибрати 1 мільйон 8-розрядних чисел із дозволеними дублікатами та замовити неважливо, тому машина з лише 1 мільйоном байтів оперативної пам’яті не має достатнього стану, щоб представити всі можливості. Але 1M (менше 2k для TCP / IP) - це 1022 * 1024 * 8 = 8372224 біт, тому рішення можливе.
Частина 1, початкове рішення
Цей підхід потребує трохи більше 1М, я вдосконалюю його, щоб він змістився в 1М пізніше.
Я буду зберігати компактний відсортований список номерів у діапазоні від 0 до 99999999 як послідовність підсписів 7-бітних чисел. Перший підпис має вміст чисел від 0 до 127, другий підліст містить цифри від 128 до 255 і т.д. 100000000/128 рівно 781250, тому 781250 таких підсписок знадобиться.
Кожен підпис складається з 2-розрядного заголовка підпису, за яким йде тіло. Тіло підпису займає 7 біт на запис підпису. Усі сублістини об'єднані разом, а формат дає змогу визначити, де закінчується один підпис і починається наступний. Загальний обсяг пам’яті, необхідний для повністю заселеного списку, становить 2 * 781250 + 7 * 1000000 = 8562500 біт, що становить приблизно 1,021 М-байт.
4 можливі значення заголовка підспісу:
00 Порожній підпис, нічого не випливає.
01 Сінглтон, у підписці є лише один запис, і наступні 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-бітових цілих чисел, сортую нові числа на місці (можливо, використовуючи велику групу), а потім об'єдную їх у новий компактний відсортований список. Повторюйте, поки не буде читання більше чисел, а потім ще раз пройдіться компактним списком, щоб отримати результат.
Рядок нижче представляє пам'ять безпосередньо перед початком операції злиття списку. "O" s - це область, яка містить відсортовані 32-бітні цілі числа. "X" - це область, що містить старий компактний список. Знаки "=" - це приміщення розширення для компактного списку, 7 біт для кожного цілого числа в "O" s. "Z" - це інші випадкові накладні витрати.
ZZZOOOOOOOOOOOOOOOOOOOOOOOOOO==========XXXXXXXXXXXXXXXXXXXXXXXXXX
Підпрограма злиття починається з читання в крайньому лівому "O" та в лівій лівій "X", і починає писати в крайній лівій частині "=". Покажчик запису не вловлює покажчик зчитування компактного списку, поки всі нові цілі числа не об'єднані, тому що обидва вказівники просувають 2 біти для кожного підпису та 7 біт для кожного запису у старому компактному списку, і є достатньо місця для 7-бітні записи для нових номерів.
Частина 2, набивши її в 1М
Щоб видавити рішення вище в 1 М, мені потрібно зробити формат списку компактніше. Я позбудусь одного з типів списків, щоб було лише 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 Підпис складається з 2 або більше повторів одного числа.
Мої 3 значення заголовка підспісу будуть "A", "B" і "C", тому мені потрібен спосіб представити підсистеми типу D.
Припустимо, у мене є заголовок підсистеми типу C, який супроводжується 3-ма записами, такими як "C [17] [101] [58]". Це не може бути частиною дійсного списку типу С, як описано вище, оскільки третій запис менший за другий, але більше, ніж перший. Я можу використовувати цей тип конструкції для представлення підспису типу D. Якщо говорити трохи, де б я не міг "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 біт числа, але ця закономірність довша за підпис, який він представляє, що робить речі дещо складнішими. П'ять знаків запитання в кінці можна вважати не частиною шаблону, тому я маю: "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" пишеться, коли лічильник дорівнює нулю. Тож наступні два біти, записані, перейдуть до слотів A і B, і тоді "10" потрапить до кінця.
З 3-х значень заголовка підспісу, представлених "00", "01" та "1", я можу призначити "1" найпопулярнішому типу підпису. Мені знадобиться невелика таблиця, щоб зіставити значення заголовків підсписів на типи підполістів, і мені знадобиться лічильник зустрічей для кожного типу підспісу, щоб я знав, що найкраще відображення заголовка підспісу.
Найгірший випадок мінімального представлення повнонаселеного компактного списку відбувається тоді, коли всі типи списків однаково популярні. У цьому випадку я зберігаю 1 біт на кожні 3 заголовки списку, тому розмір списку становить 2 * 781250 + 7 * 1000000 - 781250/3 = 8302083,3 біт. Округлення до 32-бітової межі слова, це 8302112 біт або 1037764 байт.
1М мінус 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 байт для повноцінно заповненого компактного списку, це залишає мені 8764 - 4096 = 4668 байт для "Z" s на карті пам'яті.
Цього має бути достатньо для 10 таблиць відображення заголовків підсписів, 30 підрахунків заголовок підспісу та інших кількох лічильників, покажчиків та невеликих буферів, які мені знадобляться, та місця, яке я використав, не помічаючи, як простір для стеків для повернення функцій зворотного виклику та локальні змінні.
Частина 3, скільки часу знадобиться бігти?
При порожньому компактному списку 1-бітний заголовок списку буде використовуватися для порожнього підпису, а початковий розмір списку становитиме 781250 біт. У гіршому випадку список зростає 8 біт на кожне додане число, тому 32 + 8 = 40 біт вільного простору потрібно, щоб кожне з 32-бітових номерів було розміщене у верхній частині буфера списку, а потім відсортовано та об'єднано. У гіршому випадку зміна відображення заголовка підспісу призводить до використання місця 2 * 781250 + 7 * записів - 781250/3 біт.
При політиці зміни відображення заголовка підспісу після кожного п’ятого злиття, коли в списку буде щонайменше 800000 номерів, найгірший випадок залучатиме до загальної кількості близько 30 мільйонів активних читання та запису списків.
Джерело:
http://nick.cleaton.net/ramsortsol.html