Який найефективніший спосіб зберігати 1 мільйон телефонних номерів з урахуванням пам’яті?
Очевидно, це питання співбесіди в Google, будь ласка, дайте свої ідеї.
Який найефективніший спосіб зберігати 1 мільйон телефонних номерів з урахуванням пам’яті?
Очевидно, це питання співбесіди в Google, будь ласка, дайте свої ідеї.
Apparently this is an interview question at Google, although this seems like its a bit too easy.
. Для мене нелегко
Відповіді:
Якщо пам’ять - це наше найбільше значення, тоді нам зовсім не потрібно зберігати число, а лише дельта між i та i + 1.
Зараз, якщо номери варіюються від 200 0000 - 999 9999, то існує 7 999 999 можливих телефонних номерів. Оскільки ми маємо 1 мільйон чисел, і якщо ми вважаємо, що вони рівномірно розподілені, ми маємо очікувану відстань E = n_i + 1 - n_i ~ 8 (3 біти) між послідовними числами n_i та n_i + 1. Отже, для 32-розрядного int ми могли б потенційно зберігати до 10 послідовних зсувів (~ 400 кб оптимального загального розміру пам’яті), проте цілком ймовірно, що ми матимемо випадки, коли нам потрібен зсув більше 8 (можливо, ми маємо 400, або 1500 ??). У цьому випадку ми можемо просто зарезервувати перші 2 біти int як заголовок, який повідомляє нам, який розмір кадру ми використовуємо для зчитування бітів, які він зберігає. Наприклад, можливо, ми використовуємо: 00 = 3x10, 01 = 5x6, 10 = 7x4, 11 = 1 * 30.
"1"x10^10
(усі 10 мільярдів бітів - це 1). Всі чергуються числа, що починаються з 0, будуть "01"x(10^10)/2
(повторити рядок "01" 5 мільярдів разів). Підхід провалиться, якщо ви отримаєте випадковий розподіл близько півмільярда чисел, де розмір кодування може перевищувати 10 мільярдів бітів.
Запишіть їх ASCII, розділені пробілом.
Заархівуйте отриманий рядок, використовуючи ваш улюблений алгоритм стиснення. Якщо порядок не важливий, спочатку їх сортування може допомогти стисненню, надаючи більше повторень ближче.
О, ви хотіли ефективного довільного доступу? Тоді вам слід було сказати.
Можливим рішенням є
Розподіл частоти дельта буде дуже нерівним.
Я зробив експеримент, використовуючи простий BER-подібний підхід до упаковки для дельт, використовуючи кодування 7 + 3 + 3 + ... біт; функція кодування була
def delta_write(x, b1, b2):
lim = 1 << (b1 - 1)
if x < lim:
bit_write(x, b1)
else:
bit_write(lim + (x & (lim - 1)), b1)
delta_write(x >> (b1 - 1), b2, b2)
(два параметри 7 і 3 були визначені експериментально)
За допомогою цього підходу я отримав мільйон 10-значних чисел з першими 5 цифрами, вибраними з тисячі випадкових префіксів із середнім значенням 8,83 біт на номер (розмір упаковки 1104188).
Кодування Хаффмана на блоках цифр, мабуть, дало б дуже хороші результати. Якщо номери були змішаного типу (наприклад, деякі США, деякі за кордоном, включаючи код доступу), вам знадобиться ще пара біт, щоб вказати, якого типу вони були (і, отже, які блоки використовувати).
Якби числа були в якомусь невеликому діапазоні - наприклад, сім цифр - найбільш компактним способом їх зберігання, мабуть, було б розглядати їх як цілі числа, сортувати та зберігати (закодовані Хаффманом) відмінності у значеннях. Наприклад, з 10 ^ 6 числами з 7 цифр (10 ^ 7 можливостей), ви очікуєте, що вам знадобиться приблизно log2 (10) ~ = 3,3 біта на число.
Спочатку я зауважую, що вони ніколи не починаються з 0, оскільки 0 на початку використовується як символ втечі. Тож я можу просто розглядати телефонні номери як цілі числа. Якби це було не так, я б просто додав "1" до числа, а потім перетворив його на ціле число. Це не суттєво вплине на ефективність кодування (ймовірно, постійні накладні витрати в кілька байтів). Якщо є інші символи за межами 10 цифр всередині телефонних номерів, просто кодуйте з базою вище 10. Однак це зашкодить ефективності.
Я б замовив їх за розміром за зростанням. Потім обчисліть різниці. А потім серіалізуйте відмінності, використовуючи protobuf як упаковані повторювані поля.
Цей метод подібний до методу RexKerr, за винятком того, що я використовую ліниве рішення protobuf над кодером Хаффмана. Можливо, трохи більший, оскільки цілочисельне кодування protobuf є загальним призначенням і не враховує розподіл ймовірності телефонних номерів. Але набагато простіше кодувати, оскільки мені просто потрібно використовувати існуючий серіалізатор protobuf. Це стане проблематичним, коли ви перевищите розмір UInt64, тобто є телефонні номери довші за 19 цифр. Формат файлу все ще підтримує його, але більшість реалізацій не підтримують.
Без індексу час доступу буде досить поганим, але він повинен бути досить компактним.
Дерево трійкового пошуку, яке є спеціальною структурою даних trie, буде ефективно використовувати пам’ять і все одно дозволить (як trie) часткове узгодження.
Якщо ви подивитесь на подання полів даних Північноамериканського плану нумерації , то дійдете висновку, що в США номери телефонів 1+ NPA + NXX + xxxx можуть зберігатися менше ніж у 22 біти в полі телефонного номера в коді регіону. Додайте коди регіонів, і дані, що представляють будь-який номер телефону в США (плюс Канада), можуть зручно поміститися в 32 біти. Це як бітове представлення поля, а не як int.
Однак, ваше мислення щодо цього не повинно бути американським. Звичайно, питання полягає не лише у вправі, яка полягає у стисненні 1 мільйона телефонних номерів у найменшу кількість бітів.
Американські телефонні номери можуть бути короткими від 3 цифр (внутрішня плата набору АТС) до 22 цифр (1 + NPA + NXX + xxxx + 11 цифр внутрішньої телефонної плати АТС). Якщо номер телефону обмежувався форматом номерів, визначеним МСЕ , у вас є до 15 цифр плюс 1 біт для "+".
Потім слід, мабуть, визначити подання змінного бітового поля будь-якого телефонного номера від 3 до 22 цифр (або 15 цифр для ITU), при цьому кожне бітове поле має X-бітове поле заголовка для позначення формату поля.
Потім помістіть ці бітові поля в стислий бітовий масив . Потенційно цей бітовий масив можна проіндексувати тріе або яким-небудь іншим методом.
Ефективність цього базується на форматі 1 мільйона телефонних номерів, наскільки швидко ви хочете отримати до них доступ і наскільки гнучка ця структура даних для більшої кількості телефонних номерів у майбутньому у різних форматах. Це не просто підрахунок бітів для "правильної" відповіді IMHO.
Скажімо, ми припускаємо, що кожен номер телефону відповідає американському формату (3-значний код міста) - (7-значний номер)
Це 10-значне число.
Однак існують правила взаємодії при роботі з телефонними номерами. З одного боку, вони розріджені, тобто використовуються не всі можливі коди регіонів. У цьому випадку просте дерево - це нормально. Я маю на увазі подумати про це ... вам потрібно лише 269 + 26 для Канади. Це досить мало, і ви вирізали велику частину простору ПЛЮС, збільшивши час пошуку. Мало того, але його можна доповнити для отримання інформації про місцезнаходження.
Після цього ви отримаєте 7-значне число. Це може зберігатися в одному 32-розрядному цілому числу. Відсортуйте за вставкою, і у вас є досить швидкий механізм пошуку, оскільки ви можете виконати двійковий пошук по залишку числа.
Я думаю, ми можемо використовувати тут Bit Vector розміром 1 мільйон.
Приклад Java:
private BitSet dir = new BitSet(1000000);
public void addTelephoneNumber(int number)
{
dir.set(number);
}
public void removeTelephoneNumber(int number)
{
if (dir.get(number))
{
dir.flip(number);
}
}
public boolean isNumberPresent(int number)
{
return dir.get(number);
}
Думаю, непідписаний Int32 або для міжнародних номерів непідписаний Int64
Використовуючи 32-бітові непідписані файли розміром 4 МБ
Int
або шикарної упаковки, щоб усунути ймовірно невикористані значення 0-999999999 (це більше половини вашого 34-бітового простору!).
Це справді залежить від того, які операції ви хочете виконати над збереженою базою даних.
Тривіальний підхід - використання цілих чисел без знака, якщо вам просто потрібно їх зберегти, можливо, деяке стиснення у поданні необробленого тексту за допомогою словника було б меншим.
Під час співбесіди на роботі суть цього питання полягає у визначенні навичок розв’язання проблем заявника. Оскільки в центрі уваги питання - ефективність пам’яті , на мою думку, правильною відповіддю є запитання інтерв’юера: "Чи міжнародні номери телефонів чи вони обмежені однією країною?" Якщо номери обмежені однією країною, то завдання максимізації ефективності пам'яті спрощується тим, що в кожній країні існують прості правила розподілу телефонних номерів за штатом та містом.
8 мільйонів біт з кожним бітом 1 (використовується) або 0 (доступно) для одного з 8 мільйонів прикладів чисел
100 0000
900 0000
= 8 million phone numbers, bit 1 = 1000000 and bit 8 million = 9000000
/********************************************************************************
Filename: Phone_Numbers.c
Author: Paul Romsky
Company: Autoliv AEL
Date: 11 MAR 2013
Problem: What is the most efficient way, memory-wise, to store 1 million
phone numbers?
Caveats: There is no mention if the numbers are contiguous or not, so, to save
space the numbers should be contiguous. The problem (as a specification)
is rather vague and leaves a lot to interpretation, so many different
methods may be desired, but which one(s) desired is not surely known.
Are the phone numbers just the extension (last four digits), or do they
include the exchange (the leading 3 digits), or do they also include
area code and/or international numbers?
There is no mention of the first number, only the range of numbers, so
the first number 000-0000 is used. Although many numbers are not
normally used, they could in fact be used as valid number sequences
within the phone companies and are thus phone numbers nonetheless.
Solution: A simple algorithm. If the numbers are not contiguous a fractal algorithm
could be used.
A standard ANSI C compiler should pack this program into a very small
assembly module of only a few bytes.
Revisions:
Rev Date By Description
--- ----------- -------------------- -------------------------------------------
- 11 MAR 2013 P. Romsky Initial Coding
********************************************************************************/
/* Includes */
#include <stdio.h>
/* Functions */
/********************************************************************************
*
* Main Entry Point
*
********************************************************************************/
int main()
{
unsigned int Number;
/* 1,000,000 Phone Number Storage 000-0000 through 999-9999 */
for(Number = 0000000; Number < 10000000; Number++)
{
/* Retrieve Numbers */
printf("%07u\n", Number);
}
return 0;
}
/* End */