วิธีที่มีประสิทธิภาพที่สุดในการจัดเก็บหมายเลขโทรศัพท์หลายพันหมายเลข


94

นี่คือคำถามสัมภาษณ์ของ Google:

มีหมายเลขโทรศัพท์ประมาณพันหมายเลขที่จะจัดเก็บแต่ละหมายเลขมี 10 หลัก คุณสามารถสมมติว่า 5 หลักแรกของแต่ละหลักเหมือนกันในพันตัวเลข คุณต้องดำเนินการดังต่อไปนี้: a. ค้นหาว่ามีหมายเลขที่ระบุหรือไม่ ข. พิมพ์หมายเลขทั้งหมด

วิธีการประหยัดพื้นที่ที่มีประสิทธิภาพที่สุดในการทำเช่นนี้คืออะไร?

ฉันตอบตารางแฮชและหลังจากนั้นก็เขียนโค้ด huffman แต่ผู้สัมภาษณ์ของฉันบอกว่าฉันไปไม่ถูกทาง โปรดช่วยฉันที่นี่

การใช้คำต่อท้ายจะช่วยได้หรือไม่?

ตามหลักการแล้วการจัดเก็บหมายเลข 1,000 หมายเลขจะใช้เวลา 4 ไบต์ต่อหมายเลขดังนั้นโดยรวมแล้วจะใช้เวลา 4000 ไบต์ในการจัดเก็บหมายเลข 1,000 หมายเลข ในเชิงปริมาณฉันต้องการลดพื้นที่จัดเก็บลงเหลือ <4000 ไบต์นี่คือสิ่งที่ผู้สัมภาษณ์อธิบายให้ฉันฟัง


28
ฉันขอตอบว่าการใช้ฐานข้อมูลปกติคุณสามารถจัดเก็บเป็นข้อความได้แม้กระทั่งหลายพัน / ล้านและการค้นหาจะยังคงรวดเร็วมาก ฉันจะแนะนำไม่ให้ทำสิ่งที่ "ฉลาด" เนื่องจากระบบทั้งหมดจะต้องได้รับการปรับปรุงใหม่หากต้องการให้รองรับหมายเลขระหว่างประเทศในอนาคตหรือหากหมายเลขโทรศัพท์ที่ขึ้นต้นด้วย "0" เริ่มปรากฏขึ้นหรือหากรัฐบาลตัดสินใจ เปลี่ยนรูปแบบหมายเลขโทรศัพท์และอื่น ๆ
Thomas Bonini

1
@AndreasBonini: ฉันอาจจะให้คำตอบนั้นเว้นแต่ฉันจะไปสัมภาษณ์ที่ บริษัท เช่น Google หรือ Facebook ก็ไม่ต้องแก้ปัญหาเพียงแค่อย่าตัดมันทิ้ง แม้ว่า postgres จะพยายามเช่นกัน แต่ฉันก็ไม่แน่ใจว่าสิ่งเหล่านี้จะตัดปริมาณข้อมูลที่ Google จำเป็นต้องใช้
LiKao

1
@LiKao: โปรดทราบว่า OP ระบุเฉพาะ "ประมาณหนึ่งพันหมายเลข"
Thomas Bonini

@AndreasBonini: จริงอยู่อาจเป็นการทดสอบว่าผู้ให้สัมภาษณ์รู้ตีความข้อ จำกัด ดังกล่าวอย่างถูกต้องและเลือกวิธีแก้ปัญหาที่ดีที่สุดตามนี้
LiKao

4
"มีประสิทธิภาพ" ในคำถามนี้จำเป็นต้องกำหนดจริง ๆ - มีประสิทธิภาพด้วยวิธีใด? พื้นที่เวลาทั้งสอง?
matt b

คำตอบ:


36

นี่คือการปรับปรุงคำตอบของ Aix พิจารณาใช้ "เลเยอร์" สามชั้นสำหรับโครงสร้างข้อมูล: ชั้นแรกเป็นค่าคงที่สำหรับตัวเลขห้าหลักแรก (17 บิต); ดังนั้นจากนี้ไปหมายเลขโทรศัพท์แต่ละหมายเลขจะเหลือเพียงห้าหลักเท่านั้น เราดูตัวเลขห้าหลักที่เหลือเหล่านี้เป็นจำนวนเต็มไบนารี 17 บิตและจัดเก็บkของบิตเหล่านั้นโดยใช้วิธีเดียวและ 17 - k = mด้วยวิธีการอื่นโดยกำหนดkที่ท้ายเพื่อลดพื้นที่ที่ต้องการ

อันดับแรกเราจัดเรียงหมายเลขโทรศัพท์ (ทั้งหมดลดลงเหลือ 5 หลักทศนิยม) แล้วเรานับจำนวนหมายเลขโทรศัพท์ที่มีที่เลขฐานสองประกอบด้วยแรกเมตรบิตเป็น 0 สำหรับวิธีการหลายหมายเลขโทรศัพท์แรกเมตรบิตอยู่ที่มากที่สุด ... 0 01 สำหรับวิธีการหลายหมายเลขโทรศัพท์คนแรกที่ม.บิตอยู่ที่ 0 ... 10 เป็นต้นจนถึงจำนวนหมายเลขโทรศัพท์ที่บิตmแรกคือ 1 ... 11 - จำนวนสุดท้ายนี้คือ 1,000 (ทศนิยม) มีจำนวนดังกล่าว2 ^ mและแต่ละจำนวนจะไม่เกิน 1,000 ตัวถ้าเราละตัวสุดท้าย (เพราะเรารู้ว่ามันคือ 1000 อยู่ดี) เราสามารถจัดเก็บตัวเลขทั้งหมดนี้ไว้ในบล็อกที่ต่อเนื่องกันของ (2 ^ m - 1) * 10 บิต (10 บิตเพียงพอสำหรับการจัดเก็บตัวเลขที่น้อยกว่า 1024)

kบิตสุดท้ายของหมายเลขโทรศัพท์ทั้งหมด (ลดลง) จะถูกเก็บไว้ในหน่วยความจำอย่างต่อเนื่อง ดังนั้นถ้าkคือ 7 แล้ว 7 บิตแรกของบล็อกหน่วยความจำนี้ (บิต 0 ถึง 6) จะตรงกับ 7 บิตสุดท้ายของหมายเลขโทรศัพท์ตัวแรก (ลดลง) บิตที่ 7 ถึง 13 จะตรงกับ 7 บิตสุดท้าย ของหมายเลขโทรศัพท์ที่สอง (ลดลง) ฯลฯ ต้องใช้ 1,000 * kบิตรวมเป็น 17 + (2 ^ (17 - k ) - 1) * 10 + 1000 * kซึ่งมีค่าต่ำสุด 11287 สำหรับk = 10 ดังนั้นเราจึงสามารถเก็บหมายเลขโทรศัพท์ทั้งหมดไว้ในเพดานได้ ( 11287/8) = 1411 ไบต์

สามารถบันทึกช่องว่างเพิ่มเติมได้โดยสังเกตว่าไม่มีตัวเลขใดเลยที่เริ่มต้นด้วยเช่น 1111111 (ไบนารี) เนื่องจากตัวเลขต่ำสุดที่ขึ้นต้นด้วย 130048 และเรามีทศนิยมเพียงห้าหลัก สิ่งนี้ช่วยให้เราสามารถลบข้อมูลบางส่วนออกจากบล็อกแรกของหน่วยความจำ: แทนที่จะเป็น 2 ^ m - 1 จำนวนเราต้องการเพียง ceil (99999/2 ^ k ) นั่นหมายความว่าสูตรจะกลายเป็น

17 + เพดาน (99999/2 ^ k ) * 10 + 1000 * k

ซึ่งน่าอัศจรรย์พอที่จะบรรลุ 10997 ขั้นต่ำสำหรับทั้งk = 9 และk = 10 หรือ ceil (10997/8) = 1375 ไบต์

หากเราต้องการทราบว่ามีหมายเลขโทรศัพท์บางหมายเลขอยู่ในชุดของเราหรือไม่เราต้องตรวจสอบก่อนว่าเลขฐานสองห้าหลักแรกตรงกับตัวเลขห้าหลักที่เราเก็บไว้หรือไม่ จากนั้นเราแบ่งตัวเลขห้าหลักที่เหลือออกเป็นตัวเลขบนสุดm = 7 บิต (ซึ่งก็คือตัวเลขm -bit M ) และตัวเลขk = 10 บิตที่ต่ำกว่า(หมายเลขK ) ตอนนี้เราหาจำนวน[M-1] ของหมายเลขโทรศัพท์ที่ลดลงซึ่งเป็นครั้งแรกที่ม.ตัวเลขอยู่ที่ส่วนใหญ่M - 1 และจำนวน[M] ของหมายเลขโทรศัพท์ที่ลดลงซึ่งเป็นครั้งแรกที่ม.ตัวเลขอยู่ที่ส่วนใหญ่M , ทั้งสองจากบล็อกแรกของบิต ตอนนี้เราตรวจสอบระหว่าง[M-1] TH และ[M] ลำดับของ TH kบิตในบล็อกที่สองของหน่วยความจำเพื่อดูว่าเราจะพบK ; ในกรณีที่เลวร้ายที่สุดมี 1,000 ลำดับดังกล่าวดังนั้นหากเราใช้การค้นหาแบบไบนารีเราสามารถดำเนินการใน O (log 1000) ได้

Pseudocode สำหรับพิมพ์ตัวเลขทั้งหมด 1,000 ตัวตามมาโดยที่ฉันเข้าถึงรายการK 'th k -bit ของบล็อกแรกของหน่วยความจำเป็น [K] และรายการM ' th m -bit ของหน่วยความจำบล็อกที่สองเป็นb [M] (ทั้งสองอย่างนี้จะต้องใช้การดำเนินการเล็กน้อยที่น่าเบื่อในการเขียน) ตัวเลขห้าครั้งแรกในจำนวนค

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 ) แต่ก็ง่ายพอที่จะแก้ไข

สุดท้ายจากมุมมองของเอนโทรปีจะไม่สามารถจัดเก็บเซตย่อยของจำนวนเต็มบวก 10 ^ 3 ทั้งหมดที่น้อยกว่า 10 ^ 5 ในจำนวนที่น้อยกว่า ceil (log [2] (ทวินาม (10 ^ 5, 10 ^ 3)) ) = 8073 รวม 17 ที่เราต้องการสำหรับ 5 หลักแรกก็ยังมีช่องว่าง 10997 - 8090 = 2907 บิต เป็นความท้าทายที่น่าสนใจที่จะดูว่ามีวิธีแก้ปัญหาที่ดีกว่าที่คุณยังสามารถเข้าถึงตัวเลขได้อย่างมีประสิทธิภาพหรือไม่!


4
โครงสร้างข้อมูลที่คุณกำลังอธิบายที่นี่จริงๆแล้วเป็นเพียง Trie รุ่นที่มีประสิทธิภาพมากซึ่งใช้เพียงเล็กน้อยเท่าที่จำเป็นสำหรับการจัดทำดัชนีและมีเพียงสองระดับ ในทางปฏิบัติจะเป็นการดีที่จะดูว่าสิ่งนี้สามารถเอาชนะ Trie ที่มีเลเวลมากกว่านี้ได้หรือไม่ แต่ฉันคิดว่าสิ่งนี้ขึ้นอยู่กับการกระจายของตัวเลขเป็นอย่างมาก (ในหมายเลขโทรศัพท์จริงไม่ได้สุ่มทั้งหมด แต่เกือบเท่านั้น)
LiKao

สวัสดี Erik เนื่องจากคุณบอกว่าคุณสนใจที่จะดูทางเลือกอื่นลองดูวิธีแก้ปัญหาของฉัน มันแก้ได้ใน 8,580 บิตซึ่งเป็นเพียง 490 บิตจากค่าต่ำสุดทางทฤษฎี การค้นหาตัวเลขแต่ละตัวไม่มีประสิทธิภาพเล็กน้อย แต่พื้นที่เก็บข้อมูลมีขนาดกะทัดรัดมาก
Briguy37

1
ฉันคิดว่าผู้สัมภาษณ์ที่มีไหวพริบจะชอบคำตอบว่า "a trie" แทนที่จะเป็น "ฐานข้อมูลที่ซับซ้อนแบบกำหนดเอง" หากคุณต้องการแสดงทักษะการแฮ็ก 133t ของคุณคุณสามารถเพิ่ม - "อาจเป็นไปได้ที่จะสร้างอัลกอริทึมแบบต้นไม้เฉพาะสำหรับกรณีพิเศษนี้หากจำเป็น"
KarlP

สวัสดีคุณช่วยอธิบายว่า 5 หลักใช้ 17 บิตในการจัดเก็บได้อย่างไร?
Tushar Banne

@tushar ตัวเลขห้าหลักเข้ารหัสตัวเลขระหว่าง 00000 ถึง 99999 รวม แทนจำนวนนั้นในไบนารี 2 ^ 17 = 131072 ดังนั้น 17 บิตก็เพียงพอสำหรับสิ่งนั้น แต่ 16 ไม่ได้
Erik P.

43

ในสิ่งต่อไปนี้ฉันถือว่าตัวเลขเป็นตัวแปรจำนวนเต็ม (ตรงข้ามกับสตริง):

  1. เรียงลำดับตัวเลข
  2. แบ่งแต่ละหมายเลขออกเป็นห้าหลักแรกและห้าหลักสุดท้าย
  3. ห้าหลักแรกเหมือนกันในตัวเลขดังนั้นควรเก็บไว้เพียงครั้งเดียว ต้องใช้พื้นที่เก็บข้อมูล 17 บิต
  4. จัดเก็บตัวเลขห้าหลักสุดท้ายของแต่ละหมายเลขแยกกัน ซึ่งจะต้องใช้ 17 บิตต่อหมายเลข

สรุป: 17 บิตแรกเป็นคำนำหน้าทั่วไป 1,000 กลุ่มที่ตามมาของ 17 บิตคือตัวเลขห้าหลักสุดท้ายของแต่ละหมายเลขที่จัดเก็บตามลำดับจากน้อยไปมาก

โดยรวมแล้วเรากำลังดูที่ 2128 ไบต์สำหรับ 1,000 หมายเลขหรือ 17.017 บิตต่อหมายเลขโทรศัพท์ 10 หลัก

ค้นหาO(log n)(Search binary) O(n)และการแจงนับเต็ม


อืมความซับซ้อนของอวกาศอยู่ที่ไหน
aioobe

ใช้เวลาในการสร้างมากเกินไป (O (log (n) * n k) (k คือความยาว) สำหรับการเรียงลำดับเมื่อเทียบกับ O (n k) สำหรับการสร้างสาม) นอกจากนี้พื้นที่ยังห่างไกลจากความเหมาะสมเนื่องจากคำนำหน้าทั่วไปที่ยาวกว่าจะถูกจัดเก็บแยกกัน เวลาค้นหาก็ไม่เหมาะสมเช่นกัน สำหรับข้อมูลสตริงเช่นนี้คุณสามารถลืมความยาวของตัวเลขซึ่งครอบงำการค้นหาได้อย่างง่ายดาย เช่นการค้นหาไบนารีคือ O (log (n) * k) ในขณะที่ trie ต้องการเพียง O (k) คุณสามารถลดนิพจน์เหล่านี้ได้เมื่อ k เป็นค่าคงที่ แต่จะแสดงปัญหาทั่วไปเมื่อให้เหตุผลเกี่ยวกับโครงสร้างข้อมูลที่จัดเก็บสตริง
LiKao

@LiKao: ใครพูดอะไรเกี่ยวกับสตริง? ฉันกำลังจัดการกับตัวแปรจำนวนเต็มโดยเฉพาะดังนั้นจึงkไม่เกี่ยวข้อง
NPE

1
ตกลงฉันอ่านคำตอบผิดแล้ว ถึงกระนั้นชิ้นส่วนทั่วไปจะไม่ถูกจัดเก็บไว้ด้วยกันดังนั้นประเด็นเกี่ยวกับประสิทธิภาพของพื้นที่จึงยังคงอยู่ สำหรับ 1,000 จาก 5 หลักจะมีจำนวนคำนำหน้าทั่วไปอยู่พอสมควรดังนั้นการลดจำนวนเหล่านี้จะช่วยได้มาก นอกจากนี้ในกรณีของตัวเลขเรามี O (log (n)) กับ O (k) สำหรับสตริงซึ่งยังเร็วกว่า
LiKao

1
@Geek: 1001 กลุ่ม 17 บิตคือ 17017 บิตหรือ 2128 ไบต์ (มีการเปลี่ยนแปลงบางอย่าง)
NPE

22

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

ฉันเคยสัมภาษณ์ครั้งหนึ่งที่พวกเขาถามเกี่ยวกับโครงสร้างข้อมูล ฉันลืม "อาร์เรย์"


1
+1 นั่นคือวิธีที่จะไป ฉันเรียนรู้สิ่งนี้ภายใต้ชื่ออื่นต้นไม้ห้องสมุดหรือต้นไม้ค้นหาคำศัพท์หรืออะไรบางอย่างเมื่อฉันยังเป็นนักเรียน (ถ้ามีใครจำชื่อเก่านั้นได้โปรดบอก)
Valmond

6
สิ่งนี้ไม่เป็นไปตามข้อกำหนด 4000 ไบต์ สำหรับการจัดเก็บตัวชี้เพียงอย่างเดียวสถานการณ์ในกรณีที่เลวร้ายที่สุดคือคุณต้องมีตัวชี้ 1 ตัวสำหรับใบที่ 1-4 ไปยังระดับถัดไป 10 ตัวชี้สำหรับใบที่ 5 100 สำหรับชั้นที่ 6 และ 1,000 สำหรับระดับที่ 7 8 และ 9 ซึ่งทำให้ตัวชี้ของเรารวมเป็น 3114 ซึ่งให้ตำแหน่งหน่วยความจำที่แตกต่างกันอย่างน้อย 3114 ตำแหน่งที่จำเป็นสำหรับตัวชี้ในการชี้ซึ่งหมายความว่าคุณต้องมีอย่างน้อย 12 บิตสำหรับตัวชี้ 12 * 3114 = 37368 บิต = 4671 ไบต์> 4000 ไบต์และนั่นก็ไม่ได้คิดว่าคุณจะแทนค่าของแต่ละใบได้อย่างไร!
Briguy37

15

ฉันอาจพิจารณาใช้Trieเวอร์ชันบีบอัด(อาจเป็นDAWGตามที่แนะนำโดย @Misha)

นั่นจะใช้ประโยชน์จากความจริงที่ว่าพวกเขาทั้งหมดมีคำนำหน้าทั่วไปโดยอัตโนมัติ

การค้นหาจะดำเนินการในเวลาคงที่และการพิมพ์จะดำเนินการในเวลาเชิงเส้น


คำถามเกี่ยวกับวิธีจัดเก็บข้อมูลที่ประหยัดพื้นที่ที่สุด คุณช่วยให้ประมาณว่าวิธีนี้ต้องใช้พื้นที่เท่าใดสำหรับหมายเลขโทรศัพท์ 1,000 หมายเลข ขอบคุณ.
NPE

ช่องว่างสำหรับ trie มากที่สุด O (n * k) โดยที่ n คือจำนวนสตริงและ k คือความยาวของแต่ละสตริง โดยคำนึงว่าคุณไม่จำเป็นต้องใช้อักขระ 8 บิตในการแสดงตัวเลขฉันขอแนะนำให้เก็บดัชนีฐานสิบหก 4 ตัวไว้ที่ฐานสิบหกและอีกหนึ่งตัวสำหรับบิตที่เหลือ วิธีนี้คุณต้องการสูงสุด 17 บิตต่อหมายเลข เพราะในทุกกรณีคุณจะมีการปะทะกันในทุกระดับด้วยการเข้ารหัสนี้คุณจะได้รับด้านล่างนี้ คาดว่าเราจะจัดเก็บตัวเลข 1,000 ตัวเราสามารถบันทึกได้ทั้งหมด 250 บิตสำหรับการปะทะในระดับแรก ทดสอบการเข้ารหัสที่ถูกต้องกับข้อมูลตัวอย่างให้ดีที่สุด
LiKao

@LiKao ใช่แล้วและด้วยการสังเกตว่าตัวเลข 1,000 ตัวไม่สามารถมีตัวเลขสองหลักสุดท้ายที่แตกต่างกันมากกว่า 100 ตัวเลขสามตัวอาจจะยุบลงอย่างมากในระดับสุดท้าย
aioobe

@aioobe: ใบไม้อาจจะยุบได้ในระดับสุดท้ายเพราะไม่มีลูก อย่างไรก็ตามใบไม้ในระดับที่สองถึงสุดท้ายต้องการสถานะ 2 ^ 10 = 1024 (ตัวเลขสุดท้ายแต่ละตัวสามารถเปิดหรือปิดได้) ดังนั้นจึงไม่สามารถลดจำนวนลงได้ในกรณีนี้เนื่องจากมีเพียง 1,000 หมายเลข ซึ่งหมายความว่าจำนวนตัวชี้กรณีที่เลวร้ายที่สุดจะอยู่ที่ 3114 (ดูความคิดเห็นของฉันในคำตอบของ Misha) ในขณะที่ใบที่ต้องการจะไปที่ 5 + 10 + 100 + 1000 + 1000 + 10 = 2125 ซึ่งจะไม่เปลี่ยน 12 ไบต์ที่ต้องการสำหรับแต่ละใบ ตัวชี้ ดังนั้นสิ่งนี้ยังคงทำให้โซลูชันสามเท่าที่ 4671 ไบต์โดยพิจารณาเฉพาะพอยน์เตอร์เพียงอย่างเดียว
Briguy37

@ Briguy37 ไม่แน่ใจว่าฉันได้รับอาร์กิวเมนต์" แต่ละหลักสุดท้ายอาจเปิดหรือปิด " ตัวเลขทั้งหมดมีความยาว 10 หลักใช่ไหม?
aioobe

15

ฉันเคยได้ยินปัญหานี้มาก่อน (แต่ไม่มีสมมติฐาน 5 หลักแรกเหมือนกัน) และวิธีที่ง่ายที่สุดในการทำคือRice Coding :

1) เนื่องจากลำดับไม่สำคัญเราสามารถจัดเรียงได้และบันทึกความแตกต่างระหว่างค่าที่ต่อเนื่องกัน ในกรณีของเราความแตกต่างโดยเฉลี่ยคือ 100.000 / 1000 = 100

2) เข้ารหัสความแตกต่างโดยใช้รหัสข้าว (ฐาน 128 หรือ 64) หรือแม้แต่รหัส Golomb (ฐาน 100)

แก้ไข:การประมาณค่าสำหรับการเข้ารหัสข้าวด้วยฐาน 128 (ไม่ใช่เพราะจะให้ผลลัพธ์ที่ดีที่สุด แต่เป็นเพราะคำนวณได้ง่ายกว่า):

เราจะบันทึกค่าแรกตามที่เป็นอยู่ (32 บิต)
ค่าที่เหลือ 999 ค่าเป็นค่าความแตกต่าง (เราคาดว่าจะน้อยโดยเฉลี่ย 100) จะประกอบด้วย:

ค่ายูนารีvalue / 128(จำนวนบิตตัวแปร + 1 บิตเป็นเทอร์มิเนเตอร์)
ค่าไบนารีสำหรับvalue % 128(7 บิต)

เราต้องประมาณขีด จำกัด (ขอเรียกว่าVBL) สำหรับจำนวนบิตตัวแปร:
ขีด จำกัด ล่าง: ถือว่าเราโชคดีและไม่มีความแตกต่างใดที่ใหญ่กว่าฐานของเรา (128 ในกรณีนี้) นี่หมายความว่าให้ 0 บิตเพิ่มเติม
ขีด จำกัด สูง: เนื่องจากความแตกต่างทั้งหมดที่มีขนาดเล็กกว่าฐานจะถูกเข้ารหัสในส่วนไบนารีของตัวเลขจำนวนสูงสุดที่เราต้องเข้ารหัสในยูนารีคือ 100000/128 = 781.25 (น้อยกว่านั้นด้วยซ้ำเพราะเราไม่คาดว่าความแตกต่างส่วนใหญ่จะเป็นศูนย์ ).

ดังนั้นผลลัพธ์คือ 32 + 999 * (1 + 7) + ตัวแปร (0..782) บิต = 1003 + ตัวแปร (0..98) ไบต์


คุณสามารถให้รายละเอียดเพิ่มเติมเกี่ยวกับวิธีการเข้ารหัสและการคำนวณขนาดสุดท้ายได้ไหม 1101 ไบต์หรือ 8808 บิตดูเหมือนใกล้เคียงกับขีด จำกัด ทางทฤษฎีที่ 8091 บิตมากดังนั้นฉันจึงประหลาดใจมากที่เป็นไปได้ที่จะบรรลุสิ่งนี้ในทางปฏิบัติ
LiKao

มันจะไม่เป็น32 + 999 * (1 + 7 + variable(0..782))บิต? แต่ละหมายเลข 999 value / 128ต้องการตัวแทนของ
Kirk Broadhurst

1
@ เคิร์ก: ไม่ถ้าทั้งหมดอยู่ในช่วง 5 หลัก เนื่องจากเราคาดหวังว่าผลรวมของความแตกต่างเหล่านี้ทั้งหมด (โปรดจำไว้ว่าเราเข้ารหัสความแตกต่างระหว่างค่าที่ต่อเนื่องกันไม่ใช่ระหว่างค่าแรกและค่า Nth) จะต่ำกว่า 100,000 (แม้ในกรณีที่เลวร้ายที่สุด)
ruslik

คุณต้องใช้ 34 บิตแทนที่จะเป็น 32 บิตเพื่อแทนค่าแรก (9,999,999,999> 2 ^ 32 = 4,294,967,296) นอกจากนี้ความแตกต่างสูงสุดจะเป็น 00000 ถึง 99001 เนื่องจากตัวเลขไม่ซ้ำกันซึ่งจะเพิ่ม 774 1 แทน 782 สำหรับฐาน 128 ดังนั้นช่วงของคุณสำหรับการจัดเก็บ 1,000 หมายเลขสำหรับฐาน 128 คือ 8026-8800 บิตหรือ 1004-1100 ไบต์ ฐาน 64 บิตให้พื้นที่จัดเก็บที่ดีขึ้นโดยมีช่วงตั้งแต่ 879-1072 ไบต์
Briguy37

1
@raisercostin: นี่คือสิ่งที่เคิร์กถาม ในตัวอย่างของคุณการเข้ารหัสเมื่อความแตกต่าง 20k ระหว่างค่าสองค่าแรกจะมีเพียง 80k ของช่วงสูงสุดเท่านั้นที่จะเกิดขึ้นในอนาคต สิ่งนี้จะใช้ถึง 20k / 128 = 156 บิต
ยูนารี

7

นี่เป็นปัญหาที่ทราบกันดีจาก Programming Pearls ของ Bentley

วิธีแก้ไข: ตัดตัวเลขห้าหลักแรกออกจากตัวเลขเนื่องจากเหมือนกันสำหรับทุกหมายเลข จากนั้นใช้ bitwise-operation เพื่อแสดงค่า 9999 ที่เหลืออยู่ คุณจะต้องใช้เพียง 2 ^ 17 Bits เพื่อแสดงตัวเลข แต่ละบิตแสดงถึงตัวเลข หากตั้งค่าบิตไว้แสดงว่าหมายเลขอยู่ในสมุดโทรศัพท์

ในการพิมพ์ตัวเลขทั้งหมดเพียงแค่พิมพ์ตัวเลขทั้งหมดที่มีการตั้งค่าบิตเชื่อมต่อกับคำนำหน้า ในการค้นหาตัวเลขที่กำหนดให้ทำเลขคณิตบิตที่จำเป็นเพื่อตรวจสอบการแสดงตัวเลขในระดับบิต

คุณสามารถค้นหาตัวเลขใน O (1) และประสิทธิภาพของพื้นที่จะสูงสุดเนื่องจากการสร้างบิตใหม่

HTH คริส


3
นี่จะเป็นแนวทางที่ดีสำหรับชุดตัวเลขที่หนาแน่น น่าเสียดายที่ชุดนี้เบาบางมาก: มีเพียง 1,000 หมายเลขจาก 100,000 หมายเลขที่เป็นไปได้ ดังนั้นโดยเฉลี่ยแล้วแนวทางนี้จะต้องใช้ 100 บิตต่อหมายเลข ดูคำตอบของฉันสำหรับทางเลือกที่ต้องการเพียง ~ 17 บิต
NPE

1
เวลาที่ใช้ในการพิมพ์ตัวเลขทั้งหมดจะเป็นสัดส่วน 100,000 แทน 1,000 ไม่ใช่หรือ?
aioobe

การรวมความคิดสองอย่างเข้าด้วยกันคุณจะได้ไตร่ตรองทันที การใช้ bitvector กับ 100,000 รายการเป็นวิธีการหาตำแหน่งโดยรวมและใช้พื้นที่มาก อย่างไรก็ตามการค้นหา O (log (n)) มักจะช้าเกินไป (ขึ้นอยู่กับจำนวนการสืบค้นที่นี่) ดังนั้นการใช้ hierachie ของชุดบิตสำหรับการจัดทำดัชนีคุณจะจัดเก็บได้สูงสุด 17 บิตต่อตัวเลขในขณะที่ยังคงได้รับการค้นหา O (1) นี่คือวิธีการทำงานของ Trie นอกจากนี้เวลาในการพิมพ์ยังอยู่ใน O (n) สำหรับ trie ซึ่งสืบทอดมาจากตัวพิมพ์ที่เรียงลำดับ
LiKao

นี่ไม่ใช่ "วิธีประหยัดพื้นที่ที่มีประสิทธิภาพสูงสุด"
Jake Berger

5

พื้นที่จัดเก็บคงที่ 1073 ไบต์สำหรับ 1,000 หมายเลข:

รูปแบบพื้นฐานของวิธีการจัดเก็บนี้คือการจัดเก็บตัวเลข 5 หลักแรกจำนวนสำหรับแต่ละกลุ่มและค่าชดเชยสำหรับแต่ละหมายเลขในแต่ละกลุ่ม

คำนำหน้า: คำนำหน้า
5 หลักของเราใช้17 บิตแรก

การจัดกลุ่ม:
ต่อไปเราต้องหาการจัดกลุ่มขนาดที่เหมาะสมสำหรับตัวเลข ลองมีประมาณ 1 หมายเลขต่อกลุ่ม เนื่องจากเราทราบว่ามีหมายเลขจัดเก็บประมาณ 1,000 หมายเลขเราจึงแบ่ง 99,999 ออกเป็นประมาณ 1,000 ส่วน หากเราเลือกขนาดกลุ่มเป็น 100 ก็จะมีบิตที่สูญเปล่าดังนั้นลองใช้ขนาดกลุ่ม 128 ซึ่งแทนได้ด้วย 7 บิต สิ่งนี้ทำให้เรามี 782 กลุ่มในการทำงาน

จำนวน:
ถัดไปสำหรับแต่ละกลุ่ม 782 เราจำเป็นต้องจัดเก็บจำนวนรายการในแต่ละกลุ่ม การนับ 7 บิตสำหรับแต่ละกลุ่มจะให้ผล7*782=5,474 bitsซึ่งไม่มีประสิทธิภาพมากเนื่องจากจำนวนเฉลี่ยที่แสดงเป็นค่าประมาณ 1 เนื่องจากเราเลือกกลุ่มของเราอย่างไร

ดังนั้นเราจึงมีการนับขนาดตัวแปรโดยมี 1 นำหน้าสำหรับแต่ละหมายเลขในกลุ่มตามด้วย 0 ดังนั้นหากเรามีxตัวเลขอยู่ในกลุ่มเราจะต้องx 1'sตามด้วย a 0เพื่อแทนจำนวน ตัวอย่างเช่นถ้าเรามี 5 111110หมายเลขในกลุ่มการนับจะได้รับการแสดงโดย ด้วยวิธีนี้ถ้ามี 1,000 หมายเลขเราจบลงด้วย 1000 1 และ 782 0 รวมเป็น1,000 + 782 = 1,782 บิตสำหรับการนับ

ออฟเซ็ต:
สุดท้ายรูปแบบของแต่ละหมายเลขจะเป็นเพียง 7 บิตออฟเซ็ตสำหรับแต่ละกลุ่ม ตัวอย่างเช่นถ้า 00000 และ 00001 เป็นตัวเลขเท่านั้นในกลุ่ม 0-127, 110 0000000 0000001บิตสำหรับกลุ่มที่จะเป็น สมมติว่า 1,000 ตัวเลขจะมี7,000 บิตสำหรับการชดเชย

ดังนั้นการนับครั้งสุดท้ายของเราโดยสมมติว่า 1,000 หมายเลขเป็นดังนี้:

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

ตอนนี้ให้ตรวจสอบว่าการเลือกขนาดกลุ่มของเราโดยการปัดเศษสูงสุด 128 บิตเป็นตัวเลือกที่ดีที่สุดสำหรับขนาดกลุ่มหรือไม่ การเลือกxเป็นจำนวนบิตเพื่อแสดงแต่ละกลุ่มสูตรสำหรับขนาดคือ:

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

สมการนี้ลดค่าจำนวนเต็มของxให้x=6ซึ่งผลตอบแทนถัวเฉลี่ย 8,580 บิต = 1,073 ไบต์ ดังนั้นพื้นที่จัดเก็บในอุดมคติของเรามีดังนี้:

  • ขนาดกลุ่ม: 2 ^ 6 = 64
  • จำนวนกลุ่ม: 1,562
  • พื้นที่เก็บข้อมูลทั้งหมด:

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


1

การใช้ปัญหานี้เป็นปัญหาทางทฤษฎีอย่างแท้จริงและปล่อยให้การนำไปใช้งานวิธีเดียวที่มีประสิทธิภาพมากที่สุดคือการจัดทำดัชนีชุดตัวเลขสุดท้าย 10,000 หลักที่เป็นไปได้ทั้งหมดในตารางการสร้างดัชนีขนาดมหึมา สมมติว่าคุณมีตัวเลข 1,000 ตัวคุณจะต้องมีมากกว่า 8000 บิตเล็กน้อยเพื่อระบุชุดปัจจุบันโดยไม่ซ้ำกัน ไม่มีการบีบอัดที่ใหญ่กว่านี้เพราะคุณจะมีสองชุดที่ระบุด้วยสถานะเดียวกัน

ปัญหาเกี่ยวกับเรื่องนี้คือคุณจะต้องแสดงชุด 2 ^ 8000 แต่ละชุดในโปรแกรมของคุณเป็นชุดและแม้แต่ Google ก็ไม่สามารถทำได้จากระยะไกล

การค้นหาจะเป็น O (1) พิมพ์ตัวเลขทั้งหมด O (n) การแทรกจะเป็น O (2 ^ 8000) ซึ่งในทางทฤษฎีคือ O (1) แต่ในทางปฏิบัติใช้ไม่ได้

ในการสัมภาษณ์ฉันจะให้คำตอบแค่นี้ถ้าฉันแน่ใจว่า บริษัท กำลังมองหาคนที่สามารถคิดนอกกรอบได้มาก มิฉะนั้นอาจทำให้คุณดูเหมือนนักทฤษฎีที่ไม่มีความกังวลในโลกแห่งความเป็นจริง

แก้ไข : ตกลงนี่คือ "การใช้งาน" อย่างหนึ่ง

ขั้นตอนในการสร้างการใช้งาน:

  1. ใช้อาร์เรย์ขนาดคงที่ 100000 * (1,000 เลือก 100,000) บิต ใช่ฉันตระหนักดีว่าอาร์เรย์นี้ต้องการพื้นที่มากกว่าอะตอมในจักรวาลหลายขนาด
  2. แยกอาร์เรย์ขนาดใหญ่นี้ออกเป็นชิ้นละ 100,000
  3. ในแต่ละกลุ่มจะจัดเก็บอาร์เรย์บิตสำหรับชุดค่าผสมเฉพาะของตัวเลขห้าหลักสุดท้าย

นี่ไม่ใช่โปรแกรม แต่เป็นโปรแกรมเมตาชนิดหนึ่งที่จะสร้าง LUT ขนาดมหึมาที่สามารถใช้ในโปรแกรมได้ โดยปกติสิ่งที่คงที่ของโปรแกรมจะไม่ถูกนับเมื่อคำนวณประสิทธิภาพของพื้นที่ดังนั้นเราจึงไม่สนใจเกี่ยวกับอาร์เรย์นี้เมื่อทำการคำนวณขั้นสุดท้าย

นี่คือวิธีใช้ LUT นี้:

  1. เมื่อมีคนให้หมายเลข 1,000 หมายเลขแก่คุณคุณจะเก็บห้าหลักแรกแยกจากกัน
  2. ค้นหาว่าอาร์เรย์ของคุณตรงกับชุดนี้
  3. เก็บหมายเลขของชุดไว้ในหมายเลข 8074 บิตเดียว (เรียกสิ่งนี้ว่า c)

ซึ่งหมายความว่าในการจัดเก็บข้อมูลเราต้องการเพียง 8091 บิตซึ่งเราได้พิสูจน์แล้วว่าเป็นการเข้ารหัสที่ดีที่สุด อย่างไรก็ตามการค้นหาชิ้นส่วนที่ถูกต้องจะใช้ O (100000 * (100000 เลือก 1,000)) ซึ่งตามกฎทางคณิตศาสตร์คือ O (1) แต่ในทางปฏิบัติจะใช้เวลานานกว่าเวลาของจักรวาลเสมอ

การค้นหานั้นง่ายมากแม้ว่า:

  1. แถบห้าหลักแรก (ตัวเลขที่เหลือจะเรียกว่า n ')
  2. ทดสอบว่าตรงกันหรือไม่
  3. คำนวณ i = c * 100000 + n '
  4. ตรวจสอบว่าบิตที่ i ใน LUT ถูกตั้งค่าเป็นหนึ่งหรือไม่

การพิมพ์ตัวเลขทั้งหมดก็ทำได้ง่ายเช่นกัน (และใช้ O (100000) = O (1) ตามความเป็นจริงเพราะคุณต้องตรวจสอบบิตทั้งหมดของชิ้นปัจจุบันอยู่เสมอดังนั้นฉันจึงคำนวณผิดด้านบน)

ฉันจะไม่เรียกสิ่งนี้ว่า "การนำไปใช้" เพราะความไม่สนใจข้อ จำกัด อย่างโจ่งแจ้ง (ขนาดของจักรวาลและเวลาที่จักรวาลนี้มีชีวิตอยู่หรือโลกนี้จะมีอยู่จริง) อย่างไรก็ตามในทางทฤษฎีนี่เป็นทางออกที่ดีที่สุด สำหรับปัญหาเล็ก ๆ สิ่งนี้สามารถทำได้จริงและบางครั้งก็ทำได้ ตัวอย่างเช่นการเรียงลำดับเครือข่ายเป็นตัวอย่างสำหรับวิธีการเข้ารหัสนี้และสามารถใช้เป็นขั้นตอนสุดท้ายในอัลกอริธึมการเรียงลำดับซ้ำเพื่อให้ได้ความเร็วสูง


1
วิธีการประหยัดพื้นที่ที่มีประสิทธิภาพที่สุดในการทำเช่นนี้คืออะไร?
Sven

1
เมื่อทำการคำนวณพื้นที่เวลาทำงานสิ่งนี้สามารถพิสูจน์ได้อย่างง่ายดายว่าเป็นวิธีการประหยัดพื้นที่ที่มีประสิทธิภาพสูงสุดเนื่องจากคุณระบุสถานะที่เป็นไปได้ของระบบด้วยตัวเลขเพียงตัวเดียว ไม่สามารถมีการเข้ารหัสที่เล็กกว่าสำหรับปัญหานี้ เคล็ดลับสำหรับคำตอบนี้คือขนาดของโปรแกรมแทบจะไม่เคยถูกนำมาพิจารณาเมื่อทำการคำนวณ (ลองหาคำตอบที่คำนึงถึงสิ่งนี้แล้วคุณจะเห็นว่าฉันหมายถึงอะไร) ดังนั้นสำหรับปัญหาใด ๆ ที่มีขนาดผูกไว้คุณสามารถระบุสถานะทั้งหมดได้เพื่อให้ได้วิธีที่ประหยัดพื้นที่มากที่สุดในการจัดการ
LiKao

1

สิ่งนี้เทียบเท่ากับการจัดเก็บจำนวนเต็มหนึ่งพันจำนวนเต็มซึ่งมีค่าน้อยกว่า 100,000 เราสามารถใช้บางอย่างเช่นการเข้ารหัสเลขคณิตเพื่อทำสิ่งนี้

ในที่สุดตัวเลขจะถูกเก็บไว้ในรายการที่เรียงลำดับ ฉันทราบว่าความแตกต่างที่คาดหวังระหว่างตัวเลขที่อยู่ติดกันในรายการคือ 100,000 / 1000 = 100 ซึ่งสามารถแสดงเป็น 7 บิต นอกจากนี้ยังมีหลายกรณีที่จำเป็นต้องใช้มากกว่า 7 บิต วิธีง่ายๆในการแสดงกรณีที่พบได้น้อยเหล่านี้คือการนำโครงร่าง utf-8 มาใช้โดยที่หนึ่งไบต์แทนจำนวนเต็ม 7 บิตเว้นแต่จะกำหนดบิตแรกซึ่งในกรณีนี้จะอ่านไบต์ถัดไปเพื่อสร้างจำนวนเต็ม 14 บิตของบิตแรกเป็นที่ตั้งซึ่งในกรณีไบต์ต่อไปคือการอ่านเพื่อเป็นตัวแทนของจำนวนเต็ม 21 บิต

ดังนั้นอย่างน้อยครึ่งหนึ่งของความแตกต่างระหว่างจำนวนเต็มติดต่อกันอาจแสดงด้วยหนึ่งไบต์และส่วนที่เหลือเกือบทั้งหมดต้องใช้สองไบต์ ตัวเลขสองสามตัวคั่นด้วยความแตกต่างที่ใหญ่กว่า 16,384 จะต้องใช้สามไบต์ แต่ต้องมีจำนวนไม่เกิน 61 ตัว จากนั้นหน่วยเก็บข้อมูลเฉลี่ยจะอยู่ที่ประมาณ 12 บิตต่อตัวเลขหรือน้อยกว่าเล็กน้อยหรือมากที่สุด 1,500 ไบต์

ข้อเสียของวิธีนี้คือการตรวจสอบการมีอยู่ของตัวเลขตอนนี้คือ O (n) อย่างไรก็ตามไม่ได้ระบุข้อกำหนดความซับซ้อนของเวลา

หลังจากเขียนฉันสังเกตเห็นว่า ruslik แนะนำวิธีการแตกต่างข้างต้นแล้วข้อแตกต่างเพียงอย่างเดียวคือรูปแบบการเข้ารหัส เหมืองมีแนวโน้มที่ง่ายกว่า แต่มีประสิทธิภาพน้อยกว่า


1

เพียงแค่ถามเหตุผลอย่างรวดเร็วที่เราไม่ต้องการเปลี่ยนตัวเลขเป็นฐาน 36 มันอาจไม่ประหยัดพื้นที่มากนัก แต่แน่นอนว่าจะประหยัดเวลาในการค้นหาเนื่องจากคุณจะดูน้อยกว่ามากจากนั้น 10digts หรือจะแยกเป็นไฟล์ขึ้นอยู่กับแต่ละกลุ่ม ดังนั้นฉันจะตั้งชื่อไฟล์ (111) -222.txt จากนั้นฉันจะจัดเก็บเฉพาะหมายเลขที่พอดีกับกลุ่มนั้นในนั้นจากนั้นให้พวกเขามองเห็นได้ตามลำดับตัวเลขด้วยวิธีนี้ฉันสามารถตรวจสอบได้ตลอดเวลาเพื่อดูว่าไฟล์ออกหรือไม่ ก่อนที่ฉันจะทำการค้นหาที่ใหญ่กว่านี้ หรือเพื่อให้ถูกต้องฉันจะเรียกใช้เพื่อค้นหาไบนารีหนึ่งสำหรับไฟล์เพื่อดูว่ามันออกหรือไม่ และการค้นหาอื่น ๆ ในเนื้อหาของไฟล์


0

ทำไมไม่ทำให้มันง่าย? ใช้อาร์เรย์ของโครงสร้าง

ดังนั้นเราจึงสามารถบันทึก 5 หลักแรกเป็นค่าคงที่ได้ดังนั้นอย่าลืมค่าเหล่านั้นไปก่อน

65535 เป็นจำนวนสูงสุดที่สามารถเก็บไว้ในหมายเลข 16 บิตและจำนวนสูงสุดที่เราสามารถมีได้คือ 99999 ซึ่งเหมาะกับหมายเลขบิตที่ 17 โดยมีค่าสูงสุด 131071

การใช้ชนิดข้อมูล 32 บิตเป็นเรื่องที่สิ้นเปลืองเพราะเราต้องการเพียง 1 บิตของ 16 บิตพิเศษนั้น ... ดังนั้นเราสามารถกำหนดโครงสร้างที่มีบูลีน (หรืออักขระ) และตัวเลข 16 บิต ..

สมมติว่า C / C ++

typedef struct _number {

    uint16_t number;
    bool overflow;
}Number;

โครงสร้างนี้ใช้เวลาเพียง 3 ไบต์และเราต้องการอาร์เรย์ 1,000 ดังนั้นรวม 3000 ไบต์ เราลดพื้นที่ทั้งหมดลง 25%!

เท่าที่จัดเก็บตัวเลขเราสามารถคำนวณเลขบิตง่ายๆได้

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

และผกผัน

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

ในการพิมพ์ทั้งหมดเราสามารถใช้การวนซ้ำบนอาร์เรย์ การดึงจำนวนเฉพาะเกิดขึ้นในเวลาคงที่แน่นอนเนื่องจากเป็นอาร์เรย์

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

ในการค้นหาตัวเลขเราต้องการอาร์เรย์ที่เรียงลำดับ ดังนั้นเมื่อบันทึกตัวเลขแล้วให้จัดเรียงอาร์เรย์ (ฉันจะเลือกเรียงลำดับการผสานเป็นการส่วนตัว O (nlogn)) ตอนนี้ในการค้นหาฉันจะใช้วิธีการจัดเรียงแบบผสาน แยกอาร์เรย์และดูว่าหมายเลขใดของเราอยู่ระหว่าง จากนั้นเรียกใช้ฟังก์ชันเฉพาะอาร์เรย์นั้น ทำสิ่งนี้ซ้ำ ๆ จนกว่าคุณจะมีการจับคู่และส่งคืนดัชนีมิฉะนั้นจะไม่มีอยู่และพิมพ์รหัสข้อผิดพลาด การค้นหานี้จะค่อนข้างรวดเร็วและกรณีที่เลวร้ายที่สุดก็ยังดีกว่า O (nlogn) เนื่องจากจะดำเนินการในเวลาน้อยกว่าการเรียงลำดับการผสาน (เรียกซ้ำเพียง 1 ด้านของการแยกในแต่ละครั้งแทนที่จะเป็นทั้งสองด้าน :)) คือ O (nlogn)


0

วิธีแก้ปัญหาของฉัน: กรณีที่ดีที่สุด 7.025 บิต / ตัวเลขกรณีที่เลวร้ายที่สุด 14.193 บิต / ตัวเลขค่าเฉลี่ยคร่าวๆ 8.551 บิต / ตัวเลข เข้ารหัสสตรีมไม่มีการเข้าถึงแบบสุ่ม

ก่อนที่จะอ่านคำตอบของ ruslik ฉันก็คิดถึงการเข้ารหัสความแตกต่างระหว่างตัวเลขแต่ละตัวในทันทีเนื่องจากมันจะมีขนาดเล็กและควรจะค่อนข้างสอดคล้องกัน แต่การแก้ปัญหาจะต้องสามารถรองรับสถานการณ์ที่เลวร้ายที่สุดได้ด้วย เรามีช่องว่าง 100000 หมายเลขที่มีเพียง 1,000 หมายเลข ในสมุดโทรศัพท์ที่เหมือนกันอย่างสมบูรณ์แต่ละหมายเลขจะมากกว่าหมายเลขก่อนหน้า 100:

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

หากเป็นเช่นนั้นก็จะต้องใช้พื้นที่จัดเก็บเป็นศูนย์เพื่อเข้ารหัสความแตกต่างระหว่างตัวเลขเนื่องจากเป็นค่าคงที่ที่ทราบ น่าเสียดายที่ตัวเลขอาจแตกต่างจากขั้นตอนในอุดมคติของ 100 ฉันจะเข้ารหัสความแตกต่างจากการเพิ่มขึ้นในอุดมคติของ 100 ดังนั้นหากตัวเลขสองตัวที่อยู่ติดกันแตกต่างกันด้วย 103 ฉันจะเข้ารหัสหมายเลข 3 และหากตัวเลขสองตัวที่อยู่ติดกันแตกต่างกันด้วย 92 จะเข้ารหัส -8 ฉันเรียกเดลต้าจากการเพิ่มขึ้นในอุดมคติของ 100 ว่า " ความแปรปรวน "

ค่าความแปรปรวนสามารถอยู่ในช่วงตั้งแต่ -99 (เช่นตัวเลขสองตัวติดต่อกัน) ถึง 99000 (สมุดโทรศัพท์ทั้งหมดประกอบด้วยหมายเลข 00000 … 00999 และหมายเลขที่อยู่ไกลที่สุดเพิ่มเติม 99999) ซึ่งเป็นช่วงของค่าที่เป็นไปได้ 99100 ค่า

ฉันมุ่งมั่นที่จะจัดสรรพื้นที่เก็บข้อมูลน้อยที่สุดในการเข้ารหัสความแตกต่างที่พบมากที่สุดและขยายการจัดเก็บถ้าผมพบความแตกต่างที่ใหญ่กว่า (เช่นprotobuf ‘s varint) ฉันจะใช้ชิ้นส่วนของเจ็ดบิตหกสำหรับการจัดเก็บและบิตแฟล็กเพิ่มเติมในตอนท้ายเพื่อระบุว่าค่าความแปรปรวนนี้ถูกเก็บไว้ด้วยส่วนเพิ่มเติมหลังจากค่าปัจจุบันสูงสุดสามชิ้น (ซึ่งจะให้ค่าสูงสุด 3 * 6 = 18 บิตของการจัดเก็บซึ่งเป็นค่าที่เป็นไปได้ 262144 มากกว่าจำนวนผลต่างที่เป็นไปได้ (99100) แต่ละกลุ่มเพิ่มเติมที่ตามหลังแฟล็กที่ยกขึ้นมีบิตที่มีนัยสำคัญสูงกว่าดังนั้นชิ้นแรกจึงมีบิต 0- เสมอ 5 ชิ้นที่สองที่เป็นทางเลือกมีบิต 6-11 และชิ้นที่สามที่เป็นทางเลือกมีบิต 12-17

ชิ้นเดียวมีพื้นที่เก็บข้อมูลหกบิตซึ่งรองรับได้ 64 ค่า ฉันต้องการแมปค่าความแปรปรวนที่เล็กที่สุด 64 รายการเพื่อให้พอดีกับชิ้นส่วนเดียว (เช่นความแปรปรวน -32 ถึง +31) ดังนั้นฉันจะใช้การเข้ารหัส ProtoBuf ZigZag สูงสุดถึงความแปรปรวน -99 ถึง +98 (เนื่องจากไม่จำเป็น สำหรับความแปรปรวนเชิงลบที่เกินกว่า -99) ณ จุดนั้นฉันจะเปลี่ยนเป็นการเข้ารหัสปกติชดเชยด้วย 98:  

ความแปรปรวน | ค่าที่เข้ารหัส
----------- + ----------------
    0 | 0
   -1 | 1
    1 | 2
   -2 | 3
    2 | 4
   -3 | 5
    3 | 6
   ... | ...
  -31 | 61
   31 | 62
  -32 | 63
----------- | --------------- 6 บิต
   32 | 64
  -33 | 65
   33 | 66
   ... | ...
  -98 | 195
   98 | 196
  -99 | 197
----------- | --------------- สิ้นสุดซิกแซก
   100 | 198
   101 | 199
   ... | ...
  3996 | 4094
  3997 | 4095
----------- | --------------- 12 บิต
  3998 | 4096
  3999 | 4097
   ... | ...
 262045 | 262143
----------- | --------------- 18 บิต

ตัวอย่างบางส่วนของการเข้ารหัสความแปรปรวนเป็นบิตรวมถึงแฟล็กเพื่อระบุกลุ่มเพิ่มเติม:

ความแปรปรวน | บิตเข้ารหัส
----------- + ----------------
     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

ดังนั้นตัวเลขสามตัวแรกของสมุดโทรศัพท์ตัวอย่างจะถูกเข้ารหัสเป็นกระแสของบิตดังนี้:

ถังขยะ 000101001011001000100110010000011001 000110 1 010110 1 00001 0
ภ # 55555-12345 55555-12448 55555-12491
POS 1 2 3

สถานการณ์กรณีที่ดีที่สุดสมุดโทรศัพท์มีการกระจายค่อนข้างสม่ำเสมอและไม่มีหมายเลขโทรศัพท์สองหมายเลขที่มีความแปรปรวนมากกว่า 32 ดังนั้นจึงควรใช้ 7 บิตต่อหมายเลขบวก 32 บิตสำหรับหมายเลขเริ่มต้นรวม 32 + 7 * 999 = 7025 บิต
สถานการณ์แบบผสมที่ความแปรปรวนของหมายเลขโทรศัพท์ 800 หมายเลขอยู่ในกลุ่มเดียว (800 * 7 = 5600) 180 หมายเลขจะพอดีกับสองชิ้นแต่ละชิ้น (180 * 2 * 7 = 2520) และ 19 หมายเลขพอดีในสามส่วนแต่ละกลุ่ม (20 * 3 * 7 = 399) บวกเริ่มต้น 32 บิตรวม8551 บิต
สถานการณ์กรณีที่เลวร้ายที่สุด 25 หมายเลขพอดีในสามส่วน (25 * 3 * 7 = 525 บิต) และ 974 หมายเลขที่เหลือจะพอดีกับสองส่วน (974 * 2 * 7 = 13636 บิต) บวก 32 บิตสำหรับตัวเลขแรกสำหรับแกรนด์ ทั้งหมด14193 บิต

   จำนวนตัวเลขที่เข้ารหัส |
 1 ชิ้น | 2 ชิ้น | 3 ชิ้น | จำนวนบิตทั้งหมด
--------- + ---------- + ---------- + ------------
   999 | 0 | 0 | 7025
   800 | 180 | 19 | 8551
    0 | 974 | 25 | 14193

ฉันสามารถดูการเพิ่มประสิทธิภาพเพิ่มเติมสี่รายการที่สามารถดำเนินการเพื่อลดพื้นที่ที่ต้องการเพิ่มเติมได้:

  1. ส่วนที่สามไม่จำเป็นต้องมีเจ็ดบิตเต็มสามารถเป็นเพียงห้าบิตและไม่มีบิตแฟล็ก
  2. อาจมีการส่งผ่านตัวเลขเริ่มต้นเพื่อคำนวณขนาดที่ดีที่สุดสำหรับแต่ละชิ้น บางทีสำหรับสมุดโทรศัพท์บางเล่มจะเป็นการดีที่สุดที่จะให้กลุ่มแรกมี 5 + 1 บิตที่สอง 7 + 1 และ 5 + 1 ที่สาม ซึ่งจะช่วยลดขนาดให้เหลือน้อยที่สุด 6 * 999 + 32 = 6026 บิตบวกสองชุดสามบิตเพื่อเก็บขนาดของชิ้นที่ 1 และ 2 (ขนาดของชิ้นที่ 3 คือส่วนที่เหลือของ 16 บิตที่ต้องการ) รวม 6032 บิต!
  3. พาสเริ่มต้นเดียวกันสามารถคำนวณส่วนเพิ่มที่คาดหวังได้ดีกว่าค่าเริ่มต้น 100 บางทีอาจมีสมุดโทรศัพท์ที่เริ่มตั้งแต่ 55555-50000 ดังนั้นมันจึงมีช่วงตัวเลขครึ่งหนึ่งดังนั้นการเพิ่มที่คาดไว้ควรเป็น 50 หรืออาจจะมีไม่ใช่เชิงเส้น การกระจาย (อาจจะเป็นค่าเบี่ยงเบนมาตรฐาน) และส่วนเพิ่มที่คาดว่าจะเหมาะสม สิ่งนี้จะลดความแปรปรวนโดยทั่วไปและอาจทำให้สามารถใช้ชิ้นส่วนแรกที่เล็กกว่าได้
  4. การวิเคราะห์เพิ่มเติมสามารถทำได้ในพาสแรกเพื่ออนุญาตให้แบ่งสมุดโทรศัพท์โดยแต่ละพาร์ติชั่นจะมีการเพิ่มขนาดและการปรับขนาดชิ้นตามที่คาดไว้ สิ่งนี้จะช่วยให้มีขนาดชิ้นแรกที่เล็กลงสำหรับชิ้นส่วนที่มีความสม่ำเสมอสูงบางส่วนของสมุดโทรศัพท์ (ลดจำนวนบิตที่ใช้) และขนาดชิ้นส่วนที่ใหญ่ขึ้นสำหรับชิ้นส่วนที่ไม่สม่ำเสมอ (ลดจำนวนบิตที่เสียไปกับแฟล็กการต่อเนื่อง)

0

คำถามที่แท้จริงคือการจัดเก็บหมายเลขโทรศัพท์ห้าหลัก

เคล็ดลับคือคุณต้องมี 17 บิตเพื่อจัดเก็บช่วงของตัวเลขตั้งแต่ 0..99,999 แต่การจัดเก็บ 17 บิตบนขอบเขตคำ 8 ไบต์ธรรมดานั้นเป็นเรื่องยุ่งยาก นั่นเป็นเหตุผลที่พวกเขาถามว่าคุณสามารถทำได้ในเวลาน้อยกว่า 4k หรือไม่โดยไม่ใช้จำนวนเต็ม 32 บิต

คำถาม: สามารถผสมตัวเลขทั้งหมดได้หรือไม่?

เนื่องจากลักษณะของระบบโทรศัพท์อาจมีชุดค่าผสมที่เป็นไปได้น้อยกว่า 65k ฉันจะถือว่าใช่เพราะเรากำลังพูดถึงห้าตำแหน่งหลังในหมายเลขโทรศัพท์ซึ่งตรงข้ามกับรหัสพื้นที่หรือคำนำหน้าการแลกเปลี่ยน

คำถาม: รายการนี้จะคงที่หรือไม่หรือต้องรองรับการอัปเดต?

หากเป็นแบบคงที่เมื่อถึงเวลาที่ต้องเติมข้อมูลในฐานข้อมูลให้นับจำนวนหลัก <50,000 และจำนวนหลัก> = 50,000 จัดสรรสองอาร์เรย์ของuint16ความยาวที่เหมาะสมหนึ่งสำหรับจำนวนเต็มด้านล่าง 50,000 และหนึ่งสำหรับการตั้งค่าที่สูงขึ้น เมื่อจัดเก็บจำนวนเต็มในอาร์เรย์ที่สูงขึ้นให้ลบ 50,000 และเมื่ออ่านจำนวนเต็มจากอาร์เรย์นั้นให้เพิ่ม 50,000 ตอนนี้คุณได้จัดเก็บจำนวนเต็ม 1,000 ของคุณใน 2,000 8 ไบต์คำ

การสร้างสมุดโทรศัพท์จะต้องใช้การส่งผ่านอินพุตสองครั้ง แต่โดยเฉลี่ยแล้วการค้นหาควรเกิดขึ้นในครึ่งเวลาโดยเฉลี่ยมากกว่าที่จะใช้อาร์เรย์เดียว หากเวลาในการค้นหามีความสำคัญมากคุณสามารถใช้อาร์เรย์เพิ่มเติมสำหรับช่วงที่เล็กลง แต่ฉันคิดว่าในขนาดเหล่านี้ประสิทธิภาพของคุณจะดึงอาร์เรย์จากหน่วยความจำและ 2k อาจซ่อนอยู่ในแคชของ CPU หากไม่ได้ลงทะเบียนพื้นที่ในสิ่งที่คุณใช้เหล่านี้ วัน.

ถ้าเป็นแบบไดนามิกให้จัดสรรอาร์เรย์หนึ่งอาร์เรย์ตั้งแต่ 1,000 รายการuint16ขึ้นไปและเพิ่มตัวเลขตามลำดับ ตั้งค่าไบต์แรกเป็น 50,001 และตั้งค่าไบต์ที่สองเป็นค่าว่างที่เหมาะสมเช่น NULL หรือ 65,000 เมื่อคุณจัดเก็บหมายเลขให้จัดเก็บตามลำดับที่จัดเรียง หากตัวเลขต่ำกว่า 50,001 ให้เก็บไว้ก่อนเครื่องหมาย 50,001 หากตัวเลขมีค่าตั้งแต่ 50,001 ขึ้นไปให้เก็บไว้หลังเครื่องหมาย 50,001 แต่ลบ 50,000 ออกจากค่าที่เก็บไว้

อาร์เรย์ของคุณจะมีลักษณะดังนี้:

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

ดังนั้นเมื่อคุณค้นหาหมายเลขในสมุดโทรศัพท์คุณจะสำรวจอาร์เรย์และหากคุณได้ค่า 50,001 คุณจะเริ่มเพิ่ม 50,000 ในค่าอาร์เรย์ของคุณ

สิ่งนี้ทำให้เม็ดมีดมีราคาแพงมาก แต่การค้นหาทำได้ง่ายและคุณจะไม่ใช้จ่ายมากกว่า 2k ในการจัดเก็บ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.