Hàm băm từ điển Python

Invented over half a century ago, the hash table is a classic data structure that has been fundamental to programming. To this day, it helps solve many real-life problems, such as indexing database tables, caching computed values, or implementing sets. It often comes up in job interviews, and Python uses hash tables all over the place to make name lookups almost instantaneous

Even though Python comes with its own hash table called

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67, it can be helpful to understand how hash tables work behind the curtain. A coding assessment may even task you with building one. This tutorial will walk you through the steps of implementing a hash table from scratch as if there were none in Python. Along the way, you’ll face a few challenges that’ll introduce important concepts and give you an idea of why hash tables are so fast

In addition to this, you’ll get a hands-on crash course in test-driven development [TDD] and will actively practice it while building your hash table in a step-by-step fashion. You’re not required to have any prior experience with TDD, but at the same time, you won’t get bored even if you do

In this tutorial, you’ll learn

  • How a hash table differs from a dictionary
  • How you can implement a hash table from scratch in Python
  • How you can deal with hash collisions and other challenges
  • What the desired properties of a hash function are
  • How Python’s
    >>> import string
    >>> text = string.ascii_uppercase * 100_000_000
    
    >>> text[:50]  # Show the first 50 characters
    'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'
    
    >>> len[text]
    2600000000
    
    68 works behind the scenes

It’ll help if you’re already familiar with Python dictionaries and have basic knowledge of object-oriented programming principles. To get the complete source code and the intermediate steps of the hash table implemented in this tutorial, follow the link below

Source Code. Click here to download the source code that you’ll use to build a hash table in Python

Get to Know the Hash Table Data Structure

Before diving deeper, you should familiarize yourself with the terminology because it can get slightly confusing. Colloquially, the term hash table or hash map is often used interchangeably with the word dictionary. However, there’s a subtle difference between the two concepts as the former is more specific than the latter

Remove ads

Hash Table vs Dictionary

In computer science, a dictionary is an abstract data type made up of keys and values arranged in pairs. Moreover, it defines the following operations for those elements

  • Add a key-value pair
  • Delete a key-value pair
  • Update a key-value pair
  • Find a value associated with the given key

In a sense, this abstract data type resembles a bilingual dictionary, where the keys are foreign words, and the values are their definitions or translations to other languages. But there doesn’t always have to be a sense of equivalence between keys and values. A phone book is another example of a dictionary, which combines names with the corresponding phone numbers

Note. Anytime you map one thing to another or associate a value with a key, you’re essentially using a kind of a dictionary. That’s why dictionaries are also known as maps or associative arrays

Dictionaries have a few interesting properties. Một trong số đó là bạn có thể coi từ điển như một hàm toán học dự đoán một hoặc nhiều đối số cho chính xác một giá trị. The direct consequences of that fact are the following

  • Only Key-Value Pairs. You can’t have a key without the value or the other way around in a dictionary. They always go together
  • Arbitrary Keys and Values. Keys and values can belong to two disjoint sets of the same or separate types. Both keys and values may be almost anything, such as numbers, words, or even pictures
  • Unordered Key-Value Pairs. Do điểm cuối cùng, từ điển thường không xác định bất kỳ thứ tự nào cho các cặp khóa-giá trị của chúng. Tuy nhiên, đó có thể là triển khai cụ thể
  • Phím duy nhất. Từ điển không được chứa các khóa trùng lặp, vì điều đó sẽ vi phạm định nghĩa của hàm
  • Giá trị không duy nhất. Có thể liên kết cùng một giá trị với nhiều khóa nhưng không nhất thiết phải

Có những khái niệm liên quan mở rộng ý tưởng về từ điển. Ví dụ: nhiều ánh xạ cho phép bạn có nhiều giá trị cho mỗi khóa, trong khi ánh xạ hai chiều không chỉ ánh xạ các khóa tới các giá trị mà còn cung cấp ánh xạ theo hướng ngược lại. Tuy nhiên, trong hướng dẫn này, bạn sẽ chỉ xem xét từ điển thông thường, từ điển này ánh xạ chính xác một giá trị cho mỗi khóa

Đây là một mô tả đồ họa của một từ điển giả định, ánh xạ một số khái niệm trừu tượng với các từ tiếng Anh tương ứng của chúng

Mô tả đồ họa của một loại dữ liệu trừu tượng từ điển

Đó là bản đồ một chiều của khóa tới giá trị, là hai bộ phần tử hoàn toàn khác nhau. Ngay lập tức, bạn có thể thấy ít giá trị hơn khóa vì từ cung là một từ đồng âm với nhiều nghĩa. Về mặt khái niệm, từ điển này vẫn chứa bốn cặp, mặc dù. Tùy thuộc vào cách bạn quyết định triển khai nó, bạn có thể sử dụng lại các giá trị lặp lại để tiết kiệm bộ nhớ hoặc sao chép chúng để đơn giản

Bây giờ, làm thế nào để bạn mã hóa một từ điển như vậy bằng ngôn ngữ lập trình? . Python vận chuyển với loại

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67 tích hợp sẵn, loại này đã bao bọc cấu trúc dữ liệu được tối ưu hóa cao được viết bằng C để bạn không phải tự viết từ điển

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67 của Python cho phép bạn thực hiện tất cả các thao tác từ điển được liệt kê ở đầu phần này

>>>

>>> glossary = {"BDFL": "Benevolent Dictator For Life"}
>>> glossary["GIL"] = "Global Interpreter Lock"  # Add
>>> glossary["BDFL"] = "Guido van Rossum"  # Update
>>> del glossary["GIL"]  # Delete
>>> glossary["BDFL"]  # Search
'Guido van Rossum'
>>> glossary
{'BDFL': 'Guido van Rossum'}

Với cú pháp ngoặc vuông [

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
71], bạn có thể thêm một cặp khóa-giá trị mới vào từ điển. Bạn cũng có thể cập nhật giá trị của hoặc xóa một cặp hiện có được xác định bởi một khóa. Cuối cùng, bạn có thể tra cứu giá trị được liên kết với khóa đã cho

Điều đó nói rằng, bạn có thể hỏi một câu hỏi khác. Từ điển tích hợp thực sự hoạt động như thế nào?

Tìm cách triển khai hiệu quả loại dữ liệu trừu tượng này được gọi là vấn đề từ điển. One of the most well-known solutions takes advantage of the hash table data structure that you’re about to explore. However, note that it isn’t the only way to implement a dictionary in general. Another popular implementation builds on top of a red-black tree

Hash Table. An Array With a Hash Function

Have you ever wondered why accessing sequence elements in Python works so quickly, regardless of which index you request? Say you were working with a very long string of characters, like this one

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000

There are 2. 6 billion characters from repeating ASCII letters in the

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
72 variable above, which you can count with Python’s
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
73 function. Yet, getting the first, middle, last, or any other character from this string is equally quick

>>>

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'

The same is true for all sequence types in Python, such as lists and tuples. How come? The secret to such a blazing speed is that sequences in Python are backed by an array, which is a random-access data structure. It follows two principles

  1. The array occupies a contiguous block of memory
  2. Every element in the array has a fixed size known up front

When you know the memory address of an array, which is called the offset, then you can get to the desired element in the array instantly by calculating a fairly straightforward formula

Formula to Calculate the Memory Address of a Sequence Element

You start at the array’s offset, which is also the address of the array’s first element, with the index zero. Next, you move forward by adding the required number of bytes, which you get by multiplying the element size by the target element’s index. It always takes the same amount of time to multiply and add a few numbers together

Note. Unlike arrays, Python’s lists can contain heterogeneous elements of varying sizes, which would break the above formula. To mitigate this, Python adds another level of indirection by introducing an array of pointers to memory locations rather than storing values directly in the array

Array of Pointers to Memory Addresses

Pointers are merely integer numbers, which always take up the same amount of space. It’s customary to denote memory addresses using the hexadecimal notation. Python and some other languages prefix such numbers with

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
74

Okay, so you know that finding an element in an array is quick, no matter where that element is physically located. Can you take the same idea and reuse it in a dictionary? Yes

Hash tables get their name from a trick called hashing, which lets them translate an arbitrary key into an integer number that can work as an index in a regular array. So, instead of searching a value by a numeric index, you’ll look it up by an arbitrary key without a noticeable performance loss. That’s neat

In practice, hashing won’t work with every key, but most built-in types in Python can be hashed. If you follow a few rules, then you’ll be able to create your own hashable types too. You’ll learn more about hashing in the next section

Remove ads

Understand the Hash Function

A hash function performs hashing by turning any data into a fixed-size sequence of bytes called the hash value or the hash code. Đó là một số có thể đóng vai trò là dấu vân tay kỹ thuật số hoặc thông báo, thường nhỏ hơn nhiều so với dữ liệu gốc, cho phép bạn xác minh tính toàn vẹn của nó. If you’ve ever fetched a large file from the Internet, such as a disk image of a Linux distribution, then you may have noticed an MD5 or SHA-2 checksum on the download page

Aside from verifying data integrity and solving the dictionary problem, hash functions help in other fields, including security and cryptography. For example, you typically store hashed passwords in databases to mitigate the risk of data leaks. Digital signatures involve hashing to create a message digest before encryption. Blockchain transactions are another prime example of using a hash function for cryptographic purposes

Note. A cryptographic hash function is a special type of hash function that must meet a few additional requirements. In this tutorial, you’ll only encounter the most basic form of the hash function used in the hash table data structure, though

While there are many hashing algorithms, they all share a few common properties that you’re about to discover in this section. Implementing a good hash function correctly is a difficult task that may require the understanding of advanced math involving prime numbers. Fortunately, you don’t usually need to implement such an algorithm by hand

Python comes with a built-in hashlib module, which provides a variety of well-known cryptographic hash functions, as well as less secure checksum algorithms. The language also has a global

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 function, used primarily for quick element lookup in dictionaries and sets. Bạn có thể nghiên cứu cách nó hoạt động trước để tìm hiểu về các thuộc tính quan trọng nhất của hàm băm

Kiểm tra
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 tích hợp sẵn của Python

Before taking a stab at implementing a hash function from scratch, hold on for a moment and analyze Python’s

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 to distill its properties. Điều này sẽ giúp bạn hiểu những vấn đề liên quan khi thiết kế hàm băm của riêng bạn

Ghi chú. Việc lựa chọn hàm băm có thể ảnh hưởng đáng kể đến hiệu suất của bảng băm của bạn. Do đó, bạn sẽ dựa vào hàm

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 tích hợp khi xây dựng bảng băm tùy chỉnh sau này trong hướng dẫn này. Thực hiện một hàm băm trong phần này chỉ đóng vai trò như một bài tập

Đối với người mới bắt đầu, hãy thử gọi

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 trên một vài ký tự kiểu dữ liệu được tích hợp trong Python, chẳng hạn như số và chuỗi, để xem chức năng hoạt động như thế nào

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
6

There are already several observations that you can make by looking at the result. First, the built-in hash function may return different values on your end for some of the inputs shown above. While the numeric input always seems to produce an identical hash value, the string most likely doesn’t. Why is that? It may seem like

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 is a non-deterministic function, but that couldn’t be further from the truth

When you call

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 with the same argument within your existing interpreter session, then you’ll keep getting the same result

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
9

That’s because hash values are immutable and don’t change throughout an object’s lifetime. However, as soon as you exit Python and start it again, then you’ll almost certainly see different hash values across Python invocations. You can test this by trying the

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
82 option to run a one-liner script in your terminal

  • các cửa sổ
  • Linux + macOS

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
1

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
2

That’s expected behavior, which was implemented in Python as a countermeasure against a Denial-of-Service [DoS] attack that exploited a known vulnerability of hash functions in web servers. Attackers could abuse a weak hash algorithm to deliberately create so-called hash collisions, overloading the server and making it inaccessible. Ransom was a typical motive for the attack as most victims made money through an uninterrupted online presence

Today, Python enables hash randomization by default for some inputs, such as strings, to make the hash values less predictable. This makes

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 a bit more secure and the attack more difficult. Tuy nhiên, bạn có thể tắt ngẫu nhiên hóa bằng cách đặt giá trị gốc cố định thông qua biến môi trường
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
84, chẳng hạn

  • các cửa sổ
  • Linux + macOS

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
5

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
6

Now, each Python invocation yields the same hash value for a known input. This can help in partitioning or sharing data across a cluster of distributed Python interpreters. Chỉ cần cẩn thận và hiểu những rủi ro liên quan đến việc vô hiệu hóa băm ngẫu nhiên. Nói chung,

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 của Python thực sự là một hàm xác định, đây là một trong những tính năng cơ bản nhất của hàm băm

Ngoài ra,

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 có vẻ khá phổ biến vì nó nhận đầu vào tùy ý. Nói cách khác, nó nhận các giá trị thuộc nhiều loại và kích cỡ khác nhau. The function accepts strings and floating-point numbers regardless of their length or magnitude without complaining. In fact, you can calculate a hash value of more exotic types too

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
9

Here, you called the hash function on Python’s

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87 object, the
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 function itself, and even a custom
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
89 class with a few of its instances. Điều đó nói rằng, không phải tất cả các đối tượng đều có giá trị băm tương ứng. Hàm
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 sẽ đưa ra một ngoại lệ nếu bạn thử gọi nó với một trong số ít đối tượng đó

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
0

Loại cơ bản của đầu vào sẽ xác định xem bạn có thể tính giá trị băm hay không. Trong Python, các phiên bản của các loại có thể thay đổi được tích hợp sẵn—như danh sách, bộ và ký tự—không thể băm được. Bạn đã có một gợi ý về lý do tại sao lại như vậy, nhưng bạn sẽ tìm hiểu thêm trong phần sau. Hiện tại, bạn có thể cho rằng hầu hết các loại dữ liệu sẽ hoạt động với hàm băm nói chung

Remove ads

Tìm hiểu sâu hơn về
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 của Python

Một đặc điểm thú vị khác của

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 là nó luôn tạo ra đầu ra có kích thước cố định cho dù đầu vào lớn đến đâu. Trong Python, giá trị băm là một số nguyên có độ lớn vừa phải. Đôi khi, nó có thể xuất hiện dưới dạng số âm, vì vậy hãy tính đến điều đó nếu bạn định dựa vào giá trị băm theo cách này hay cách khác

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
1

The natural consequence of a fixed-size output is that most of the original information gets irreversibly lost during the process. Rốt cuộc, điều đó tốt vì bạn muốn giá trị băm kết quả hoạt động như một bản tóm tắt thống nhất của dữ liệu lớn tùy ý. However, because the hash function projects a potentially infinite set of values onto a finite space, this can lead to a hash collision when two different inputs produce the same hash value

Note. Nếu bạn thiên về toán học, thì bạn có thể sử dụng nguyên tắc chuồng bồ câu để mô tả các xung đột hàm băm một cách chính thức hơn

Given m items and n containers, if m > n, then there’s at least one container with more than one item

Trong bối cảnh này, các mục là một số lượng giá trị có khả năng vô hạn mà bạn nạp vào hàm băm, trong khi các vùng chứa là các giá trị băm của chúng được chỉ định từ một nhóm hữu hạn

Hash collisions are an essential concept in hash tables, which you’ll revisit later in more depth when implementing your custom hash table. For now, you can think of them as highly undesirable. Bạn nên tránh xung đột băm càng nhiều càng tốt vì chúng có thể dẫn đến việc tra cứu rất kém hiệu quả và có thể bị tin tặc khai thác. Therefore, a good hash function must minimize the likelihood of a hash collision for both security and efficiency

Trong thực tế, điều này thường có nghĩa là hàm băm phải gán các giá trị được phân phối đồng đều trên không gian có sẵn. You can visualize the distribution of hash values produced by Python’s

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 by plotting a textual histogram in your terminal. Copy the following block of code and save it in a file named
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
94

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
2

Nó sử dụng một phiên bản

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
95 để biểu thị thuận tiện biểu đồ giá trị băm của các mục được cung cấp. The hash values are spread over the specified number of containers by wrapping them with the modulo operator. Giờ đây, bạn có thể lấy một trăm ký tự ASCII có thể in chẳng hạn, sau đó tính giá trị băm của chúng và hiển thị phân phối của chúng

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
3

Khi chỉ có hai vùng chứa, bạn sẽ mong đợi phân phối khoảng 50-50. Việc thêm nhiều thùng chứa sẽ dẫn đến việc lấp đầy chúng nhiều hơn hoặc ít hơn. Như bạn có thể thấy, hàm

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 tích hợp khá tốt nhưng không hoàn hảo trong việc phân phối các giá trị băm một cách đồng đều

Related to that, the uniform distribution of hash values is typically pseudo-random, which is especially important for cryptographic hash functions. Điều này ngăn những kẻ tấn công tiềm năng sử dụng phân tích thống kê để thử và dự đoán mối tương quan giữa đầu vào và đầu ra của hàm băm. Cân nhắc việc thay đổi một chữ cái trong chuỗi và kiểm tra xem điều đó ảnh hưởng như thế nào đến giá trị băm thu được trong Python

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
4

It’s a completely different hash value now, despite only one letter being different. Hash values are often subject to an avalanche effect, as even the smallest input change gets amplified. Nevertheless, this feature of the hash function isn’t essential for the sake of implementing a hash table data structure

In most cases, Python’s

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 exhibits yet another nonessential feature of cryptographic hash functions, which stems from the pigeonhole principle mentioned earlier. It behaves like a one-way function because finding its inverse is next to impossible in the majority of cases. However, there are notable exceptions

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
5

Hash values of small integers are equal to themselves, which is an implementation detail that CPython uses for simplicity and efficiency. Bear in mind that the actual hash values don’t matter as long as you can calculate them in a deterministic way

Last but not least, calculating a hash value in Python is fast, even for very big inputs. On a modern computer, calling

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 with a string of 100 million characters as the argument returns instantaneously. If it weren’t so fast, then the additional overhead of the hash value computation would offset the benefits of hashing in the first place

Identify Hash Function Properties

Based on what you’ve learned so far about Python’s

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68, you can now draw conclusions about the desired properties of a hash function in general. Here’s a summary of those features, comparing the regular hash function with its cryptographic flavor

FeatureHash FunctionCryptographic Hash FunctionDeterministic✔️✔️Universal Input✔️✔️Fixed-Sized Output✔️✔️Fast to Compute✔️✔️Uniformly Distributed✔️✔️Randomly Distributed✔️Randomized Seed✔️One-Way Function✔️Avalanche Effect✔️

The goals of both hash function types overlap, so they share a few common features. On the other hand, a cryptographic hash function provides additional guarantees around security

Before building your own hash function, you’ll take a look at another function built into Python that’s seemingly its most straightforward substitute

Remove ads

Compare an Object’s Identity With Its Hash

Probably one of the most straightforward hash function implementations imaginable in Python is the built-in

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
00, which tells you an object’s identity. In the standard Python interpreter, identity is the same as the object’s memory address expressed as an integer

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
6

The

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
00 function has most of the desired hash function properties. After all, it’s super fast and works with any input. It returns a fixed-size integer in a deterministic way. At the same time, you can’t easily retrieve the original object based on its memory address. The memory addresses themselves are immutable during an object’s lifetime and somewhat randomized between interpreter runs

So, why does Python insist on using a different function for hashing then?

First of all, the intent of

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
00 is different from
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68, so other Python distributions may implement identity in alternative ways. Second, memory addresses are predictable without having a uniform distribution, which is both insecure and severely inefficient for hashing. Finally, equal objects should generally produce the same hash code even if they have distinct identities

Note. Later, you’ll learn more about the contract between the equality of values and the corresponding hash codes

With that out of the way, you can finally think of making your own hash function

Make Your Own Hash Function

Designing a hash function that meets all requirements from scratch is hard. As mentioned before, you’ll be using the built-in

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 function to create your hash table prototype in the next section. However, trying to build a hash function from the ground up is a great way of learning how it works. By the end of this section, you’ll only have a rudimentary hash function that’s far from perfect, but you’ll have gained valuable insights

In this exercise, you can limit yourself to only one data type at first and implement a crude hash function around it. For example, you could consider strings and sum up the ordinal values of the individual characters in them

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
7

You iterate over the text using a generator expression, then turn each individual character into the corresponding Unicode code point with the built-in

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
05 function, and finally sum the ordinal values together. This will throw out a single number for any given text provided as an argument

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
8

Right away, you’ll notice a few problems with this function. Not only is it string-specific, but it also suffers from poor distribution of hash codes, which tend to form clusters at similar input values. Một thay đổi nhỏ trong đầu vào ít ảnh hưởng đến đầu ra quan sát được. Even worse, the function remains insensitive to character order in the text, which means anagrams of the same word, such as Loren and Loner, lead to a hash code collision

To fix the first problem, try converting the input to a string with a call to

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
06. Now, your function will be able to deal with any type of argument

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
9

You can call

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
07 with an argument of any data type, including a string, a floating-point number, or a Boolean value

Note that this implementation will only be as good as the corresponding string representation. Some objects may not have a textual representation suitable for the code above. In particular, custom class instances without the special methods

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
08 and
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
09 properly implemented are a good example. Plus, you won’t be able to distinguish between different data types anymore

>>>

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
0

In reality, you’d want to treat the string

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
10 and the floating-point number
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
11 as distinct objects with different hash codes. Một cách để giảm thiểu điều này là đổi
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
06 lấy
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
13, bao gồm biểu diễn chuỗi có dấu nháy đơn bổ sung [
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
14]

>>>

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
1

That’ll improve your hash function to some extent

>>>

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
2

Strings are now distinguishable from numbers. To tackle the issue with anagrams, like Loren and Loner, you might modify your hash function by taking into consideration the character’s value as well as its position within the text

>>>

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
3

Tại đây, bạn lấy tổng các tích có được từ việc nhân các giá trị thứ tự của các ký tự và chỉ số tương ứng của chúng. Notice you enumerate the indices from one rather than zero. Otherwise, the first character would always be discarded as its value would be multiplied by zero

Bây giờ, hàm băm của bạn khá phổ biến và không gây ra nhiều va chạm như trước, nhưng đầu ra của nó có thể lớn tùy ý vì chuỗi càng dài, mã băm càng lớn. Ngoài ra, nó khá chậm đối với đầu vào lớn hơn

>>>

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
4

Bạn luôn có thể giải quyết vấn đề tăng trưởng không giới hạn bằng cách lấy modulo [

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
15] mã băm của mình theo kích thước tối đa đã biết, chẳng hạn như một trăm

>>>

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
5

Hãy nhớ rằng việc chọn nhóm mã băm nhỏ hơn sẽ làm tăng khả năng xảy ra xung đột mã băm. If you don’t know the number of your input values up front, then it’s best to leave that decision until later if you can. Bạn cũng có thể áp đặt giới hạn cho mã băm của mình bằng cách giả định một giá trị tối đa hợp lý, chẳng hạn như

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
16, đại diện cho giá trị cao nhất của các số nguyên được hỗ trợ nguyên bản trong Python

Bỏ qua tốc độ chậm của hàm trong giây lát, bạn sẽ nhận thấy một vấn đề đặc biệt khác với hàm băm của mình. Nó dẫn đến việc phân phối mã băm dưới mức tối ưu thông qua phân cụm và do không tận dụng được tất cả các vị trí có sẵn

>>>

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
6

Sự phân bố không đồng đều. Hơn nữa, có sẵn sáu vùng chứa, nhưng một vùng bị thiếu trong biểu đồ. Vấn đề này bắt nguồn từ thực tế là hai dấu nháy đơn được thêm vào bởi

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
13 khiến hầu như tất cả các khóa trong ví dụ này dẫn đến một số băm chẵn. Bạn có thể tránh điều này bằng cách xóa dấu nháy đơn trái nếu nó tồn tại

>>>

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
7

Cuộc gọi đến

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
18 sẽ chỉ ảnh hưởng đến một chuỗi nếu nó bắt đầu bằng tiền tố được chỉ định để loại bỏ

Naturally, you can continue improving your hash function further. If you’re curious about the implementation of

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 for strings and byte sequences in Python, then it currently uses the SipHash algorithm, which might fall back to a modified version of FNV if the former is unavailable. Để biết thuật toán băm nào mà trình thông dịch Python của bạn đang sử dụng, hãy truy cập mô-đun
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
20

>>>

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
8

Tại thời điểm này, bạn đã nắm khá rõ về hàm băm, cách thức hoạt động của nó và những thách thức bạn có thể gặp phải khi triển khai nó. In the upcoming sections, you’ll use a hash function to build a hash table. Việc lựa chọn một thuật toán băm cụ thể sẽ ảnh hưởng đến hiệu suất của bảng băm. Với kiến ​​thức đó làm nền tảng, bạn có thể thoải mái gắn bó với

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 tích hợp từ giờ trở đi

Remove ads

Xây dựng Nguyên mẫu Bảng băm bằng Python với TDD

Trong phần này, bạn sẽ tạo một lớp tùy chỉnh đại diện cho cấu trúc dữ liệu bảng băm. It won’t be backed by a Python dictionary, so you can build a hash table from scratch and practice what you’ve learned so far. At the same time, you’ll model your implementation after the built-in dictionary by mimicking its most essential features

Ghi chú. Đây chỉ là một lời nhắc nhanh rằng việc triển khai bảng băm chỉ là một bài tập và một công cụ giáo dục để dạy cho bạn về các vấn đề mà cấu trúc dữ liệu này giải quyết. Just like coding a custom hash function before, a pure-Python hash table implementation has no practical use in real-life applications

Dưới đây là danh sách các yêu cầu cấp cao cho bảng băm của bạn mà bạn sẽ triển khai ngay bây giờ. Đến cuối phần này, bảng băm của bạn sẽ thể hiện các tính năng cốt lõi sau. It’ll let you

  • Tạo một bảng băm trống
  • Insert a key-value pair to the hash table
  • Xóa một cặp khóa-giá trị khỏi bảng băm
  • Find a value by key in the hash table
  • Update the value associated with an existing key
  • Kiểm tra xem bảng băm có khóa đã cho không

Ngoài những tính năng này, bạn sẽ triển khai một số tính năng không cần thiết nhưng vẫn hữu ích. Specifically, you should be able to

  • Create a hash table from a Python dictionary
  • Tạo một bản sao nông của bảng băm hiện có
  • Trả về giá trị mặc định nếu không tìm thấy khóa tương ứng
  • Báo cáo số lượng cặp khóa-giá trị được lưu trữ trong bảng băm
  • Trả về khóa, giá trị và cặp khóa-giá trị
  • Make the hash table iterable
  • Make the hash table comparable by using the equality test operator
  • Hiển thị một đại diện văn bản của bảng băm

While implementing these features, you’ll actively exercise test-driven development by gradually adding more features to your hash table. Lưu ý rằng nguyên mẫu của bạn sẽ chỉ bao gồm những điều cơ bản. You’ll learn how to cope with some more advanced corner cases a bit later in this tutorial. Đặc biệt, phần này sẽ không đề cập đến cách

  • Resolve hash code collisions
  • Giữ nguyên thứ tự chèn
  • Thay đổi kích thước bảng băm động
  • Calculate the load factor

Vui lòng sử dụng các tài liệu bổ sung làm điểm kiểm soát nếu bạn gặp khó khăn hoặc nếu bạn muốn bỏ qua một số bước tái cấu trúc trung gian. Each subsection ends with a complete implementation stage and the corresponding tests that you can start from. Lấy liên kết sau và tải xuống các tài liệu hỗ trợ với mã nguồn hoàn chỉnh và các bước trung gian được sử dụng trong hướng dẫn này

Source Code. Click here to download the source code that you’ll use to build a hash table in Python

Tham gia một khóa học cấp tốc về phát triển dựa trên thử nghiệm

Bây giờ bạn đã biết các thuộc tính cấp cao của hàm băm và mục đích của nó, bạn có thể thực hiện phương pháp phát triển dựa trên thử nghiệm để xây dựng bảng băm. Nếu bạn chưa bao giờ thử kỹ thuật lập trình này trước đây, thì nó sẽ tóm tắt thành ba bước mà bạn có xu hướng lặp lại theo chu kỳ

  1. 🟥 Đỏ. Hãy nghĩ về một trường hợp thử nghiệm duy nhất và tự động hóa nó bằng cách sử dụng khung thử nghiệm đơn vị do bạn chọn. Your test will fail at this point, but that’s okay. Người chạy thử nghiệm thường biểu thị một thử nghiệm không đạt bằng màu đỏ, do đó có tên của giai đoạn chu kỳ này
  2. 🟩 Green. Implement the bare minimum to make your test pass, but no more. This will ensure higher code coverage and spare you from writing redundant code. Báo cáo thử nghiệm sẽ sáng lên với màu xanh lá cây ưng ý sau đó
  3. ♻️ Tái cấu trúc. Tùy chọn, sửa đổi mã của bạn mà không thay đổi hành vi của nó miễn là tất cả các trường hợp thử nghiệm vẫn vượt qua. You can use this as an opportunity to remove duplication and improve the readability of your code

Python comes with the unittest package out of the box, but the third-party pytest library has an arguably shallower learning curve, so you’ll use that in this tutorial instead. Go ahead and install

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
22 in your virtual environment now

  • các cửa sổ
  • Linux + macOS

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
9

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
60

Remember that you can verify each implementation stage against several control checkpoints. Next, create a file named

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
23 and define a dummy test function in it to check if pytest will pick it up

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
61

The framework leverages the built-in

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
24 statement to run your tests and report their results. That means you can just use regular Python syntax, without importing any specific API until absolutely necessary. It also detects test files and test functions as long as their names start with the
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
25 prefix

The

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
24 statement takes a Boolean expression as an argument, followed by an optional error message. When the condition evaluates to
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
27, then nothing happens, as if there were no assertion at all. Otherwise, Python will raise an
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
28 and display the message on the standard error stream [stderr]. Meanwhile, pytest intercepts assertion errors and builds a report around them

Now, open the terminal, change your working directory to wherever you saved that test file, and run the

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
22 command without any arguments. Its output should look similar to this

  • các cửa sổ
  • Linux + macOS

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
62

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
63

Uh-oh. There’s a failure in your test. To find the root cause, increase the verbosity of pytest’s output by appending the

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
30 flag to the command. Now you can pinpoint where the problem lies

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
64

Đầu ra hiển thị giá trị thực tế và dự kiến ​​cho xác nhận không thành công. In this case, adding two plus two results in four rather than twenty-two. Bạn có thể sửa mã bằng cách sửa giá trị dự kiến

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
65

Khi bạn chạy lại pytest, sẽ không còn test fail nữa

  • các cửa sổ
  • Linux + macOS

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
66

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67

Đó là nó. Bạn vừa học các bước thiết yếu trong việc tự động hóa các trường hợp thử nghiệm để triển khai bảng băm của mình. Đương nhiên, bạn có thể sử dụng một IDE chẳng hạn như PyCharm hoặc trình chỉnh sửa như VS Code tích hợp với các khung thử nghiệm nếu điều đó thuận tiện hơn cho bạn. Tiếp theo, bạn sẽ áp dụng kiến ​​thức mới này vào thực tế

Remove ads

Xác định một lớp
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 tùy chỉnh

Hãy nhớ tuân theo chu trình tái cấu trúc đỏ-xanh lá cây được mô tả trước đó. Therefore, you must start by identifying your first test case. Ví dụ: bạn sẽ có thể khởi tạo một bảng băm mới bằng cách gọi lớp

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 giả định được nhập từ mô-đun
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
33. This call should return a non-empty object

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68

Tại thời điểm này, bài kiểm tra của bạn sẽ từ chối chạy do có câu lệnh nhập không đạt yêu cầu ở đầu tệp. You’re in the red phase, after all. Giai đoạn màu đỏ là thời điểm duy nhất bạn được phép thêm các tính năng mới, vì vậy hãy tiếp tục và tạo một mô-đun khác có tên là

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
34 và đặt định nghĩa lớp
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 vào đó

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
69

Đó là một trình giữ chỗ lớp cơ bản, nhưng nó chỉ đủ để vượt qua bài kiểm tra của bạn. Nhân tiện, nếu bạn đang sử dụng trình chỉnh sửa mã, thì bạn có thể chia chế độ xem thành các cột một cách thuận tiện, hiển thị mã đang được kiểm tra và các bài kiểm tra tương ứng cạnh nhau

Chia màn hình trong PyCharm

Nếu bạn tò mò về bảng màu được mô tả trong ảnh chụp màn hình ở trên, thì đó là Chủ đề Dracula. It’s available for many code editors and not just PyCharm

Once running pytest succeeds, then you can start thinking of another test case since there’s barely anything to refactor yet. Ví dụ: một bảng băm cơ bản phải chứa một chuỗi các giá trị. Ở giai đoạn đầu này, bạn có thể giả định rằng chuỗi có kích thước cố định được thiết lập tại thời điểm tạo bảng băm. Modify your existing test case accordingly

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
90

You specify the size using a keyword argument. However, before adding new code to the

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 class, rerun your tests to confirm that you’ve entered the red phase again. Chứng kiến ​​một bài kiểm tra thất bại có thể là vô giá. When you implement a piece of code later, you’ll know whether it satisfies a particular group of tests or if they remain unaffected. Mặt khác, các bài kiểm tra của bạn có thể nói dối bạn bằng cách xác minh điều gì đó khác với bạn nghĩ

After confirming that you’re in the red phase, declare the

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
37 method in the
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 class with the expected signature, but don’t implement its body

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
91

bùng nổ. Bạn đang quay lại giai đoạn xanh một lần nữa, vậy lần này tái cấu trúc một chút thì sao? . Đừng quên thay đổi trường hợp kiểm tra trước, sau đó chạy pytest và luôn cập nhật mã đang kiểm tra ở bước cuối cùng

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
92

Như bạn có thể thấy, chu trình tái cấu trúc đỏ-xanh lục bao gồm các giai đoạn ngắn, thường kéo dài không quá vài giây mỗi giai đoạn. Vì vậy, tại sao bạn không tiếp tục bằng cách thêm nhiều trường hợp thử nghiệm hơn? . Thêm một trường hợp thử nghiệm khác và quan sát xem nó thất bại thảm hại như thế nào

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
93

To handle

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
73 correctly, you must implement the special method
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
43 in your class and remember the capacity supplied through the class initializer

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
94

Bạn có thể nghĩ rằng TDD không đưa bạn đi đúng hướng. That’s not how you might have envisioned the hash table implementation. Đâu là chuỗi các giá trị mà bạn đã bắt đầu từ đầu? . Do đó, nó có thể không phù hợp với các dự án liên quan đến nhiều thử nghiệm

On the other hand, implementing a well-known data structure such as a hash table is a perfect application of this software development methodology. Bạn có những kỳ vọng rõ ràng, dễ dàng mã hóa thành các trường hợp thử nghiệm. Chẳng mấy chốc, bạn sẽ tự mình thấy rằng việc thực hiện bước tiếp theo sẽ dẫn đến một thay đổi nhỏ trong quá trình triển khai. Tuy nhiên, đừng lo lắng về điều đó, vì bản thân việc hoàn thiện mã không quan trọng bằng việc vượt qua các ca kiểm thử của bạn.

As you keep adding more constraints through the test cases, you frequently have to rethink your implementation. Ví dụ: một bảng băm mới có thể bắt đầu với các vị trí trống cho các giá trị được lưu trữ

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
95

Nói cách khác, một bảng băm mới sẽ hiển thị thuộc tính

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
44 có độ dài được yêu cầu và chứa đầy các phần tử
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87. By the way, it’s common to use such verbose function names because they’ll appear in your test report. Các bài kiểm tra của bạn càng dễ đọc và mô tả, bạn càng nhanh chóng tìm ra phần nào cần sửa

Ghi chú. Theo nguyên tắc chung, các trường hợp thử nghiệm của bạn phải độc lập và nguyên tử nhất có thể, điều này thường có nghĩa là chỉ sử dụng một xác nhận cho mỗi chức năng. Tuy nhiên, các kịch bản thử nghiệm của bạn đôi khi có thể cần một chút thiết lập hoặc phân tích. They may also involve a few steps. Trong những trường hợp như vậy, theo thông lệ, bạn sẽ cấu trúc chức năng của mình xung quanh cái gọi là quy ước đã cho-khi-thì

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
96

Phần đã cho mô tả trạng thái ban đầu và các điều kiện tiên quyết cho trường hợp thử nghiệm của bạn, trong khi when đại diện cho hành động của mã của bạn đang được thử nghiệm và sau đó chịu trách nhiệm khẳng định kết quả thu được

Đây là một trong nhiều cách khả thi để đáp ứng các bài kiểm tra hiện tại của bạn

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
97

Bạn thay thế thuộc tính

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
46 bằng một danh sách độ dài được yêu cầu chỉ chứa các phần tử
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87. Nhân một số với một danh sách hoặc theo cách khác là một cách nhanh chóng để điền vào danh sách đó một hoặc nhiều giá trị đã cho. Other than that, you update the special method
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
43 so that all tests will pass

Note. A Python dictionary has a length corresponding to the actual number of key-value pairs stored instead of its internal capacity. You’ll achieve a similar effect soon

Now that you’re acquainted with TDD, it’s time to dive deeper and add the remaining features to your hash table

Remove ads

Chèn một cặp khóa-giá trị

Bây giờ bạn có thể tạo một bảng băm, đã đến lúc cung cấp cho nó một số khả năng lưu trữ. The traditional hash table is backed by an array capable of storing only one data type. Do đó, việc triển khai bảng băm trong nhiều ngôn ngữ, chẳng hạn như Java, yêu cầu bạn phải khai báo loại cho các khóa và giá trị của chúng trước

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
98

This particular hash table maps strings to integers, for example. Tuy nhiên, vì mảng không có nguồn gốc từ Python nên bạn sẽ tiếp tục sử dụng danh sách để thay thế. As a side effect, your hash table will be able to accept arbitrary data types for both the keys and values, just like Python’s

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67

Note. Python có một bộ sưu tập

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
50 hiệu quả, nhưng nó chỉ dành cho các giá trị số. You may sometimes find it convenient for processing raw binary data

Now add another test case for inserting key-value pairs into your hash table using the familiar square bracket syntax

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
99

Đầu tiên, bạn tạo một bảng băm với một trăm vị trí trống, sau đó điền vào đó ba cặp khóa và giá trị thuộc nhiều loại khác nhau, bao gồm chuỗi, số dấu phẩy động và Booleans. Finally, you assert that the hash table contains the expected values in whatever order. Note that your hash table only remembers the values but not the associated keys at the moment

The most straightforward and perhaps slightly naive implementation that would satisfy this test is as follows

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
10

It completely ignores the key and just appends the value to the right end of the list, increasing its length. Bạn rất có thể nghĩ ngay đến một trường hợp thử nghiệm khác. Inserting elements into the hash table shouldn’t grow its size. Tương tự như vậy, việc xóa một phần tử không nên thu nhỏ bảng băm, nhưng bạn chỉ cần ghi nhớ điều đó trong đầu vì chưa có khả năng xóa các cặp khóa-giá trị

Ghi chú. Bạn cũng có thể viết một bài kiểm tra trình giữ chỗ và yêu cầu pytest bỏ qua nó vô điều kiện cho đến sau này

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
11

It leverages one of the decorators provided by pytest

In the real world, you’d want to create separate test cases with descriptive names dedicated to testing these behaviors. Tuy nhiên, vì đây chỉ là hướng dẫn nên bạn sẽ thêm một xác nhận mới vào chức năng hiện có cho ngắn gọn

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
12

Bây giờ bạn đang ở giai đoạn màu đỏ, vì vậy hãy viết lại phương thức đặc biệt của bạn để luôn cố định dung lượng

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
13

Bạn biến một khóa tùy ý thành giá trị băm số và sử dụng toán tử modulo để hạn chế chỉ mục kết quả trong không gian địa chỉ có sẵn. Tuyệt quá. Báo cáo thử nghiệm của bạn lại sáng lên màu xanh lá cây

Ghi chú. Đoạn mã trên dựa vào hàm

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 tích hợp sẵn của Python, hàm này có yếu tố ngẫu nhiên, như bạn đã học. Do đó, thử nghiệm của bạn có thể thất bại trong một số trường hợp hiếm hoi khi hai khóa ngẫu nhiên tạo ra một mã băm giống hệt nhau. Vì bạn sẽ xử lý xung đột mã băm sau, nên bạn có thể tắt tính năng ngẫu nhiên hóa hàm băm hoặc sử dụng hạt giống có thể dự đoán được khi chạy pytest trong thời gian chờ đợi

  • các cửa sổ
  • Linux + macOS

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
14

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
15

Đảm bảo chọn hạt giống băm không gây ra bất kỳ xung đột nào trong dữ liệu mẫu của bạn. Tìm một cái có thể liên quan đến một chút thử và sai. Trong trường hợp của tôi, giá trị

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
52 dường như hoạt động tốt

Nhưng bạn có thể nghĩ ra một số trường hợp cạnh không? . Bạn sẽ muốn tránh điều đó

As always, start by writing a test case to express the desired behavior

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
16

One way to work around this would be to replace

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87 in your
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
37 method with another unique value that users are unlikely to insert. For example, you could define a special constant by creating a brand-new object to represent blank spaces in your hash table

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
17

Bạn chỉ cần một phiên bản trống duy nhất để đánh dấu các vị trí là trống. Naturally, you’ll need to update an older test case to get back to the green phase

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
18

Then, write a positive test case exercising the happy path for handling the insertion of a

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87 value

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
19

You create an empty hash table with one hundred slots and insert

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87 associated with some arbitrary key. Nó sẽ hoạt động tốt nếu bạn đã theo sát các bước cho đến nay. If not, then look at the error messages because they often contain clues as to what went wrong. Ngoài ra, hãy kiểm tra mã mẫu có sẵn để tải xuống tại liên kết này

Source Code. Click here to download the source code that you’ll use to build a hash table in Python

In the next subsection, you’ll add the ability to retrieve values by their associated keys

Remove ads

Tìm giá trị theo khóa

Để truy xuất một giá trị từ bảng băm, bạn sẽ muốn tận dụng cú pháp dấu ngoặc vuông giống như trước đây, chỉ khi không sử dụng câu lệnh gán. Bạn cũng sẽ cần một bảng băm mẫu. Để tránh sao chép mã thiết lập giống nhau trên các trường hợp thử nghiệm riêng lẻ trong bộ thử nghiệm của bạn, bạn có thể bọc nó trong một bộ cố định thử nghiệm do pytest đưa ra

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
20

A test fixture is a function annotated with the

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
58 decorator. Nó trả về dữ liệu mẫu cho các trường hợp thử nghiệm của bạn, chẳng hạn như bảng băm được điền bằng các khóa và giá trị đã biết. Pytest của bạn sẽ tự động gọi hàm đó cho bạn và đưa kết quả của nó vào bất kỳ hàm kiểm tra nào khai báo một đối số có cùng tên với đối số của bạn. Trong trường hợp này, chức năng kiểm tra mong đợi một đối số
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
59, tương ứng với tên lịch thi đấu của bạn

To be able to find values by key, you can implement the element lookup through another special method called

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
60 in your
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 class

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
21

You calculate the index of an element based on the hash code of the provided key and return whatever sits under that index. Nhưng còn thiếu chìa khóa thì sao? . To replicate how a Python

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67 would work in such a case, you should raise a
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
63 exception instead

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
22

Bạn tạo một bảng băm trống và thử truy cập một trong các giá trị của nó thông qua một khóa bị thiếu. Khung pytest bao gồm một cấu trúc đặc biệt để kiểm tra các ngoại lệ. Ở trên, bạn sử dụng trình quản lý ngữ cảnh

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
64 để mong đợi một loại ngoại lệ cụ thể trong khối mã sau. Handling this case is a matter of adding a conditional statement to your accessor method

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
23

If the value at the given index is blank, then you raise the exception. Nhân tiện, bạn sử dụng toán tử

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
65 thay vì toán tử kiểm tra đẳng thức [
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
66] để đảm bảo rằng bạn đang so sánh các danh tính chứ không phải giá trị. Although the default implementation of the equality test in custom classes falls back to comparing the identities of their instances, most built-in data types distinguish between the two operators and implement them differently

Because you can now determine whether a given key has an associated value in your hash table, you might as well implement the

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
67 operator to mimic a Python dictionary. Hãy nhớ viết và trình bày các trường hợp thử nghiệm này một cách riêng lẻ để tôn trọng các nguyên tắc phát triển dựa trên thử nghiệm

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
24

Cả hai trường hợp thử nghiệm đều tận dụng vật cố định thử nghiệm mà bạn đã chuẩn bị trước đó và xác minh phương pháp đặc biệt

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
68, mà bạn có thể thực hiện theo cách sau

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
25

Khi truy cập vào khóa đã cho sẽ tạo ra một

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
63, bạn chặn ngoại lệ đó và trả về
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
70 để chỉ ra một khóa bị thiếu. Otherwise, you combine the
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
71 …
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
72 block with an
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
73 clause and return
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
27. Xử lý ngoại lệ rất tốt nhưng đôi khi có thể bất tiện, đó là lý do tại sao
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
75 cho phép bạn chỉ định một giá trị mặc định tùy chọn. You can replicate the same behavior in your custom hash table

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
26

Mã của

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
76 trông giống như phương pháp đặc biệt mà bạn vừa thực hiện

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
27

You attempt to return the value corresponding to the provided key. Trong trường hợp có ngoại lệ, bạn trả về giá trị mặc định, là đối số tùy chọn. Khi người dùng để nó không xác định, thì nó bằng

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87

For completeness, you’ll add the capability to delete a key-value pair from your hash table in the upcoming subsection

Remove ads

Xóa cặp khóa-giá trị

Từ điển Python cho phép bạn xóa các cặp khóa-giá trị đã chèn trước đó bằng cách sử dụng từ khóa

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
78 tích hợp, loại bỏ thông tin về cả khóa và giá trị. Here’s how this could work with your hash table

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
28

Trước tiên, bạn xác minh xem bảng băm mẫu có khóa và giá trị mong muốn hay không. Sau đó, bạn xóa cả hai bằng cách chỉ cho biết khóa và lặp lại các xác nhận nhưng với kỳ vọng đảo ngược. Bạn có thể vượt qua bài kiểm tra này như sau

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
29

Bạn tính toán chỉ mục được liên kết với một khóa và xóa giá trị tương ứng khỏi danh sách một cách vô điều kiện. Tuy nhiên, bạn ngay lập tức nhớ lại ghi chú trong đầu trước đó về việc khẳng định rằng bảng băm của bạn sẽ không co lại khi bạn xóa các phần tử khỏi nó. Vì vậy, bạn thêm hai khẳng định nữa

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
50

These will ensure that the size of your hash table’s underlying list remains unaffected. Bây giờ, bạn cần cập nhật mã của mình để nó đánh dấu một vị trí trống thay vì vứt bỏ hoàn toàn

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
51

Xét rằng bạn đang ở giai đoạn xanh một lần nữa, bạn có thể tận dụng cơ hội này để dành thời gian tái cấu trúc. Cùng một công thức lập chỉ mục xuất hiện ba lần ở những nơi khác nhau. Bạn có thể giải nén nó và đơn giản hóa mã

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
52

Suddenly, after applying only this slight modification, a pattern emerges. Xóa một mục tương đương với việc chèn một đối tượng trống. So, you can rewrite your deletion routine to take advantage of the mutator method

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
53

Gán một giá trị thông qua cú pháp dấu ngoặc vuông ủy quyền cho phương thức

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
79. Được rồi, thế là đủ tái cấu trúc cho bây giờ. Điều quan trọng hơn nhiều là nghĩ về các trường hợp thử nghiệm khác vào thời điểm này. Ví dụ: điều gì xảy ra khi bạn yêu cầu xóa một khóa bị thiếu?

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
54

Bao gồm trường hợp thử nghiệm này tương đối đơn giản vì bạn có thể dựa vào mã mà bạn đã viết khi triển khai toán tử

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
67

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
55

Nếu bạn tìm thấy khóa trong bảng băm của mình, thì bạn sẽ ghi đè giá trị được liên kết bằng giá trị trọng điểm để xóa cặp đó. Otherwise, you raise an exception. All right, there’s one more basic hash table operation to cover, which you’ll do next

Cập nhật giá trị của một cặp hiện có

Phương thức chèn đã đảm nhiệm việc cập nhật cặp khóa-giá trị, vì vậy, bạn sẽ chỉ thêm một trường hợp thử nghiệm có liên quan và kiểm tra xem nó có hoạt động như mong đợi không

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
56

Sau khi sửa đổi giá trị, xin chào, của một khóa hiện có và thay đổi nó thành hallo, bạn cũng kiểm tra xem các cặp khóa-giá trị khác, cũng như độ dài của bảng băm, có còn nguyên không. Đó là nó. You already have a basic hash table implementation, but a few extra features that are relatively cheap to implement are still missing

Remove ads

Nhận các cặp khóa-giá trị

Đã đến lúc nói chuyện với con voi trong phòng. Từ điển Python cho phép bạn lặp lại các khóa, giá trị hoặc cặp khóa-giá trị được gọi là mục. Tuy nhiên, bảng băm của bạn thực sự chỉ là một danh sách các giá trị được lập chỉ mục ưa thích ở trên cùng. Nếu bạn từng muốn truy xuất các khóa ban đầu được đưa vào bảng băm của mình, thì bạn sẽ không gặp may vì việc triển khai bảng băm hiện tại sẽ không bao giờ nhớ chúng

Trong tiểu mục này, bạn sẽ cấu trúc lại bảng băm của mình rất nhiều để thêm khả năng giữ lại các khóa và giá trị. Hãy nhớ rằng sẽ có một số bước liên quan và nhiều thử nghiệm sẽ bắt đầu thất bại do đó. If you’d like to skip those intermediate steps and see the effect, then jump ahead to defensive copying

Đợi tí. Bạn tiếp tục đọc về các cặp khóa-giá trị trong hướng dẫn này, vậy tại sao không thay thế các giá trị bằng các bộ? . Thậm chí tốt hơn, bạn có thể sử dụng các bộ dữ liệu được đặt tên để tận dụng lợi thế của việc tra cứu phần tử được đặt tên của chúng. Nhưng trước tiên, bạn cần nghĩ ra một bài kiểm tra

Ghi chú. Hãy nhớ tập trung vào chức năng hướng tới người dùng cấp cao khi tìm ra trường hợp thử nghiệm. Đừng tiến hành kiểm tra một đoạn mã mà bạn có thể dự đoán dựa trên kinh nghiệm hoặc cảm tính của lập trình viên của bạn. It’s the tests that should ultimately drive your implementation in TDD, not the other way around

First of all, you’re going to need another attribute in your

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 class to hold the key-value pairs

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
57

The order of key-value pairs is unimportant at this point, so you can assume that they might come out in any order each time you request them. However, instead of introducing another field to the class, you could reuse

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
44 by renaming it to
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85 and making other necessary adjustments. Có một vài. Chỉ cần lưu ý rằng điều này sẽ tạm thời khiến một số thử nghiệm không thành công cho đến khi bạn khắc phục việc triển khai

Ghi chú. Nếu bạn đang sử dụng trình chỉnh sửa mã, thì bạn có thể đổi tên biến hoặc thành viên lớp một cách thuận tiện chỉ bằng một lần bấm nút bằng cách tận dụng khả năng tái cấu trúc. In PyCharm, for example, you can right-click on a variable, choose Refactor from the context menu, and then Rename…. Or you can use the corresponding keyboard shortcut

Công cụ đổi tên của PyCharm

That’s the most straightforward and reliable way of changing the name of a variable in your project. Trình chỉnh sửa mã sẽ cập nhật tất cả các tham chiếu biến trên các tệp dự án của bạn

Khi bạn đã đổi tên

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
44 thành
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85 trong
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
34 và
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
23, thì bạn cũng cần cập nhật phương thức đặc biệt của
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
79. In particular, it should store tuples of the key and the associated value now

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
58

Chèn một phần tử vào bảng băm của bạn bao bọc khóa và giá trị trong một bộ rồi đặt bộ đó vào chỉ mục mong muốn trong danh sách các cặp của bạn. Lưu ý rằng danh sách của bạn ban đầu sẽ chỉ chứa các đối tượng trống thay vì các bộ dữ liệu, do đó, bạn sẽ sử dụng hai loại dữ liệu khác nhau trong danh sách các cặp của mình. One is a tuple, while the other one could be anything but a tuple to denote a blank slot

Do đó, bạn không cần bất kỳ hằng số canh gác đặc biệt nào nữa để đánh dấu một vị trí trống. Bạn có thể xóa hằng số

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
91 của mình một cách an toàn và thay thế nó bằng
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87 đơn giản một lần nữa nếu cần, vì vậy hãy tiếp tục và thực hiện điều đó ngay bây giờ

Ghi chú. Việc xóa mã có thể khó chấp nhận lúc đầu, nhưng càng ít càng tốt. As you can see, test-driven development can sometimes make you run in circles

Bạn có thể lùi lại một bước để giành lại quyền kiểm soát việc xóa một mục

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
59

Thật không may, phương pháp

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
93 của bạn không còn có thể tận dụng cú pháp dấu ngoặc vuông vì điều này sẽ dẫn đến việc gói bất kỳ giá trị trọng điểm nào bạn đã chọn trong một bộ dữ liệu không cần thiết. Bạn phải sử dụng một câu lệnh gán rõ ràng ở đây để tránh những logic phức tạp không cần thiết sau này

Phần quan trọng cuối cùng của câu đố là cập nhật phương thức

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
60

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
60

Bạn xem nhanh một chỉ mục, hy vọng tìm thấy một cặp khóa-giá trị. Nếu bạn không nhận được gì cả, thì bạn đưa ra một ngoại lệ. On the other hand, if you see something interesting, then you grab the tuple’s second element at index one, which corresponds to the mapped value. Tuy nhiên, bạn có thể viết điều này một cách tao nhã hơn bằng cách sử dụng một bộ dữ liệu có tên, như đã đề xuất trước đó

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
61

Lớp

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
95 bao gồm các thuộc tính
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
96 và
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
97, được tự do nhận giá trị của bất kỳ kiểu dữ liệu nào. Đồng thời, lớp của bạn kế thừa tất cả các hành vi của bộ thông thường vì nó mở rộng lớp cha
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
98. Note that you have to explicitly call
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
99 on your key and value in the
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
79 method to take advantage of the named attribute access in
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
60

Ghi chú. Mặc dù sử dụng loại dữ liệu tùy chỉnh để biểu thị các cặp khóa-giá trị, bạn có thể viết các bài kiểm tra của mình để mong đợi các phiên bản

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
95 hoặc các bộ dữ liệu thông thường, nhờ khả năng tương thích của cả hai loại

Đương nhiên, bạn có một số trường hợp thử nghiệm cần cập nhật vào thời điểm này trước khi báo cáo có thể chuyển sang màu xanh trở lại. Dành thời gian của bạn và xem xét cẩn thận bộ thử nghiệm của bạn. Ngoài ra, hãy xem mã từ các tài liệu hỗ trợ nếu bạn cảm thấy bế tắc hoặc xem qua tại đây

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
62

There will be another test case that needs special care. Specifically, it’s about verifying that an empty hash table has no

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87 values when created. You’ve just replaced a list of values with a list of pairs. Để tìm lại các giá trị, bạn có thể sử dụng cách hiểu danh sách như thế này

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
63

Nếu bạn lo lắng về việc nhét quá nhiều logic vào trường hợp thử nghiệm, thì bạn hoàn toàn đúng. Rốt cuộc, bạn muốn kiểm tra hành vi của bảng băm. Nhưng đừng lo lắng về điều đó. Bạn sẽ sớm xem lại trường hợp thử nghiệm này

Sử dụng sao chép phòng thủ

Khi bạn đã trở lại giai đoạn xanh, hãy cố gắng tìm ra các trường hợp phạt góc có thể xảy ra. Ví dụ:

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85 được hiển thị dưới dạng thuộc tính công khai mà bất kỳ ai cũng có thể cố ý hoặc vô ý giả mạo. Trong thực tế, các phương thức truy cập không bao giờ được làm rò rỉ triển khai nội bộ của bạn mà phải tạo các bản sao phòng thủ để bảo vệ các thuộc tính có thể thay đổi khỏi các sửa đổi bên ngoài

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
64

Bất cứ khi nào bạn yêu cầu các cặp khóa-giá trị từ bảng băm, bạn sẽ nhận được một đối tượng hoàn toàn mới với một danh tính duy nhất. You can hide a private field behind a Python property, so create one and replace every reference to

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85 with
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
606 in your
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 class only. The leading underscore is a standard naming convention in Python that indicates internal implementation

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
65

When you request a list of key-value pairs stored in your hash table, you’ll get their shallow copy each time. Vì bạn sẽ không có tham chiếu đến trạng thái bên trong của bảng băm, nên nó sẽ không bị ảnh hưởng bởi những thay đổi tiềm ẩn đối với bản sao của nó

Ghi chú. Thứ tự của các phương thức lớp mà bạn đến có thể hơi khác so với thứ tự trong khối mã được trình bày ở trên. Điều đó không sao vì thứ tự phương thức không quan trọng theo quan điểm của Python. Tuy nhiên, theo thông lệ, bạn nên bắt đầu với các phương thức lớp hoặc tĩnh, tiếp theo là giao diện chung của lớp mà bạn có nhiều khả năng xem xét nhất. The internal implementation should typically appear at the very end

Để tránh phải nhảy xung quanh mã của bạn, bạn nên tổ chức các phương thức theo cách giống như một câu chuyện. Specifically, a higher-level function should be listed before the low-level functions that are called

Để bắt chước hơn nữa

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
608 trong tài sản của bạn, danh sách các cặp kết quả không được bao gồm các vị trí trống. Nói cách khác, không nên có bất kỳ giá trị
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87 nào trong danh sách đó

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
66

Để đáp ứng thử nghiệm này, bạn có thể lọc các giá trị trống bằng cách thêm một điều kiện vào phần hiểu danh sách trong thuộc tính của mình

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67

Bạn không cần gọi rõ ràng tới

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
610 vì tính năng hiểu danh sách sẽ tạo một danh sách mới. Đối với mọi cặp trong danh sách ban đầu của các cặp khóa-giá trị, bạn kiểm tra xem cặp cụ thể đó có đúng không và giữ nó trong danh sách kết quả. Tuy nhiên, điều này sẽ phá vỡ hai bài kiểm tra khác mà bạn cần cập nhật ngay bây giờ

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68

That’s not ideal because one of your tests reaches out for the internal implementation instead of focusing on public interfaces. Tuy nhiên, thử nghiệm như vậy được gọi là thử nghiệm hộp trắng, có vị trí của nó

Lấy khóa và giá trị

Bạn có nhớ trường hợp thử nghiệm mà bạn đã sửa đổi bằng cách thêm khả năng hiểu danh sách để truy xuất các giá trị từ các cặp khóa-giá trị của mình không?

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
63

Dòng được đánh dấu trông giống như những gì bạn cần để triển khai thuộc tính

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
44 mà bạn đã thay thế bằng
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85 trước đó. Bạn có thể cập nhật chức năng kiểm tra để tận dụng lại
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
44

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
16

Nó có thể cảm thấy như thể đó là một nỗ lực lãng phí. Tuy nhiên, các giá trị này hiện được đánh giá động thông qua thuộc tính getter, trong khi trước đây chúng được lưu trữ trong danh sách có kích thước cố định. Để đáp ứng bài kiểm tra này, bạn có thể sử dụng lại một phần triển khai cũ của nó, sử dụng khả năng hiểu danh sách

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
91

Lưu ý rằng bạn không còn phải chỉ định điều kiện lọc tùy chọn ở đây nữa, vì đã có một điều kiện ẩn đằng sau thuộc tính

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85

Những người theo chủ nghĩa thuần túy có thể nghĩ đến việc sử dụng cách hiểu theo tập hợp thay vì cách hiểu theo danh sách để truyền đạt sự thiếu đảm bảo cho thứ tự của các giá trị. Tuy nhiên, điều đó sẽ dẫn đến việc mất thông tin về các giá trị trùng lặp trong bảng băm. Protect yourself from such a possibility up front by writing another test case

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
92

Ví dụ: nếu bạn có một bảng băm có tên và tuổi và nhiều người có cùng độ tuổi, thì

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
44 sẽ giữ tất cả các giá trị tuổi được lặp lại. Bạn có thể sắp xếp các độ tuổi để đảm bảo chạy thử nghiệm lặp lại. Mặc dù trường hợp thử nghiệm này không yêu cầu viết mã mới, nhưng nó sẽ bảo vệ chống lại hồi quy

Cần kiểm tra các giá trị dự kiến, loại và số lượng của chúng. Tuy nhiên, bạn không thể so sánh trực tiếp hai danh sách vì các giá trị thực trong bảng băm có thể xuất hiện theo thứ tự không thể đoán trước. Để bỏ qua thứ tự trong bài kiểm tra của mình, bạn có thể chuyển đổi cả hai danh sách thành bộ hoặc sắp xếp chúng như trước đây. Thật không may, các bộ loại bỏ các bản sao tiềm năng, trong khi không thể sắp xếp khi danh sách chứa các loại không tương thích

Để kiểm tra một cách đáng tin cậy xem hai danh sách có chính xác các phần tử giống nhau của các loại tùy ý với các bản sao tiềm ẩn trong khi bỏ qua thứ tự của chúng hay không, bạn có thể sử dụng thành ngữ Python sau

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
93

It leverages the built-in

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
616 function, but it’s quite verbose. Có lẽ bạn nên sử dụng plugin
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
617. Đừng quên cài đặt nó vào môi trường ảo của bạn trước

  • các cửa sổ
  • Linux + macOS

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
94

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
95

Tiếp theo, nhập hàm

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
618 vào bộ thử nghiệm của bạn và sử dụng nó để bọc các giá trị của bảng băm

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
96

Làm như vậy sẽ chuyển đổi các giá trị thành danh sách không có thứ tự, danh sách này xác định lại toán tử kiểm tra đẳng thức để nó không tính đến thứ tự khi so sánh các phần tử danh sách. Ngoài ra, các giá trị của bảng băm trống phải là một danh sách trống, trong khi thuộc tính

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
44 phải luôn trả về một bản sao danh sách mới

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
97

Mặt khác, các khóa của bảng băm phải là duy nhất, vì vậy cần nhấn mạnh điều đó bằng cách trả về một tập hợp thay vì một danh sách các khóa. Rốt cuộc, các bộ theo định nghĩa là các bộ sưu tập các mục không có thứ tự không có bản sao

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
98

There’s no empty set literal in Python, so you have to call the built-in

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
620 function directly in this case. Việc triển khai hàm getter tương ứng sẽ trông quen thuộc

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
99

Nó giống với thuộc tính

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
44. Sự khác biệt là bạn sử dụng dấu ngoặc nhọn thay vì dấu ngoặc vuông và tham chiếu đến thuộc tính
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
96 thay vì
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
97 trong bộ dữ liệu được đặt tên của bạn. Ngoài ra, bạn có thể sử dụng
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
624 nếu muốn, nhưng có vẻ khó đọc hơn

Điều này cũng nhắc nhở bạn về sự cần thiết của một trường hợp thử nghiệm tương tự mà bạn đã bỏ lỡ khi bao hàm thuộc tính

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85. Thật hợp lý khi trả về một tập hợp các cặp vì mục đích nhất quán

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
00

Vì vậy, thuộc tính

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85 cũng sẽ sử dụng cách hiểu tập hợp kể từ bây giờ

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
01

Bạn không cần lo lắng về việc mất bất kỳ thông tin nào vì mỗi cặp khóa-giá trị là duy nhất. Tại thời điểm này, bạn sẽ lại ở giai đoạn xanh

Lưu ý rằng bạn có thể tận dụng thuộc tính

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85 để chuyển đổi bảng băm của mình thành một từ điển cũ đơn giản và sử dụng
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
628 và
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
44 để kiểm tra điều đó

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
02

Để bỏ qua thứ tự của các phần tử, hãy nhớ bọc các khóa từ điển và các cặp khóa-giá trị bằng các bộ trước khi thực hiện so sánh. Ngược lại, các giá trị trong bảng băm của bạn xuất hiện dưới dạng danh sách, vì vậy hãy đảm bảo sử dụng hàm

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
618 để so sánh các danh sách trong khi bỏ qua thứ tự phần tử

Được rồi, bảng băm của bạn hiện đang thực sự bắt đầu hình thành

Báo cáo độ dài của bảng băm

Có 1 chi tiết nhỏ mà bạn cố ý để cho đơn giản đến tận bây giờ. Đó là độ dài của bảng băm của bạn, hiện đang báo cáo dung lượng tối đa của nó ngay cả khi chỉ có các vị trí trống. May mắn thay, không mất nhiều công sức để khắc phục điều này. Tìm hàm của bạn có tên là

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
631, đổi tên như hiển thị bên dưới và kiểm tra xem độ dài của bảng băm trống có bằng 0 thay vì 100 không

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
03

Để làm cho dung lượng không phụ thuộc vào độ dài, hãy sửa đổi phương thức đặc biệt của bạn

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
43 để nó đề cập đến thuộc tính công khai với các cặp được lọc thay vì danh sách riêng tư của tất cả các vị trí

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
04

Bạn vừa xóa dấu gạch dưới đầu tên biến. Nhưng sự thay đổi nhỏ đó hiện đang khiến cả đống bài kiểm tra kết thúc đột ngột với một lỗi và một số bài không thành công

Ghi chú. Các bài kiểm tra thất bại ít nghiêm trọng hơn vì các xác nhận của chúng đánh giá tới

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
70, trong khi các lỗi cho thấy hành vi hoàn toàn không mong muốn trong mã của bạn

Có vẻ như hầu hết các trường hợp thử nghiệm đều mắc phải cùng một ngoại lệ chưa được xử lý do chia cho 0 khi ánh xạ khóa tới một chỉ mục. Điều đó hợp lý vì

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
634 sử dụng độ dài của bảng băm để tìm phần còn lại từ việc chia khóa băm cho số lượng vị trí có sẵn. Tuy nhiên, độ dài của bảng băm bây giờ có một ý nghĩa khác. Thay vào đó, bạn cần lấy độ dài của danh sách nội bộ

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
05

Bây giờ tốt hơn nhiều. Ba trường hợp thử nghiệm vẫn thất bại do sử dụng các giả định sai về độ dài của bảng băm. Thay đổi những giả định đó để làm cho các bài kiểm tra vượt qua

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
06

Bạn đã quay lại trò chơi, nhưng

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
635 là một dấu hiệu đáng báo động khiến bạn ngay lập tức muốn thêm các trường hợp thử nghiệm bổ sung

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
07

Tạo một

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 với công suất không tích cực không có nhiều ý nghĩa. Làm cách nào bạn có thể có vùng chứa có độ dài âm? . Cách tiêu chuẩn để chỉ ra các đối số đầu vào không chính xác như vậy trong Python là đưa ra một ngoại lệ
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
637

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
08

Nếu bạn siêng năng, thì có lẽ bạn cũng nên kiểm tra các loại đối số không hợp lệ, nhưng điều đó nằm ngoài phạm vi của hướng dẫn này. Bạn có thể coi nó như một bài tập tự nguyện

Tiếp theo, thêm một kịch bản khác để kiểm tra độ dài của bảng băm không trống được cung cấp dưới dạng vật cố định bởi pytest

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
09

Có ba cặp khóa-giá trị, vì vậy độ dài của bảng băm cũng phải là ba. Bài kiểm tra này không yêu cầu bất kỳ mã bổ sung nào. Cuối cùng, vì hiện tại bạn đang trong giai đoạn tái cấu trúc nên bạn có thể thêm một chút đường cú pháp bằng cách giới thiệu thuộc tính

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
46 và sử dụng thuộc tính này nếu có thể

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
10

Dung lượng không đổi và được xác định tại thời điểm tạo bảng băm. Bạn có thể lấy nó từ độ dài của danh sách các cặp bên dưới

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
11

Khi bạn giới thiệu từ vựng mới cho miền vấn đề của mình, nó sẽ giúp bạn khám phá những cơ hội mới để đặt tên chính xác hơn, rõ ràng hơn. Ví dụ: bạn đã thấy các cặp từ được sử dụng thay thế cho nhau để chỉ cả cặp khóa-giá trị thực tế được lưu trữ trong bảng băm của bạn và danh sách vị trí nội bộ cho các cặp. Đây có thể là cơ hội tốt để cấu trúc lại mã của bạn bằng cách thay đổi

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
606 thành
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
640 ở mọi nơi

Sau đó, một trong những trường hợp thử nghiệm trước đó của bạn sẽ truyền đạt mục đích rõ ràng hơn

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
12

Có lẽ sẽ hợp lý hơn nếu đổi tên bài kiểm tra này bằng cách thay thế từ giá trị bằng cặp trong đó

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
13

Bạn có thể nghĩ rằng những suy nghĩ triết học như vậy là không cần thiết. Tuy nhiên, tên của bạn càng mô tả rõ ràng thì mã của bạn sẽ càng dễ đọc—nếu không dành cho người khác, thì chắc chắn là dành cho bạn trong tương lai. Thậm chí còn có sách và truyện cười về nó. Các bài kiểm tra của bạn là một dạng tài liệu, vì vậy bạn nên duy trì mức độ chú ý đến từng chi tiết đối với chúng giống như đối với mã của bạn đang kiểm tra

Làm cho bảng băm có thể lặp lại

Python cho phép bạn lặp lại từ điển thông qua các khóa, giá trị hoặc cặp khóa-giá trị được gọi là mục. You want the same behavior in your custom hash table, so you start by sketching out a few test cases

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
14

Phép lặp theo khóa, giá trị hoặc cặp khóa-giá trị hoạt động hiệu quả với cách triển khai hiện tại vì các tập hợp và danh sách cơ bản đã có thể xử lý việc đó. Mặt khác, để làm cho các thể hiện của lớp

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 của bạn có thể lặp lại, bạn phải định nghĩa một phương thức đặc biệt khác, phương thức này sẽ cho phép bạn hợp tác trực tiếp với các vòng lặp
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
642

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
15

Unlike before, you hand over a reference to the

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 instance here, but the behavior is the same as if you were iterating over the
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
628 property. Hành vi này tương thích với
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67 tích hợp trong Python

Phương thức đặc biệt mà bạn cần,

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
646, phải trả về một đối tượng vòng lặp mà vòng lặp sử dụng bên trong

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
16

This is an example of a generator iterator, which takes advantage of the yield keyword in Python

Ghi chú. Biểu thức

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
647 ủy quyền phép lặp cho một trình tạo phụ, có thể là một đối tượng có thể lặp khác, chẳng hạn như danh sách hoặc tập hợp

Từ khóa

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
648 cho phép bạn xác định một trình vòng lặp tại chỗ bằng cách sử dụng kiểu chức năng mà không cần tạo một lớp khác. The special method named
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
646 gets called by the
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
642 loop when it starts

Được rồi, chỉ có một số tính năng không cần thiết bị thiếu trong bảng băm của bạn vào thời điểm này

Biểu diễn Bảng băm trong Văn bản

Bit tiếp theo mà bạn sẽ thực hiện trong phần này sẽ làm cho bảng băm của bạn trông đẹp mắt khi được in trên đầu ra tiêu chuẩn. Biểu diễn văn bản của bảng băm của bạn sẽ trông giống như một ký tự Python

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
17

Nghĩa đen sử dụng dấu ngoặc nhọn, dấu phẩy và dấu hai chấm, trong khi các khóa và giá trị có biểu diễn tương ứng. Ví dụ: các chuỗi được đặt trong dấu nháy đơn. As you don’t know the exact order of the key-value pairs, you check if the string representation of your hash table conforms to one of the possible pair permutations

To make your class work with the built-in

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
06 function, you must implement the corresponding special method in
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
18

Bạn lặp lại các khóa và giá trị thông qua thuộc tính

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85 và sử dụng chuỗi f để định dạng các cặp riêng lẻ. Lưu ý cờ chuyển đổi
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
655 trong chuỗi mẫu, thực thi gọi
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
13 thay vì mặc định
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
06 trên các khóa và giá trị. Điều này đảm bảo biểu diễn rõ ràng hơn, khác nhau giữa các loại dữ liệu. Ví dụ: nó kết thúc các chuỗi trong dấu nháy đơn

Sự khác biệt giữa

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
06 và
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
13 là tinh tế hơn. Nói chung, cả hai đều dùng để chuyển đổi đối tượng thành chuỗi. Tuy nhiên, trong khi bạn có thể mong đợi
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
06 trả về văn bản thân thiện với con người, thì
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
13 thường trả về một đoạn mã Python hợp lệ mà bạn có thể đánh giá để tạo lại đối tượng ban đầu

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
19

Trong ví dụ này, biểu diễn chuỗi của phân số Python là

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
662, nhưng biểu diễn chuỗi chính tắc của cùng một đối tượng biểu thị lệnh gọi đến lớp
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
663

Bạn có thể đạt được hiệu quả tương tự trong lớp

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 của mình. Thật không may, trình khởi tạo lớp của bạn hiện không cho phép tạo các phiên bản mới từ các giá trị. You’ll address this by introducing a class method that’ll let you create
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 instances from Python dictionaries

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
20

Điều này hoạt động độc đáo với chuyển đổi trước đó của bạn, theo hướng ngược lại. Tuy nhiên, bạn sẽ cần giả định dung lượng đủ lớn cho bảng băm để chứa tất cả các cặp khóa-giá trị từ từ điển gốc. Một ước tính hợp lý dường như gấp mười lần số cặp. Bạn có thể mã hóa nó ngay bây giờ

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
21

You create a new hash table and set its capacity using an arbitrary factor. Sau đó, bạn chèn các cặp khóa-giá trị bằng cách sao chép chúng từ từ điển được truyền dưới dạng đối số cho phương thức. Bạn có thể cho phép ghi đè dung lượng mặc định nếu muốn, vì vậy hãy thêm một trường hợp thử nghiệm tương tự

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
22

Để làm cho dung lượng trở thành tùy chọn, bạn có thể tận dụng lợi thế của việc đánh giá ngắn mạch các biểu thức Boolean

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
23

Nếu

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
40 không được chỉ định, thì bạn sẽ quay lại hành vi mặc định, nhân độ dài của từ điển lên mười. Với điều này, cuối cùng bạn cũng có thể cung cấp một biểu diễn chuỗi chuẩn cho các phiên bản
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 của mình

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
24

Như trước đây, bạn kiểm tra tất cả các hoán vị thứ tự thuộc tính có thể có trong biểu diễn kết quả. Biểu diễn chuỗi chính tắc của bảng băm hầu như giống như với

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
06, nhưng nó cũng bao hàm ký tự chính tả trong lời gọi phương thức lớp
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
669 mới của bạn. Việc triển khai tương ứng ủy quyền cho phương thức đặc biệt
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
08 bằng cách gọi hàm
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
06 tích hợp

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
25

Lưu ý rằng bạn không mã hóa cứng tên lớp, để tránh các sự cố nếu bạn chọn đổi tên lớp của mình sau này

Nguyên mẫu bảng băm của bạn gần như đã sẵn sàng, vì bạn đã triển khai gần như tất cả các tính năng cốt lõi và không cần thiết từ danh sách được đề cập trong phần giới thiệu của phần này. Bạn vừa thêm khả năng tạo bảng băm từ Python

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67 và cung cấp các biểu diễn chuỗi cho các phiên bản của nó. Bit cuối cùng và cuối cùng là đảm bảo rằng các trường hợp bảng băm có thể được so sánh theo giá trị

Kiểm tra sự bằng nhau của các bảng băm

Các bảng băm giống như các bộ theo nghĩa là chúng không áp đặt bất kỳ thứ tự cụ thể nào cho nội dung của chúng. Trên thực tế, bảng băm và bộ có nhiều điểm chung hơn bạn nghĩ, vì cả hai đều được hỗ trợ bởi hàm băm. Đó là hàm băm làm cho các cặp khóa-giá trị trong bảng băm không có thứ tự. Tuy nhiên, hãy nhớ rằng bắt đầu từ Python 3. 6,

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67 thực sự giữ lại thứ tự chèn như một chi tiết triển khai

Hai bảng băm nên so sánh bằng nhau khi và chỉ khi chúng có cùng một bộ cặp khóa-giá trị. Tuy nhiên, Python mặc định so sánh danh tính đối tượng vì nó không biết cách diễn giải giá trị của các loại dữ liệu tùy chỉnh. Vì vậy, hai phiên bản của bảng băm của bạn sẽ luôn so sánh không bằng nhau ngay cả khi chúng chia sẻ cùng một bộ cặp khóa-giá trị

Để khắc phục điều này, bạn có thể triển khai toán tử kiểm tra đẳng thức [

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
66] bằng cách cung cấp phương thức đặc biệt
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
675 trong lớp
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 của mình. Ngoài ra, Python sẽ gọi phương thức này để bạn đánh giá toán tử không bằng [
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
677] trừ khi bạn cũng triển khai
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
678 một cách rõ ràng

Bạn muốn bảng băm bằng chính nó, bản sao của nó hoặc một phiên bản khác có cùng cặp khóa-giá trị bất kể thứ tự của chúng. Ngược lại, một bảng băm không được bằng một phiên bản có tập hợp các cặp khóa-giá trị khác hoặc một kiểu dữ liệu hoàn toàn khác

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
26

Bạn sử dụng

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
669, được giới thiệu trong tiểu mục trước, để nhanh chóng đưa vào bảng băm mới các giá trị. Bạn có thể tận dụng cùng một phương thức lớp để tạo một bản sao mới của một thể hiện bảng băm. Đây là mã đáp ứng các trường hợp thử nghiệm này

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
27

Phương thức đặc biệt

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
675 lấy một số đối tượng để so sánh làm đối số. Nếu đối tượng đó là phiên bản hiện tại của
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31, thì bạn trả về
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
27 vì cùng một danh tính hàm ý giá trị bình đẳng. Nếu không, bạn tiếp tục bằng cách so sánh các loại và tập hợp các cặp khóa-giá trị. Việc chuyển đổi thành bộ giúp làm cho thứ tự không còn phù hợp ngay cả khi bạn quyết định biến
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85 thành một danh sách có thứ tự khác trong tương lai

Nhân tiện, bản sao thu được không chỉ có các cặp khóa-giá trị giống nhau mà còn có cùng dung lượng với bảng băm nguồn. Đồng thời, hai bảng băm có dung lượng khác nhau nên vẫn so sánh ngang nhau

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
28

Hai thử nghiệm này sẽ hoạt động với triển khai

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 hiện tại của bạn, vì vậy bạn không cần phải viết mã thêm bất cứ điều gì

Nguyên mẫu bảng băm tùy chỉnh của bạn vẫn còn thiếu một số tính năng không cần thiết mà từ điển tích hợp sẵn cung cấp. Bạn có thể thử tự thêm chúng như một bài tập. Ví dụ: bạn có thể sao chép các phương thức khác từ từ điển Python, chẳng hạn như

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
685 hoặc
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
686. Ngoài ra, bạn có thể triển khai một trong các toán tử bitwise được hỗ trợ bởi
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67 kể từ Python 3. 9, cho phép hoạt động công đoàn

Tốt lắm. Đây là bộ thử nghiệm đã hoàn thành cho hướng dẫn và bảng băm của bạn đã vượt qua tất cả các bài kiểm tra đơn vị. Hãy tự thưởng cho mình một cái vỗ nhẹ xứng đáng vì đó là một công việc tuyệt vời. Hay là nó?

Giả sử bạn giảm dung lượng của bảng băm của mình xuống chỉ tính đến các cặp được chèn. Bảng băm sau đây phải chứa cả ba cặp khóa-giá trị được lưu trữ trong từ điển nguồn

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
29

Tuy nhiên, khi bạn tiết lộ các khóa và giá trị của bảng băm kết quả, đôi khi bạn sẽ thấy rằng có ít mục hơn. Để làm cho đoạn mã này có thể lặp lại, hãy chạy nó với tính năng băm ngẫu nhiên bị tắt bằng cách đặt biến môi trường

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
84 thành
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
689

More often than not, you’ll end up losing information even though there’s enough space available in the hash table. Đó là bởi vì hầu hết các hàm băm không hoàn hảo, gây ra các xung đột băm, mà bạn sẽ tìm hiểu cách giải quyết trong phần tiếp theo

Giải quyết xung đột mã băm

Trong phần này, bạn sẽ khám phá hai chiến lược phổ biến nhất để xử lý xung đột mã băm và bạn sẽ triển khai chúng trong lớp

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 của mình. Nếu bạn muốn làm theo các ví dụ bên dưới, thì đừng quên đặt biến
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
84 thành
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
689 để tắt tính năng ngẫu nhiên hóa hàm băm của Python

Đến bây giờ, bạn đã có một ý tưởng tốt về va chạm băm là gì. Đó là khi hai khóa tạo ra mã băm giống hệt nhau, dẫn đến các giá trị riêng biệt xung đột với nhau tại cùng một chỉ mục trong bảng băm. Ví dụ: trong một bảng băm có một trăm vị trí, các khóa

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
693 và
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
694 tình cờ chia sẻ cùng một chỉ mục khi ngẫu nhiên hóa hàm băm bị tắt

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
30

Hiện tại, bảng băm của bạn không thể phát hiện các nỗ lực lưu trữ các giá trị khác nhau trong cùng một vị trí

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
31

Cuối cùng, bạn không chỉ ghi đè lên cặp được xác định bởi khóa

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
693, mà tệ hơn nữa, việc truy xuất giá trị của khóa hiện không tồn tại này sẽ cho bạn câu trả lời không chính xác

Có ba chiến lược dành cho bạn để giải quyết xung đột hàm băm

Chiến lược Mô tả Băm hoàn hảo Chọn một hàm băm hoàn hảo để tránh xung đột băm ngay từ đầu. Mở Địa chỉ Trải rộng các giá trị bị xung đột theo cách có thể dự đoán được để cho phép bạn truy xuất chúng sau này. Đóng địa chỉGiữ các giá trị xung đột trong một cấu trúc dữ liệu riêng biệt để tìm kiếm thông qua

Mặc dù chỉ có thể băm hoàn hảo khi bạn biết trước tất cả các giá trị, nhưng hai phương pháp giải quyết xung đột băm còn lại thực tế hơn, vì vậy bạn sẽ xem xét kỹ hơn về chúng trong hướng dẫn này. Lưu ý rằng địa chỉ mở có thể được biểu diễn bằng một số thuật toán cụ thể, bao gồm

  • chim cu băm
  • băm đôi
  • nhảy lò cò băm
  • thăm dò tuyến tính
  • thăm dò bậc hai
  • Băm Robin Hood

Ngược lại, địa chỉ đóng được biết đến nhiều nhất với chuỗi riêng biệt. Ngoài ra, còn có hàm băm kết hợp, kết hợp các ý tưởng đằng sau cả địa chỉ mở và địa chỉ đóng thành một thuật toán

Để theo dõi quá trình phát triển dựa trên thử nghiệm, trước tiên bạn cần thiết kế một trường hợp thử nghiệm. Nhưng làm thế nào để bạn kiểm tra một va chạm băm? . Việc chọn hạt giống băm theo cách thủ công với biến môi trường

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
84 sẽ không thực tế và khiến các trường hợp thử nghiệm của bạn trở nên dễ vỡ

Cách tốt nhất để giải quyết vấn đề này là sử dụng thư viện mô phỏng, chẳng hạn như Python's

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
697

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
32

Bản vá tạm thời thay thế một đối tượng bằng một đối tượng khác. Ví dụ: bạn có thể thay thế hàm

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 tích hợp sẵn bằng một hàm giả luôn trả về cùng một giá trị mong đợi, giúp cho các thử nghiệm của bạn có thể lặp lại. Sự thay thế này chỉ có hiệu lực trong khi gọi hàm, sau đó
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 ban đầu được đưa trở lại

Bạn có thể áp dụng trình trang trí

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
900 cho toàn bộ chức năng thử nghiệm của mình hoặc giới hạn phạm vi của đối tượng giả bằng trình quản lý bối cảnh

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
33

Với trình quản lý bối cảnh, bạn có thể truy cập chức năng

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 tích hợp cũng như phiên bản mô phỏng của nó trong cùng một trường hợp thử nghiệm. Bạn thậm chí có thể có nhiều hương vị của chức năng giả định nếu bạn muốn. Tham số
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
902 cho phép bạn chỉ định một ngoại lệ để nâng cao hoặc một chuỗi các giá trị mà đối tượng giả định của bạn sẽ trả về trong các lần gọi liên tiếp

Trong phần còn lại của hướng dẫn này, bạn sẽ tiếp tục thêm nhiều tính năng hơn vào lớp

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 của mình mà không tuân thủ nghiêm ngặt quá trình phát triển dựa trên thử nghiệm. Các bài kiểm tra mới sẽ được bỏ qua cho ngắn gọn, trong khi sửa đổi lớp sẽ khiến một số bài kiểm tra hiện tại không thành công. Tuy nhiên, bạn sẽ tìm thấy một bộ thử nghiệm đang hoạt động trong các tài liệu đi kèm có sẵn để tải xuống

Tìm các phím bị va chạm thông qua thăm dò tuyến tính

Dừng lại một chút để hiểu lý thuyết đằng sau xung đột mã băm. Khi nói đến việc xử lý chúng, thăm dò tuyến tính là một trong những kỹ thuật lâu đời nhất, đơn giản nhất và hiệu quả nhất, đồng thời. Nó yêu cầu một vài bước bổ sung cho các thao tác chèn, tra cứu, xóa và cập nhật mà bạn sắp tìm hiểu

Hãy xem xét một bảng băm mẫu đại diện cho bảng thuật ngữ Python với các từ viết tắt phổ biến. Nó có tổng cộng 10 vị trí, nhưng 4 vị trí trong số đó đã được sử dụng bởi các cặp khóa-giá trị sau

IndexKeyValue01BDFLBenevolent Dictator For Life23REPLRead–Evaluate–Print Loop45678PEPPython Enhancement Proposal9WSGIWeb Server Gateway Interface

Bây giờ bạn muốn đưa một thuật ngữ khác vào bảng thuật ngữ của mình để xác định từ viết tắt MRO, viết tắt của thứ tự giải quyết phương pháp. You calculate the hash code of the key and truncate it with the modulo operator to get an index between zero and nine

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
34

Thay vì sử dụng toán tử modulo, bạn có thể cắt bớt mã băm bằng một bitmask thích hợp, đó là cách mà

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67 của Python hoạt động bên trong

Ghi chú. Để có mã băm nhất quán, hãy đặt biến môi trường

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
84 thành
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
689 để tắt ngẫu nhiên hóa hàm băm

Great. Có một vị trí trống trong bảng băm của bạn ở chỉ số năm, nơi bạn có thể chèn một cặp khóa-giá trị mới

IndexKeyValue01BDFLBNhà độc tài nhân từ vì cuộc sống23REPLĐọc–Đánh giá–In Vòng lặp45MThứ tự giải quyết phương thức678PEPPĐề xuất cải tiến Python9WSGIGiao diện cổng máy chủ web

Càng xa càng tốt. Bảng băm của bạn vẫn còn 50 phần trăm dung lượng trống, vì vậy bạn tiếp tục thêm nhiều thuật ngữ hơn cho đến khi thử chèn từ viết tắt EAFP. Hóa ra mã băm của EAFP cắt ngắn thành một, là chỉ số của một vị trí bị chiếm giữ bởi thuật ngữ BDFL

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
35

Khả năng băm hai khóa khác nhau thành mã băm va chạm là tương đối nhỏ. Tuy nhiên, việc chiếu các mã băm đó lên một phạm vi nhỏ các chỉ số mảng lại là một câu chuyện khác. With linear probing, you can detect and mitigate such collisions by storing the collided key-value pairs next to each other

IndexKeyValue01BDFLBNhà độc tài nhân từ suốt đời2EAFPDễ xin phép hơn là xin phép3REPLĐọc–Đánh giá–In Vòng lặp45MThứ tự giải quyết phương thức678PEPPĐề xuất cải tiến Python9WSGIGiao diện cổng máy chủ web

Mặc dù các khóa BDFL và EAFP cho cùng một chỉ số bằng một, nhưng chỉ cặp khóa-giá trị được chèn đầu tiên mới nhận được nó. Cặp nào về nhì sẽ được đặt cạnh chỉ số đã chiếm. Do đó, thăm dò tuyến tính làm cho bảng băm nhạy cảm với thứ tự chèn

Note. Khi bạn sử dụng thăm dò tuyến tính hoặc các phương pháp giải quyết va chạm hàm băm khác, thì bạn không thể chỉ dựa vào mã băm để tìm vị trí tương ứng. Bạn cũng cần phải so sánh các phím

Cân nhắc thêm một từ viết tắt khác, ABC, cho các lớp cơ sở trừu tượng, có mã băm cắt ngắn thành chỉ số tám. Lần này bạn không thể chèn nó vào vị trí sau vì nó đã bị WSGI chiếm mất rồi. Trong các trường hợp bình thường, bạn sẽ tiếp tục tìm kiếm một vị trí miễn phí ở phía dưới, nhưng vì bạn đã đạt đến chỉ mục cuối cùng, bạn phải bao quanh và chèn từ viết tắt mới vào chỉ mục 0

IndexKeyValue0ABCLớp cơ sở trừu tượng1BDFLLBNhà độc tài nhân từ suốt đời2EAFPPEDễ xin phép tha thứ hơn là cho phép3REPLĐọc–Đánh giá–In Vòng lặp45MThứ tự giải quyết phương thức678PEPPĐề xuất cải tiến Python9WSGIGiao diện cổng máy chủ web

Để tìm kiếm các cặp khóa-giá trị được nhồi vào bảng băm như thế này, hãy làm theo một thuật toán tương tự. Bắt đầu bằng cách nhìn vào chỉ số dự kiến ​​đầu tiên. Ví dụ: để tìm giá trị được liên kết với khóa ABC, hãy tính mã băm của nó và ánh xạ nó tới một chỉ mục

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
36

Có một cặp khóa-giá trị được lưu trữ ở chỉ số tám, nhưng nó có một khóa khác bằng PEP, vì vậy bạn bỏ qua nó bằng cách tăng chỉ mục. Một lần nữa, vị trí đó bị chiếm giữ bởi một thuật ngữ không liên quan, WSGI, vì vậy bạn thoát ra và quay vòng để cuối cùng tìm thấy cặp của mình có khóa phù hợp ở chỉ số 0. Đó là câu trả lời của bạn

Nói chung, có ba điều kiện dừng có thể xảy ra đối với hoạt động tìm kiếm

  1. Bạn đã tìm thấy một khóa phù hợp
  2. Bạn đã sử dụng hết tất cả các vị trí mà không tìm thấy khóa phù hợp
  3. Bạn đã tìm thấy một khe trống, điều này cũng cho thấy một khóa bị thiếu

Điểm cuối cùng làm cho việc xóa một cặp khóa-giá trị hiện tại trở nên phức tạp hơn. Nếu bạn vừa xóa một mục khỏi bảng băm, thì bạn sẽ giới thiệu một vị trí trống, điều này sẽ dừng tra cứu ở đó bất kể có bất kỳ va chạm nào trước đó. Để làm cho các cặp khóa-giá trị bị xung đột có thể truy cập lại được, bạn phải thử lại chúng hoặc sử dụng chiến lược xóa chậm

Cái sau ít khó thực hiện hơn nhưng có thêm chi phí tăng số lượng các bước tra cứu cần thiết. Về cơ bản, thay vì xóa một cặp khóa-giá trị, bạn thay thế nó bằng một giá trị trọng điểm, được mô tả bằng dấu chữ thập đỏ [❌] bên dưới, giúp tìm kiếm các mục nhập đã xung đột trước đó có thể. Giả sử bạn muốn xóa các điều khoản BDFL và PEP

IndexKeyValue0ABCLớp cơ sở trừu tượng1❌2EAFPEDễ dàng yêu cầu sự tha thứ hơn so với quyền3REPLRead–Evaluate–Print Loop45MThứ tự giải quyết phương thức678❌9WSGIGiao diện cổng máy chủ web

Bạn đã thay thế các cặp khóa-giá trị tương ứng bằng hai phiên bản của giá trị trọng điểm. Sau đó, khi bạn tìm khóa ABC, chẳng hạn, bạn bật ra khỏi chốt ở chỉ số tám, sau đó tiếp tục đến WSGI và cuối cùng đến chỉ mục 0 với khóa phù hợp. Nếu không có một trong các lính canh, bạn sẽ dừng việc tìm kiếm sớm hơn nhiều, kết luận sai lầm rằng không có chìa khóa nào như vậy

Ghi chú. Dung lượng của bảng băm của bạn không bị ảnh hưởng vì bạn có thể tự do ghi đè lên các lính canh khi chèn các cặp khóa-giá trị mới. Mặt khác, nếu bạn điền vào bảng băm và xóa hầu hết các phần tử của nó, thì thực tế bạn sẽ kết thúc với thuật toán tìm kiếm tuyến tính

Cho đến giờ, bạn đã học về chèn, xóa và tra cứu. Tuy nhiên, có một lưu ý về việc cập nhật giá trị của một mục hiện có trong bảng băm với tính năng thăm dò tuyến tính. Khi tìm kiếm một cặp để cập nhật, bạn chỉ nên bỏ qua vị trí nếu vị trí đó bị chiếm bởi một cặp khác có khóa khác hoặc nếu vị trí đó chứa giá trị trọng điểm. Ngược lại, nếu khe trống hoặc có khóa phù hợp, thì bạn nên đặt giá trị mới

Trong tiểu mục tiếp theo, bạn sẽ sửa đổi lớp

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 của mình để sử dụng thăm dò tuyến tính để giải quyết va chạm hàm băm

Sử dụng thăm dò tuyến tính trong lớp
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31

Sau một thời gian ngắn đi vào lý thuyết thăm dò tuyến tính, bây giờ bạn quay lại viết mã. Bởi vì thăm dò tuyến tính sẽ được sử dụng trong cả bốn thao tác CRUD cơ bản trong bảng băm, nên sẽ giúp viết một phương thức trợ giúp trong lớp của bạn để gói gọn logic truy cập các vị trí của bảng băm

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
37

Được cung cấp một khóa, bạn bắt đầu bằng cách sử dụng mã băm tương ứng để tìm chỉ mục dự kiến ​​của nó trong bảng băm. Sau đó, bạn lặp qua tất cả các vị trí có sẵn trong bảng băm, bắt đầu từ chỉ mục được tính toán. Ở mỗi bước, bạn trả lại chỉ mục hiện tại và cặp được liên kết, chỉ mục này có thể trống hoặc được đánh dấu là đã xóa. Sau đó, bạn tăng chỉ số, bao quanh gốc tọa độ nếu cần

Tiếp theo, bạn có thể viết lại phương thức

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
79 của mình. Đừng quên nhu cầu về một giá trị trọng điểm mới. Giá trị này sẽ cho phép bạn phân biệt giữa các vị trí chưa bao giờ được sử dụng và những vị trí đã từng xung đột trước đó nhưng hiện đã bị xóa

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
38

Nếu vị trí trống hoặc chứa một cặp có khóa phù hợp, thì bạn chỉ định lại một cặp khóa-giá trị mới tại chỉ mục hiện tại và dừng thăm dò tuyến tính. Mặt khác, nếu một cặp khác chiếm vị trí đó và có một khóa khác hoặc vị trí được đánh dấu là đã xóa, thì bạn sẽ tiếp tục cho đến khi bạn tìm thấy một vị trí trống hoặc sử dụng hết tất cả các vị trí có sẵn. Nếu bạn hết chỗ có sẵn, bạn đưa ra một ngoại lệ

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
910 để cho biết dung lượng không đủ của bảng băm

Ghi chú. Thật trùng hợp, phương pháp

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
79 cũng bao gồm việc cập nhật giá trị của một cặp hiện có. Bởi vì các cặp được đại diện bởi các bộ bất biến, bạn thay thế toàn bộ cặp bằng khóa phù hợp chứ không chỉ thành phần giá trị của nó

Nhận và xóa các cặp khóa-giá trị khỏi bảng băm bằng thăm dò tuyến tính hoạt động gần như giống nhau

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
39

Sự khác biệt duy nhất là trong các dòng được đánh dấu. Để xóa một cặp, bạn phải biết vị trí của nó trong bảng băm để thay thế bằng giá trị trọng điểm. Mặt khác, bạn chỉ quan tâm đến giá trị tương ứng khi tìm kiếm theo khóa. Nếu việc sao chép mã này làm phiền bạn, thì bạn có thể thử cấu trúc lại nó như một bài tập. Tuy nhiên, việc nó được viết một cách rõ ràng sẽ giúp làm rõ quan điểm

Ghi chú. Đây là một triển khai trong sách giáo khoa của bảng băm, thăm dò các phần tử bằng cách so sánh các khóa bằng cách sử dụng toán tử kiểm tra đẳng thức [

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
66]. Tuy nhiên, đây là một hoạt động có khả năng tốn kém, điều mà việc triển khai thực tế tránh được bằng cách lưu trữ mã băm, cùng với các khóa và giá trị, theo bộ ba thay vì theo cặp. Mặt khác, mã băm rẻ để so sánh

Bạn có thể tận dụng hợp đồng hash-equal để tăng tốc mọi thứ. Nếu hai mã băm khác nhau, thì chúng được đảm bảo xuất phát từ các khóa khác nhau, do đó, không cần phải thực hiện kiểm tra tính bằng nhau tốn kém ngay từ đầu. Thủ thuật này làm giảm đáng kể số lượng so sánh chính

Có một chi tiết quan trọng khác cần lưu ý. Các vị trí của bảng băm không còn có thể ở một trong hai trạng thái - nghĩa là trống hoặc bị chiếm dụng. Chèn giá trị sentinel vào bảng băm để đánh dấu một vị trí là đã xóa sẽ làm rối tung các thuộc tính

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85,
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
628 và
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
44 của bảng băm và báo cáo độ dài không chính xác. Để khắc phục điều này, bạn phải lọc ra cả hai giá trị
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87 và
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
917 khi trả về các cặp khóa-giá trị

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
40

Với bản cập nhật nhỏ đó, bảng băm của bạn giờ đây có thể xử lý các xung đột băm bằng cách trải rộng các cặp bị va chạm và tìm kiếm chúng theo kiểu tuyến tính

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
41

Mặc dù có cùng mã băm bằng 24, nhưng cả hai khóa xung đột,

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
693 và
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
694, đều xuất hiện cạnh nhau trong danh sách
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
640. Lưu ý rằng chúng được liệt kê theo cùng thứ tự mà bạn đã thêm chúng vào bảng băm. Hãy thử hoán đổi thứ tự chèn hoặc thay đổi dung lượng của bảng băm và quan sát điều đó ảnh hưởng đến các vị trí như thế nào

Hiện tại, dung lượng của bảng băm vẫn cố định. Trước khi triển khai thăm dò tuyến tính, bạn vẫn không biết và tiếp tục ghi đè lên các giá trị bị xung đột. Giờ đây, bạn có thể phát hiện khi không còn chỗ trống trong bảng băm của mình và đưa ra một ngoại lệ tương ứng. Tuy nhiên, sẽ không tốt hơn nếu để bảng băm tự động thay đổi quy mô dung lượng của nó khi cần thiết?

Để bảng băm tự động thay đổi kích thước

Có hai chiến lược khác nhau khi thay đổi kích thước bảng băm. Bạn có thể đợi cho đến giây phút cuối cùng và chỉ thay đổi kích thước bảng băm khi nó đầy hoặc bạn có thể làm như vậy một cách háo hức sau khi đạt đến một ngưỡng nhất định. Cả hai cách đều có ưu và nhược điểm. Chiến lược lười biếng được cho là dễ thực hiện hơn, vì vậy bạn sẽ xem xét kỹ chiến lược này trước. Tuy nhiên, nó có thể dẫn đến nhiều va chạm hơn và hiệu suất kém hơn

Lần duy nhất bạn hoàn toàn phải tăng số lượng vị trí trong bảng băm của mình là khi việc chèn một cặp mới không thành công, làm tăng ngoại lệ

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
910. Hãy tiếp tục và thay thế câu lệnh
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
922 bằng một lệnh gọi đến một phương thức trợ giúp khác mà bạn sẽ tạo, tiếp theo là một lệnh gọi đệ quy tới
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
79 thông qua cú pháp dấu ngoặc vuông

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
42

When you determine that all slots are occupied, by either a legitimate pair or the sentinel value, you must allocate more memory, copy the existing pairs, and try inserting that new key-value pair again

Note. Putting your old key-value pairs into a bigger hash table will make them hash to entirely different slots. Rehashing takes advantage of the extra slots that you just created, reducing the number of collisions in the new hash table. Moreover, it saves space by reclaiming slots that used to be marked as deleted with the sentinel value. You don’t need to worry about past collisions, because the key-value pairs will find new slots anyway

Now, implement resizing and rehashing in the following way

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
43

Create a local copy of the hash table. Because it’s difficult to predict how many more slots you may need, take a wild guess and double the capacity size. Then, iterate over your existing set of key-value pairs and insert them into the copy. Finally, reassign the

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
640 attribute in your instance so that it points to the enlarged list of slots

Your hash table can now dynamically increase its size when needed, so give it a spin. Create an empty hash table with a capacity of one and try inserting some key-value pairs into it

  • các cửa sổ
  • Linux + macOS

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
44

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
45

In successfully inserting twenty key-value pairs, you never got any errors. With this rough depiction, you can clearly see the doubling of slots, which takes place when the hash table becomes full and needs more slots

Thanks to the automatic resizing that you’ve just implemented, you can assume a default capacity for new hash tables. This way, creating an instance of the

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 class will no longer require specifying the initial capacity, although doing so could improve performance. A common choice for the initial capacity is a small power of two, such as eight

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
46

That makes it possible to create hash tables with a call to the parameterless initializer

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
926. Note that you can also update your class method
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
927 to use the dictionary’s length as the initial capacity. Previously, you multiplied the dictionary’s length by an arbitrary factor, which was necessary to make your tests pass, due to unhandled hash collisions

As stated before, there’s one problem with the lazy resizing strategy, and that’s the increased likelihood of collisions. You’re going to address that next

Calculate the Load Factor

Waiting until your hash table becomes saturated isn’t optimal. You can try an eager strategy to resize the hash table before reaching its total capacity, keeping the collisions from happening in the first place. How do you decide on the best moment to resize and rehash? Use the load factor

The load factor is the ratio of the number of currently occupied slots, including the deleted ones, to all slots in the hash table. The higher the load factor, the bigger the chance of a hash collision, which results in worse lookup performance. Therefore, you want the load factor to remain relatively small at all times. Bumping up the hash table’s size is due whenever the load factor reaches a certain threshold

The choice of a particular threshold is a classic example of the space–time trade-off in computer science. More frequent hash table resizing is cheaper and leads to better performance at the cost of more memory consumption. Conversely, waiting longer can save you some memory, but the key lookups will be slower. The chart below depicts the relationship between the amount of allocated memory and the average number of collisions

The data behind this chart measures the average number of collisions caused by inserting one hundred elements into an empty hash table with an initial capacity of one. The measurement was repeated many times for various load factor thresholds, at which the hash table resized itself in discrete jumps by doubling its capacity

The intersection of both plots, which appears at around 0. 75, indicates the threshold’s sweet spot, with the lowest amount of memory and number of collisions. Using a higher load factor threshold doesn’t provide significant memory savings, but it increases the number of collisions exponentially. A smaller threshold improves the performance but for a high price of mostly wasted memory. Remember that all you really need is one hundred slots

You can experiment with different load factor thresholds, but resizing the hash table when 60 percent of its slots are taken might be a good starting point. Here’s how you can implement the load factor calculation in your

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 class

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
47

You start by filtering slots that are truthy, which would be anything but

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87, and then take the ratio according to the load factor’s definition. Lưu ý rằng nếu bạn quyết định sử dụng biểu thức hiểu, thì đó phải là biểu thức hiểu danh sách để đếm tất cả các lần xuất hiện giá trị trọng điểm. In this case, using a set comprehension would filter out the repeated markers of deleted pairs, leaving only one instance and resulting in a wrong load factor

Next, modify your class to accept an optional load factor threshold and use it to eagerly resize and rehash the slots

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
48

The load factor threshold defaults to 0. 6, which means 60 percent of all slots are occupied. You use a weak inequality [

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
930] instead of a strict one [
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
931] to account for the load factor threshold at its maximum value, which can never be greater than one. If the load factor equals one, then you must also resize the hash table before inserting another key-value pair

Brilliant. Your hash table has just become a bit faster. That concludes the open addressing example in this tutorial. Next up, you’re going to resolve hash collisions using one of the most popular closed addressing techniques

Isolate Collided Keys With Separate Chaining

Separate chaining is another extremely popular hash collision resolution method, perhaps even more widespread than linear probing. The idea is to group similar items by a common feature into so-called buckets to narrow down the search space. For example, you could imagine harvesting fruits and collecting them into color-coded baskets

Fruits Grouped by Color in Each Basket

Each basket contains fruits of roughly the same color. So, when you’re craving an apple, for example, you only need to search through the basket labeled with red. In an ideal world, each basket should contain no more than one element, making the search instantaneous. You can think of the labels as hash codes and the fruits with the same color as the collided key-value pairs

A hash table based on separate chaining is a list of references to buckets, typically implemented as linked lists that form chains of elements

Chains of Collided Key-Value Pairs

Each linked list contains key-value pairs whose keys share the same hash code due to a collision. When looking for a value by key, you need to locate the right bucket first, then traverse it using linear search to find the matching key, and finally return the corresponding value. Linear search just means going through each item in the bucket, one by one, until you find the right key

Note. Linked lists’ elements have a small memory overhead because every node contains a reference to the next element. At the same time, such a memory layout makes appending and removing elements very quick compared to a regular array

To adapt your

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 class to use separate chaining, start by removing the
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
933 method and replacing slots with buckets. Now, instead of having a
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87 value or a pair at each index, you’ll make each index hold a bucket that might be empty or not. Each bucket will be a linked list

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
49

Instead of implementing a linked list from scratch, you may take advantage of Python’s double-ended queue, or deque, available in the

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
935 module, which uses a doubly-linked list under the hood. It lets you append and remove elements more efficiently than a plain list

Don’t forget to update the

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85,
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
46, and
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
938 properties so that they refer to buckets instead of slots

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
50

Bạn không còn cần giá trị sentinel để đánh dấu các phần tử là đã xóa, vì vậy hãy tiếp tục và xóa hằng số

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
917. Điều này làm cho các cặp khóa-giá trị và định nghĩa của hệ số tải trở nên đơn giản hơn. Dung lượng đồng nghĩa với số lượng nhóm vì bạn muốn giữ tối đa một cặp khóa-giá trị trong mỗi nhóm, giảm thiểu số lần xung đột mã băm

Ghi chú. Hệ số tải được xác định như thế này có thể lớn hơn một khi số lượng cặp khóa-giá trị được lưu trữ trong bảng băm vượt quá số lượng nhóm

Nhân tiện, việc cho phép quá nhiều va chạm sẽ biến bảng băm của bạn thành một danh sách phẳng với độ phức tạp thời gian tuyến tính, làm giảm đáng kể hiệu suất của nó. Những kẻ tấn công có thể lợi dụng thực tế này bằng cách tạo ra càng nhiều va chạm càng tốt

With separate chaining, all basic hash table operations boil down to finding the right bucket and searching through it, which makes the corresponding methods look similar

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
51

Các phiên bản Deque đảm nhiệm việc cập nhật các tham chiếu nội bộ của chúng khi bạn xóa một mục theo chỉ mục. Nếu bạn đã sử dụng danh sách được liên kết tùy chỉnh, thì bạn phải viết lại thủ công các cặp khóa-giá trị bị xung đột sau mỗi lần sửa đổi. Như trước đây, việc cập nhật cặp khóa-giá trị hiện có yêu cầu thay thế cặp khóa cũ bằng cặp khóa hoàn toàn mới vì cặp khóa-giá trị là bất biến

Nếu bạn muốn tránh lặp lại chính mình, thì hãy thử tái cấu trúc ba phương pháp trên bằng cách sử dụng khớp mẫu cấu trúc, được giới thiệu trong Python 3. 10. Bạn sẽ tìm thấy một giải pháp khả thi trong các tài liệu đi kèm

Được rồi, bạn đã biết cách đối phó với xung đột mã băm và giờ bạn đã sẵn sàng để tiếp tục. Tiếp theo, bạn sẽ làm cho bảng băm của mình trả về các khóa và giá trị theo thứ tự chèn của chúng

Giữ lại thứ tự chèn trong bảng băm

Vì cấu trúc dữ liệu bảng băm cổ điển sử dụng hàm băm để phân bổ các khóa một cách đồng đều và đôi khi giả ngẫu nhiên nên nó không thể đảm bảo thứ tự của chúng. Theo nguyên tắc thông thường, bạn đừng bao giờ cho rằng các phần tử của bảng băm sẽ xuất hiện theo một thứ tự nhất quán khi bạn yêu cầu chúng. Nhưng đôi khi nó có thể hữu ích hoặc thậm chí cần thiết để áp đặt một thứ tự cụ thể cho các yếu tố của bạn

Cho đến Python 3. 6, the only way to enforce order on dictionary elements was to use the

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
940 wrapper from the standard library. Sau đó, kiểu dữ liệu
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67 tích hợp bắt đầu duy trì thứ tự chèn của các cặp khóa-giá trị. Regardless, it may still be wise to assume a lack of element order to make your code compatible with older or alternative Python versions

Làm cách nào bạn có thể sao chép cách bảo quản thứ tự chèn tương tự trong lớp

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
31 tùy chỉnh của mình? . Start by declaring another internal field in your class

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
52

It’s an empty list of keys that’ll grow and shrink as you modify the hash table’s contents

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
53

You remove the key when there’s no longer an associated key-value pair in the hash table. On the other hand, you only add a key when you insert a brand-new key-value pair into the hash table for the first time. You don’t want to insert a key when you do an update, because that would result in multiple copies of the same key

Next, you can change the three properties

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
628,
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
44, and
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
85 so that they follow the same order of the inserted keys

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
54

Make sure to return a copy of your keys to avoid leaking the internal implementation. Also, note that all of these three properties now return lists instead of sets because you want them to retain the right order. In turn, this lets you zip keys and values to make pairs

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
55

Keys and values are always returned in the same order so that, for example, the first key and the first value map to each other. This means that you can zip them like in the example above

Now you know how to keep the insertion order of the key-value pairs in your hash table. On the other hand, if you want to sort them by more advanced criteria, then you can follow the same techniques as with sorting a built-in dictionary. There’s no additional code to write at this point

Use Hashable Keys

Your hash table is complete and fully functional now. It can map arbitrary keys to values using the built-in

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 function. It can detect and resolve hash code collisions and even retain the insertion order of the key-value pairs. You could theoretically use it over Python
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
67 if you wanted to, without noticing much difference apart from the poor performance and occasionally more verbose syntax

Note. As mentioned before, you should rarely need to implement a data structure such as a hash table yourself. Python comes with many useful collections that have unparalleled performance and are tested in the field by countless developers. For specialized data structures, you should check PyPI for third-party libraries before attempting to make one of your own. You’ll save yourself a lot of time and significantly reduce the risk of bugs

Until now, you’ve taken it for granted that most built-in types in Python can work as hash table keys. However, to use any hash table implementation in practice, you’ll have to restrict keys to only hashable types and understand the implications of doing so. That’ll be especially helpful when you decide to bring custom data types into the equation

Hashability vs Immutability

You learned earlier that some data types, including most primitive data types in Python, are hashable, while others aren’t. The primary trait of hashability is the ability to calculate the hash code of a given object

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
56

For example, instances of the

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
948 data type in Python are hashable, while ordinary sets don’t implement hashing at all. Hashability directly impacts whether objects of certain types can become dictionary keys or set members, as both of these data structures use the
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 function internally

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
57

While an instance of

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
948 is hashable, the corresponding set with precisely the same values is not. Be aware that you can still use an unhashable data type for the dictionary values. It’s the dictionary keys that must be able to calculate their corresponding hash codes

Note. When you insert an unhashable object into a hashable container, such as a

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
948, then that container also becomes unhashable

Hashability is closely related to mutability, or the ability to change the internal state of an object during its lifetime. The relationship between the two is a bit like changing an address. When you move to another place, you’re still the same person, but your old friends might have a hard time trying to find you

Các loại không thể sửa đổi trong Python, chẳng hạn như danh sách, bộ hoặc từ điển, là các vùng chứa có thể thay đổi, vì bạn có thể sửa đổi giá trị của chúng bằng cách thêm hoặc xóa các phần tử. On the other hand, most built-in hashable types in Python are immutable. Does this mean mutable types can’t be hashable?

The correct answer is they can be both mutable and hashable, but they rarely should be. Mutating a key would result in changing its memory address within a hash table. Consider this custom class as an example

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
58

This class represents a person with a name. Python provides a default implementation for the special method

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
952 in your classes, which merely uses the object’s identity to derive its hash code

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
59

Each individual

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
89 instance has a unique hash code even when it’s logically equal to other instances. To make the object’s value determine its hash code, you can override the default implementation of
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
952 like so

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
60

You call

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 on the
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
956 attribute so that instances of the
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
89 class with equal names always have the same hash code. This is convenient for looking them up in a dictionary, for example

Note. You can explicitly mark your class as unhashable by setting its

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
958 attribute equal to
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
87

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
61

This will prevent

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 from working on instances of your class

Another trait of hashable types is the ability to compare their instances by value. Recall that a hash table compares keys with the equality test operator [

>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
66], so you must implement another special method,
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
675, in your class to allow for that

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
62

This code fragment should look familiar if you went through the equality test of hash tables before. In a nutshell, you check if the other object is the exact same instance, an instance of another type, or another instance of the same type and equal value to the

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
956 attribute

Note. Coding special methods such as

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
675 and
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
952 can be repetitive, tedious, and error-prone. If you’re on Python 3. 7 or above, then you can achieve the same effect more compactly by using data classes

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
63

While a data class generates

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
675 based on your class attributes, you must set the
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
967 option to enable the correct
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
952 method generation

Having implemented

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
675 and
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
952, you can use the
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
89 class instances as dictionary keys

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
64

Perfect. It doesn’t matter if you find an employee by the

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
972 reference that you created earlier or a brand-new
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
973 instance. Unfortunately, things get complicated when Bob suddenly decides to change his name and go by Bobby instead

>>>

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
65

You no longer have a way of retrieving the corresponding value even though you still use the original key object that you inserted before. What’s more surprising, though, is that you can’t access the value through a new key object with the updated person’s name or with the old one. Can you tell why?

The hash code of the original key determined which bucket the associated value got stored in. Mutating the state of your key made its hash code indicate a completely different bucket or slot, which doesn’t contain the expected value. But using a key with the old name doesn’t help either. While it points to the right bucket, the stored key has mutated, making equality comparison between

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
974 and
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
975 evaluate to
>>> text[0]  # The first element
'A'

>>> text[len[text] // 2]  # The middle element
'A'

>>> text[-1]  # The last element, same as text[len[text] - 1]
'Z'
70 rather than matching

Therefore, hash codes must be immutable for the hashing to work as expected. Since hash codes are typically derived from an object’s attributes, their state should be fixed and never change over time. In practice, this also means that objects intended as hash table keys should be immutable themselves

To sum up, a hashable data type has the following traits

  1. Has a
    >>> import string
    >>> text = string.ascii_uppercase * 100_000_000
    
    >>> text[:50]  # Show the first 50 characters
    'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'
    
    >>> len[text]
    2600000000
    
    952 method to calculate the instance’s hash code
  2. Has an
    >>> import string
    >>> text = string.ascii_uppercase * 100_000_000
    
    >>> text[:50]  # Show the first 50 characters
    'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'
    
    >>> len[text]
    2600000000
    
    675 method to compare instances by value
  3. Has immutable hash codes, which don’t change during instances’ lifetimes
  4. Conforms to the hash-equal contract

The fourth and final trait of hashable types is that they must comply with the hash-equal contract, which you’ll learn more about in the following subsection. Nói tóm lại, các đối tượng có giá trị bằng nhau phải có mã băm giống hệt nhau

The Hash-Equal Contract

Để tránh các sự cố khi sử dụng các lớp tùy chỉnh làm khóa bảng băm, chúng nên tuân thủ hợp đồng băm bằng. If there’s one thing to remember about that contract, it’s that when you implement

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
675, you should always implement a corresponding
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
952. Lần duy nhất bạn không phải triển khai cả hai phương thức là khi bạn sử dụng trình bao bọc chẳng hạn như lớp dữ liệu hoặc bộ có tên bất biến đã thực hiện việc này cho bạn

Also, not implementing both methods can be okay as long as you’re absolutely sure that you won’t ever use objects of your data type as dictionary keys or set members. But can you be so sure?

Note. If you can’t use a data class or a named tuple, and you’d like to manually compare and hash more than one field in a class, then wrap them in a tuple

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
66

It makes sense to define a private property to return that tuple if there are relatively many fields in your class

While you can implement both methods however you like, they must satisfy the hash-equal contract, which states that two equal objects must hash to equal hash codes. This makes it possible to find the right bucket based on a provided key. However, the reverse isn’t true, because collisions may occasionally cause the same hash code to be shared by unequal values. You can express this more formally by using these two implications

  1. >>> import string
    >>> text = string.ascii_uppercase * 100_000_000
    
    >>> text[:50]  # Show the first 50 characters
    'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'
    
    >>> len[text]
    2600000000
    
    981 ⇒
    >>> import string
    >>> text = string.ascii_uppercase * 100_000_000
    
    >>> text[:50]  # Show the first 50 characters
    'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'
    
    >>> len[text]
    2600000000
    
    982
  2. >>> import string
    >>> text = string.ascii_uppercase * 100_000_000
    
    >>> text[:50]  # Show the first 50 characters
    'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'
    
    >>> len[text]
    2600000000
    
    982 ⇏
    >>> import string
    >>> text = string.ascii_uppercase * 100_000_000
    
    >>> text[:50]  # Show the first 50 characters
    'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'
    
    >>> len[text]
    2600000000
    
    981

The hash-equal contract is a one-way contract. If two keys are logically equal, then their hash codes must also be equal. Mặt khác, nếu hai khóa chia sẻ cùng một mã băm, thì có khả năng chúng là cùng một khóa, nhưng cũng có thể chúng là các khóa khác nhau. You need to compare them to be sure if they really match. By the law of contraposition, you can derive another implication from the first one

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
985 ⇒
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
986

If you know that two keys have different hash codes, then there’s no point in comparing them. That can help improve performance, as the equality test tends to be costly

Some IDEs offer to automatically generate the

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
952 and
>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
675 methods for you, but you need to remember to regenerate them every time you modify your class attributes. Therefore, stick to Python’s data classes or named tuples whenever you can to guarantee the proper implementation of hashable types

Conclusion

At this point, you can implement a hash table from scratch in Python, using different strategies to resolve hash collisions. You know how to make your hash table resize and rehash dynamically to accommodate more data and how to preserve the insertion order of key-value pairs. Along the way, you practiced test-driven development [TDD] by adding features to your hash table step by step

Other than that, you have a deep understanding of Python’s built-in

>>> import string
>>> text = string.ascii_uppercase * 100_000_000

>>> text[:50]  # Show the first 50 characters
'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'

>>> len[text]
2600000000
68 function and can create one of your own. You understand the hash-equal contract, the difference between hashable and unhashable data types, and their relationship with immutable types. You know how to create custom hashable classes using various tools in Python

In this tutorial, you learned

  • How a hash table differs from a dictionary
  • How you can implement a hash table from scratch in Python
  • How you can deal with hash collisions and other challenges
  • What the desired properties of a hash function are
  • How Python’s
    >>> import string
    >>> text = string.ascii_uppercase * 100_000_000
    
    >>> text[:50]  # Show the first 50 characters
    'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX'
    
    >>> len[text]
    2600000000
    
    68 works behind the scenes

With this knowledge, you’re prepared to answer most questions that you might get on a job interview related to the hash table data structure

You can download the complete source code and the intermediate steps used throughout this tutorial by clicking the link below

Source Code. Click here to download the source code that you’ll use to build a hash table in Python

Mark as Completed

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team

Send Me Python Tricks »

About Bartosz Zaczyński

Bartosz is a bootcamp instructor, author, and polyglot programmer in love with Python. He helps his students get into software engineering by sharing over a decade of commercial experience in the IT industry

» More about Bartosz

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are

Aldren

Geir Arne

Ian

Kate

Leodanis

Master Real-World Python Skills With Unlimited Access to Real Python

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas

Level Up Your Python Skills »

Bạn nghĩ sao?

Rate this article

Tweet Share Share Email

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know

Commenting Tips. Những nhận xét hữu ích nhất là những nhận xét được viết với mục đích học hỏi hoặc giúp đỡ các sinh viên khác. Nhận các mẹo để đặt câu hỏi hay và nhận câu trả lời cho các câu hỏi phổ biến trong cổng thông tin hỗ trợ của chúng tôi

What is Python dictionary hash function?

Python hash[] function is a built-in function and returns the hash value of an object if it has one . The hash value is an integer which is used to quickly compare dictionary keys while looking at a dictionary.

Can you hash a dictionary in Python?

On the other hand, the main use cases of the Python hash function is to compare dictionary keys during a lookup. Anything that is hashable can be used as a key in a dictionary , for example {[1,2]. "hi there"} . This situation sets us up for a simple MD5 based hashing of dictionaries.

Is Python dictionary a Hashmap or Hashtable?

Yes, it is a hash mapping or hash table . You can read a description of python's dict implementation, as written by Tim Peters, here. You can read more about hash tables or check how it has been implemented in python and why it is implemented that way.

Are Python dictionary keys hashed?

Trong Python, các kiểu dữ liệu Từ điển thể hiện việc triển khai các bảng băm. The Keys in the dictionary satisfy the following requirements. The keys of the dictionary are hashable i. e. the are generated by hashing function which generates unique result for each unique value supplied to the hash function.

Chủ Đề