Зберігання 1 мільйона телефонних номерів [закрито]


74

Який найефективніший спосіб зберігати 1 мільйон телефонних номерів з урахуванням пам’яті?

Очевидно, це питання співбесіди в Google, будь ласка, дайте свої ідеї.



29
@Dylan: не зовсім нульовий, ти повинен пам’ятати, де ти залишив роздруківку.
Steve Jessop

4
Apparently this is an interview question at Google, although this seems like its a bit too easy.. Для мене нелегко
Бенджамін Крузьє

3
Спочатку потрібно визначити номер телефону. 7 цифр (місцеві жителі США)? 10 цифр (США на великі відстані)? Або щось більш екзотичне - від 5 до 8 цифр (місцевий Китай)? 9-12 цифр (Китай, набраний за межами країни)? Я впевнений, що є й інші закономірності, це лише ті, які я знаю. Щільність простору має значення в тому, як ви його запакуєте.
Loren Pechtel

3
Хтось ще помічає, що це в основному спрощення проблеми сортування диска на перших 20 сторінках програмування перлів? До використання телефонних номерів як домену та пам’яті як найбільшого уваги при зваженні компромісних варіантів. Відповідь - бітовий або бітовий вектор.
nsfyn55

Відповіді:


46

Якщо пам’ять - це наше найбільше значення, тоді нам зовсім не потрібно зберігати число, а лише дельта між 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.


9
Мені дуже подобається цей коментар. Для неспеціаліста вам потрібно відсортувати числа від найменшого до найбільшого. Збережіть перший номер у списку. Тоді для наступного числа зберігайте лише різницю. Отже, простим прикладом є а) 555-1234 б) 555-2234. У цьому випадку 5552234 - 5551234 = 1000. Отже, ваше сховище буде 5551234,1000, ... Чудово, думаю, це те, що Google шукав би. вони ніколи не згадували швидкість доступу, але я б включив альтернативну відповідь із цим, яка враховує це.
Талон

1
Цінуйте "дельту" між одним числом і другим ... Ого! Чудово!
CodeMad

Скільки ще можна отримати, якщо використовувати бітовий масив для зберігання чисел, як пропонується в іншій відповіді? Для 10-значних телефонних номерів вам знадобиться 10-мільярдний бітовий масив. Потім ви можете стиснути шаблони, наприклад, усі можливі телефонні номери кодуються як "1"x10^10(усі 10 мільярдів бітів - це 1). Всі чергуються числа, що починаються з 0, будуть "01"x(10^10)/2(повторити рядок "01" 5 мільярдів разів). Підхід провалиться, якщо ви отримаєте випадковий розподіл близько півмільярда чисел, де розмір кодування може перевищувати 10 мільярдів бітів.
Маріус

28

Запишіть їх ASCII, розділені пробілом.

Заархівуйте отриманий рядок, використовуючи ваш улюблений алгоритм стиснення. Якщо порядок не важливий, спочатку їх сортування може допомогти стисненню, надаючи більше повторень ближче.

О, ви хотіли ефективного довільного доступу? Тоді вам слід було сказати.


2
Загальна упаковка є досить неефективною, оскільки вона не може серйозно скористатися сортуванням. Використовуючи, наприклад, сортування + дельта-кодування, ви можете отримати близько 1/3 розміру bzip2 - найкращого (я зробив тест з одним мільйоном чисел з 10 цифр з 1000 5-значними префіксами: bzip2 = 3660874, delta = 1104188, raw = 10000000)
6502

10

Можливим рішенням є

  1. сортувати числа
  2. кодувати дельти з одного числа на наступне

Розподіл частоти дельта буде дуже нерівним.

Я зробив експеримент, використовуючи простий 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).


Я хоч про ті самі кроки 1. та 2., з використанням Хаффмана для кодування. Цікаво, чи це дасть кращий результат ...
ДК.

@DK: Може бути. Приємна річ кодування BER полягає в тому, що немає дерева для зберігання (оскільки воно є заздалегідь визначеним), як звичайно YMMV. Якщо я правильно зробив обчислення, мінімальне теоретичне середнє значення бітів на число при упаковці одного мільйона чисел, побудованих з 1000 префіксів та 5 вільних цифр, становить приблизно 4,68 біта на число (плюс пам'ять для 1000 префіксів), тому, мабуть, 8,83 все ще далеко від оптимальна.
6502

7

Кодування Хаффмана на блоках цифр, мабуть, дало б дуже хороші результати. Якщо номери були змішаного типу (наприклад, деякі США, деякі за кордоном, включаючи код доступу), вам знадобиться ще пара біт, щоб вказати, якого типу вони були (і, отже, які блоки використовувати).

Якби числа були в якомусь невеликому діапазоні - наприклад, сім цифр - найбільш компактним способом їх зберігання, мабуть, було б розглядати їх як цілі числа, сортувати та зберігати (закодовані Хаффманом) відмінності у значеннях. Наприклад, з 10 ^ 6 числами з 7 цифр (10 ^ 7 можливостей), ви очікуєте, що вам знадобиться приблизно log2 (10) ~ = 3,3 біта на число.


7

Спочатку я зауважую, що вони ніколи не починаються з 0, оскільки 0 на початку використовується як символ втечі. Тож я можу просто розглядати телефонні номери як цілі числа. Якби це було не так, я б просто додав "1" до числа, а потім перетворив його на ціле число. Це не суттєво вплине на ефективність кодування (ймовірно, постійні накладні витрати в кілька байтів). Якщо є інші символи за межами 10 цифр всередині телефонних номерів, просто кодуйте з базою вище 10. Однак це зашкодить ефективності.

Я б замовив їх за розміром за зростанням. Потім обчисліть різниці. А потім серіалізуйте відмінності, використовуючи protobuf як упаковані повторювані поля.

Цей метод подібний до методу RexKerr, за винятком того, що я використовую ліниве рішення protobuf над кодером Хаффмана. Можливо, трохи більший, оскільки цілочисельне кодування protobuf є загальним призначенням і не враховує розподіл ймовірності телефонних номерів. Але набагато простіше кодувати, оскільки мені просто потрібно використовувати існуючий серіалізатор protobuf. Це стане проблематичним, коли ви перевищите розмір UInt64, тобто є телефонні номери довші за 19 цифр. Формат файлу все ще підтримує його, але більшість реалізацій не підтримують.

Без індексу час доступу буде досить поганим, але він повинен бути досить компактним.


7

Дерево трійкового пошуку, яке є спеціальною структурою даних trie, буде ефективно використовувати пам’ять і все одно дозволить (як trie) часткове узгодження.

http://en.wikipedia.org/wiki/Ternary_search_tree


5

Якщо ви подивитесь на подання полів даних Північноамериканського плану нумерації , то дійдете висновку, що в США номери телефонів 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

Скажімо, ми припускаємо, що кожен номер телефону відповідає американському формату (3-значний код міста) - (7-значний номер)

Це 10-значне число.

Однак існують правила взаємодії при роботі з телефонними номерами. З одного боку, вони розріджені, тобто використовуються не всі можливі коди регіонів. У цьому випадку просте дерево - це нормально. Я маю на увазі подумати про це ... вам потрібно лише 269 + 26 для Канади. Це досить мало, і ви вирізали велику частину простору ПЛЮС, збільшивши час пошуку. Мало того, але його можна доповнити для отримання інформації про місцезнаходження.

Після цього ви отримаєте 7-значне число. Це може зберігатися в одному 32-розрядному цілому числу. Відсортуйте за вставкою, і у вас є досить швидкий механізм пошуку, оскільки ви можете виконати двійковий пошук по залишку числа.


2

Я думаю, ми можемо використовувати тут 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);
}

1
Наскільки ефективним є це рішення BitSet з точки зору простору?
Олексій Фрунзе

Це найкраща відповідь
ремесленник

Це невдала відповідь. Передбачається зберігати номери телефонів, а не номери від 1-1000000. Сказавши це, неможливо отримати з цього телефонні номери. Ви можете мати функцію хешування, яка відображає телефонні номери до певного індексу в цьому бітовому масиві, але знову ж таки функції хешування є одним із способів, тому неможливо відновити вихідний список телефонних номерів. Ідея зберігання речей полягає в можливості отримати їх після.
Аріан Акоста

1

Думаю, непідписаний Int32 або для міжнародних номерів непідписаний Int64

Використовуючи 32-бітові непідписані файли розміром 4 МБ


Ну, оскільки телефонні номери складають щонайменше 7 цифр (з мого досвіду), ви б витратили місце для зберігання цифр 0 - 999 999.
Кай

Номери телефонів - це не номери. Не намагайтеся зберігати їх як цілі числа.
Нік Джонсон,

На практиці (в більшості США) я б поставив заклад, що вам потрібно зберегти код міста, щоб бути корисним. Це робить телефонні номери 10-значними і потребує принаймні 34-бітної Intабо шикарної упаковки, щоб усунути ймовірно невикористані значення 0-999999999 (це більше половини вашого 34-бітового простору!).
Thomas M. DuBuisson

1
В інших країнах вам, можливо, доведеться зберігати перші 0, і вам доведеться обробляти вилучення
Мартін Беккет

@NickJohnson Вони не є, але задля проблеми ви можете використовувати ціле число як індекс для телефонного номера, так? напр. "(311) -0031-1151" можна проіндексувати до 31100311151. Цілочисельному індексу знадобиться 37 біт, але вам знадобиться принаймні 7 * 11 бітів, необхідних для збереження того самого числа, що і ASCII.
Andrew J

1

Це справді залежить від того, які операції ви хочете виконати над збереженою базою даних.

Тривіальний підхід - використання цілих чисел без знака, якщо вам просто потрібно їх зберегти, можливо, деяке стиснення у поданні необробленого тексту за допомогою словника було б меншим.


Ні! Номери телефонів - це не номери!
Нік Джонсон,

Нулі, що ведуть, можуть бути значними, можливо, вам також доведеться врахувати продовження
Мартін Беккет

Найпростіший спосіб мати справу з початковими нулями, якщо ви дійсно хочете використовувати таку схему кодування, - це додати "1" до рядка, а потім кодувати як ціле число. Коли ви декодуєте ціле число назад до рядка, зніміть провідну "1". Тож номер телефону "456" зберігається як "1456", тоді як номер телефону "0015833258881" зберігається як "10015833258881". Є й інші проблеми із збереженням телефонних номерів як цілих чисел, але головні нулі "проблема" - не одна з них.
ТІЛЬКИ МОЙ правильний ДУМКА

Я погоджуюсь з @NickJohnson щодо цього. Я б не намагався зберігати їх як тип даних типу int або будь-якого числа. Реальність така, вони є рядками - вони не мають жодного "обчислювального" значення
Роберт Перрі,

1

Під час співбесіди на роботі суть цього питання полягає у визначенні навичок розв’язання проблем заявника. Оскільки в центрі уваги питання - ефективність пам’яті , на мою думку, правильною відповіддю є запитання інтерв’юера: "Чи міжнародні номери телефонів чи вони обмежені однією країною?" Якщо номери обмежені однією країною, то завдання максимізації ефективності пам'яті спрощується тим, що в кожній країні існують прості правила розподілу телефонних номерів за штатом та містом.


1

8 мільйонів біт з кожним бітом 1 (використовується) або 0 (доступно) для одного з 8 мільйонів прикладів чисел

100 0000
900 0000
= 8 million phone numbers, bit 1 = 1000000 and bit 8 million = 9000000 

Це називається сортування голубів.
Ole Tange

А як про спробу уникнути використання дублікатів такої кількості бітів. Структура містить лише те, що потрібно. Якщо телефонні номери є рідкісними, це також може значно заощадити витрати без проблем зіткнення. Я припускаю, що ваше рішення не враховує колізій.
Ендрю Скотт Еванс,

-11
/******************************************************************************** 

  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 */

1
Чи можу я запитати, що це? Це навіть не повністю. Як це відповідає на запитання?
Містичний

Містично, це програма на Сі. Сподіваюсь, ви можете все це побачити, це переважно коментарі, а остаточний код, який потрапляє в пам’ять, знаходиться внизу.
Пол Ромський,

Не могли б ви додати пояснення щодо того, як це вирішує проблему?
Мартін Пітерс

Мартін, ти можеш побачити головну функцію в кінці програми? Це простий цикл. Розмір програми дуже малий, але містить 1 мільйон послідовних чисел. Форматування заплуталося, коли я вирізав і вставив свій код.
Пол Ромський,

int main () {unsigned int Number; / * 1 000 000 зберігання телефонних номерів 000-0000 до 999-9999 / для (Number = 0000000; Number <10000000; Number ++) {/ Отримати номери / printf ("% 07u \ n", Number); } повернути 0; } / Кінець * /
Пол Ромський,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.