Bin telefon numarasını saklamanın en verimli yolu


94

Bu bir Google röportaj sorusudur:

Her biri 10 haneden oluşan, saklanacak yaklaşık bin telefon numarası vardır. Bin rakamlar arasında her birinin ilk 5 rakamının aynı olduğunu varsayabilirsiniz. Aşağıdaki işlemleri gerçekleştirmelisiniz: a. Belirli bir numara varsa arayın. b. Tüm numarayı yazdır

Bunu yapmanın en verimli yerden tasarruf yolu nedir?

Hash tablosunu ve daha sonra Huffman kodlamasını cevapladım ama görüşmeci benim doğru yönde gitmediğimi söyledi. Lütfen bana yardım edin.

Bir son ek trie yardımcı olabilir mi?

İdeal olarak 1000 numara depolamak, numara başına 4 bayt alır, bu nedenle toplamda 1000 sayıyı depolamak 4000 bayt alır. Niceliksel olarak, depolamayı 4000 bayta düşürmek istiyorum, görüşmecim bana bunu açıkladı.


28
Normal bir veritabanı kullanarak bunları metin, hatta binlerce / milyon olarak saklayabileceğinizi ve arama işlemlerinin hala çok hızlı olacağını söyleyeceğim. Gelecekte uluslararası numaraları desteklemek isterlerse veya "0" ile başlayan telefon numaraları görünmeye başlarsa veya hükümet karar verirse, tüm sistemin yeniden yapılması gerekeceğinden "akıllıca" şeyler yapmamanızı tavsiye edeceğim. telefon numarası biçimini değiştirin, vb.
Thomas Bonini

1
@AndreasBonini: Google veya Facebook gibi bir şirkette röportaj yapmadığım sürece, muhtemelen bu cevabı verirdim, kutunun dışında çözümler sadece kesmeyin. Örneğin postgres'in denemesi de olsa, bunların Google'ın alması gereken veri verimini azalttığından emin olamam.
LiKao

1
@LiKao: OP'nin özellikle "yaklaşık bin sayı" belirttiğini unutmayın
Thomas Bonini

@AndreasBonini: Doğru, görüşülen kişinin bu tür kısıtlamaları doğru yorumlayıp buna göre en iyi çözümü seçtiğini bildiği bir test de olabilirdi.
LiKao

4
Bu soruda "verimli" nin gerçekten tanımlanması gerekiyor - hangi yönlerden verimli? uzay, zaman, ikisi de?
matt b

Yanıtlar:


36

İşte aix'in cevabında bir gelişme . Veri yapısı için üç "katman" kullanmayı düşünün: ilki, ilk beş basamak (17 bit) için bir sabittir; yani bundan sonra, her telefon numarasında yalnızca kalan beş basamak kaldı. İkili sistem tam sayılan ve mağaza 17-bit gibi, bu, geri kalan beş basamak görüntülemek k - bir yöntem ve 17 kullanılarak bitlerinin k = m belirleyen farklı bir yöntem ile, k gerekli alanı en aza indirmek için sonunda.

Önce telefon numaralarını sıralıyoruz (tümü 5 ondalık haneye indirgenmiştir). Daha sonra, ilk m bitlerinden oluşan ikili sayının tümü 0 olan kaç telefon numarası olduğunu, ilk m bitlerinin kaç telefon numarası için en fazla 0 ... 01 olduğunu, ilk m kaç telefon numarası için bitler, ilk m bitleri 1 ... 11 olan telefon numaralarının sayısına kadar en fazla 0 ... 10, vb. - bu son sayı 1000'dir (ondalık). Bu tür 2 ^ m sayım vardır ve her sayı en fazla 1000'dir. Sonuncuyu atlarsak (zaten 1000 olduğunu bildiğimiz için), tüm bu sayıları (2 ^ m - 1) bitişik bir blokta saklayabiliriz. * 10 bit. (1024'ten küçük bir sayıyı saklamak için 10 bit yeterlidir.)

Tüm (küçültülmüş) telefon numaralarının son k biti ardışık olarak bellekte depolanır; yani k , örneğin 7 ise, bu bellek bloğunun ilk 7 biti (0'dan 6'ya kadar olan bitler) ilk (azaltılmış) telefon numarasının son 7 bitine karşılık gelir, 7'den 13'e kadar olan bitler son 7 bit'e karşılık gelir ikinci (azaltılmış) telefon numarası, vb. Bu, toplam 17 + (2 ^ (17 - k ) - 1) * 10 + 1000 * k için 1000 * k bit gerektirir ; bu, k = 10 için minimum 11287'ye ulaşır . Böylece tüm telefon numaralarını ceil'de saklayabiliriz ( 11287/8) = 1411 bayt.

Numaralarımızın hiçbirinin örneğin 1111111 (ikili) ile başlayamayacağı gözlemlenerek ek alan kaydedilebilir, çünkü bununla başlayan en düşük sayı 130048'dir ve sadece beş ondalık basamağa sahibiz. Bu, hafızanın ilk bloğundan birkaç girişi tıraş etmemize izin verir: 2 ^ m - 1 saymak yerine, sadece ceil'e (99999/2 ^ k ) ihtiyacımız var. Bu, formülün

17 + tavan (99999/2 ^ k ) * 10 + 1000 * k

bu şaşırtıcı bir şekilde hem k = 9 hem de k = 10 veya ceil (10997/8) = 1375 bayt için minimum 10997'ye ulaşır.

Setimizde belirli bir telefon numarasının olup olmadığını bilmek istiyorsak, önce ilk beş ikili rakamın kaydettiğimiz beş rakamla eşleşip eşleşmediğini kontrol ederiz. Sonra kalan beş basamağı üst m = 7 bit (yani m- bit sayısı M ) ve alt k = 10 bit ( K sayısı) olarak böleriz . Şimdi numarası bir [M-1] ', ilk olan, indirgenmiş telefon numaralarının m basamak en altındadır M 1 ve sayısı - bir [M] ilki için azaltılmış telefon numaralarının m basamak en fazla olduğu , M , her ikisi de ilk bit bloğundan. Şimdi a arasında kontrol ediyoruz[M-1] 'inci ve bir [M] th dizisi k bulduğumuz, hafıza ikinci blokta bit görmek için K ; en kötü durumda, bu tür 1000 dizi vardır, bu nedenle ikili aramayı kullanırsak, O (log 1000) işlemlerini bitirebiliriz.

1000 sayının tümünü yazdırmak için sözde kod takip eder, burada ilk bellek bloğunun K 'inci k- bit girişine bir [K] ve ikinci bellek bloğunun M ' inci m- bit girişine b [M] olarak erişirim (bunların her ikisi de yazmak için yorucu birkaç bitlik işlem gerektirir). İlk beş rakam c numarasındadır .

i := 0;
for K from 0 to ceil(99999 / 2^k) do
  while i < a[K] do
    print(c * 10^5 + K * 2^k + b[i]);
    i := i + 1;
  end do;
end do;

K = ceil (99999/2 ^ k ) için sınır durumunda bir şeyler ters gidebilir , ancak bunu düzeltmek yeterince kolaydır.

Son olarak, entropi açısından bakıldığında, tümü 10 ^ 5'ten küçük 10 ^ 3 pozitif tamsayılardan oluşan bir alt kümeyi ceil'den daha az (log [2] (binom (10 ^ 5, 10 ^ 3)) olarak depolamak mümkün değildir. ) = 8073. İlk 5 basamak için ihtiyacımız olan 17 de dahil olmak üzere, hala 10997 - 8090 = 2907 bitlik bir boşluk var. Rakamlara nispeten verimli bir şekilde erişebileceğiniz daha iyi çözümler olup olmadığını görmek ilginç bir zorluktur!


4
Burada tanımladığınız veri yapısı aslında sadece indeksleme için gerektiği kadar az ve sadece iki seviye kullanan çok verimli bir triet versiyonudur. Pratikte, bunun daha fazla seviyeli bir trie'yi geçip geçemeyeceğini görmek güzel olurdu, ancak bence bu, büyük ölçüde sayıların dağılımına bağlı (Gerçek canlı telefon numaraları tamamen rastgele değil, sadece neredeyse).
LiKao

Merhaba Erik, diğer alternatifleri görmek isteyeceğini söylediğine göre, çözümüme bir bak. Teorik minimumdan sadece 490 bit olan 8.580 bitte çözer. Tek tek numaralara bakmak biraz verimsizdir, ancak depolama çok kompakttır.
Briguy37

1
Aklı başında bir görüşmecinin "karmaşık bir özel yapılmış veritabanı" yerine "bir üçlü" cevabını tercih edeceğini tahmin ediyorum. 133t hackleme becerilerinizi göstermek istiyorsanız, şunu ekleyebilirsiniz - "gerekirse, bu özel durum için belirli bir ağaç algoritması yapmak mümkün olacaktır."
KarlP

Merhaba, 5 hanenin nasıl depolanacağını 17 bit aldığını açıklar mısınız?
Tushar Banne

@tushar Beş basamak, 00000 ile 99999 arasındaki bir sayıyı kodlar. Bu sayıyı ikili olarak temsil edin. 2 ^ 17 = 131072, yani 17 bit bunun için yeterli, ancak 16 bit değil.
Erik P.

43

Aşağıda, sayıları tam sayı değişkenleri olarak ele alıyorum (dizelerin aksine):

  1. Numaraları sıralayın.
  2. Her sayıyı ilk beş haneye ve son beş haneye bölün.
  3. İlk beş basamak sayılar arasında aynıdır, bu nedenle onları yalnızca bir kez saklayın. Bu, 17 bit depolama gerektirir.
  4. Her numaranın son beş rakamını ayrı ayrı kaydedin. Bu, numara başına 17 bit gerektirecektir.

Özetlemek gerekirse: ilk 17 bit ortak önek, sonraki 1000 17 bitlik grup, artan sırada saklanan her sayının son beş basamağıdır.

Toplamda 1000 numara için 2128 bayta veya 10 basamaklı telefon numarası başına 17.017 bit'e bakıyoruz.

Arama O(log n)(ikili arama) ve tam numaralandırma O(n).


Uhm, uzay karmaşıklığı nerede?
aioobe

Bir trie oluşturmak için O (n k) ile karşılaştırıldığında sıralama için O (log (n) * n k) (k uzunluktur) oluşturmak için çok fazla zaman . Ayrıca, daha uzun ortak önekler ayrı ayrı saklandığı için alan optimum olmaktan uzaktır. Arama süresi de optimal değildir. Bunun gibi dizi verileri için, aramaya hakim olan sayıların uzunluğunu unutmak kolaydır. Yani ikili arama O (log (n) * k) iken bir trie sadece O (k) 'ya ihtiyaç duyar. K sabit olduğunda bu ifadeleri azaltabilirsiniz, ancak bu, dizeleri depolayan veri yapıları hakkında mantık yürütürken genel bir sorun göstermektir.
LiKao

@LiKao: Dizeler hakkında kim bir şey söyledi? Ben sadece tamsayı değişkenlerle uğraşıyorum, bu yüzden kalakasız.
NPE

1
Tamam, o zaman cevabı yanlış anladım. Yine de, ortak parçalar bir arada depolanmadığından, alan verimliliği ile ilgili nokta kalır. 1000 veya 5 basamaklı sayı için oldukça fazla sayıda ortak önek olacaktır, bu nedenle bunları azaltmak çok yardımcı olacaktır. Ayrıca sayılar söz konusu olduğunda, dizeler için O (log (n)) ve O (k) olur, ki bu hala daha hızlıdır.
LiKao

1
@Geek: 17 bitlik 1001 grup 17017 bit veya 2128 bayttır (bazı değişikliklerle).
NPE

22

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

Bir keresinde veri yapıları hakkında sorular soran bir röportaj yapmıştım. "Dizi" yi unuttum.


1
+1 kesinlikle gitmenin yoludur. Bunu öğrenciyken başka bir isimle, Kütüphane ağacı veya sözcüksel arama ağacı altında öğrendim (eğer birisi o eski ismi hatırlarsa lütfen söyleyin).
Valmond

6
Bu 4000 bayt gereksinimini karşılamıyor. Yalnızca işaretçi depolaması için, en kötü durum senaryosu, 1-4. Yapraklar için bir sonraki seviyeye 1 işaretçi, 5. için 10, 6. için 100 ve 7., 8. ve 9. seviyeler için 1000 işaretçi ihtiyacınız olacaktır. , bu da göstericimizin toplamını 3114'e getiriyor. Bu, işaretçilerin göstermesi için gereken en az 3114 farklı bellek konumu veriyor, bu da her işaretçi için en az 12 bit'e ihtiyacınız olacağı anlamına geliyor. 12 * 3114 = 37368 bit = 4671 bayt> 4000 bayt ve bu, her yaprağın değerini nasıl temsil ettiğinizi anlamıyor bile!
Briguy37

15

Muhtemelen bazı sıkıştırılmış sürümünü kullanarak düşünün Trie (muhtemelen bir DAWG @Misha önerdiği gibi).

Bu, otomatik olarak hepsinin ortak bir ön eke sahip olduğu gerçeğinden yararlanır.

Arama sabit zamanda gerçekleştirilecek ve baskı doğrusal zamanda yapılacaktır.


Soru, verileri depolamanın en verimli yolu ile ilgilidir. Bu yöntemin 1000 telefon numarası için ne kadar alan gerektireceğine dair bir tahminde bulunabilir misiniz? Teşekkürler.
NPE

Trie için boşluk en fazla O (n * k) 'dir; burada n, dizelerin sayısı ve k, her dizinin uzunluğudur. Sayıları temsil etmek için 8 bit karaktere ihtiyacınız olmadığını göz önünde bulundurarak, 4 onaltılık dizini onaltılık ve kalan bit için bir tane depolamanızı öneririm. Bu şekilde, numara başına maksimum 17 bite ihtiyacınız vardır. Her durumda bu kodlama ile her seviyede çatışmalar yaşayacağınız için aslında bunun altına inebilirsiniz. 1000 numara sakladığımızı düşünürsek, birinci seviyede çatışmalar için şimdiden toplam 250 bit kaydedebiliriz. En iyi örnek veriler üzerinde doğru kodlamayı test edin.
LiKao

@LiKao, doğru ve örneğin 1000 sayının 100'den fazla farklı son iki basamağa sahip olamayacağını belirterek, trie son seviyelerde önemli ölçüde daraltılabilir.
aioobe

@aioobe: Çocuklar olmadığı için yapraklar son aşamada çökebilir. Bununla birlikte, ikinci ila son seviyedeki yapraklar 2 ^ 10 = 1024 duruma ihtiyaç duyar (her son rakam açık veya kapalı olabilir), bu nedenle sadece 1000 sayı olduğundan bu durumda indirgenemez. Bu, en kötü durum işaretçilerinin sayısının 3114'te kaldığı anlamına gelir (Misha'nın cevabıyla ilgili yorumuma bakın), ihtiyaç duyulan yapraklar 5 + 10 + 100 + 1000 + 1000 + 10 = 2125'e gider, bu da her biri için gerekli 12 baytı değiştirmez. Işaretçi. Bu nedenle, bu, yalnızca işaretçileri dikkate alarak 4671 bayta bir trie çözümü koyar.
Briguy37

@ Briguy37, " her son rakam açık veya kapalı olabilir " argümanınızı anladığımdan emin değilim . Tüm sayılar 10 basamak uzunluğundadır, değil mi?
aioobe

15

Bu sorunu daha önce duymuştum (ancak ilk 5 basamak aynı varsayım olmadan) ve bunu yapmanın en basit yolu Pirinç Kodlamasıydı :

1) Sıranın önemi olmadığından, onları sıralayabilir ve sadece ardışık değerler arasındaki farkları kaydedebiliriz. Bizim durumumuzda ortalama farklar 100.000 / 1000 = 100 olacaktır.

2) Farklılıkları Rice kodlarını (128 veya 64 tabanı) veya hatta Golomb kodlarını (100 tabanı) kullanarak kodlayın.

DÜZENLEME: 128 tabanlı Rice kodlaması için bir tahmin (en iyi sonuçları vereceği için değil, hesaplaması daha kolay olduğu için):

İlk değeri olduğu gibi (32 bit) kaydedeceğiz.
999 değerin geri kalanı farklılıklardır (bunların küçük olmasını bekliyoruz, ortalama olarak 100):

tekli değer value / 128(değişken bit sayısı + sonlandırıcı olarak 1 bit) (7 bit)
için ikili değervalue % 128

VBLDeğişken bit sayısı için sınırları bir şekilde tahmin etmeliyiz (buna diyelim ):
alt sınır: şanslı olduğumuzu ve tabanımızdan daha büyük bir fark olmadığını düşünün (bu durumda 128). bu 0 ek bit vermek anlamına gelir.
yüksek limit: tabandan küçük tüm farklılıklar sayının ikili kısmında kodlanacağından, tekli olarak kodlamamız gereken maksimum sayı 100000/128 = 781.25'tir (daha da az, çünkü farklılıkların çoğunun sıfır olmasını beklemiyoruz. ).

Böylece sonuç 32 + 999 * (1 + 7) + değişken (0..782) bit = 1003 + değişken (0..98) bayt olur.


Kodlama şekliniz ve son boyut hesaplaması hakkında daha fazla ayrıntı verebilir misiniz? 1101 bayt veya 8808 bit, 8091 bitlik teorik sınıra çok yakın görünüyor, bu yüzden pratikte böyle bir şeyi başarmanın mümkün olduğuna çok şaşırdım.
LiKao

32 + 999 * (1 + 7 + variable(0..782))Bit olmaz mıydı ? 999 sayının her birinin temsiline ihtiyaç duyar value / 128.
Kirk Broadhurst

1
@Kirk: hayır, eğer hepsi 5 rakam aralığındaysa. Bunun nedeni, tüm bu farklılıkların toplamının (hatırlayın, ilk ve N. değer arasındaki farkları değil, ardışık değerler arasındaki farklılıkları
kodluyoruz

İlk değeri temsil etmek için 32 bit yerine 34 bite ihtiyacınız vardır (9,999,999,999> 2 ^ 32 = 4,294,967,296). Ayrıca, sayılar benzersiz olduğundan maksimum fark 00000 ila 99001 olacaktır ve bu, 128 tabanı için 782 yerine 774 1'ler ekleyecektir. Bu nedenle, 128 tabanı için 1000 numara depolama aralığınız 8026-8800 bit veya 1004-1100 bayttır. 64 bit taban, 879-1072 bayt aralığında daha iyi depolama sağlar.
Briguy37

1
@raisercostin: Kirk'ün sorduğu buydu. Örneğinizde, ilk iki değer arasındaki 20k'lik farkı bir kez kodlayarak, gelecekte yalnızca 80k maksimum aralığın oluşması mümkün olacaktır. Bu, maksimum
782'den

7

Bu, Bentley'in Programlama İncilerinden iyi bilinen bir sorundur.

Çözüm: Her numara için aynı olduklarından, sayılardan ilk beş haneyi çıkarın. Sonra kalan 9999 olası değeri temsil etmek için bitsel işlemleri kullanın. Sayıları temsil etmek için yalnızca 2 ^ 17 Bite ihtiyacınız olacak. Her Bit bir sayıyı temsil eder. Bit ayarlanmışsa, numara telefon rehberindedir.

Tüm sayıları yazdırmak için, bitin önek ile bitiştirilmiş olduğu tüm sayıları yazdırmanız yeterlidir. Belirli bir sayıyı aramak için, sayının bitsel gösterimini kontrol etmek için gerekli bit aritmetiğini yapın.

O (1) 'de bir sayı arayabilirsiniz ve alan verimliliği, bit gösterimi nedeniyle maksimumdur.

HTH Chris.


3
Bu, yoğun bir sayı kümesi için iyi bir yaklaşım olacaktır. Ne yazık ki, burada set çok seyrek: olası 100.000'den yalnızca 1.000 sayı var. Bu nedenle bu yaklaşım, ortalama olarak sayı başına 100 bit gerektirecektir. Yalnızca ~ 17 bit gerektiren bir alternatif için cevabıma bakın.
NPE

1
Tüm sayıları basmak için geçen süre 1.000 yerine 100.000 ile orantılı olmaz mıydı?
aioobe

İki fikri birleştirerek, temelde trie'yi hemen elde edersiniz. 100.000 girişli bir bitvector kullanmak çok fazla yer kaplar ve çok yer kaplar. Ancak O (log (n)) araması genellikle çok yavaştır (buradaki sorgu sayısına bağlıdır). Dolayısıyla, indeksleme için bit setlerinden oluşan bir hiyerarşiyi kullanarak, O (1) aramasını almaya devam ederken, sayı başına maksimum 17 bit depolayacaksınız. Trie böyle çalışır. Ayrıca, sıralanan durumdan devraldığı triye için yazdırma zamanı O (n) 'dır.
LiKao

Bu, "bunu yapmanın en verimli yer tasarrufu yolu" değildir.
Jake Berger

5

1.000 numara için 1073 baytlık sabit depolama:

Bu depolama yönteminin temel biçimi, ilk 5 basamağı, her grup için bir sayımı ve her gruptaki her numara için ofseti saklamaktır.

Önek:
5 basamaklı ön ekimiz ilk 17 biti kaplar .

Gruplama:
Ardından, sayılar için iyi boyutlu bir gruplandırma bulmamız gerekir. Grup başına yaklaşık 1 numara alalım. Depolanacak yaklaşık 1000 numara olduğunu bildiğimiz için 99.9999'u yaklaşık 1000 parçaya böleriz. Grup boyutunu 100 olarak seçersek, boşa giden bitler olur, o halde 7 bit ile temsil edilebilen 128 grup boyutunu deneyelim. Bu bize 782 grupla çalışmamızı sağlıyor.

Sayımlar:
Sonra, 782 grubun her biri için, her gruptaki giriş sayısını kaydetmemiz gerekir. Her grup için 7 bitlik bir sayım 7*782=5,474 bits, çok verimsizdir, çünkü temsil edilen ortalama sayı, gruplarımızı nasıl seçtiğimizden dolayı yaklaşık 1'dir.

Bu nedenle, bunun yerine, bir gruptaki her sayı için önde gelen 1'leri ve ardından 0'ı içeren değişken boyutlu sayımlarımız var. Dolayısıyla, xbir grupta sayılar olsaydı , sayımı temsil etmek için x 1'sonu a 0izlerdik. Örneğin, bir grupta 5 sayımız olsaydı, sayı ile temsil edilirdi 111110. Bu yöntemle, 1000 sayı varsa, sayılar için toplam 1000 + 782 = 1.782 bit olmak üzere 1000 1 ve 782 0 ile sonuçlanır .

Uzaklık:
Son olarak, her sayının biçimi, her grup için yalnızca 7 bitlik ofset olacaktır. Örneğin, 0-127 grubundaki tek sayı 00000 ve 00001 ise, bu grup için bitler olacaktır 110 0000000 0000001. 1.000 sayı varsayıldığında , ofsetler için 7.000 bit olacaktır .

Böylelikle, 1000 sayı varsayan son sayımız aşağıdaki gibidir:

17 (prefix) + 1,782 (counts) + 7,000 (offsets) = 8,799 bits = 1100 bytes

Şimdi, 128 bite yuvarlayarak grup boyutu seçimimizin grup boyutu için en iyi seçenek olup olmadığını kontrol edelim. Seçme xHer bir grubu temsil etmek için bit sayısı olarak, boyut formülü:

Size in bits = 17 (prefix) + 1,000 + 99,999/2^x + x * 1,000

Ve tam sayı değerleri için denklemi en aza indirmek xverir x=68.580 bit = getirir ve burada, 1.073 bayt . Dolayısıyla ideal depolamamız aşağıdaki gibidir:

  • Grup büyüklüğü: 2 ^ 6 = 64
  • Grup sayısı: 1.562
  • Toplam depolama:

    1017 (prefix plus 1's) + 1563 (0's in count) + 6*1000 (offsets) = 8,580 bits = 1,073 bytes


1

Bunu tamamen teorik bir problem olarak ele alıp uygulamayı bir kenara bırakarak, tek ve en etkili yol, devasa bir indeksleme tablosundaki tüm olası 10000 son rakam kümesini indekslemektir. Tam olarak 1000 sayıya sahip olduğunuzu varsayarsak, mevcut seti benzersiz bir şekilde tanımlamak için 8000 bitten biraz fazlasına ihtiyacınız olacaktır. Daha büyük bir sıkıştırma mümkün değildir, çünkü o zaman aynı durumla tanımlanan iki setiniz olur.

Bununla ilgili problemler, programınızdaki 2 ^ 8000 setin her birini bir lut olarak temsil etmeniz gerekmesi ve google bile bunu uzaktan yapamaz.

Arama, tüm O (n) sayısını yazdırarak O (1) olacaktır. Ekleme, teoride O (1) olan, ancak pratikte kullanılamayan O (2 ^ 8000) olacaktır.

Bir röportajda bu cevabı sadece, şirketin kutunun dışında düşünebilen birini aradığından emin olsaydım verirdim. Aksi takdirde, bu sizi gerçek dünya endişeleri olmayan bir teorisyen gibi görünmenize neden olabilir.

DÜZENLEME : Tamam, işte bir "uygulama".

Uygulamayı oluşturmak için adımlar:

  1. 100 000 * (1000, 100000 seçin) bitlik sabit bir dizi alın. Evet, bu dizinin evrendeki atomlardan birkaç büyüklükte daha fazla alana ihtiyaç duyacağının farkındayım.
  2. Bu büyük diziyi her biri 100.000'lik parçalara ayırın.
  3. Her yığınta, son beş basamağın belirli bir kombinasyonu için bir bit dizisi depolanır.

Bu program değil, artık bir programda kullanılabilecek devasa bir LUT oluşturacak bir tür meta programdır. Programın sabit içeriği normalde alan verimliliği hesaplanırken sayılmaz, bu nedenle son hesaplamalarımızı yaparken bu diziyi önemsemiyoruz.

İşte bu LUT'u nasıl kullanacağınız:

  1. Birisi size 1000 numara verdiğinde, ilk beş rakamı ayrı olarak kaydedersiniz.
  2. Dizinizin hangi parçalarının bu kümeyle eşleştiğini bulun.
  3. Setin numarasını tek bir 8074 bitlik numarada saklayın (bunu c olarak adlandırın).

Bu, depolama için yalnızca 8091 bite ihtiyacımız olduğu anlamına gelir ve burada bunu en uygun kodlama olduğunu kanıtladık. Doğru parçayı bulmak, matematik kurallarına göre O (1) olan O (100000 * (100000 seç 1000)) alır, ancak pratikte her zaman evrenin zamanından daha uzun sürecektir.

Yine de arama basittir:

  1. ilk beş basamaklı şerit (kalan sayı n 'olarak adlandırılacaktır).
  2. eşleşip eşleşmediklerini test et
  3. İ = c * 100000 + n 'yi hesaplayın
  4. LUT'taki i'deki bitin bire ayarlanıp ayarlanmadığını kontrol edin

Tüm sayıların yazdırılması da basittir (ve aslında O (100000) = O (1) alır, çünkü her zaman mevcut öbürün tüm bitlerini kontrol etmeniz gerekir, bu yüzden yukarıda bunu yanlış hesapladım).

Sınırları (evrenin boyutu ve bu evrenin yaşadığı zaman veya bu evrenin var olacağı) bariz bir şekilde göz ardı edildiğinden, buna "uygulama" demezdim. Ancak teoride bu en uygun çözümdür. Daha küçük sorunlar için, bu aslında yapılabilir ve bazen yapılacaktır. Örneğin ağları sıralama , bu kodlama yöntemine bir örnektir ve büyük bir hızlanma elde etmek için özyinelemeli sıralama algoritmalarında son bir adım olarak kullanılabilir.


1
Bunu yapmanın en verimli yerden tasarruf yolu nedir?
Sven

1
Çalışma zamanı alanı hesaplamaları yaparken, bunun yerden tasarruf sağlayan en verimli yol olduğu kolayca kanıtlanabilir, çünkü sistemin herhangi bir olası durumunu yalnızca bir numara ile numaralandırırsınız. Bu problem için daha küçük bir kodlama olamaz. Bu cevabın püf noktası, hesaplamaları yaparken program boyutunun neredeyse hiç dikkate alınmamasıdır (bunu hesaba katan herhangi bir cevap bulmaya çalışın, ne demek istediğimi göreceksiniz). Dolayısıyla, boyut sınırı olan herhangi bir problem için, her zaman tüm durumları numaralandırabilir, üstesinden gelmek için en fazla alan tasarrufu yolunu elde edebilirsiniz.
LiKao

1

Bu, her biri 100.000'den küçük olan negatif olmayan bin tam sayının depolanmasına eşdeğerdir. Bunu yapmak için aritmetik kodlama gibi bir şey kullanabiliriz.

Sonuçta, numaralar sıralı bir listede saklanacaktır. Listedeki bitişik sayılar arasında beklenen farkın 100.000 / 1000 = 100 olduğunu ve 7 bit olarak gösterilebileceğini not ediyorum. Ayrıca 7 bitten fazlasının gerekli olduğu birçok durum olacaktır. Bu daha az yaygın durumları temsil etmenin basit bir yolu, ilk bit ayarlanmadıkça bir baytın 7 bitlik bir tamsayıyı temsil ettiği utf-8 şemasını benimsemektir; bu durumda, sonraki bayt 14 bitlik bir tamsayı üretmek için okunur. Bunu birinci bit, bu durumda bir sonraki biti, bir 21-bit tam sayı temsil okunan ayarlanır.

Dolayısıyla, ardışık tam sayılar arasındaki farkların en az yarısı bir bayt ile temsil edilebilir ve geri kalanın neredeyse tamamı iki bayt gerektirir. 16.384'ten daha büyük farklarla ayrılmış birkaç sayı, üç bayta ihtiyaç duyacaktır, ancak bunlardan 61'den fazlası olamaz. Ortalama depolama bu durumda sayı başına yaklaşık 12 bit veya biraz daha az veya en fazla 1500 bayt olacaktır.

Bu yaklaşımın dezavantajı, bir sayının varlığını kontrol etmenin artık O (n) olmasıdır. Ancak, zaman karmaşıklığı gerekliliği belirtilmedi.

Yazdıktan sonra, ruslik'in yukarıda fark yöntemini önerdiğini fark ettim, tek fark kodlama şeması. Benimki muhtemelen daha basit ama daha az verimli.


1

Sadece sayıları 36 tabanına değiştirmek istemememiz için herhangi bir nedeni hızlıca sormak için. Çok fazla yer tasarrufu sağlamayabilir, ancak aramada 10 basamaktan çok daha azına bakacağından emin olabilirsiniz. Ya da her gruba bağlı olarak onları dosyalara bölerdim. bu yüzden bir dosyayı (111) -222.txt olarak adlandırırdım ve sonra sadece o gruba uyan numaraları saklardım ve sonra bunları sayısal sırayla bu şekilde aranabilir hale getirirdim. Dosyanın çıkıp çıkmadığını görmek için her zaman chack yapabilirim. Daha büyük bir arama yapmadan önce. veya doğru olmak gerekirse, çıkıp çıkmadığını görmek için dosyayı ikili aramalarda çalıştırırdım. ve dosyanın içeriğiyle ilgili başka bir kemik araştırması


0

Neden basit tutmuyorsunuz? Bir yapı dizisi kullanın.

Böylece ilk 5 haneyi sabit olarak kaydedebiliriz, bu yüzden şimdilik unutun.

65535, 16 bitlik bir sayı içinde saklanabilecek en yüksek sayıdır ve sahip olabileceğimiz maksimum sayı 99999'dur, bu da 17'inci bit sayısı ile maks. 131071'e uymaktadır.

32 bit veri türlerini kullanmak israftır çünkü fazladan 16 bitten sadece 1 bitine ihtiyacımız var ... bu nedenle boole (veya karakter) ve 16 bit sayı içeren bir yapı tanımlayabiliriz ..

C / C ++ varsayıldığında

typedef struct _number {

    uint16_t number;
    bool overflow;
}Number;

Bu yapı sadece 3 bayt yer kaplıyor ve 1000, yani toplam 3000 baytlık bir diziye ihtiyacımız var. Toplam alanı% 25 oranında azalttık!

Sayıları depolamaya gelince, basit bitsel matematik yapabiliriz

overflow = (number5digits & 0x10000) >> 4;
number = number5digits & 0x1111;

Ve tersi

//Something like this should work
number5digits = number | (overflow << 4);

Hepsini yazdırmak için dizi üzerinde basit bir döngü kullanabiliriz. Belirli bir sayının alınması, bir dizi olduğu için elbette sabit zamanda gerçekleşir.

for(int i=0;i<1000;i++) cout << const5digits << number5digits << endl;

Bir sayıyı aramak için sıralı bir dizi isteriz. Bu yüzden sayılar kaydedildiğinde diziyi sıralayın (kişisel olarak bir birleştirme sıralaması seçerdim, O (nlogn)). Şimdi aramak için, birleştirme sıralama yaklaşımına gidiyorum. Diziyi bölün ve sayımızın hangisinin arasında olduğunu görün. Ardından işlevi yalnızca bu dizide çağırın. Bir eşleşme elde edene ve dizini döndürene kadar bunu yinelemeli olarak yapın, aksi takdirde mevcut olmaz ve bir hata kodu yazdırın. Bu arama oldukça hızlı olacaktır ve en kötü durum yine de O (nlogn) 'dan daha iyidir çünkü kesinlikle birleştirme sıralamasından daha kısa sürede çalışacaktır (her seferinde bölünmenin yalnızca 1 tarafını yineleyerek, her iki taraf yerine :)), ki O (nlogn).


0

Benim çözümüm: en iyi durum 7.025 bit / sayı, en kötü durum 14.193 bit / sayı, kaba ortalama 8.551 bit / sayı. Akış kodlu, rastgele erişim yok.

Ruslik'in cevabını okumadan önce bile, her sayı arasındaki farkı kodlamayı düşündüm, çünkü küçük olacak ve nispeten tutarlı olmalı, ancak çözüm aynı zamanda en kötü durum senaryosunu da barındırabilmelidir. Sadece 1000 sayı içeren 100000 sayılık bir alanımız var. Mükemmel bir şekilde tekdüze bir telefon rehberinde, her numara bir önceki sayıdan 100 ile daha büyük olacaktır:

55555-12 3 45
55555-12 4 45
55555-12 5 45

Durum buysa, bilinen bir sabit olduğu için sayılar arasındaki farklılıkları kodlamak için sıfır depolama gerekir. Ne yazık ki, sayılar 100'lük ideal adımlardan farklı olabilir. 100'lük ideal artıştan farkı kodlardım, böylece iki bitişik sayı 103 farklıysa, 3 sayısını kodlardım ve iki bitişik sayı 92 farklıysa, I -8 kodlayacaktır. Ben deltayı 100'lük ideal artıştan “ varyans ” olarak adlandırıyorum.

Varyans -99 (yani ardışık iki numara) ile 99000 arasında değişebilir (telefon defterinin tamamı 00000… 00999 sayılarından ve ek olarak en uzaktaki 99999 numaradan oluşur), bu 99100 olası değer aralığıdır.

Ben (gibi daha büyük farklılıkları karşılaşırsanız en yaygın farklılıkları kodlamak ve depolama genişletmek için minimal bir depolama tahsis amacı ediyorum Protobuf ‘ler varint). Yedi bitlik yığınlar, depolama için altı ve sonunda ek bir bayrak biti kullanacağım, bu varyansın mevcut olandan sonra en fazla üç parçaya kadar ek bir yığınla depolandığını belirtmek için (maksimum 3 * 6 = 262144 olası değer olan 18 bit depolama, olası varyansların sayısından (99100) daha fazladır. Yükseltilmiş bir bayrağı izleyen her ek öbek, daha yüksek öneme sahip bitlere sahiptir, bu nedenle ilk öbek her zaman 0- bit içerir 5, isteğe bağlı ikinci parçaların 6-11 bitleri ve isteğe bağlı üçüncü parçanın 12-17 bitleri vardır.

Tek bir yığın, 64 değeri barındırabilen altı bitlik depolama sağlar. Bu tek parçaya sığacak en küçük 64 varyansı eşlemek istiyorum (yani -32 ila +31 varyansları), bu nedenle -99 ila +98 varyanslarına kadar ProtoBuf ZigZag kodlamasını kullanacağım (çünkü buna gerek yok -99'un ötesinde negatif bir varyans için), bu noktada normal kodlamaya geçeceğim, 98 ile dengelenecek:  

Varyans | Kodlanmış Değer
----------- + ----------------
    0 | 0
   -1 | 1
    1 | 2
   -2 | 3
    2 | 4
   -3 | 5
    3 | 6
   ... | ...
  -31 | 61
   31 | 62
  -32 | 63
----------- | --------------- 6 bit
   32 | 64
  -33 | 65
   33 | 66
   ... | ...
  -98 | 195
   98 | 196
  -99 | 197
----------- | --------------- ZigZag Sonu
   100 | 198
   101 | 199
   ... | ...
  3996 | 4094
  3997 | 4095
----------- | --------------- 12 bit
  3998 | 4096
  3999 | 4097
   ... | ...
 262045 | 262143
----------- | --------------- 18 bit

Ek bir öbeği belirtmek için bayrak dahil, varyansların bit olarak nasıl kodlanacağına dair bazı örnekler:

Varyans | Kodlanmış Bitler
----------- + ----------------
     0 | 000000 0
     5 | 001010 0
    -8 | 001111 0
   -32 | 111111 0
    32 | 000000 1 000001 0
   -99 | 000101 1 000011 0
   177 | 010011 1 000100 0
 14444 | 001110 1 100011 1 000011 0

Dolayısıyla, örnek bir telefon rehberinin ilk üç numarası, aşağıdaki gibi bir bit akışı olarak kodlanacaktır:

BIN 000101001011001000100110010000011001 000110 1 010110 1 00001 0
PH # 55555-12345 55555-12448 55555-12491
POS 1 2 3

En iyi durum senaryosu , telefon defteri bir şekilde eşit olarak dağıtılmıştır ve varyansı 32'den büyük olan iki telefon numarası yoktur, bu nedenle sayı başına 7 bit artı başlangıç ​​numarası için 32 bit toplam 32 + 7 * 999 olacaktır. = 7025 bit . 800 telefon numarası varyansının bir parçaya (800 * 7 = 5600), 180 numaranın her birine iki parçaya (180 * 2 * 7 = 2520) ve 19 numaranın her birine üç parçaya (20 * 3) sığdığı
karma bir senaryo * 7 = 399), artı ilk 32 bit toplam 8551 bittir .
En kötü senaryo , 25 sayı üç parçaya sığar (25 * 3 * 7 = 525 bit) ve kalan 974 sayı iki parçaya sığar (974 * 2 * 7 = 13636 bit), artı bir grand için ilk sayı için 32 bit toplamı14193 bit .

   Kodlanmış sayıların miktarı |
 1 parça | 2 parça | 3 parça | Toplam bit
--------- + ---------- + ---------- + ------------
   999 | 0 | 0 | 7025
   800 | 180 | 19 | 8551
    0 | 974 | 25 | 14193

Gereken alanı daha da azaltmak için gerçekleştirilebilecek dört ek optimizasyon görebiliyorum:

  1. Üçüncü yığın yedi bitin tamamına ihtiyaç duymaz, sadece beş bit olabilir ve bayrak biti olmadan olabilir.
  2. Her yığın için en iyi boyutları hesaplamak için sayıların ilk geçişi olabilir. Belki belirli bir telefon defteri için, ilk öbekte 5 + 1 bit, ikinci 7 + 1 ve üçüncü 5 + 1 olması en uygun olacaktır. Bu, boyutu en az 6 * 999 + 32 = 6026 bit, artı 1 ve 2 parçalarının boyutlarını depolamak için üç bitlik iki sete (parça 3'ün boyutu gerekli 16 bitin geri kalanıdır) daha da düşürecektir. 6032 bit!
  3. Aynı başlangıç ​​geçişi, varsayılan 100'den daha iyi beklenen bir artışı hesaplayabilir. Belki 55555-50000'den başlayan bir telefon rehberi vardır ve bu nedenle sayı aralığının yarısı olduğundan beklenen artış 50 olmalıdır. Ya da belki doğrusal olmayan dağılım (belki standart sapma) ve diğer bazı optimum beklenen artışlar kullanılabilir. Bu, tipik varyansı azaltabilir ve daha da küçük bir ilk parçanın kullanılmasına izin verebilir.
  4. Her bölüm kendi beklenen artışına ve yığın boyutu optimizasyonlarına sahip olacak şekilde, telefon rehberinin bölümlenmesini sağlamak için ilk geçişte daha fazla analiz yapılabilir. Bu, telefon rehberinin belirli yüksek oranda tekdüze bölümleri için daha küçük bir ilk yığın boyutuna (tüketilen bit sayısını azaltarak) ve tek tip olmayan parçalar için daha büyük parça boyutlarına (devam bayraklarında boşa harcanan bit sayısını azaltarak) izin verecektir.

0

Asıl soru, beş haneli telefon numaralarını saklamaktır.

İşin püf noktası, 0,99,999'dan itibaren sayı aralığını saklamak için 17 bite ihtiyacınız olmasıdır. Ancak geleneksel 8 baytlık kelime sınırlarında 17 bit depolamak bir güçlüktür. Bu yüzden 32 bitlik tamsayılar kullanmayarak 4k'den daha azını yapıp yapamayacağınızı soruyorlar.

Soru: tüm sayı kombinasyonları mümkün mü?

Telefon sisteminin doğası gereği, 65.000'den az olası kombinasyon olabilir. Evet olduğunu varsayacağım çünkü alan kodu veya değişim öneklerinin aksine, telefon numarasındaki son beş konumdan bahsediyoruz.

Soru: Bu liste statik olacak mı yoksa güncellemeleri desteklemesi gerekecek mi?

Eğer durum bu ise , statik , o zaman basamak sayısını <50.000 ve hane sayısı> = 50,000, veritabanı doldurmak saymak zamanı geldiğinde. Tahsis iki dizi arasında uint1650,000 altında tamsayılar için bir ve yüksek kümesi için bir: Uygun uzunlukta. Tam sayıları üst dizide saklarken 50.000 çıkarın ve bu diziden tamsayı okurken 50.000 ekleyin. Şimdi 1.000 tamsayınızı 2.000 8 baytlık kelimede sakladınız.

Telefon rehberinin oluşturulması iki giriş geçişi gerektirecektir, ancak aramalar tek bir dizide olduğundan ortalama olarak yarı zamanda yapılmalıdır. Arama süresi çok önemli olsaydı, daha küçük aralıklar için daha fazla dizi kullanabilirsiniz, ancak bu boyutlarda performans sınırınızın dizileri bellekten çekeceğini ve 2k, bunları kullanacağınız herhangi bir şey için alan kaydetmezseniz muhtemelen CPU önbelleğine saklanacağını düşünüyorum. günler.

Eğer durum bu ise dinamik , tahsis tek dizi 1000 ya da öylesine uint16, ve sıralanmış sırada sayıları toplayın. İlk baytı 50.001 olarak ayarlayın ve ikinci baytı NULL veya 65.000 gibi uygun bir boş değere ayarlayın. Numaraları kaydederken, sıralı olarak saklayın. Bir sayı 50.001'in altındaysa , 50.001 işaretinden önce saklayın . Bir sayı 50.001 veya daha büyükse, bunu 50.001 işaretinden sonra saklayın , ancak saklanan değerden 50.000 çıkarın.

Diziniz şöyle görünecek:

00001 = 00001
12345 = 12345
50001 = reserved
00001 = 50001
12345 = 62345
65000 = end-of-list

Yani, telefon rehberinde bir sayı aradığınızda, dizide gezinirsiniz ve 50.001 değerine ulaşırsanız dizi değerlerinize 50.000 eklemeye başlarsınız.

Bu, ekleri çok pahalı hale getirir, ancak aramalar kolaydır ve depolama için 2 binden fazla harcamayacaksınız.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.