存储一百万个电话号码


74

从内存的角度来看,最有效的方法是存储100万个电话号码?

显然,这是Google的面试问题,请提出您的想法。



29
@Dylan:不太为零,您必须记住离开打印输出的位置。
史蒂夫·杰索普

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位数字(中国,从国外拨打)?我敢肯定,还有其他模式,这些只是我所知道的。空间的密度与包装方式有关。
罗伦·佩希特尔

3
其他人是否注意到这基本上简化了Programming Pearls前20页中的磁盘排序问题?在权衡设计权衡时,将电话号码用作域和内存是您最大的考虑因素。答案是一个位数组或位向量。
nsfyn55 2012年

Answers:


46

如果内存是我们最大的考虑因素,那么我们根本不需要存储数字,只需存储i和i + 1之间的差值即可。

现在,如果电话号码的范围在200 0000-999 9999之间,则可能有7,999,999个电话号码。由于我们有100万个数字,并且假设它们是均匀分布的,那么在连续数字n_i和n_i + 1之间的期望距离为E = n_i + 1-n_i〜8(3位)。因此,对于32位int,我们最多可以存储10个连续偏移量(〜400kb最佳总内存占用量),但是在某些情况下,我们可能需要大于8的偏移量(也许有400个偏移量,或者1500 ??)。在这种情况下,我们可以简单地将int的前2位保留为标头,以告知我们用于读取其存储的位的帧大小。例如,也许我们使用:00 = 3x10、01 = 5x6、10 = 7x4、11 = 1 * 30。


9
我真的很喜欢这个评论。对于外行人,您需要做的是将数字从最小到最大排序。将第一个号码存储在列表中。然后,对于下一个数字,仅存储差异。所以一个简单的例子是a)555-1234 b)555-2234。在这种情况下5552234-5551234 =1000。因此您的存储空间将为5551234,1000,...好极了,我想这就是Google想要的。他们从来没有提到访问速度,但是我会在其中包括一个替代答案,并将其考虑在内。
利爪

1
欣赏一个数字与另一个数字之间的“增量” ...哇!太棒了!
CodeMad 2015年

如另一个答案所示,如果使用位数组存储数字,还能得到多少呢?对于10位电话号码,您需要一个100亿位的位数组。然后,您可以压缩模式,例如将所有可能的电话号码编码为"1"x10^10(所有100亿位均为1)。所有从0开始的交替数字都是"01"x(10^10)/2(重复字符串“ 01” 50亿次)。如果您获得大约五亿个数字的随机分布,则该方法将失败,其中编码大小可能超过100亿位。
Marius

28

将它们以ASCII格式书写,以空格分隔。

使用您喜欢的压缩算法压缩结果字符串。如果顺序不重要,则将其首先排序可能会有助于压缩,从而使更多的重复点更紧密地结合在一起。

哦,您要高效的随机访问吗?那你应该说。


2
通用包装的效率很低,因为它不能从分类中获得很大的优势。例如,使用排序+增量编码,您可以获得bzip2的大小的1/3左右-最佳(我用一百万个10位数字和1000个5位数字前缀进行了测试:bzip2 = 3660874,delta = 1104188,原始= 10000000)
6502

10

一个可能的解决方案是

  1. 排序数字
  2. 编码从一个数字到下一个数字的增量

Delta频率分布将高度偏斜。

我使用7 + 3 + 3 + ...位编码,使用简单的类似BER的打包方法对增量进行了实验。编码功能原为

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。

@DK:可能是。BER编码的好处是,没有树可以存储(因为它是预定义的),因此像通常的YMMV一样。如果我正确地进行了计算,那么在打包由1000个前缀和5个自由数字构成的100万个数字时,每个数字的最小理论平均位数约为每个数字4.68位(加上1000个前缀的存储量),因此显然8.83仍与最佳。
6502,

7

在数字块上进行霍夫曼编码可能会得到很好的结果。如果数字是混合类型(例如,一些美国,一些包括访问代码的海外代码),则需要另外几位来指定它们是哪种类型(以及要使用的块)。

如果数字在较小范围内(例如7位数字),则存储它们的最紧凑方法可能是将它们视为整数,对其进行排序,然后存储(霍夫曼编码)值的差异。例如,以7位数字表示10 ^ 6的数字(可能的10 ^ 7),您希望每个数字大约需要log2(10)〜= 3.3位。


7

首先,我观察到它们从不以0开头,因为0开头被用作转义字符。因此,我可以简单地将电话号码视为整数。如果不是这种情况,我只需在数字前加上“ 1”,然后将其转换为整数即可。这不会显着影响编码效率(可能是几个字节不变的开销)。如果电话号码内的10位数字之外还有其他字符,则只能使用大于10的基数进行编码。

我会按尺寸升序排序。然后计算差异。然后使用protobuf作为打包的重复字段序列化差异。

该方法类似于RexKerr的方法,除了我在霍夫曼编码器上使用protobuf的惰性解决方案。可能更大一点,因为protobuf整数编码是通用的,并且没有考虑电话号码的概率分布。但是,由于我只需要使用现有的protobuf序列化程序,因此编写代码要容易得多。一旦超出UInt64的大小,这将成为问题,即电话号码长于19位数字。该文件格式仍然支持它,但是大多数实现均不支持。

没有索引的访问时间将是非常糟糕的,但是它应该相当紧凑。



5

如果查看“北美编号计划”的数据字段表示形式,您将得出结论:每个区号中每个电话号码字段中存储的美国电话号码1+ NPA + NXX + xxxx少于22位。添加区号,代表任何美国(加上加拿大)电话号码的数据都可以轻松容纳32位。这是位字段表示形式,而不是int形式。

但是,您对此的思考不应以美国为中心。当然,问题不仅仅在于将一百万个电话号码压缩成尽可能少的位数。

美国电话号码可以短至3位数字(内部PBX拨号计划)到最长22位数字(1 + NPA + NXX + xxxx + 11位内部PBX拨号计划)。如果电话号码仅限于ITU指定的号码格式,则您最多可以输入15位数字,外加1位的“ +”号。

然后,您可能应该定义一个介于3位和22位之间(对于ITU为15位)的任何电话号码的可变位域表示,每个位域都有一个X比特头域,以指示该域的格式。

然后将这些位字段放入压缩的位数组中。可能可以使用trie或某些其他方法来索引该位数组。

效率的高低取决于一百万个电话号码的格式,您要多快地访问它们以及将来以不同格式将数据结构用于更多电话号码的灵活性。它不仅在为“正确”答案恕我直言计算位数。


3

假设我们假设每个电话号码与(3位区号)-(7位数字)的美国格式一致

这是一个10位数字。

但是,在处理电话号码时有参与规则。一方面,它们稀疏,这意味着并非使用所有可能的区号。在这种情况下,一棵简单的树是可以的。我的意思是考虑一下……加拿大只需要269 + 26。这很小,您已经节省了很大一部分空间,而且增加了搜索时间。不仅如此,还可以增加位置信息。

之后,您将获得一个7位数字。可以将其存储为单个32位整数。对插入内容进行排序,您将获得一种非常快速的检索机制,因为您可以对数字的其余部分进行二进制搜索。


2

我认为我们可以在这里使用大小为100万的位向量。

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解决方案的效率如何?
阿列克谢·弗伦兹

这是最好的答案
craftsmannadeem

这不是一个好答案。应该存储电话号码,而不是1-1,000,000的号码。话虽如此,不可能从中检索电话号码。您可能具有将电话号码映射到该位数组中特定索引的哈希函数,但是哈希函数还是一种方法,因此无法重建原始电话号码列表。存储事物背后的想法是能够在以后检索它们。
阿里安·阿科斯塔

1

我猜一个未签名的Int32或国际数字一个未签名的Int64

使用将为4MB的32位无符号整数


好吧,由于电话号码至少为7位数字(以我的经验),您会浪费空间来存储数字0-999,999。

电话号码不是号码。不要尝试将它们存储为整数。
尼克·约翰逊

我敢打赌,在美国(大多数美国),您需要存储区号才有用。这会使电话号码变成10位数字,并且至少需要34位Int或时髦的包装才能消除可能未使用的0-999999999值(这是您的34位空间的一半!)。
Thomas M. DuBuisson 2011年

1
在其他国家/地区,您可能必须存储前导0,并且必须处理引伸-Martin
Beckett

@NickJohnson不是,但是为了解决这个问题,您可以使用整数作为电话号码的索引,对吗?例如。可以将“(311)-0031-1151”索引为31100311151。整数索引将需要37位,但是您至少需要7 * 11位才能存储与ASCII相同的数字。
Andrew J

1

这确实取决于您要在存储的数据库上运行哪些操作。

普通的方法是使用无符号整数,如果您只需要存储它们,则使用字典对原始文本表示进行一些压缩可能会更小。


没有!电话号码不是号码!
尼克·约翰逊

前导零可能很重要,您可能还需要考虑一下扩展问题
Martin Beckett

如果您真的想使用这样的编码方案,处理前导零的简单方法是在字符串前加上“ 1”,然后编码为整数。当您将整数解码回字符串时,请去除开头的“ 1”。因此,电话号码“ 456”存储为“ 1456”,而电话号码“ 0015833258881”存储为“ 10015833258881”。将电话号码存储为整数还有其他问题,但是前导零“问题”不是其中之一。
我的正确观点

我同意@NickJohnson在这一点上。我不会尝试将它们存储为int或任何基于数字的数据类型。现实是,它们是字符串-它们没有任何“计算”值
罗伯特·佩里

1

在求职面试中,这个问题的重点是扩大申请人的解决问题的能力。因为问题的重点是记忆效率,所以我认为正确的答案是问访调员:“电话号码是国际电话,还是仅限于一个国家?” 如果将号码限制在一个国家/地区,则每个国家/地区都有按州和市分配电话号码的简单规则,从而简化了最大化存储效率的任务。


1

8百万个数字,其中800万个数字之一为1(已使用)或0(可用)。

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

这称为鸽子洞排序。
Ole Tange 2012年

怎样避免使用那么多位的重复呢?该结构仅包含所需的内容。如果电话号码稀疏,那么即使没有碰撞问题,也可以节省大量成本。我假设您的解决方案无法解决冲突。
安德鲁·斯科特·埃文斯

-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
请问这是什么?它甚至还不完整。它如何回答这个问题?
Mysticial

神秘的,它是一个C程序。我希望您能看到全部内容,主要是注释,而进入内存的最终代码在底部。
Paul Romsky

您能补充一下如何解决问题的说明吗?
马丁·彼得

Martijn,您可以在程序末尾看到主要功能吗?这是一个简单的循环。该程序的规模很小,但拥有一百万个连续数字。当我剪切并粘贴代码时,格式混乱了。
Paul Romsky 2013年

int main(){unsigned int Number; / * 1,000,000电话号码存储区000-0000至999-9999 / for(数字= 0000000;数字<10000000;数字++){/检索数字/ printf(“%07u \ n”,数字); }返回0; } /结尾* /
Paul Romsky
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.