Là chuỗi hàng đợi ưu tiên python

Hàng đợi là xương sống của nhiều thuật toán được tìm thấy trong trò chơi, trí tuệ nhân tạo, định vị vệ tinh và lập lịch tác vụ. Chúng là một trong những loại dữ liệu trừu tượng hàng đầu mà sinh viên khoa học máy tính học sớm trong giáo dục của họ. Đồng thời, các kỹ sư phần mềm thường tận dụng hàng đợi tin nhắn cấp cao hơn để đạt được khả năng mở rộng tốt hơn của kiến ​​trúc microservice. Ngoài ra, sử dụng hàng đợi trong Python đơn giản là thú vị

Python cung cấp một vài hương vị hàng đợi tích hợp sẵn mà bạn sẽ thấy trong hướng dẫn này. Bạn cũng sẽ tìm hiểu nhanh về lý thuyết hàng đợi và các loại của chúng. Cuối cùng, bạn sẽ xem qua một số thư viện bên ngoài để kết nối với các nhà môi giới tin nhắn phổ biến có sẵn trên các nhà cung cấp nền tảng đám mây lớn

Trong hướng dẫn này, bạn sẽ học cách

  • Differentiate between various types of queues
  • Implement the queue data type in Python
  • Solve practical problems by applying the right queue
  • Use Python’s thread-safe, asynchronous, and interprocess queues
  • Integrate Python with distributed message queue brokers through libraries

To get the most out of this tutorial, you should be familiar with Python’s sequence types, such as lists and tuples, and the higher-level collections in the standard library

You can download the complete source code for this tutorial with the associated sample data by clicking the link in the box below

Get Source Code. Click here to get access to the source code and sample data that you’ll use to explore queues in Python

Learning About the Types of Queues

A queue is an abstract data type that represents a sequence of elements arranged according to a set of rules. In this section, you’ll learn about the most common types of queues and their corresponding element arrangement rules. At the very least, every queue provides operations for adding and removing elements in constant time or O[1] using the Big O notation. That means both operations should be instantaneous regardless of the queue’s size

Some queues may support other, more specific operations. It’s time to learn more about them

Remove ads

Queue. First-In, First-Out [FIFO]

The word queue can have different meanings depending on the context. However, when people refer to a queue without using any qualifiers, they usually mean a FIFO queue, which resembles a line that you might find at a grocery checkout or tourist attraction

Tourists Queuing Up to Enter the American Museum of Natural History in New York

Note that unlike the line in the photo, where people are clustering side by side, a queue in a strict sense will be single file, with people admitted one at a time

FIFO is short for first-in, first-out, which describes the flow of elements through the queue. Elements in such a queue will be processed on a first-come, first-served basis, which is how most real-life queues work. To better visualize the element movement in a FIFO queue, have a look at the following animation

Unbounded FIFO Queue

Notice that, at any given time, a new element is only allowed to join the queue on one end called the tail—which is on the right in this example—while the oldest element must leave the queue from the opposite end. When an element leaves the queue, then all of its followers shift by exactly one position towards the head of the queue. These few rules ensure that elements are processed in the order of their arrival

Note. You can think of elements in a FIFO queue as cars stopping at a traffic light

Adding an element to the FIFO queue is commonly referred to as an enqueue operation, while retrieving one from it is known as a dequeue operation. Don’t confuse a dequeue operation with the deque [double-ended queue] data type that you’ll learn about later

Enqueuing and dequeuing are two independent operations that may be taking place at different speeds. This fact makes FIFO queues the perfect tool for buffering data in streaming scenarios and for scheduling tasks that need to wait until some shared resource becomes available. For example, a web server flooded with HTTP requests might place them in a queue instead of immediately rejecting them with an error

Note. In programs that leverage concurrency, a FIFO queue often becomes the shared resource itself to facilitate two-way communication between asynchronous workers. By temporarily locking the read or write access to its elements, a blocking queue can elegantly coordinate a pool of producers and a pool of consumers. You’ll find more information about this use case in later sections about queues in multithreading and multiprocessing

Another point worth noting about the queue depicted above is that it can grow without bounds as new elements arrive. Picture a checkout line stretching to the back of the store during a busy shopping season. In some situations, however, you might prefer to work with a bounded queue that has a fixed capacity known up front. A bounded queue can help to keep scarce resources under control in two ways

  1. Bằng cách từ chối không thể đảo ngược các yếu tố không phù hợp
  2. By overwriting the oldest element in the queue

Under the first strategy, once a FIFO queue becomes saturated, it won’t take any more elements until others leave the queue to make some space. You can see an animated example of how this works below

Bounded FIFO Queue [Bounce]

This queue has a capacity of three, meaning it can hold at most three elements. If you try to stuff more elements into it, then they’ll bounce off into oblivion without leaving any trace behind. At the same time, as soon as the number of elements occupying the queue decreases, the queue will start accepting new elements again

Where would you find such a bounded FIFO queue in the wild?

Most digital cameras support the burst mode for continuously shooting a series of pictures as rapidly as possible in the hope of capturing at least one sharp photo of a moving object. Because saving data onto a memory card is the bottleneck, there’s usually an internal buffer that enables the camera to keep taking new pictures while earlier ones are being compressed and saved

In older still cameras, the buffer was usually quite small and would fill up within a few seconds. Khi điều đó xảy ra, việc giữ nút chụp sẽ không còn tác dụng nữa hoặc tốc độ chụp ảnh mới sẽ giảm đáng kể. Tốc độ tối đa của máy ảnh sẽ chỉ phục hồi hoàn toàn sau khi sử dụng hết bộ đệm dữ liệu

Chiến lược thứ hai để xử lý các phần tử đến trong hàng đợi FIFO có giới hạn cho phép bạn triển khai bộ đệm ẩn cơ bản quên phần tử cũ nhất mà không cần xem xét bạn đã truy cập phần tử đó bao nhiêu lần hoặc tần suất như thế nào. Bộ đệm FIFO hoạt động tốt nhất khi các phần tử mới hơn có nhiều khả năng được sử dụng lại hơn các phần tử cũ hơn. Ví dụ: bạn có thể sử dụng chiến lược loại bỏ bộ đệm ẩn FIFO để đăng xuất mạnh mẽ những người dùng đã đăng nhập từ lâu bất kể họ có còn hoạt động hay không

Ghi chú. For a brief comparison of other cache eviction strategies, head over to Caching in Python Using the LRU Cache Strategy

Here’s a visual depiction of a bounded FIFO queue that can hold up to three elements but, unlike before, won’t prevent you from adding more elements

Bounded FIFO Queue [Overwrite]

When this queue reaches its maximum capacity, then adding a new element will first shift all existing elements by one position towards the head, discarding the oldest element and making room for the new one. Notice that the discarded element gets overwritten by its immediate neighbor

While the unbounded FIFO queue and its two bounded counterparts cover a wide range of use cases, they all share one common feature—that is, having separate entry and exit points. In the next section, you’ll learn about yet another popular type of queue, which has a slightly different layout

Remove ads

Stack. Last-In, First-Out [LIFO]

A stack is a more specialized queue, also known as a LIFO or last-in, first-out queue. It works almost exactly like a regular queue, except that elements must now join and leave it through only one end called the top of the stack. The name top reflects the fact that real-world stacks tend to be vertical. A pile of plates in the kitchen sink is an example of a stack

A Pile of Dirty Dishes Left in an Office Kitchen Sink

When the dishwasher is full, employees will push their dirty plates on the top of the stack after having a meal. Once there are no more clean plates in the cabinet, a hungry employee will have to pop the last dirty plate from the top of the stack and clean it with a sponge before microwaving their lunch

If there’s a much-needed fork at the bottom of the stack, then some poor soul will have to dig through all of the plates above, one by one, to get to the desired utensil. Similarly, when the cleaning personnel comes to the office at the end of a business day, they’ll have to go through the plates in reverse order before getting to the last one at the bottom of the stack

You’ll see this element movement in the following animated stack

Unbounded LIFO Queue

Even though the LIFO queue above is oriented horizontally, it preserves the general idea of a stack. New elements grow the stack by joining it only on the right end, as in the previous examples. This time, however, only the last element pushed onto the stack can leave it. Phần còn lại phải đợi cho đến khi không còn phần tử nào tham gia vào ngăn xếp sau

Stacks are widely used in computing for various purposes. Perhaps the most familiar context for a programmer is the call stack containing the functions in the order they were called. Python will reveal this stack to you through a traceback in case of an unhandled exception. It’s usually a bounded stack with a limited capacity, which you’ll find out about during a stack overflow error caused by too many recursive calls

In compiled languages with static type checking, local variables are allocated on the stack, which is a fast memory region. Stacks can help detect unmatched brackets in a code block or evaluate arithmetic expressions represented in reverse Polish notation [RPN]. You can also use stacks to solve the Tower of Hanoi puzzle or keep track of the visited nodes in a graph or a tree traversed with the depth-first search [DFS] algorithm

Note. When you replace the stack, or LIFO queue, with a FIFO queue in the DFS algorithm and make a few minor tweaks, then you’ll get the breadth-first search [BFS] algorithm almost for free. You’ll explore both algorithms in more detail later in this tutorial

While a stack is a specialization of a queue, the deque or double-ended queue is a generalization that you can use as a basis to implement both FIFO and LIFO queues. You’ll see how deques work and where you can use them in the next section

Deque. Hàng đợi hai đầu

A double-ended queue or deque [pronounced deck] is a more generic data type that combines and extends the ideas behind the stack and the queue. It allows you to enqueue or dequeue elements from both ends in constant time at any given moment. Therefore, a deque can work as a FIFO or a LIFO queue, as well as anything in between and beyond

Using the same example of a line of people as before, you can take advantage of a deque to model more sophisticated corner cases. In real life, the last person in the queue might get impatient and decide to leave the queue early or join another queue at a new checkout that has just opened. Conversely, someone who has booked a visit online for a particular date and time in advance may be allowed to join the queue at the front without waiting

Below is an animation that shows an unbounded deque in action

Unbounded Double-Ended Queue

In this particular example, most elements generally follow one direction by joining the queue on the right and leaving it on the left, just like in a plain FIFO queue. However, some privileged elements are allowed to join the queue from the left end, while the last element can leave the queue through the opposite end

Adding an element to a bounded deque that has already reached its full capacity will overwrite the element currently located at the opposite end. That feature might be handy for isolating the first few or the last few elements from a sequence. You may also want to stop anywhere in that sequence and then move to the left or right in smaller steps

Bounded Double-Ended Queue

Suppose you were calculating the moving average of pixel intensities in a scan line of a raster image. Moving left or right would give you a preview of the few consecutive pixel values and dynamically calculate their average. This is more or less how convolution kernels work for applying filters in advanced image processing

Most deques support two additional operations called rotate left and rotate right, which shift the elements a specified number of places in one or the other direction in a circular fashion. Because the deque’s size remains unchanged, elements that would stick out get wrapped around at the ends, as in an analog car odometer

Rotation of a Double-Ended Queue

When rotated right, the last element in the deque becomes first. On the other hand, when rotated left, the first element becomes the last one. Perhaps you could imagine this process more easily by arranging the deque’s elements in a circle so that both ends meet. Then, rotating right and left would correspond to a clockwise and counterclockwise rotation, respectively

Rotations, combined with the deque’s core capabilities, open up interesting possibilities. For example, you could use a deque to implement a load balancer or a task scheduler working in a round-robin fashion. In a GUI application, you could use a deque to store recently opened files, allow a user to undo and redo their actions, or let the user navigate back and forth through their web browsing history

As you can see, deques find many practical uses, especially in tracking the most recent activity. However, some problems will require you to take advantage of yet another type of queue, which you’ll read about next

Remove ads

Priority Queue. Sorted From High to Low

A priority queue is different from those you’ve seen so far because it can’t store ordinary elements. Instead, each element must now have an associated priority to compare against other elements. The queue will maintain a sorted order, letting new elements join where necessary while shuffling the existing elements around if needed. When two elements are of equal priority, they’ll follow their insertion order

Note. Make sure to choose a data type for your priorities whose values are comparable through the comparison operators, such as less than [

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
02]. For example, integers and timestamps would be fine, while complex numbers wouldn’t work for indicating priority because they don’t implement any relevant comparison operator

This kind of queue works in a way that’s analogous to priority boarding on a plane

Jet Bridge Connected to an Airbus A380 at Frankfurt Airport

Regular passengers will join the queue at the very end unless they’re accompanied by small children, have disabilities, or have earned loyalty points, in which case they’ll be fast-tracked to the front of the line. Business-class travelers usually enjoy the luxury of a separate, much smaller queue, but even they sometimes have to let the first-class travelers pass

The animation below illustrates a sample flow of elements having three distinct priorities through an unbounded priority queue

Unbounded Priority Queue

Blue squares represent the lowest priority, yellow triangles are higher in the hierarchy, and red circles are the most important. A new element gets inserted between one with a higher or equal priority and another one with a lower priority. This rule resembles the insertion sort algorithm, which happens to be stable, as elements with the same priority never swap their initial places

You could use the priority queue to sort a sequence of elements by a given key or get the top few elements. However, that would be overkill because there are far more efficient sorting algorithms available. Hàng đợi ưu tiên phù hợp hơn với các tình huống khi các phần tử có thể đến và đi một cách linh hoạt. One such situation would be searching for the shortest path in a weighted graph using Dijkstra’s algorithm, which you’ll read about later

Ghi chú. Even though the priority queue is conceptually a sequence, its most efficient implementation builds on top of the heap data structure, which is a kind of binary tree. Therefore, the terms heap and priority queue are sometimes used interchangeably

That was a longish introduction to the theory and taxonomy of queues. Trong quá trình thực hiện, bạn đã tìm hiểu về hàng đợi FIFO, ngăn xếp [hàng đợi LIFO], deques và hàng đợi ưu tiên. Bạn cũng đã thấy sự khác biệt giữa hàng đợi có giới hạn và không có giới hạn, đồng thời bạn đã có ý tưởng về các ứng dụng tiềm năng của chúng. Bây giờ, đã đến lúc bạn tự mình thực hiện một số hàng đợi đó

Triển khai hàng đợi trong Python

Trước hết, bạn có nên tự triển khai hàng đợi trong Python không? . Ngôn ngữ đi kèm với pin và hàng đợi cũng không ngoại lệ. Trên thực tế, bạn sẽ phát hiện ra rằng Python có rất nhiều triển khai hàng đợi phù hợp để giải quyết các vấn đề khác nhau

Nói như vậy, cố gắng xây dựng thứ gì đó từ đầu có thể là một trải nghiệm học tập vô giá. Bạn cũng có thể được yêu cầu cung cấp cách triển khai hàng đợi trong cuộc phỏng vấn kỹ thuật. Vì vậy, nếu bạn thấy chủ đề này thú vị, thì hãy đọc tiếp. Mặt khác, nếu bạn chỉ muốn sử dụng hàng đợi trong thực tế, thì vui lòng bỏ qua phần này hoàn toàn

Biểu diễn hàng đợi FIFO và LIFO bằng Deque

Để thể hiện hàng đợi FIFO trong bộ nhớ của máy tính, bạn sẽ cần một chuỗi có O[1] hoặc thời gian không đổi, hiệu suất cho thao tác xếp hàng ở một đầu và thao tác xếp hàng hiệu quả tương tự ở đầu kia. Như bạn đã biết bây giờ, deque hoặc hàng đợi kết thúc kép đáp ứng các yêu cầu đó. Ngoài ra, nó cũng đủ phổ biến để thích ứng với hàng đợi LIFO

Tuy nhiên, vì viết mã sẽ nằm ngoài phạm vi của hướng dẫn này, nên bạn sẽ tận dụng bộ sưu tập

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
03 của Python từ thư viện chuẩn

Ghi chú. A deque is an abstract data type that you may implement in a few ways. Using a doubly linked list as the underlying implementation will ensure that accessing and removing elements from both ends will have the desired O[1] time complexity. If your deque has a fixed size, then you can use a circular buffer instead, letting you access any element in constant time. Unlike a linked list, a circular buffer is a random-access data structure

Why not use a Python

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
04 instead of
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
05 as a building block for your FIFO queue?

Both sequences allow for enqueuing elements with their

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
06 methods rather cheaply, with a small reservation for lists, which will occasionally require copying all elements to a new memory location when their number exceeds a certain threshold

Unfortunately, dequeuing an element from the front of a list with

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
07, or equivalently inserting one with
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
08, is far less efficient. Such operations always shift the remaining elements, resulting in a linear, or O[n], time complexity. In contrast,
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
09 and
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
10 avoid that step altogether

With that, you can proceed to define your custom

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
11 class based on Python’s
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
03 collection

Remove ads

Building a Queue Data Type

Now that you’ve chosen a suitable queue representation, you can fire up your favorite code editor, such as Visual Studio Code or PyCharm, and create a new Python module for the different queue implementations. You can call the file

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
13 [plural form] to avoid a conflict with the similarly named
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
14 [singular form] module already available in Python’s standard library

Note. You’ll have a closer look at the built-in

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
14 module in a later section devoted to thread-safe queues in Python

Because you want your custom FIFO queue to support at least the enqueue and dequeue operations, go ahead and write a bare-bones

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
11 class that’ll delegate those two operations to
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
17 and
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
09 methods, respectively

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
7

This class merely wraps a

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
05 instance and calls it
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
20. The leading underscore in the attribute’s name indicates an internal bit of implementation, which only the class should access and modify. Such fields are sometimes called private because they’re not supposed to be visible outside of the class body

You can test your FIFO queue by importing it from the local module within an interactive Python interpreter session

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'

As expected, the enqueued elements come back to you in their original order. If you want, you may improve your class by making it iterable and able to report its length and optionally accept initial elements

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
1

A

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
03 takes an optional iterable, which you can provide through a varying number of positional arguments,
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
22, in your initializer method. By implementing the special
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
23 method, you’ll make your class instances usable in a
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
24 loop, while implementing
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
25 will make them compatible with the
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
26 function. The
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
23 method above is an example of a generator iterator, which yields elements lazily

Note. The implementation of

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
23 causes your custom queue to reduce its size by dequeuing elements from itself as you iterate over it

Restart the Python interpreter and import your class again to see the updated code in action

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
0

The queue has three elements initially, but its length drops to zero after consuming all elements in a loop. Next up, you’ll implement a stack data type that’ll dequeue elements in reverse order

Building a Stack Data Type

Building a stack data type is considerably more straightforward because you’ve already done the bulk of the hard work. Vì hầu hết việc triển khai sẽ không thay đổi, bạn có thể mở rộng lớp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
11 của mình bằng cách sử dụng tính kế thừa và ghi đè phương thức
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
30 để xóa các phần tử khỏi đỉnh ngăn xếp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
3

Đó là nó. Các phần tử hiện được bật ra từ cùng một đầu của hàng đợi mà bạn đã đẩy chúng qua trước đó. Bạn có thể nhanh chóng xác minh điều này trong phiên Python tương tác

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
4

Với dữ liệu thử nghiệm và thiết lập giống hệt như trước đây, các phần tử sẽ trả về cho bạn theo thứ tự ngược lại, đây là hành vi dự kiến ​​của hàng đợi LIFO

Ghi chú. Trong hướng dẫn này, bạn sử dụng tính kế thừa như một cơ chế thuận tiện để sử dụng lại mã. Tuy nhiên, mối quan hệ lớp hiện tại không đúng về mặt ngữ nghĩa, bởi vì ngăn xếp không phải là kiểu con của hàng đợi. Bạn cũng có thể xác định ngăn xếp trước và để hàng đợi mở rộng nó. Trong thế giới thực, có lẽ bạn nên làm cho cả hai lớp kế thừa từ một lớp cơ sở trừu tượng vì chúng có chung giao diện

Trong các tập lệnh một lần, bạn có thể thoát khỏi việc sử dụng Python

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
04 cũ đơn giản làm ngăn xếp thô sơ khi bạn không bận tâm đến chi phí bổ sung khi thỉnh thoảng phải sao chép các giá trị

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
6

Danh sách Python có thể lặp lại ngay lập tức. Họ có thể báo cáo độ dài của họ và có một đại diện văn bản hợp lý. Trong phần tiếp theo, bạn sẽ chọn chúng làm nền tảng cho hàng đợi ưu tiên

Remove ads

Biểu diễn hàng đợi ưu tiên bằng một đống

Hàng đợi cuối cùng mà bạn sẽ triển khai trong hướng dẫn này sẽ là hàng đợi ưu tiên. Không giống như ngăn xếp, hàng đợi ưu tiên không thể mở rộng lớp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
11 được xác định trước đó, bởi vì nó không thuộc cùng một hệ thống phân cấp loại. Thứ tự của các phần tử trong hàng đợi FIFO hoặc LIFO chỉ được xác định bởi thời gian đến của các phần tử. Trong hàng đợi ưu tiên, đó là mức độ ưu tiên của phần tử và thứ tự chèn cùng xác định vị trí cuối cùng trong hàng đợi

Có nhiều cách để triển khai hàng đợi ưu tiên, chẳng hạn như

  • Một danh sách các phần tử không có thứ tự và mức độ ưu tiên của chúng mà bạn luôn tìm kiếm trước khi loại bỏ phần tử có mức độ ưu tiên cao nhất
  • Một danh sách các phần tử được sắp xếp theo thứ tự và mức độ ưu tiên của chúng, mà bạn sắp xếp mỗi khi liệt kê một phần tử mới
  • Một danh sách các phần tử được sắp xếp theo thứ tự và mức độ ưu tiên của chúng mà bạn sắp xếp theo thứ tự bằng cách tìm đúng vị trí cho một phần tử mới bằng cách sử dụng tìm kiếm nhị phân
  • A binary tree that maintains the heap invariant after the enqueue and dequeue operations

Bạn có thể coi hàng đợi ưu tiên là một danh sách cần được sắp xếp mỗi khi có một phần tử mới để bạn có thể xóa phần tử cuối cùng có mức độ ưu tiên cao nhất khi thực hiện thao tác dequeue. Ngoài ra, bạn có thể bỏ qua thứ tự phần tử cho đến khi loại bỏ phần tử có mức độ ưu tiên cao nhất mà bạn có thể tìm thấy bằng thuật toán tìm kiếm tuyến tính

Tra cứu một phần tử trong danh sách không có thứ tự có độ phức tạp thời gian O[n]. Sắp xếp toàn bộ hàng đợi sẽ còn tốn kém hơn, đặc biệt là khi thực hiện thường xuyên. Phương thức

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
33 của Python sử dụng một thuật toán gọi là Timsort, có độ phức tạp thời gian trong trường hợp xấu nhất là O[n log[n]]. Chèn một phần tử với
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
34 sẽ tốt hơn một chút vì nó có thể tận dụng danh sách đã được sắp xếp sẵn, nhưng mức tăng được bù lại bằng cách chèn chậm sau đó

May mắn thay, bạn có thể thông minh trong việc sắp xếp các phần tử trong hàng đợi ưu tiên bằng cách sử dụng cấu trúc dữ liệu heap dưới mui xe. Nó cung cấp một triển khai hiệu quả hơn so với những gì được liệt kê trước đó. Dưới đây là bảng so sánh nhanh về độ phức tạp của thời gian đối với các thao tác xếp hàng và xếp hàng được cung cấp bởi các cách triển khai khác nhau đó

Thực hiệnEnqueueDequeueTìm Tối đa trên DequeueO[1]O[n]Sắp xếp trên EnqueueO[n log[n]]O[1]Chia đôi và Chèn trên EnqueueO[n]O[1]Đẩy lên một đống trên EnqueueO[log[n]]O

Heap có hiệu suất tổng thể tốt nhất cho khối lượng dữ liệu lớn. Mặc dù sử dụng phương pháp chia đôi để tìm đúng vị trí cho một phần tử mới là O[log[n]], nhưng việc chèn phần tử đó thực sự là O[n], khiến nó ít được mong muốn hơn so với một đống

Python có mô-đun

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
35, cung cấp một số chức năng thuận tiện có thể biến một danh sách thông thường thành một đống và thao tác nó một cách hiệu quả. Hai hàm sẽ giúp bạn tạo hàng đợi ưu tiên là

  1. >>> from queues import Queue
    
    >>> fifo = Queue[]
    >>> fifo.enqueue["1st"]
    >>> fifo.enqueue["2nd"]
    >>> fifo.enqueue["3rd"]
    
    >>> fifo.dequeue[]
    '1st'
    >>> fifo.dequeue[]
    '2nd'
    >>> fifo.dequeue[]
    '3rd'
    
    36
  2. >>> from queues import Queue
    
    >>> fifo = Queue[]
    >>> fifo.enqueue["1st"]
    >>> fifo.enqueue["2nd"]
    >>> fifo.enqueue["3rd"]
    
    >>> fifo.dequeue[]
    '1st'
    >>> fifo.dequeue[]
    '2nd'
    >>> fifo.dequeue[]
    '3rd'
    
    37

Khi bạn đẩy một phần tử mới vào một đống không trống, nó sẽ kết thúc ở đúng vị trí, duy trì sự bất biến của đống. Lưu ý rằng điều này không nhất thiết có nghĩa là các phần tử kết quả sẽ được sắp xếp

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
3

Tên trái cây trong đống kết quả trong ví dụ trên không theo thứ tự bảng chữ cái. Tuy nhiên, nếu bạn đẩy chúng theo một thứ tự khác, chúng có thể

Điểm quan trọng của một đống không phải là sắp xếp các phần tử mà là giữ chúng trong một mối quan hệ nhất định để cho phép tra cứu nhanh. Điều thực sự quan trọng là phần tử đầu tiên trên heap luôn có giá trị nhỏ nhất [min-heap] hoặc cao nhất [max-heap], tùy thuộc vào cách bạn xác định điều kiện cho mối quan hệ được đề cập. Heap của Python là min-heap, nghĩa là phần tử đầu tiên có giá trị nhỏ nhất

Khi bạn bật một phần tử từ một đống, bạn sẽ luôn nhận được phần tử đầu tiên, trong khi các phần tử còn lại có thể xáo trộn một chút

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
4

Chú ý đổi chỗ cho quả chuối và quả cam để đảm bảo phần tử đầu tiên tiếp tục nhỏ nhất. Khi bạn yêu cầu Python so sánh hai đối tượng chuỗi theo giá trị, nó bắt đầu xem xét từng cặp ký tự của chúng từ trái sang phải và kiểm tra từng cặp một. Ký tự có điểm mã Unicode thấp hơn được coi là nhỏ hơn, giải quyết trật tự từ

Bây giờ, bạn sắp xếp các ưu tiên như thế nào? . Để giải quyết vấn đề này, bạn có thể tận dụng phép so sánh bộ của Python, tính năng này sẽ tính đến các thành phần của bộ, nhìn từ trái sang phải cho đến khi biết kết quả

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
5

Ở đây, bạn có ba bộ đại diện cho những người khác nhau. Mỗi người đều có tên, họ và tuổi. Python xác định rằng

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
38 phải đi trước
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
39 dựa trên họ của họ vì họ có cùng tên, nhưng Python không nhìn vào tuổi của họ vì thứ tự đã được biết. Độ tuổi trở nên quan trọng trong lần so sánh thứ hai giữa
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
39 và
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
41, những người tình cờ có cùng họ và tên

Bạn có thể thực thi thứ tự ưu tiên trên heap bằng cách lưu trữ các bộ dữ liệu có phần tử đầu tiên là ưu tiên. Tuy nhiên, sẽ có một vài chi tiết nhỏ mà bạn cần phải cẩn thận. Bạn sẽ tìm hiểu thêm về chúng trong phần tiếp theo

Remove ads

Xây dựng loại dữ liệu hàng đợi ưu tiên

Hãy tưởng tượng bạn đang xây dựng phần mềm cho một công ty ô tô. Các phương tiện hiện đại thực tế là các máy tính trên bánh xe, sử dụng xe buýt mạng khu vực điều khiển [CAN] để phát thông báo về các sự kiện khác nhau đang diễn ra trong ô tô của bạn, chẳng hạn như mở khóa cửa hoặc bung túi khí. Rõ ràng, một số sự kiện đó quan trọng hơn những sự kiện khác và cần được ưu tiên phù hợp

Sự thật thú vị. Bạn có thể tải xuống ứng dụng di động cho điện thoại thông minh của mình, chẳng hạn như Torque, ứng dụng này sẽ cho phép bạn kết nối với bus CAN của ô tô qua Bluetooth hoặc mạng WiFi đặc biệt thông qua một thiết bị quét nhỏ được kết nối với thiết bị chẩn đoán trên ô tô của bạn [

Thiết lập này sẽ cho phép bạn theo dõi các thông số của xe theo thời gian thực, ngay cả khi chúng không hiển thị trên bảng điều khiển. Điều này bao gồm những thứ như nhiệt độ nước làm mát, điện áp pin, dặm mỗi gallon, và lượng khí thải. Hơn nữa, bạn sẽ có thể kiểm tra xem ECU trên ô tô của mình có báo cáo bất kỳ mã lỗi nào không

Bạn có thể bỏ lỡ thông báo đèn pha bị lỗi hoặc đợi thêm một thời gian nữa để mức âm lượng giảm xuống. Tuy nhiên, khi bạn nhấn bàn đạp phanh, bạn sẽ mong đợi nó có tác dụng ngay lập tức vì đây là một hệ thống con quan trọng về an toàn. Each message has a priority in the CAN bus protocol, which tells the intermediate units whether they should relay the message further or disregard it completely

Mặc dù đây là một sự đơn giản hóa quá mức của vấn đề, nhưng bạn có thể coi bus CAN như một hàng đợi ưu tiên sắp xếp các thư theo mức độ quan trọng của chúng. Bây giờ, hãy quay lại trình chỉnh sửa mã của bạn và xác định lớp sau trong mô-đun Python mà bạn đã tạo trước đó

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
0

Đó là một triển khai hàng đợi ưu tiên cơ bản, định nghĩa một đống phần tử bằng cách sử dụng danh sách Python và hai phương thức thao tác nó. Phương thức

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
42 nhận hai đối số, mức độ ưu tiên và giá trị tương ứng, sau đó nó bao bọc trong một bộ và đẩy lên heap bằng cách sử dụng mô-đun
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
35. Lưu ý rằng mức độ ưu tiên xuất hiện trước giá trị để tận dụng cách Python so sánh các bộ dữ liệu

Thật không may, có một số vấn đề với việc triển khai ở trên trở nên rõ ràng khi bạn cố gắng sử dụng nó

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
1

Bạn đã xác định ba mức độ ưu tiên. quan trọng, quan trọng và trung lập. Tiếp theo, bạn đã khởi tạo một hàng đợi ưu tiên và sử dụng nó để liệt kê một vài thư với những ưu tiên đó. Tuy nhiên, thay vì xếp hàng tin nhắn có mức độ ưu tiên cao nhất, bạn có một bộ dữ liệu tương ứng với tin nhắn có mức độ ưu tiên thấp nhất

Ghi chú. Cuối cùng, tùy thuộc vào cách bạn muốn xác định thứ tự ưu tiên của mình. Trong hướng dẫn này, mức ưu tiên thấp hơn tương ứng với giá trị số thấp hơn, trong khi mức ưu tiên cao hơn có giá trị lớn hơn

Điều đó nói rằng, có thể thuận tiện hơn để đảo ngược thứ tự này trong một số trường hợp. Ví dụ: trong thuật toán đường đi ngắn nhất của Dijkstra, bạn sẽ muốn ưu tiên các đường dẫn có tổng chi phí nhỏ hơn so với những đường dẫn có chi phí cao. Để xử lý tình huống như vậy, bạn sẽ triển khai một lớp khác sau

Vì heap của Python là min-heap nên phần tử đầu tiên của nó luôn có giá trị thấp nhất. Để khắc phục điều này, bạn có thể lật dấu hiệu của mức độ ưu tiên khi đẩy một bộ vào heap, đặt mức độ ưu tiên thành một số âm để mức cao nhất trở thành mức thấp nhất

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
2

Với thay đổi nhỏ này, bạn sẽ đẩy các thông báo quan trọng lên trước các thông báo quan trọng và trung lập. Ngoài ra, khi thực hiện thao tác dequeue, bạn sẽ giải nén giá trị khỏi bộ dữ liệu bằng cách truy cập thành phần thứ hai của nó, nằm ở chỉ mục một bằng cách sử dụng cú pháp dấu ngoặc vuông [

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
44]

Bây giờ, nếu bạn quay lại trình thông dịch Python tương tác của mình, hãy nhập mã đã cập nhật và liệt kê lại các thông báo tương tự một lần nữa, sau đó chúng sẽ quay lại với bạn theo thứ tự hợp lý hơn

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
3

Bạn nhận được thông báo quan trọng đầu tiên, tiếp theo là hai thông báo quan trọng và sau đó là thông báo trung lập. Cho đến nay, rất tốt, phải không? . Một trong số chúng bạn đã có thể nhìn thấy ở đầu ra, trong khi cái còn lại sẽ chỉ xuất hiện trong những trường hợp cụ thể. Bạn có thể phát hiện ra những vấn đề này?

Xử lý các trường hợp góc trong hàng đợi ưu tiên của bạn

Hàng đợi của bạn có thể sắp xếp chính xác các phần tử theo mức độ ưu tiên, nhưng đồng thời, nó vi phạm tính ổn định của sắp xếp khi so sánh các phần tử có mức độ ưu tiên như nhau. Điều này có nghĩa là trong ví dụ trên, nhấp nháy đèn báo nguy hiểm được ưu tiên hơn là bật cần gạt nước, mặc dù thứ tự này không tuân theo trình tự thời gian của các sự kiện. Cả hai tin nhắn đều có cùng mức độ ưu tiên, quan trọng, vì vậy hàng đợi sẽ sắp xếp chúng theo thứ tự chèn của chúng

To be clear, this is a direct consequence of tuple comparison in Python, which moves to the next component in a tuple if the earlier ones didn’t resolve the comparison. Vì vậy, nếu hai thông báo có mức độ ưu tiên như nhau, thì Python sẽ so sánh chúng theo giá trị, đó sẽ là một chuỗi trong ví dụ của bạn. Các chuỗi tuân theo thứ tự từ điển, trong đó từ Hazard xuất hiện trước từ Windshield, do đó thứ tự không nhất quán

Có một vấn đề khác liên quan đến điều đó, điều này sẽ phá vỡ hoàn toàn so sánh bộ dữ liệu trong một số trường hợp hiếm hoi. Cụ thể, sẽ không thành công nếu bạn cố gắng xếp một phần tử không hỗ trợ bất kỳ toán tử so sánh nào, chẳng hạn như một thể hiện của lớp tùy chỉnh và hàng đợi đã chứa ít nhất một phần tử có cùng mức độ ưu tiên mà bạn muốn sử dụng. Hãy xem xét lớp dữ liệu sau đây để biểu thị các thư trong hàng đợi của bạn

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
4

Các đối tượng

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
45 có thể thuận tiện hơn khi làm việc với các chuỗi đơn giản, nhưng không giống như các chuỗi, chúng không thể so sánh được trừ khi bạn cho Python biết cách thực hiện so sánh. Như bạn có thể thấy, các thể hiện của lớp tùy chỉnh không cung cấp triển khai cho toán tử nhỏ hơn [
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
02] theo mặc định

Miễn là bạn liệt kê các thư có mức độ ưu tiên khác nhau, Python sẽ không so sánh chúng theo giá trị

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
5

Ví dụ: khi bạn liệt kê một thông báo quan trọng và một thông báo quan trọng, Python sẽ xác định thứ tự của chúng một cách rõ ràng bằng cách xem xét các mức độ ưu tiên tương ứng. Tuy nhiên, ngay khi bạn thử xử lý một thư quan trọng khác, bạn sẽ gặp lỗi quen thuộc

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
6

Khoảng thời gian này, việc so sánh không thành công vì hai trong số các thông báo có mức độ ưu tiên như nhau và Python quay lại so sánh chúng theo giá trị mà bạn chưa xác định cho các phiên bản lớp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
45 tùy chỉnh của mình

Bạn có thể giải quyết cả hai vấn đề—tức là, tính không ổn định của sắp xếp và so sánh bộ dữ liệu bị hỏng—bằng cách đưa một thành phần khác vào các phần tử được lưu trữ trên heap. Thành phần bổ sung này phải có thể so sánh được và biểu thị thời gian đến. Khi được đặt giữa mức độ ưu tiên và giá trị của phần tử trong một bộ, nó sẽ giải quyết thứ tự nếu hai phần tử có cùng mức độ ưu tiên, bất kể giá trị của chúng là gì

Cách đơn giản nhất để biểu thị thời gian đến trong hàng đợi ưu tiên có lẽ là một bộ đếm đơn điệu tăng dần. Nói cách khác, bạn muốn đếm số thao tác xếp hàng được thực hiện mà không cần xem xét các thao tác xếp hàng tiềm năng có thể đang diễn ra. Sau đó, bạn sẽ lưu trữ giá trị hiện tại của bộ đếm trong mọi phần tử được xếp hàng để phản ánh trạng thái hàng đợi của bạn ngay lúc đó

Bạn có thể sử dụng trình vòng lặp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
48 từ mô-đun
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
49 để đếm từ 0 đến vô cùng một cách ngắn gọn

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
7

Bộ đếm được khởi tạo khi bạn tạo một phiên bản

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
50 mới. Bất cứ khi nào bạn liệt kê một giá trị, bộ đếm sẽ tăng lên và giữ nguyên trạng thái hiện tại của nó trong một bộ được đẩy lên heap. Vì vậy, nếu sau này bạn xếp một giá trị khác có cùng mức độ ưu tiên, thì giá trị trước đó sẽ được ưu tiên hơn vì bạn đã xếp nó với một bộ đếm nhỏ hơn

Chi tiết nhỏ cuối cùng cần ghi nhớ sau khi đưa thành phần bộ đếm bổ sung này vào bộ dữ liệu là cập nhật chỉ mục giá trị được bật lên trong hoạt động dequeue. Bởi vì các phần tử bây giờ là các bộ có ba thành phần, bạn phải trả về giá trị nằm ở chỉ mục hai thay vì một. Tuy nhiên, sẽ an toàn hơn nếu sử dụng giá trị âm làm chỉ mục để chỉ ra thành phần cuối cùng của bộ dữ liệu, bất kể độ dài của nó

Hàng đợi ưu tiên của bạn gần như đã sẵn sàng, nhưng nó thiếu hai phương thức đặc biệt,

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
25 và
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
23, mà bạn đã triển khai trong hai lớp hàng đợi khác. Mặc dù bạn không thể sử dụng lại mã của chúng thông qua kế thừa, vì hàng đợi ưu tiên không phải là kiểu con của hàng đợi FIFO, Python cung cấp một cơ chế mạnh mẽ cho phép bạn giải quyết vấn đề đó

Remove ads

Tái cấu trúc mã bằng lớp Mixin

Để sử dụng lại mã trên các lớp không liên quan, bạn có thể xác định mẫu số chung nhỏ nhất của chúng và sau đó trích xuất mã đó vào một lớp mixin. Một lớp mixin giống như một loại gia vị. Nó không thể tự đứng vững, vì vậy bạn thường không khởi tạo nó, nhưng nó có thể bổ sung thêm hương vị đó khi bạn trộn nó vào một lớp khác. Đây là cách nó sẽ hoạt động trong thực tế

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
8

Bạn đã di chuyển các phương thức

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
25 và
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
23 từ lớp
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
11 sang một lớp
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
56 riêng biệt và làm cho lớp cũ mở rộng mixin đó. Bạn cũng đã tạo kế thừa
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
50 từ cùng một lớp mixin. Điều này khác với kế thừa tiêu chuẩn như thế nào?

Không giống như các ngôn ngữ lập trình như Scala hỗ trợ mixin trực tiếp với các đặc điểm, Python sử dụng nhiều kế thừa để triển khai cùng một khái niệm. Tuy nhiên, việc mở rộng một lớp mixin khác về mặt ngữ nghĩa so với việc mở rộng một lớp thông thường, đây không còn là một dạng chuyên môn hóa kiểu. Để nhấn mạnh sự khác biệt này, một số người gọi nó là sự bao gồm của một lớp mixin hơn là kế thừa thuần túy

Lưu ý rằng lớp mixin của bạn đề cập đến thuộc tính

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
20 mà bạn chưa xác định. Nó được cung cấp bởi các lớp cụ thể, chẳng hạn như
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
11 và
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
50, mà bạn đưa vào hỗn hợp sau này. Mixin rất tốt cho việc đóng gói hành vi hơn là trạng thái, giống như các phương thức mặc định trong giao diện Java. Bằng cách soạn một lớp với một hoặc nhiều mixin, bạn có thể thay đổi hoặc bổ sung hành vi ban đầu của nó

Mở rộng phần có thể thu gọn bên dưới để hiển thị mã nguồn hoàn chỉnh

Mã nguồn hoàn chỉnh cho hàng đợiHiển thị/Ẩn

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
9

Với ba lớp xếp hàng tại chỗ, cuối cùng bạn cũng có thể áp dụng chúng để giải một bài toán thực tế

Sử dụng hàng đợi trong thực tế

As mentioned in the introduction to this tutorial, queues are the backbone of many important algorithms. Một lĩnh vực ứng dụng đặc biệt thú vị là truy cập các nút trong biểu đồ, chẳng hạn biểu đồ này có thể biểu thị bản đồ các con đường giữa các thành phố. Hàng đợi có thể hữu ích trong việc tìm đường đi ngắn nhất hoặc tối ưu nhất giữa hai địa điểm

Trong phần này, bạn sẽ sử dụng hàng đợi mà bạn vừa tạo để triển khai các thuật toán duyệt đồ thị cổ điển. Có rất nhiều cách để biểu diễn đồ thị trong mã và một số thư viện Python tương đương đã làm tốt điều đó. Để đơn giản, bạn sẽ tận dụng các thư viện networkx và pygraphviz, cũng như ngôn ngữ mô tả biểu đồ DOT được sử dụng rộng rãi

Bạn có thể cài đặt các thư viện đó vào môi trường ảo của mình bằng cách sử dụng

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
61

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
10

Ngoài ra, bạn có thể cài đặt tất cả các phụ thuộc cần thiết cho phần còn lại của hướng dẫn này trong một bước bằng cách làm theo hướng dẫn trong tệp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
62 mà bạn sẽ tìm thấy trong các tài liệu bổ sung. Lưu ý rằng việc cài đặt pygraphviz có thể hơi khó khăn vì nó yêu cầu chuỗi công cụ biên dịch C. Kiểm tra hướng dẫn cài đặt chính thức để biết thêm chi tiết

Dữ liệu mẫu. Bản đồ đường đi của Vương quốc Anh

Khi bạn đã cài đặt các thư viện cần thiết, bạn sẽ đọc biểu đồ có trọng số và vô hướng của các thành phố ở Vương quốc Anh từ tệp DOT, bạn có thể tìm thấy tệp này trong các tài liệu đi kèm

Get Source Code. Click here to get access to the source code and sample data that you’ll use to explore queues in Python

Biểu đồ này có 70 nút đại diện cho các thành phố của Vương quốc Anh và 137 cạnh có trọng số theo khoảng cách ước tính tính bằng dặm giữa các thành phố được kết nối

Các thành phố ở Vương quốc Anh

Lưu ý rằng biểu đồ được mô tả ở trên là một mô hình đơn giản hóa của mạng lưới đường bộ ở Vương quốc Anh, vì nó không tính đến các loại đường, sức chứa, giới hạn tốc độ, giao thông hoặc đường tránh. Nó cũng bỏ qua thực tế là thường có nhiều hơn một con đường nối hai thành phố. Vì vậy, đường đi ngắn nhất được xác định bằng điều hướng vệ tinh hoặc Google Maps rất có thể sẽ khác với đường đi mà bạn sẽ tìm thấy khi xếp hàng trong hướng dẫn này

Điều đó nói rằng, biểu đồ trên biểu thị các kết nối đường thực tế giữa các thành phố trái ngược với các đường thẳng theo đường chim bay. Mặc dù các cạnh có thể trông giống như các đường thẳng trong hình ảnh trực quan, nhưng chúng chắc chắn không có trong đời thực. Về mặt đồ họa, bạn có thể biểu diễn cùng một biểu đồ theo vô số cách

Tiếp theo, bạn sẽ sử dụng thư viện networkx để đọc biểu đồ này bằng Python

Remove ads

Đại diện đối tượng của các thành phố và đường

Mặc dù networkx không thể tự đọc các tệp DOT, nhưng thư viện cung cấp một số chức năng trợ giúp ủy quyền nhiệm vụ này cho các thư viện bên thứ ba khác. Bạn sẽ sử dụng pygraphviz để đọc tệp DOT mẫu trong hướng dẫn này

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
11

Mặc dù pygraphviz có thể hơi khó cài đặt trên một số hệ điều hành, nhưng cho đến nay, đây là cách nhanh nhất và phù hợp nhất với các tính năng nâng cao của định dạng DOT. Theo mặc định, networkx đại diện cho các nút biểu đồ bằng cách sử dụng các mã định danh văn bản có thể tùy chọn có một từ điển các thuộc tính được liên kết

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
12

Ví dụ: chuỗi

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
63 ánh xạ tới một từ điển tương ứng của các cặp khóa-giá trị. Thuộc tính
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
64, chứa tọa độ được chuẩn hóa sau khi áp dụng phép chiếu Mercator cho kinh độ và vĩ độ, được Graphviz tôn trọng đối với vị trí của các nút trong trực quan hóa biểu đồ. Thuộc tính
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
65 cho biết thời điểm một thành phố có trạng thái. Khi bằng 0, nó có nghĩa là thời xa xưa

Vì đó không phải là cách thuận tiện nhất để suy nghĩ về biểu đồ, nên bạn sẽ xác định loại dữ liệu tùy chỉnh đại diện cho một thành phố trong bản đồ chỉ đường của mình. Hãy tiếp tục, tạo một tệp mới có tên là

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
66 và triển khai lớp sau trong đó

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
13

Bạn mở rộng một bộ được đặt tên để đảm bảo rằng các đối tượng nút của bạn có thể băm được, điều này được yêu cầu bởi networkx. Thay vào đó, bạn có thể sử dụng một lớp dữ liệu được định cấu hình đúng, nhưng một bộ dữ liệu được đặt tên có thể được băm ngay lập tức. Nó cũng có thể so sánh được mà sau này bạn có thể cần để xác định thứ tự duyệt đồ thị. Phương thức lớp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
67 lấy từ điển các thuộc tính được trích xuất từ ​​tệp DOT và trả về một thể hiện mới của lớp
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
68 của bạn

Để tận dụng lớp mới của mình, bạn sẽ cần tạo một thể hiện đồ thị mới và lưu ý ánh xạ của các định danh nút tới các thể hiện thành phố. Thêm chức năng trợ giúp sau vào mô-đun

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
69 của bạn

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
14

Hàm lấy tên tệp và nhà máy có thể gọi được cho các đối tượng nút, chẳng hạn như phương thức lớp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
70 của bạn. Nó bắt đầu bằng cách đọc tệp DOT và xây dựng ánh xạ các mã định danh nút tới biểu diễn hướng đối tượng của các nút biểu đồ. Cuối cùng, nó trả về ánh xạ đó và một biểu đồ mới bao gồm các nút và các cạnh có trọng số

Giờ đây, bạn có thể bắt đầu chơi lại với bản đồ đường đi của Vương quốc Anh trong phiên phiên dịch Python tương tác

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
15

Sau khi nhập hàm trợ giúp và lớp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
68 từ mô-đun của bạn, bạn tải biểu đồ từ tệp DOT mẫu và lưu kết quả vào hai biến. Biến
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
72 cho phép bạn có được một tham chiếu đến một thể hiện của lớp
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
68 theo tên đã chỉ định, trong khi biến
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
69 chứa toàn bộ đối tượng
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
75 của networkx

Khi tìm kiếm con đường ngắn nhất giữa hai thành phố, bạn sẽ muốn xác định các lân cận ngay lập tức của một thành phố nhất định để tìm các tuyến đường có sẵn để đi theo. Bạn có thể làm điều đó theo một số cách với biểu đồ networkx. Trong trường hợp đơn giản nhất, bạn sẽ gọi phương thức

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
76 trên biểu đồ với nút được chỉ định làm đối số

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
16

Điều này chỉ hiển thị các nút lân cận mà không có trọng số có thể có của các cạnh kết nối, chẳng hạn như khoảng cách hoặc thời gian di chuyển ước tính mà bạn có thể cần biết để chọn đường đi tốt nhất. Nếu bạn muốn bao gồm các trọng số, hãy truy cập một nút bằng cú pháp dấu ngoặc vuông

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
17

Các hàng xóm luôn được liệt kê theo cùng thứ tự mà bạn đã xác định chúng trong tệp DOT. Để sắp xếp chúng theo một hoặc nhiều trọng số, bạn có thể sử dụng đoạn mã sau

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
18

Đầu tiên, bạn xác định một hàm trợ giúp trả về danh sách các hàng xóm và trọng số của chúng được sắp xếp theo chiến lược đã chỉ định. Chiến lược lấy một từ điển gồm tất cả các trọng số được liên kết với một cạnh và trả về một khóa sắp xếp. Tiếp theo, bạn xác định một chiến lược cụ thể tạo ra khoảng cách dấu phẩy động dựa trên từ điển đầu vào. Cuối cùng, bạn duyệt qua các vùng lân cận của Luân Đôn, được sắp xếp theo khoảng cách theo thứ tự tăng dần

Với kiến ​​thức cơ bản về thư viện networkx này, giờ đây bạn có thể chuyển sang triển khai thuật toán duyệt đồ thị dựa trên các loại dữ liệu hàng đợi tùy chỉnh mà bạn đã tạo trước đó

Remove ads

Tìm kiếm theo chiều rộng đầu tiên bằng cách sử dụng hàng đợi FIFO

Trong thuật toán tìm kiếm theo chiều rộng, bạn tìm kiếm một nút thỏa mãn một điều kiện cụ thể bằng cách khám phá biểu đồ theo các lớp hoặc cấp độ đồng tâm. Bạn bắt đầu duyệt đồ thị tại nút nguồn được chọn tùy ý trừ khi đồ thị là cấu trúc dữ liệu cây, trong trường hợp đó bạn thường bắt đầu tại nút gốc của cây đó. Ở mỗi bước, bạn truy cập tất cả các hàng xóm trực tiếp của nút hiện tại trước khi đi sâu hơn

Ghi chú. Để tránh bị mắc kẹt trong một vòng lặp khi biểu đồ chứa các chu kỳ, hãy theo dõi những người hàng xóm mà bạn đã truy cập và bỏ qua họ trong lần tiếp theo bạn gặp họ. Ví dụ: bạn có thể thêm các nút đã truy cập vào một tập hợp Python và sau đó sử dụng toán tử

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
77 để kiểm tra xem tập hợp có chứa một nút nhất định hay không

Ví dụ: giả sử bạn muốn tìm bất kỳ địa điểm nào ở Vương quốc Anh đã được công nhận là thành phố trong thế kỷ XX, bắt đầu tìm kiếm của bạn ở Edinburgh. Thư viện networkx đã triển khai nhiều thuật toán, bao gồm tìm kiếm theo chiều rộng, có thể giúp kiểm tra chéo việc triển khai trong tương lai của bạn. Gọi hàm

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
78 trên biểu đồ của bạn để hiển thị thứ tự duyệt theo chiều rộng

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
19

Các dòng được đánh dấu cho biết tất cả sáu hàng xóm trực tiếp của Edinburgh, là nút nguồn của bạn. Lưu ý rằng chúng được truy cập theo trình tự mà không bị gián đoạn trước khi chuyển sang lớp tiếp theo của biểu đồ. Lớp tiếp theo bao gồm các nút lân cận cấp hai bắt đầu từ nút nguồn

Bạn khám phá những người hàng xóm chưa được ghé thăm của các thành phố nổi bật. Cái đầu tiên là Dundee, có hàng xóm bao gồm Aberdeen và Perth, nhưng bạn đã đến thăm Perth rồi, vì vậy bạn bỏ qua nó và chỉ ghé thăm Aberdeen. Glasgow không có bất kỳ hàng xóm nào không được thăm viếng, trong khi Perth chỉ có Inverness. Tương tự, bạn đã đến thăm những người hàng xóm của Stirling nhưng không phải của Carlisle, nơi kết nối với Lancaster. Bạn ngừng tìm kiếm vì Lancaster là câu trả lời của bạn

Kết quả tìm kiếm của bạn đôi khi có thể khác nhau tùy thuộc vào lựa chọn điểm xuất phát của bạn, cũng như thứ tự của các lân cận nếu có nhiều hơn một nút thỏa mãn một điều kiện. Để đảm bảo kết quả nhất quán, bạn có thể sắp xếp các hàng xóm theo một số tiêu chí. Ví dụ: trước tiên bạn có thể đến thăm các thành phố có vĩ độ cao hơn

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
00

Bây giờ, câu trả lời đã khác vì Newcastle được viếng thăm trước Carlisle do có vĩ độ cao hơn một chút. Đổi lại, điều này làm cho thuật toán tìm kiếm theo chiều rộng tìm thấy Sunderland trước Lancaster, đây là một nút thay thế phù hợp với điều kiện của bạn

Ghi chú. Trong trường hợp bạn đang thắc mắc tại sao

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
79 lại kết thúc một danh sách các hàng xóm đã sắp xếp trong một cuộc gọi tới
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
80, thì đó là bởi vì
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
78 mong đợi một đối tượng trình vòng lặp làm đầu vào cho đối số
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
82 của nó

Bây giờ bạn đã nắm được ý tưởng chung về thuật toán tìm kiếm theo chiều rộng, đã đến lúc tự triển khai nó. Vì phép duyệt theo chiều rộng là cơ sở cho các thuật toán thú vị khác, nên bạn sẽ trích xuất logic của nó thành một hàm riêng biệt mà bạn có thể ủy quyền cho

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
01

Hàm đầu tiên lấy biểu đồ networkx và nút nguồn làm đối số trong khi mang lại các nút được truy cập với giao dịch truyền tải theo chiều rộng đầu tiên. Lưu ý rằng nó sử dụng hàng đợi FIFO của bạn từ mô-đun

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
83 để theo dõi các nút lân cận, đảm bảo rằng bạn sẽ khám phá chúng theo trình tự trên mỗi lớp. Hàm này cũng đánh dấu các nút đã truy cập bằng cách thêm chúng vào một bộ Python, để mỗi nút lân cận được truy cập nhiều nhất một lần

Ghi chú. Thay vì sử dụng vòng lặp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
84 cùng với toán tử hải mã [
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
85] để tạo ra một nút bị xếp hàng trong một biểu thức, bạn có thể tận dụng thực tế là hàng đợi tùy chỉnh của bạn có thể lặp lại bằng cách loại bỏ các phần tử bằng cách sử dụng vòng lặp
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
24

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
02

Tuy nhiên, điều này phụ thuộc vào chi tiết triển khai không rõ ràng trong lớp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
11 của bạn, vì vậy bạn sẽ gắn bó với vòng lặp
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
84 thông thường hơn trong suốt phần còn lại của hướng dẫn này

Hàm thứ hai được xây dựng trên đỉnh của hàm thứ nhất bằng cách lặp qua các nút đã tạo và dừng khi nút hiện tại đáp ứng các tiêu chí dự kiến. Nếu không có nút nào làm cho vị từ trung thực, thì hàm sẽ trả về ngầm định

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
89

Để kiểm tra hoạt động triển khai tìm kiếm và duyệt theo chiều rộng đầu tiên của bạn, bạn có thể thay thế chức năng tiện lợi được tích hợp trong networkx bằng chức năng của riêng bạn

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
03

Như bạn có thể thấy, thứ tự truyền tải giống hệt với lần thử đầu tiên của bạn với networkx, xác nhận rằng thuật toán của bạn hoạt động chính xác cho tập dữ liệu này. Tuy nhiên, chức năng của bạn không cho phép sắp xếp hàng xóm theo thứ tự cụ thể. Hãy thử sửa đổi mã để nó chấp nhận chiến lược sắp xếp tùy chọn. Bạn có thể nhấp vào phần có thể thu gọn bên dưới để xem một giải pháp khả thi

Giải pháp. Sắp xếp hàng xómHiển thị/Ẩn

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
04

Thuật toán tìm kiếm theo chiều rộng đảm bảo rằng cuối cùng bạn sẽ khám phá tất cả các nút được kết nối trong biểu đồ trong khi tìm kiếm nút thỏa mãn điều kiện mong muốn. Bạn có thể sử dụng nó để giải quyết một mê cung chẳng hạn. Phép duyệt theo chiều rộng cũng là cơ sở để tìm đường đi ngắn nhất giữa hai nút trong đồ thị vô hướng và không trọng số. Trong phần tiếp theo, bạn sẽ điều chỉnh mã của mình để làm việc đó

Remove ads

Đường dẫn ngắn nhất sử dụng tính năng truyền tải theo chiều rộng đầu tiên

Trong nhiều trường hợp, càng ít nút trên đường đi từ nguồn đến đích thì khoảng cách càng ngắn. Bạn có thể tận dụng quan sát này để ước tính khoảng cách ngắn nhất nếu các kết nối giữa các thành phố của bạn không có trọng số. Điều đó sẽ tương đương với việc có trọng lượng bằng nhau trên mọi cạnh

Duyệt qua biểu đồ bằng cách sử dụng phương pháp tiếp cận theo chiều rộng sẽ tạo ra một đường dẫn được đảm bảo có ít nút nhất. Đôi khi có thể có nhiều hơn một đường đi ngắn nhất giữa hai nút. Ví dụ: có hai con đường ngắn nhất như vậy giữa Aberdeen và Perth khi bạn bỏ qua khoảng cách đường. Như trước đây, kết quả thực tế trong trường hợp này sẽ phụ thuộc vào cách bạn sắp xếp thứ tự các nút lân cận

Bạn có thể sử dụng networkx để hiển thị tất cả các con đường ngắn nhất giữa hai thành phố, sẽ có cùng độ dài tối thiểu

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
05

Sau khi tải biểu đồ, bạn liệt kê các con đường ngắn nhất giữa hai thành phố và in chúng lên màn hình. Bạn có thể thấy chỉ có hai con đường ngắn nhất giữa Aberdeen và Perth. Ngược lại, London và Edinburgh có bốn đường đi ngắn nhất riêng biệt với chín nút mỗi đường, nhưng giữa chúng tồn tại nhiều đường đi dài hơn

Làm thế nào để duyệt theo chiều rộng đầu tiên giúp bạn tìm ra con đường ngắn nhất một cách chính xác?

Bất cứ khi nào bạn truy cập một nút, bạn phải theo dõi nút trước đó đã dẫn bạn đến nút đó bằng cách lưu thông tin này dưới dạng cặp khóa-giá trị trong từ điển. Later, you’ll be able to trace back your way from the destination to the source by following the previous nodes. Quay lại trình chỉnh sửa mã của bạn và tạo một hàm khác bằng cách sao chép và điều chỉnh mã từ hàm

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
90 trước đó của bạn

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
06

Hàm mới này lấy một nút khác làm đối số và tùy chọn cho phép bạn sắp xếp các nút lân cận bằng cách sử dụng chiến lược tùy chỉnh. Nó cũng xác định một từ điển trống mà bạn điền vào khi truy cập một hàng xóm bằng cách liên kết nó với nút trước đó trên đường dẫn của bạn. Tất cả các cặp khóa-giá trị trong từ điển này là hàng xóm ngay lập tức mà không có bất kỳ nút nào giữa chúng

Nếu một đường dẫn tồn tại giữa nguồn và đích của bạn, thì hàm này sẽ trả về một danh sách các nút được tạo bằng một hàm trợ giúp khác thay vì trả về các nút riêng lẻ như

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
90

Ghi chú. Bạn có thể thử cấu trúc lại mã này bằng cách kết hợp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
92 và
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
90 thành một hàm nếu muốn. Tuy nhiên, các lập trình viên có kinh nghiệm thường đồng ý rằng đôi khi lặp lại một chút cũng không sao miễn là nó giúp mã của bạn dễ hiểu hơn và tập trung vào một trách nhiệm.

Để tạo lại đường đi ngắn nhất giữa nguồn và đích, bạn có thể lặp lại tra cứu từ điển được tạo trước đó khi duyệt qua biểu đồ bằng cách tiếp cận theo chiều rộng

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
07

Bởi vì bạn bắt đầu từ điểm đến và quay trở lại, nên việc sử dụng bộ sưu tập Python

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
03 với thao tác chắp thêm nhanh ở bên trái có thể hữu ích. Tại mỗi lần lặp, bạn thêm nút hiện tại vào đường dẫn và di chuyển đến nút trước đó. Bạn lặp lại các bước này cho đến khi đến nút nguồn hoặc không có nút nào trước đó

Khi bạn gọi triển khai đường đi ngắn nhất dựa trên hàng đợi, bạn sẽ nhận được kết quả tương tự như với networkx

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
08

Đường dẫn đầu tiên tuân theo thứ tự tự nhiên của các hàng xóm từ tệp DOT, trong khi đường dẫn thứ hai ưu tiên các hàng xóm có vĩ độ cao hơn mà bạn chỉ định thông qua chiến lược sắp xếp tùy chỉnh. Để thực thi thứ tự giảm dần, bạn thêm dấu trừ [______095] trước thuộc tính

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
96

Lưu ý rằng một đường dẫn có thể không tồn tại đối với một số nút. Ví dụ: Belfast và Glasgow không có kết nối đất liền vì chúng nằm trên hai hòn đảo riêng biệt. Bạn cần đi phà để đi từ thành phố này sang thành phố khác. Giao dịch theo chiều rộng đầu tiên có thể cho bạn biết liệu hai nút có còn được kết nối hay không. Đây là cách thực hiện kiểm tra như vậy

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
09

Sau khi bắt đầu tại nút nguồn và duyệt qua toàn bộ biểu đồ con của các nút được kết nối, chẳng hạn như Bắc Ireland, từ điển của các nút trước đó sẽ không bao gồm nút đích của bạn. Do đó, quá trình quay lại sẽ dừng ngay lập tức và trả về

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
89, cho bạn biết không có đường dẫn giữa nguồn và đích

Bạn có thể xác minh điều này trong phiên phiên dịch Python tương tác

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
30

Đáng kinh ngạc. Với hàng đợi FIFO tùy chỉnh của bạn, bạn có thể duyệt qua biểu đồ, tìm đường đi ngắn nhất giữa hai nút và thậm chí xác định xem chúng có được kết nối hay không. Bằng cách thêm một tinh chỉnh nhỏ vào mã của bạn, bạn sẽ có thể thay đổi thứ tự truyền tải từ thứ tự theo chiều rộng sang thứ tự theo chiều sâu, điều mà bạn sẽ thực hiện ngay bây giờ

Tìm kiếm theo chiều sâu bằng hàng đợi LIFO

Đúng như tên gọi, đường duyệt theo chiều sâu đi theo một đường dẫn từ nút nguồn bằng cách đi sâu vào biểu đồ càng sâu càng tốt trước khi quay lại đường đi qua cạnh cuối cùng và thử một nhánh khác. Lưu ý sự khác biệt trong thứ tự truyền tải khi bạn sửa đổi một ví dụ trước đó bằng cách thay thế

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
78 bằng
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
99

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
31

Bây giờ, các nút lân cận được đánh dấu của nút nguồn không còn được khám phá theo trình tự. Sau khi đến Dundee, thuật toán tiếp tục đi theo cùng một con đường thay vì truy cập hàng xóm tiếp theo của Edinburgh trên lớp biểu đồ đầu tiên

Để tạo điều kiện quay lui, về cơ bản, bạn có thể thay thế hàng đợi FIFO bằng hàng đợi LIFO trong chức năng truyền tải theo chiều rộng trước và bạn sẽ tiến rất gần đến quá trình truyền tải theo chiều sâu. Tuy nhiên, nó sẽ chỉ hoạt động chính xác khi duyệt qua các cấu trúc dữ liệu dạng cây. Có một sự khác biệt nhỏ trong biểu đồ có chu kỳ, yêu cầu thay đổi bổ sung trong mã của bạn. Nếu không, bạn sẽ triển khai tính năng duyệt đồ thị dựa trên ngăn xếp, hoạt động hoàn toàn khác

Ghi chú. Trong duyệt cây nhị phân, thuật toán tìm kiếm theo chiều sâu xác định một vài thứ tự nổi tiếng cho các nút con truy cập—ví dụ: thứ tự trước, thứ tự sau và thứ tự sau

In the classic depth-first traversal, in addition to replacing the queue with a stack, you initially won’t mark the source node as visited

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
32

Lưu ý rằng các nút đã truy cập của bạn được khởi tạo thành một tập hợp trống trước khi bạn bắt đầu lấy các phần tử ra khỏi ngăn xếp. Bạn cũng kiểm tra xem nút đã được truy cập sớm hơn nhiều so với khi bạn truy cập theo chiều rộng trước chưa. Khi lặp lại các hàng xóm, bạn đảo ngược thứ tự của chúng để giải thích cho sự đảo ngược của hàng đợi LIFO. Cuối cùng, bạn không đánh dấu những người hàng xóm là đã truy cập ngay sau khi đẩy họ vào ngăn xếp

Vì quá trình truyền tải theo chiều sâu dựa trên cấu trúc dữ liệu ngăn xếp, nên bạn có thể tận dụng ngăn xếp cuộc gọi tích hợp để lưu đường dẫn tìm kiếm hiện tại để quay lui sau này và viết lại hàm của bạn theo cách đệ quy

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
33

Bằng cách đó, bạn tránh phải duy trì một ngăn xếp của riêng mình, vì Python đẩy từng lệnh gọi hàm vào một ngăn xếp ở hậu trường cho bạn. Nó bật lên khi hàm tương ứng trả về. Bạn chỉ cần theo dõi các nút đã truy cập. Another advantage of the recursive implementation is the fact that you don’t have to reverse the neighbors when iterating over them, and you don’t push already visited neighbors onto the stack

With the traversal function in place, you can now implement the depth-first search algorithm. Because both breadth-first and depth-first search algorithms look almost identical and only differ in the traversal order, you can refactor your code by delegating the common parts of both algorithms to a template function

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
34

Now, your

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
100 and
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
101 functions call
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
102 with the corresponding traversal strategy. Go ahead and test them in an interactive Python interpreter session

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
35

Even though the search result happens to be the same as with your breadth-first search algorithm, you can clearly see that the order of traversal is now different and follows a linear path

You’ve seen how choosing between a FIFO and a LIFO queue can affect the underlying graph traversal algorithm. So far, you’ve only considered the number of intermediate nodes when looking for the shortest path between two cities. In the next section, you’ll take it one step further by leveraging a priority queue to find the most optimal route, which may sometimes contain more nodes

Dijkstra’s Algorithm Using a Priority Queue

According to the graph in the sample DOT file, the paths with the fewest nodes between London and Edinburgh have exactly nine stops and a total distance ranging from 451 to 574 miles. There are four such paths

451 miles460 miles465 miles574 milesCity of LondonCity of LondonCity of LondonCity of LondonCoventryPeterboroughPeterboroughBristolBirminghamLincolnNottinghamNewportStoke-on-TrentSheffieldSheffieldSt AsaphLiverpoolWakefieldWakefieldLiverpoolPrestonYorkYorkPrestonLancasterDurhamDurhamLancasterCarlisleNewcastle upon TyneNewcastle upon TyneCarlisleEdinburghEdinburghEdinburghEdinburgh

There’s a significant overlap between these paths, as they quickly merge at a few intersections before your destination. To some degree, they also overlap with the only path with the shortest distance between London and Edinburgh, equal to 436 miles, despite having two more stops

  1. City of London
  2. St Albans
  3. Coventry
  4. Birmingham
  5. Stoke-on-Trent
  6. Manchester
  7. Salford
  8. Preston
  9. Lancaster
  10. Carlisle
  11. Edinburgh

Sometimes, it’s worthwhile to take a detour on your route to save time, fuel, or miles, even if it means going through more places along the way

When you throw edge weights into the mix, then interesting possibilities open up in front of you. For example, you can implement rudimentary artificial intelligence in a video game by assigning negative weights to edges that lead to a virtual enemy and positive weights that point you toward some reward. You may also represent moves in a game like the Rubik’s Cube as a decision tree to find the most optimal solution

Perhaps the most common use for traversing a weighted graph is when planning a route. A recipe to find the shortest path in a weighted graph, or a multigraph with many parallel connections, is Dijkstra’s algorithm, which builds on top of the breadth-first search algorithm. However, Dijkstra’s algorithm uses a special priority queue instead of the regular FIFO queue

Explaining Dijkstra’s shortest path algorithm is beyond the scope of this tutorial. However, in a nutshell, you can break it down into the following two steps

  1. Build the shortest-path three from a fixed source node to every other node in the graph
  2. Trace back the path from the destination to the source node in the same way as you did before with the plain shortest-path algorithm

The first part is about sweeping the weighted edges of every unvisited node in a greedy manner by checking whether they provide a cheaper connection from the source to one of the current neighbors. The total cost of a path from the source to the neighbor is the sum of the edge’s weight and the cumulative cost from the source to the currently visited node. Sometimes, a path consisting of more nodes will have a smaller total cost

Here’s a sample result of the first step of Dijkstra’s algorithm for the paths originating in Belfast

CityPreviousTotal CostArmaghLisburn41Belfast-0DerryBelfast71LisburnBelfast9NewryLisburn40

The first column in the table above indicates a destination city on the shortest path from the source. The second column shows the previous city on the shortest path from the source through which you’ll arrive at your destination. The last column contains information about the total distance to a city from the source

Belfast is the source city, so it has no previous node leading to it and a distance of zero. The source neighbors Derry and Lisburn, which you can reach from Belfast directly at the cost of their corresponding edges. To get to Armagh or Newry, going through Lisburn will give you the shortest total distance from Belfast

Now, if you want to find the shortest path between Belfast and Armagh, then start at your destination and follow the previous column. To reach Armagh, you must go through Lisburn, and to get to Lisburn, you must start in Belfast. That’s your shortest path in reverse order

You’ll only need to implement the first part of Dijkstra’s algorithm because you already have the second part, which is responsible for retracing the shortest path based on the previous nodes. However, to enqueue the unvisited nodes, you’ll have to use a mutable version of a min-heap so that you can update the element priorities as you discover cheaper connections. Expand the collapsible section below for the implementation of this new queue

Complete Code For the Mutable Min-HeapShow/Hide

Internally, this specialized priority queue stores data class elements instead of tuples because the elements must be mutable. Notice the additional

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
103 flag, which makes the elements comparable, just like tuples

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
36

This mutable min-heap behaves mostly the same as the regular priority queue that you coded before, but it also lets you peek or modify the priority of an element using the square bracket syntax

Once you have all elements in place, you can finally connect them together

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
37

Initially, the distance to all destination cities is unknown, so you assign an infinite cost to each unvisited city except for the source, which has a distance equal to zero. Later, when you find a cheaper path to a neighbor, you update its total distance from the source in the priority queue, which rebalances itself so that an unvisited node with the shortest distance will pop up first next time

You can test-drive your Dijkstra’s algorithm interactively and compare it against the networkx implementation

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
38

Success. Both functions yield exactly the same shortest path between London and Edinburgh

That concludes the theoretical part of this tutorial, which was quite intense. At this point, you have a pretty solid understanding of the different kinds of queues, you can implement them from scratch efficiently, and you know which one to choose in a given algorithm. In practice, however, you’re more likely to rely on the high-level abstractions provided by Python

Using Thread-Safe Queues

Now suppose you’ve written a program with more than one flow of execution. Beyond being a valuable algorithmic tool, queues can help abstract away concurrent access to a shared resource in a multithreaded environment without the need for explicit locking. Python provides a few synchronized queue types that you can safely use on multiple threads to facilitate communication between them

In this section, you’re going to implement the classic multi-producer, multi-consumer problem using Python’s thread-safe queues. More specifically, you’ll create a command-line script that lets you decide on the number of producers and consumers, their relative speed rates, and the type of queue

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
39

All parameters are optional and have sensible defaults. When you run this script, you’ll see an animated simulation of the producer and consumer threads communicating over a synchronized queue

Trực quan hóa Nhà sản xuất, Người tiêu dùng và Hàng đợi An toàn cho Chủ đề

The script uses the Rich library, which you’ll need to install into your virtual environment first

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
40

This will let you add colors, emojis, and visual components to your terminal. Note that some terminals may not support this kind of rich text formatting. Remember that at any point, you can download the complete source code of the scripts mentioned in this tutorial by following the link below if you haven’t already

Get Source Code. Click here to get access to the source code and sample data that you’ll use to explore queues in Python

Before you start using queues, you’ll have to do a bit of scaffolding. Create a new file named

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
104 and define the entry point to your script, which will parse arguments with the
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
105 module

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
41

Trước tiên, bạn nhập các lớp mô-đun và hàng đợi cần thiết vào không gian tên chung. Hàm

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
106 là điểm vào của bạn, hàm này nhận các đối số được phân tích cú pháp do
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
107 cung cấp, được định nghĩa bên dưới. Từ điển
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
108 ánh xạ tên hàng đợi tới các lớp tương ứng mà bạn gọi để tạo một thể hiện hàng đợi mới dựa trên giá trị của đối số dòng lệnh

Tiếp theo, bạn xác định các sản phẩm mà nhà sản xuất của bạn sẽ chọn ngẫu nhiên và giả vờ đang làm việc trên đó.

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
42

These are textual codes that Rich will eventually replace with the corresponding emoji glyphs. Ví dụ:

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
109 sẽ hiển thị dưới dạng 🎈. You can find all emoji codes available in Rich by running
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
110 in your terminal

Your producer and consumer threads will share a wealth of attributes and behaviors, which you can encapsulate in a common base class

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
43

Lớp công nhân mở rộng lớp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
111 và tự định cấu hình thành luồng daemon để các phiên bản của nó không ngăn chương trình của bạn thoát khi luồng chính kết thúc, chẳng hạn như do tín hiệu ngắt bàn phím. Một đối tượng công nhân mong đợi tốc độ hoạt động và hàng đợi bộ đệm để đưa thành phẩm vào hoặc lấy chúng từ

In addition to that, you’ll be able to check the state of a worker thread and request that it simulate some work or idle time

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
44

The

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
112 property returns a string with either the product’s name and the progress of work or a generic message indicating that the worker is currently idle. The
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
113 method resets the state of a worker thread and goes to sleep for a few randomly chosen seconds. Similarly,
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
114 picks a random delay in seconds adjusted to the worker’s speed and progresses through the work

Studying the presentation layer based on the Rich library isn’t crucial to understanding this example, but feel free to expand the collapsible section below for more details

Source Code For the

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
115 ClassShow/Hide

The code below defines a view that renders the current state of your producers, consumers, and the queue ten times a second

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
45

Notice the use of structural pattern matching to set the title and products based on the queue type. You’ll create an instance of the view and call its

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
116 method once the producers and consumers are in place

Next up, you’ll define the producer and consumer classes, and connect the pieces together

queue. Queue

The first synchronized queue that you’ll give a try is an unbounded FIFO queue or, simply put, a queue. You’ll need to pass it around to both your producers and consumers, so go ahead and define them now. The producer thread will extend a worker class and take an additional collection of products to choose from

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
46

The

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
117 method is where all the magic happens. A producer works in an infinite loop, choosing a random product and simulating some work before putting that product onto the queue, called a
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
118. It then goes to sleep for a random period, and when it wakes up again, the process repeats

A consumer is very similar, but even more straightforward than a producer

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
47

It also works in an infinite loop, waiting for a product to appear in the queue. The

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
119 method is blocking by default, which will keep the consumer thread stopped and waiting until there’s at least one product in the queue. This way, a waiting consumer won’t be wasting any CPU cycles while the operating system allocates valuable resources to other threads doing useful work

Note. To avoid a deadlock, you can optionally set a timeout on the

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
119 method by passing a
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
121 keyword argument with the number of seconds to wait before giving up

Each time you get something from a synchronized queue, its internal counter increases to let other threads know the queue hasn’t been drained yet. Therefore, it’s important to mark a dequeued task as done when you’re finished processing it unless you don’t have any threads joining the queue. Doing so decreases the queue’s internal counter

Now, go back to your

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
106 function, create the producer and consumer threads, and start them

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
48

The number of producers and consumers is determined by the command-line arguments passed into your function. They’ll begin working and using the queue for interthread communication as soon as you start them. The

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
115 instance at the bottom continually re-renders the screen to reflect the current state of your application

Thread-Safe FIFO Queue

Producers will always push their finished products through that queue to the consumers. Even though it may sometimes appear as if a consumer takes an element directly from a producer, it’s only because things are happening too fast to notice the enqueue and dequeue operations

Note. It’s worth noting that whenever a producer puts something onto a synchronized queue, at most one consumer will dequeue that element and process it without other consumers ever knowing. If you wish to notify more than one consumer about a particular event in your program, then have a look at other thread coordination primitives in the

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
124 module

You can increase the number of producers, their speed, or both to see how these changes affect the overall capacity of your system. Because the queue is unbounded, it’ll never slow down the producers. However, if you specified the queue’s

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
125 parameter, then it would start blocking them until there was enough space in the queue again

Using a FIFO queue makes the producers put elements on the left end of the queue in the visualization above. At the same time, consumers compete with each other for the rightmost product in the queue. In the next section, you’ll see how this behavior changes when you call the script with the

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
126 option

queue. LifoQueue

From your workers’ perspective, there’s absolutely no need to make any changes to your code in order to modify how they communicate. Merely by injecting a different type of synchronized queue into them, you can modify the rules of the workers’ communication. Run your script with a LIFO queue now

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
49

When you use a LIFO queue or a stack, each new product that has just been created will take precedence over the older ones in the queue

Thread-Safe LIFO Queue

In rare cases, when new products are created more quickly than your consumers can cope with them, older products might suffer from starvation because they get stuck at the bottom of the stack and never get consumed. On the other hand, that might not be a problem when you have a big enough consumer pool or when you don’t get as many incoming products. Starvation can also involve elements on a priority queue, which you’ll read about next

queue. PriorityQueue

To use a synchronized priority queue or a heap, you’ll need to make a few adjustments in your code. First of all, you’re going to need a new kind of product that has an associated priority, so define two new data types

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
60

To represent products, you use a data class with a customized string representation and ordering enabled, but you’re careful not to compare the products by their label. In this case, you expect the label to be a string, but generally, it could be any object that might not be comparable at all. You also define an enum class with known priority values and three products with descending priorities from highest to lowest

Note. Contrary to your earlier priority queue implementation, Python’s thread-safe queue orders elements with the lowest numeric priority value first

Additionally, when the user supplies the

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
127 option in the command line, then you must supply the right collection of products to your producer threads

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
61

You provide either plain or prioritized products depending on a command-line argument using a conditional expression

The rest of your code can remain agnostic to this change as long as the producers and consumers know how to deal with a new product type. Because this is only a simulation, the worker threads don’t really do anything useful with the products, so you can run your script with the

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
127 flag and see the effect

Thread-Safe Priority Queue

Remember that a heap data structure is a binary tree, which keeps a specific relationship between its elements. Therefore, even though the products in the priority queue don’t appear to be arranged quite correctly, they’re actually consumed in the right order. Also, because of the non-deterministic nature of multithreaded programming, Python queues don’t always report their most up-to-date size

All right, you’ve witnessed the traditional way of coordinating worker threads using three types of synchronized queues. Python threads are well-suited to I/O-bound tasks, which spend most of their time waiting for data on the network, the file system, or a database. However, there’s recently been a single-threaded alternative to synchronized queues, taking advantage of Python’s asynchronous features. That’s what you’ll look at now

Sử dụng hàng đợi không đồng bộ

Nếu bạn muốn sử dụng hàng đợi trong ngữ cảnh không đồng bộ, thì Python đã bảo vệ bạn. Mô-đun

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
129 cung cấp các bản sao không đồng bộ cho các hàng đợi từ mô-đun
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
124, bạn có thể sử dụng mô-đun này trong các hàm coroutine trên một luồng đơn lẻ. Vì cả hai họ hàng đợi đều có chung một giao diện nên việc chuyển đổi từ họ này sang họ khác sẽ tương đối dễ dàng

In this section, you’ll write a rudimentary web crawler, which recursively follows links on a specified website up to a given depth level and counts the number of visits per link. Để tìm nạp dữ liệu không đồng bộ, bạn sẽ sử dụng thư viện

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
131 phổ biến và để phân tích cú pháp các siêu liên kết HTML, bạn sẽ dựa vào
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
132. Đảm bảo cài đặt cả hai thư viện vào môi trường ảo của bạn trước khi tiếp tục

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
62

Now you can make HTTP requests asynchronously and select HTML elements from a so-called tag soup received from the server

Ghi chú. Bạn có thể sử dụng Beautiful Soup và Python để xây dựng công cụ quét web, thu thập dữ liệu có giá trị khi truy cập các trang web

Để đặt nền tảng cho trình thu thập dữ liệu web của bạn, trước tiên bạn sẽ tạo một vài khối xây dựng. Tạo một tệp mới có tên

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
133 và xác định cấu trúc sau trong đó

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
63

As with most asynchronous programs, you pass your

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
106 coroutine to
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
135 so that it can execute it on the default event loop. The coroutine takes a few command-line arguments parsed with a helper function defined below, starts a new
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
136, and defines a counter of the visited links. Sau đó, coroutine hiển thị danh sách các liên kết được sắp xếp theo số lượt truy cập theo thứ tự giảm dần

Để chạy tập lệnh của bạn, bạn sẽ chỉ định một URL gốc và tùy chọn độ sâu tối đa và số lượng công nhân. Đây là một ví dụ

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
64

Vẫn còn thiếu một vài phần như tìm nạp nội dung và phân tích cú pháp liên kết HTML, vì vậy hãy thêm chúng vào tệp của bạn

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
65

You’ll only return the received content as long as it’s HTML, which you can tell by looking at the

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
137 HTTP header. Khi trích xuất các liên kết từ nội dung HTML, bạn sẽ bỏ qua JavaScript nội tuyến trong thuộc tính
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
138 và tùy ý nối một đường dẫn tương đối với URL hiện tại

Tiếp theo, bạn sẽ xác định một kiểu dữ liệu mới đại diện cho một công việc mà bạn sẽ đưa vào hàng đợi, cũng như một công nhân không đồng bộ đang thực hiện công việc đó

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
66

Một công việc bao gồm địa chỉ URL để truy cập và độ sâu hiện tại mà nhân viên sẽ sử dụng để dừng thu thập dữ liệu theo cách đệ quy. Thanks to specifying a job as a named tuple, you unpack its individual components on the highlighted line after dequeuing it. When you don’t specify the depth for a job, then it defaults to one

Công nhân ngồi trong một vòng lặp vô hạn, chờ một công việc đến trong hàng đợi. Sau khi sử dụng một công việc duy nhất, công nhân có thể đặt một hoặc nhiều công việc mới với độ sâu tăng lên trong hàng đợi để công nhân đó hoặc công nhân khác sử dụng

Because your worker is both a producer and a consumer, it’s crucial to unconditionally mark a job as done in a

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
139 …
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
140 clause to avoid a deadlock. Bạn cũng nên xử lý các lỗi trong nhân viên của mình vì các trường hợp ngoại lệ chưa được xử lý sẽ khiến nhân viên của bạn ngừng nhận công việc mới.

Note. Bạn có thể sử dụng hàm

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
141 trong mã không đồng bộ—ví dụ: để ghi thông báo chẩn đoán—vì mọi thứ chạy trên một luồng đơn. On the other hand, you’d have to replace it with the
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
142 module in a multithreaded code because the
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
141 function isn’t thread-safe

Also, notice that you print the diagnostic messages to standard error [stderr], while the output of your program prints to standard output [stdout], which are two completely separate streams. Điều này cho phép bạn chuyển hướng một hoặc cả hai đến một tệp, chẳng hạn

Nhân viên của bạn tăng số lần truy cập khi truy cập một URL. Additionally, if the current URL’s depth doesn’t exceed the maximum allowed depth, then the worker fetches the HTML content that the URL points to and iterates over its links. The walrus operator [

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
85] lets you await an HTTP response, check if the content was returned, and assign the result to the
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
145 variable in a single expression

The last remaining step is to create an instance of the asynchronous queue and pass it to the workers

không đồng bộ. Queue

Trong phần này, bạn sẽ cập nhật quy trình đăng ký

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
106 của mình bằng cách tạo hàng đợi và các tác vụ không đồng bộ chạy công nhân của bạn. Mỗi worker sẽ nhận được một mã định danh duy nhất để phân biệt nó trong thông báo tường trình, phiên
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
131, phiên bản hàng đợi, bộ đếm lượt truy cập vào một liên kết cụ thể và độ sâu tối đa. Bởi vì bạn đang sử dụng một luồng duy nhất, bạn không cần đảm bảo quyền truy cập loại trừ lẫn nhau vào các tài nguyên được chia sẻ

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
67

Here’s a line-by-line breakdown of the updated code

  • Line 9 instantiates an asynchronous FIFO queue
  • Các dòng 10 đến 21 tạo một số worker coroutine được bao bọc trong các tác vụ không đồng bộ bắt đầu chạy ngay khi có thể trong nền trên vòng lặp sự kiện
  • Dòng 23 đưa công việc đầu tiên vào hàng đợi, bắt đầu quá trình thu thập thông tin
  • Dòng 24 khiến quy trình đăng ký chính phải đợi cho đến khi hàng đợi đã được thoát và không còn công việc nào để thực hiện
  • Lines 26 to 29 do a graceful cleanup when the background tasks are no longer needed

Vui lòng không chạy trình thu thập dữ liệu web đối với một trang web thực tế được lưu trữ trực tuyến. Nó có thể gây ra sự tăng đột biến không mong muốn trong lưu lượng mạng và khiến bạn gặp rắc rối. Để kiểm tra trình thu thập thông tin của bạn, tốt hơn hết bạn nên khởi động một máy chủ HTTP được tích hợp trong Python, máy chủ này sẽ biến một thư mục cục bộ trong hệ thống tệp của bạn thành một trang web có thể điều hướng. For example, the following command will start a server in a local folder with a Python virtual environment

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
68

Tuy nhiên, đây không phải là một sự tương đồng lý tưởng với một trang web trong thế giới thực, bởi vì các tệp và thư mục tạo nên một hệ thống phân cấp giống như cây, trong khi các trang web thường được biểu thị bằng nhiều đồ thị dày đặc với các liên kết ngược. Anyway, when you run the web crawler against a chosen URL address in another terminal window, you’ll notice that the crawler follows the links in their natural order of appearance

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
69

Nó truy cập URL duy nhất ở cấp độ đầu tiên với độ sâu bằng một. Sau đó, sau khi truy cập tất cả các liên kết ở cấp độ thứ hai, trình thu thập thông tin sẽ chuyển sang cấp độ thứ ba, v.v. cho đến khi đạt đến độ sâu tối đa được yêu cầu. Khi tất cả các liên kết ở một cấp độ nhất định được khám phá, trình thu thập thông tin sẽ không bao giờ quay lại cấp độ trước đó. That’s a direct consequence of using a FIFO queue, which is different from using a stack or a LIFO queue

asyncio. LifoQueue

As with the synchronized queues, their asynchronous companions let you change the behavior of your workers without modifying their code. Go back to your

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
148 module and replace the existing FIFO queue with a LIFO one

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
30

Without stopping your HTTP server, run the web crawler using the same options again

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
31

Assuming the content hasn’t changed since the last run, the crawler visits identical links but in a different order. Các dòng được đánh dấu cho biết việc truy cập một liên kết ở mức độ sâu đã khám phá trước đó

Note. If you kept track of the already visited links and skipped them on the subsequent encounters, then it could lead to different outputs depending on the queue type used. That’s because many alternative paths might originate on different depth levels but lead up to the same destination

Tiếp theo, bạn sẽ thấy hàng đợi ưu tiên không đồng bộ đang hoạt động

không đồng bộ. Hàng đợi ưu tiên

To use your jobs in a priority queue, you must specify how to compare them when deciding on their priorities. Ví dụ: bạn có thể muốn truy cập các URL ngắn hơn trước. Go ahead and add the

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
149 special method to your
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
150 class, to which the less than [
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
02] operator delegates when comparing two job instances

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
32

If you compare a job to a completely different data type, then you can’t say which one is smaller, so you implicitly return

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
89. On the other hand, when comparing two instances of the
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
150 class, you resolve their priorities by examining the lengths of their corresponding
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
154 fields

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
33

The shorter the URL, the higher the priority because smaller values take precedence in a min-heap

The last change to make in your script is using the asynchronous priority queue instead of the other two

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
34

Try running your web crawler with an even bigger maximum depth value—say, five

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
35

You’ll immediately notice that links are generally explored in the order determined by the URL lengths. Đương nhiên, thứ tự chính xác sẽ thay đổi một chút sau mỗi lần chạy do tính chất không xác định của thời gian máy chủ trả lời

Asynchronous queues are a fairly new addition to the Python standard library. They deliberately mimic an interface of the corresponding thread-safe queues, which should make any seasoned Pythonista feel at home. You can use asynchronous queues to exchange data between coroutines

In the next section, you’ll familiarize yourself with the last family of queues available in the Python standard library, which lets you communicate across two or more OS-level processes

Using
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
155 for Interprocess Communication [IPC]

So far, you’ve looked into queues that can only help in scenarios with strictly I/O-bound tasks, whose progress doesn’t depend on the available computational power. On the other hand, the traditional approach to running CPU-bound tasks on multiple CPU cores in parallel with Python takes advantage of cloning the interpreter process. Your operating system provides the interprocess communication [IPC] layer for sharing data across these processes

For example, you can start a new Python process with

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
156 or use a pool of such processes from the
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
157 module. Cả hai mô-đun đều được thiết kế cẩn thận để giúp quá trình chuyển đổi từ luồng sang quy trình diễn ra suôn sẻ nhất có thể, giúp việc song song hóa mã hiện tại của bạn trở nên đơn giản hơn. In some cases, it’s just a matter of replacing an import statement because the rest of the code follows a standard interface

You’ll only find the FIFO queue in the

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
156 module, which comes in three variants

  1. >>> from queues import Queue
    
    >>> fifo = Queue[]
    >>> fifo.enqueue["1st"]
    >>> fifo.enqueue["2nd"]
    >>> fifo.enqueue["3rd"]
    
    >>> fifo.dequeue[]
    '1st'
    >>> fifo.dequeue[]
    '2nd'
    >>> fifo.dequeue[]
    '3rd'
    
    155
  2. >>> from queues import Queue
    
    >>> fifo = Queue[]
    >>> fifo.enqueue["1st"]
    >>> fifo.enqueue["2nd"]
    >>> fifo.enqueue["3rd"]
    
    >>> fifo.dequeue[]
    '1st'
    >>> fifo.dequeue[]
    '2nd'
    >>> fifo.dequeue[]
    '3rd'
    
    160
  3. >>> from queues import Queue
    
    >>> fifo = Queue[]
    >>> fifo.enqueue["1st"]
    >>> fifo.enqueue["2nd"]
    >>> fifo.enqueue["3rd"]
    
    >>> fifo.dequeue[]
    '1st'
    >>> fifo.dequeue[]
    '2nd'
    >>> fifo.dequeue[]
    '3rd'
    
    161

Tất cả chúng đều được mô phỏng theo

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
162 dựa trên luồng nhưng khác nhau về mức độ hoàn thiện.
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
163 mở rộng lớp
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
155 bằng cách thêm các phương thức
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
165 và
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
166, cho phép bạn đợi cho đến khi tất cả các tác vụ được xếp hàng đã được xử lý. Nếu bạn không cần tính năng này, hãy sử dụng
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
155 để thay thế.
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
168 là một lớp riêng biệt, được sắp xếp hợp lý đáng kể, chỉ có các phương thức
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
119,
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
170 và
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
171

Ghi chú. Chia sẻ tài nguyên, chẳng hạn như hàng đợi, giữa các quy trình của hệ điều hành đắt hơn và hạn chế hơn nhiều so với chia sẻ giữa các luồng. Không giống như các luồng, các quy trình không chia sẻ một vùng bộ nhớ chung, vì vậy dữ liệu phải được sắp xếp theo thứ tự và không được sắp xếp theo thứ tự ở cả hai đầu mỗi khi bạn chuyển một thông báo từ quy trình này sang quy trình khác

Hơn nữa, Python sử dụng mô-đun

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
172 để tuần tự hóa dữ liệu, mô-đun này không xử lý mọi loại dữ liệu và tương đối chậm và không an toàn. Do đó, bạn chỉ nên xem xét nhiều quy trình khi hiệu suất được cải thiện bằng cách chạy mã song song có thể bù đắp chi phí khởi động và tuần tự hóa dữ liệu bổ sung

Để xem ví dụ thực tế về

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
155, bạn sẽ mô phỏng một nhiệm vụ tính toán chuyên sâu bằng cách cố gắng đảo ngược giá trị băm MD5 của một văn bản ngắn bằng cách sử dụng phương pháp brute-force. Mặc dù có nhiều cách tốt hơn để giải quyết vấn đề này, cả về mặt thuật toán và lập trình, nhưng việc chạy song song nhiều quy trình sẽ giúp bạn giảm đáng kể thời gian xử lý

Đảo ngược MD5 Hash trên một luồng đơn

Trước khi song song hóa tính toán của bạn, bạn sẽ tập trung vào việc triển khai phiên bản thuật toán đơn luồng và đo thời gian thực hiện đối với một số đầu vào thử nghiệm. Tạo một mô-đun Python mới có tên là

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
174 và đặt đoạn mã sau vào đó

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
36

Lines 8 to 14 define a function that’ll try to reverse an MD5 hash value provided as the first argument. Theo mặc định, hàm chỉ xem xét văn bản bao gồm tối đa sáu chữ cái ASCII viết thường. Bạn có thể thay đổi bảng chữ cái và độ dài tối đa của văn bản để đoán bằng cách cung cấp hai đối số tùy chọn khác

For every possible combination of letters in the alphabet with the given length,

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
175 calculates a hash value and compares it against the input. Nếu khớp thì dừng và trả về văn bản đã đoán

Note. Ngày nay, MD5 được coi là không an toàn về mật mã vì bạn có thể tính toán các thông báo như vậy một cách nhanh chóng. Tuy nhiên, sáu ký tự được lấy từ 26 chữ cái ASCII mang lại tổng cộng 308.915.776 kết hợp riêng biệt, rất nhiều cho một chương trình Python

Lines 16 to 19 call the function with a sample MD5 hash value passed as an argument and measure its execution time using a Python timer. On a veteran desktop computer, it can take a few seconds to find a combination that hashes to the specified input

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
37

Như bạn có thể thấy, từ queue là câu trả lời vì nó có thông báo MD5 khớp với giá trị băm được mã hóa cứng của bạn trên dòng 18. Seven seconds isn’t terrible, but you can probably do better by taking advantage of your idle CPU cores, which are eager to do some work for you. Để tận dụng tiềm năng của chúng, bạn phải chia nhỏ dữ liệu và phân phối dữ liệu đó cho các quy trình công nhân của bạn

Phân phối khối lượng công việc đồng đều theo khối

Bạn muốn thu hẹp không gian tìm kiếm trong mỗi công nhân bằng cách chia toàn bộ tập hợp các chữ cái thành một vài tập hợp con rời rạc nhỏ hơn. To ensure that workers don’t waste time doing work that’s already been done by another worker, the sets can’t have any overlap. Mặc dù bạn không biết kích thước của một khối riêng lẻ, nhưng bạn có thể cung cấp số lượng khối bằng với số lõi CPU

Để tính chỉ số của các khối tiếp theo, hãy sử dụng hàm trợ giúp bên dưới

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
38

It yields tuples consisting of the first index of the current chunk and its last index increased by one, making the tuple convenient to use as input to the built-in

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
176 function. Because of rounding of the subsequent chunk lengths, those with varying lengths end up nicely interleaved

>>>

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
39

For example, a total length of twenty divided into six chunks yields indices that alternate between three and four elements

Để giảm thiểu chi phí tuần tự hóa dữ liệu giữa các quy trình của bạn, mỗi công nhân sẽ tạo ra một đoạn kết hợp chữ cái của riêng mình dựa trên phạm vi chỉ số được chỉ định trong đối tượng công việc xếp hàng. Bạn sẽ muốn tìm một tổ hợp chữ cái hoặc n-bộ của m-set cho một chỉ mục cụ thể. To make your life easier, you can encapsulate the formula for the combination in a new class

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
40

Loại dữ liệu tùy chỉnh này đại diện cho một tập hợp các kết hợp chữ cái trong bảng chữ cái với độ dài nhất định. Nhờ có hai phương thức đặc biệt và tăng ngoại lệ

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
177 khi tất cả các kết hợp đã cạn kiệt, bạn có thể lặp lại các phiên bản của lớp
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
178 bằng cách sử dụng một vòng lặp

Công thức trên xác định ký tự tại một vị trí nhất định trong một tổ hợp được chỉ định bởi một chỉ số, giống như đồng hồ đo quãng đường hoạt động trong ô tô hoặc hệ thống vị trí trong toán học. Các chữ cái ở vị trí ngoài cùng bên phải thay đổi thường xuyên nhất, trong khi các chữ cái ít thay đổi hơn khi chúng ở xa bên trái hơn

You can now update your MD5-reversing function to use the new class and remove the

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
179 import statement

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
41

Unfortunately, replacing a built-in function implemented in C with a pure-Python one and doing some calculations in Python make the code an order of magnitude slower

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
42

Có một vài tối ưu hóa mà bạn có thể thực hiện để đạt được vài giây. Ví dụ: bạn có thể triển khai

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
23 trong lớp
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
178 của mình để tránh đưa ra câu lệnh
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
182 hoặc đưa ra một ngoại lệ. Bạn cũng có thể lưu trữ độ dài của bảng chữ cái dưới dạng thuộc tính thể hiện. Tuy nhiên, những tối ưu hóa này không quan trọng vì lợi ích của ví dụ

Tiếp theo, bạn sẽ tạo quy trình công nhân, loại dữ liệu công việc và hai hàng đợi riêng biệt để giao tiếp giữa quy trình chính và quy trình con của nó

Communicating in Full-Duplex Mode

Each worker process will have a reference to the input queue with jobs to consume and a reference to the output queue for the prospective solution. Các tham chiếu này cho phép giao tiếp hai chiều đồng thời giữa worker và quy trình chính, được gọi là giao tiếp song công hoàn toàn. Để định nghĩa một worker process, bạn mở rộng lớp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
183, lớp này cung cấp phương thức
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
117 quen thuộc, giống như một luồng

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
43

Later, the main process will periodically check whether one of the workers has placed a reversed MD5 text on the output queue and terminate the program early in such a case. The workers are daemons, so they won’t hold up the main process. Cũng lưu ý rằng công nhân lưu trữ giá trị băm đầu vào để đảo ngược

Thêm một lớp

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
150 mà Python sẽ tuần tự hóa và đặt vào hàng đợi đầu vào để các quy trình worker sử dụng

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
44

By implementing the special method

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
186 in a job, you make objects of your class callable. Thanks to that, the workers can call these jobs just like regular functions when they receive them. Phần thân của phương thức này tương tự nhưng hơi khác với
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
175, bạn có thể xóa phương thức này ngay bây giờ vì bạn sẽ không cần đến nó nữa

Cuối cùng, tạo cả hai hàng đợi và điền vào hàng đợi đầu vào với các công việc trước khi bắt đầu các quy trình worker của bạn

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
45

Như trong các ví dụ trước, bạn phân tích các đối số dòng lệnh bằng cách sử dụng mô-đun

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
105. Đối số bắt buộc duy nhất cho tập lệnh của bạn là giá trị băm cần đảo ngược, chẳng hạn như

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
46

Bạn có thể tùy ý chỉ định số lượng worker process bằng cách sử dụng tham số dòng lệnh

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
189, mặc định là số lượng lõi CPU của bạn. Thường không có lợi ích gì khi tăng số lượng công nhân lên trên số lượng đơn vị xử lý logic hoặc vật lý trong phần cứng do chi phí chuyển đổi ngữ cảnh bổ sung bắt đầu tăng lên

On the other hand, context switching becomes almost negligible in I/O-bound tasks, where you might end up having thousands of worker threads or coroutines. Processes are a different story because they’re much more expensive to create. Even if you front-load this cost using a process pool, there are certain limits

At this point, your workers engage in two-way communication with the main process through the input and output queues. Tuy nhiên, chương trình thoát đột ngột ngay sau khi bắt đầu vì tiến trình chính kết thúc mà không đợi các daemon con xử lý xong công việc của chúng. Bây giờ là lúc để thăm dò định kỳ hàng đợi đầu ra cho một giải pháp tiềm năng và thoát ra khỏi vòng lặp khi bạn tìm thấy một giải pháp.

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
47

You set the optional

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
121 parameter on the queue’s
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
119 method to avoid blocking and allow the while-loop to run its condition. Khi một giải pháp được tìm thấy, bạn loại bỏ nó khỏi hàng đợi đầu ra, in văn bản phù hợp trên đầu ra tiêu chuẩn cùng với thời gian thực hiện ước tính và thoát ra khỏi vòng lặp. Note that
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
155 raises exceptions defined in the
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
14 module, which you might need to import

However, when there’s no matching solution, the loop will never stop because your workers are still alive, waiting for more jobs to process even after having consumed all of them. Họ bị mắc kẹt trong cuộc gọi

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
194 đang chặn. You’ll fix that in the upcoming section

Killing a Worker With the Poison Pill

Vì số lượng công việc cần sử dụng đã được biết trước, bạn có thể yêu cầu công nhân tắt máy một cách nhẹ nhàng sau khi hoàn thành hàng đợi. A typical pattern to request a thread or process stop working is by putting a special sentinel value at the end of the queue. Whenever a worker finds that sentinel, it’ll do the necessary cleanup and escape the infinite loop. Lính gác như vậy được gọi là thuốc độc vì nó giết chết công nhân

Việc chọn giá trị cho một sentinel có thể phức tạp, đặc biệt là với mô-đun

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
156 vì cách nó xử lý không gian tên chung. Kiểm tra hướng dẫn lập trình trong tài liệu chính thức để biết thêm chi tiết. Có lẽ an toàn nhất là bám vào một giá trị được xác định trước, chẳng hạn như
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
89, có danh tính đã biết ở mọi nơi

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
48

Nếu bạn đã sử dụng một phiên bản

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
197 tùy chỉnh được định nghĩa là một biến toàn cục, thì mỗi quy trình công nhân của bạn sẽ có bản sao riêng của đối tượng đó với một danh tính duy nhất. Một đối tượng sentinel được một công nhân xử lý sẽ được giải tuần tự hóa thành một thể hiện hoàn toàn mới trong một công nhân khác, có một danh tính khác với biến toàn cục của nó. Do đó, bạn sẽ không thể phát hiện ra một viên thuốc độc trong hàng đợi

Một sắc thái khác cần chú ý là cẩn thận đặt viên thuốc độc trở lại hàng nguồn sau khi tiêu thụ nó.

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
49

Điều này sẽ tạo cơ hội cho những công nhân khác uống viên thuốc độc. Ngoài ra, nếu bạn biết chính xác số lượng công nhân của mình, thì bạn có thể đặt nhiều viên thuốc độc đó, mỗi người một viên. Sau khi tiêu thụ và đưa lính canh trở lại hàng đợi, một công nhân thoát ra khỏi vòng lặp vô hạn, kết thúc cuộc đời của nó

Cuối cùng, đừng quên thêm viên thuốc độc làm thành phần cuối cùng trong hàng đợi đầu vào

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
50

Bây giờ, tập lệnh của bạn đã hoàn tất và có thể xử lý việc tìm văn bản phù hợp cũng như đối mặt với các tình huống khi không thể đảo ngược giá trị băm MD5. Trong phần tiếp theo, bạn sẽ chạy một số điểm chuẩn để xem liệu toàn bộ bài tập này có xứng đáng với nỗ lực không

Analyzing the Performance of Parallel Execution

Khi bạn so sánh tốc độ thực thi của phiên bản đơn luồng ban đầu và phiên bản đa xử lý, thì bạn có thể thất vọng. Mặc dù bạn đã quan tâm đến việc giảm thiểu chi phí tuần tự hóa dữ liệu, nhưng việc viết lại các đoạn mã thành Python thuần túy là nút cổ chai thực sự

Điều đáng ngạc nhiên hơn nữa là tốc độ dường như thay đổi khi thay đổi giá trị băm đầu vào cũng như số lượng worker process

Số lượng quy trình công nhân so với thời gian thực hiện

Bạn sẽ nghĩ rằng việc tăng số lượng công nhân sẽ làm giảm thời gian tính toán tổng thể và điều đó xảy ra ở một mức độ nhất định. Có một sự sụt giảm lớn từ một công nhân thành nhiều công nhân. Tuy nhiên, thời gian thực hiện nhảy qua nhảy lại định kỳ khi bạn thêm nhiều công nhân hơn. Có một vài yếu tố chơi ở đây

Primarily, the lucky worker that gets assigned a chunk containing your solution will run longer if the matching combination is located near the end of that chunk. Tùy thuộc vào các điểm phân chia trong không gian tìm kiếm của bạn, xuất phát từ số lượng công nhân, bạn sẽ có một khoảng cách khác đến giải pháp trong một đoạn. Thứ hai, bạn càng tạo ra nhiều công nhân, thì tác động chuyển đổi ngữ cảnh bắt đầu có tác động càng lớn, ngay cả khi khoảng cách vẫn giữ nguyên

On the other hand, if all of your workers always had the same amount of work to do, then you’d observe a roughly linear trend without the sudden jumps. Như bạn có thể thấy, song song hóa việc thực thi mã Python không phải lúc nào cũng đơn giản. Điều đó nói rằng, với một chút kiên nhẫn và bền bỉ, bạn chắc chắn có thể tối ưu hóa một số nút cổ chai đó. Ví dụ, bạn có thể

  • Tìm ra một công thức thông minh hơn
  • Trao đổi bộ nhớ để lấy tốc độ bằng cách lưu vào bộ nhớ đệm và tính toán trước các kết quả trung gian
  • Các lệnh gọi hàm nội tuyến và các cấu trúc đắt tiền khác
  • Tìm thư viện C của bên thứ ba có liên kết Python
  • Viết mô-đun mở rộng Python C hoặc sử dụng ctypes hoặc Cython
  • Bring just-in-time [JIT] compilation tools for Python
  • Chuyển sang một trình thông dịch Python thay thế như PyPy

Tại thời điểm này, bạn đã bao gồm tất cả các loại hàng đợi có sẵn trong thư viện chuẩn Python, bao gồm hàng đợi an toàn cho chuỗi được đồng bộ hóa, hàng đợi không đồng bộ và hàng đợi FIFO cho xử lý song song dựa trên quy trình. Trong phần tiếp theo, bạn sẽ xem qua một vài thư viện của bên thứ ba sẽ cho phép bạn tích hợp với các trình môi giới hàng đợi tin nhắn độc lập

Tích hợp Python với hàng đợi tin nhắn phân tán

Trong các hệ thống phân tán có nhiều bộ phận chuyển động, bạn thường muốn tách riêng các thành phần ứng dụng của mình bằng cách sử dụng một nhà môi giới thông báo trung gian, điều này sẽ chịu trách nhiệm phân phối thông báo linh hoạt giữa nhà sản xuất và dịch vụ người tiêu dùng. Nó thường yêu cầu cơ sở hạ tầng riêng, đây vừa là ưu điểm vừa là nhược điểm

On the one hand, it’s yet another abstraction layer that adds complexity and needs maintenance, but when configured correctly, it can provide these benefits

  • khớp nối lỏng lẻo. Bạn có thể sửa đổi hoặc thay thế một thành phần này bằng một thành phần khác mà không ảnh hưởng đến phần còn lại của hệ thống
  • Flexibility. Bạn có thể thay đổi quy tắc kinh doanh của hệ thống bằng cách thay đổi cấu hình trình môi giới và quy tắc gửi tin nhắn mà không cần viết mã
  • khả năng mở rộng. Bạn có thể tự động thêm nhiều thành phần của một loại nhất định để xử lý khối lượng công việc gia tăng trong một khu vực chức năng cụ thể
  • độ tin cậy. Consumers may need to acknowledge a message before the broker removes it from a queue to ensure safe delivery. Chạy trình môi giới trong cụm có thể cung cấp khả năng chịu lỗi bổ sung
  • kiên trì. Người môi giới có thể giữ một số tin nhắn trong hàng đợi trong khi người tiêu dùng ngoại tuyến do lỗi
  • Màn biểu diễn. Việc sử dụng cơ sở hạ tầng chuyên dụng cho trình trung chuyển tin nhắn sẽ giảm tải các dịch vụ ứng dụng của bạn

Có nhiều loại trình môi giới tin nhắn và tình huống khác nhau mà bạn có thể sử dụng chúng. Trong phần này, bạn sẽ được nếm thử một vài trong số chúng

ThỏMQ.
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
198

RabbitMQ có lẽ là một trong những nhà môi giới tin nhắn nguồn mở phổ biến nhất, cho phép bạn định tuyến tin nhắn từ nhà sản xuất đến người tiêu dùng theo nhiều cách. You can conveniently start a new RabbitMQ broker without installing it on your computer by running a temporary Docker container

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
51

Khi nó bắt đầu, bạn có thể kết nối với nó trên máy chủ cục bộ của mình và cổng mặc định 5672. Tài liệu chính thức khuyến nghị sử dụng thư viện Pika để kết nối với phiên bản RabbitMQ trong Python. Đây là những gì một nhà sản xuất thô sơ có thể trông giống như

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
52

Bạn mở một kết nối bằng các tham số mặc định, giả định rằng RabbitMQ đã chạy trên máy cục bộ của bạn. Sau đó, bạn tạo một kênh mới, là một bản tóm tắt nhẹ trên kết nối TCP. You can have multiple independent channels for separate transmissions. Trước khi vào vòng lặp, bạn đảm bảo rằng một hàng đợi có tên

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
199 tồn tại trong nhà môi giới. Cuối cùng, bạn tiếp tục xuất bản các tin nhắn đã đọc từ người dùng

Người tiêu dùng chỉ lâu hơn một chút vì nó yêu cầu xác định chức năng gọi lại để xử lý tin nhắn

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
53

Hầu hết mã soạn sẵn trông giống với nhà sản xuất của bạn. Tuy nhiên, bạn không cần viết một vòng lặp rõ ràng vì người tiêu dùng sẽ nghe tin nhắn vô thời hạn

Hãy tiếp tục và bắt đầu một vài nhà sản xuất và người tiêu dùng trong các tab thiết bị đầu cuối riêng biệt. Lưu ý điều gì sẽ xảy ra khi người tiêu dùng đầu tiên kết nối với RabbitMQ sau khi hàng đợi đã có một số tin nhắn chưa được sử dụng hoặc nếu bạn có nhiều người tiêu dùng kết nối với nhà môi giới

làm lại.
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
000

Redis là viết tắt của Remote Dictionary Server, nhưng nó thực sự có nhiều thứ được ngụy trang. Đó là kho lưu trữ dữ liệu khóa-giá trị trong bộ nhớ, thường hoạt động như một bộ đệm cực nhanh giữa cơ sở dữ liệu SQL truyền thống và máy chủ. Đồng thời, nó có thể phục vụ như một cơ sở dữ liệu NoSQL liên tục và cũng là một nhà môi giới tin nhắn trong mô hình đăng ký xuất bản. Bạn có thể khởi động máy chủ Redis cục bộ bằng Docker

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
54

Khi thực hiện, bạn sẽ có thể kết nối với một bộ chứa đang chạy bằng giao diện dòng lệnh Redis

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
55

Hãy xem danh sách các lệnh trong tài liệu chính thức và thử chúng khi bạn kết nối với máy chủ Redis. Ngoài ra, bạn có thể nhảy ngay vào Python. Thư viện đầu tiên được liệt kê trên trang Redis chính thức là

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
000, nhưng điều đáng chú ý là bạn có thể chọn từ nhiều giải pháp thay thế, bao gồm cả những thư viện không đồng bộ

Viết một nhà xuất bản cơ bản không mất nhiều hơn một vài dòng mã Python

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
56

Bạn kết nối với phiên bản máy chủ Redis cục bộ và ngay lập tức bắt đầu xuất bản thông báo trên kênh

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
002. Bạn không cần phải tạo các kênh, vì Redis sẽ làm điều đó cho bạn. Đăng ký kênh yêu cầu thêm một bước, tạo đối tượng
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
003 để gọi phương thức
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
004 trên

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
57

Tin nhắn mà người đăng ký nhận được là từ điển Python với một số siêu dữ liệu, cho phép bạn quyết định cách xử lý chúng. Nếu bạn có nhiều người đăng ký đang hoạt động đang nghe trên một kênh thì tất cả họ sẽ nhận được cùng một thông báo. Mặt khác, các tin nhắn không được duy trì theo mặc định

Kiểm tra Cách sử dụng Redis với Python để tìm hiểu thêm

Apache Kafka.
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
005

Kafka cho đến nay là trình trung gian tiên tiến và phức tạp nhất trong số ba trình trung chuyển tin nhắn mà bạn sẽ gặp trong hướng dẫn này. Đó là một nền tảng phát trực tuyến phân tán được sử dụng trong các ứng dụng hướng sự kiện theo thời gian thực. Điểm bán hàng chính của nó là khả năng xử lý khối lượng dữ liệu lớn mà hầu như không có độ trễ hiệu suất

Để chạy Kafka, bạn sẽ cần thiết lập một cụm phân tán. Bạn có thể sử dụng Docker Compose để khởi động ứng dụng Docker nhiều vùng chứa trong một lần. Ví dụ: bạn có thể lấy Apache Kafka do Bitnami đóng gói

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
58

Khi bạn lưu cấu hình này trong tệp có tên

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
006, thì bạn có thể khởi động hai dịch vụ bằng cách chạy lệnh bên dưới

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
59

Đôi khi, bạn có thể gặp sự cố khi phiên bản Kafka không khớp với phiên bản thư viện máy khách của bạn. The Python library that seems to support a fairly recent Kafka is

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
005, modeled on the Java client

Your producer can send messages on a given topic like so

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
00

Phương thức

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
008 không đồng bộ vì nó trả về một đối tượng trong tương lai mà bạn có thể đợi bằng cách gọi phương thức chặn
>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
119 của nó. Về phía người tiêu dùng, bạn sẽ có thể đọc các tin nhắn đã gửi bằng cách lặp lại người tiêu dùng

>>> from queues import Queue

>>> fifo = Queue[]
>>> fifo.enqueue["1st"]
>>> fifo.enqueue["2nd"]
>>> fifo.enqueue["3rd"]

>>> fifo.dequeue[]
'1st'
>>> fifo.dequeue[]
'2nd'
>>> fifo.dequeue[]
'3rd'
01

Trình xây dựng của người tiêu dùng có một hoặc nhiều chủ đề mà nó có thể quan tâm

Đương nhiên, bạn hầu như không làm trầy xước bề mặt với những gì có thể với những người môi giới tin nhắn mạnh mẽ này. Mục tiêu của bạn trong phần này là có được cái nhìn tổng quan nhanh và điểm khởi đầu trong trường hợp bạn muốn tự mình khám phá chúng

Sự kết luận

Bây giờ bạn đã có hiểu biết vững chắc về lý thuyết hàng đợi trong khoa học máy tính và biết cách sử dụng thực tế của chúng, từ việc tìm đường đi ngắn nhất trong biểu đồ đến đồng bộ hóa các công nhân đồng thời và tách rời các hệ thống phân tán. Bạn có thể nhận ra các vấn đề mà hàng đợi có thể giải quyết một cách tao nhã

Bạn có thể triển khai FIFO, LIFO và hàng đợi ưu tiên từ đầu bằng cách sử dụng các cấu trúc dữ liệu khác nhau trong Python, hiểu được sự đánh đổi của chúng. Đồng thời, bạn biết mọi hàng đợi được tích hợp trong thư viện tiêu chuẩn, bao gồm hàng đợi an toàn theo luồng, hàng đợi không đồng bộ và hàng đợi cho xử lý song song dựa trên quy trình. Bạn cũng đã biết về các thư viện cho phép tích hợp Python với hàng đợi trình chuyển tin nhắn phổ biến trên đám mây

Trong hướng dẫn này, bạn đã học cách

  • Differentiate between various types of queues
  • Implement the queue data type in Python
  • Solve practical problems by applying the right queue
  • Use Python’s thread-safe, asynchronous, and interprocess queues
  • Integrate Python with distributed message queue brokers through libraries

Đồng thời, bạn đã triển khai tìm kiếm theo chiều rộng [BFS], tìm kiếm theo chiều sâu [DFS] và thuật toán đường đi ngắn nhất của Dijkstra. Bạn đã xây dựng một mô phỏng trực quan về vấn đề của nhiều nhà sản xuất, nhiều người tiêu dùng, trình thu thập dữ liệu web không đồng bộ và chương trình đảo ngược hàm băm MD5 song song. Để lấy mã nguồn cho các ví dụ thực hành này, hãy theo liên kết bên dưới

Get Source Code. Click here to get access to the source code and sample data that you’ll use to explore queues in Python

Đánh dấu là đã hoàn thành

🐍 Thủ thuật Python 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. Không có thư rác bao giờ. Hủy đăng ký bất cứ lúc nào. Được quản lý bởi nhóm Real Python

Gửi cho tôi thủ thuật Python »

Giới thiệu về Bartosz Zaczyński

Bartosz is a bootcamp instructor, author, and polyglot programmer in love with Python. Anh ấy giúp sinh viên của mình tiếp cận công nghệ phần mềm bằng cách chia sẻ kinh nghiệm thương mại hơn một thập kỷ trong ngành CNTT

» Thông tin thêm về Bartosz

Mỗi hướng dẫn tại Real Python được tạo bởi một nhóm các nhà phát triển để nó đáp ứng các tiêu chuẩn chất lượng cao của chúng tôi. Các thành viên trong nhóm đã làm việc trong hướng dẫn này là

Aldren

Geir Arne

kate

Bậc thầy Kỹ năng Python trong thế giới thực Với quyền truy cập không giới hạn vào Python thực

Tham gia với chúng tôi và có quyền truy cập vào hàng nghìn hướng dẫn, khóa học video thực hành và cộng đồng các Pythonistas chuyên gia

Nâng cao kỹ năng Python của bạn »

Bậc thầy Kỹ năng Python trong thế giới thực
Với quyền truy cập không giới hạn vào Python thực

Tham gia với chúng tôi và có quyền truy cập vào hàng ngàn hướng dẫn, khóa học video thực hành và cộng đồng Pythonistas chuyên gia

Nâng cao kỹ năng Python của bạn »

Bạn nghĩ sao?

Đánh giá bài viết này

Tweet Chia sẻ Chia sẻ Email

Bài học số 1 hoặc điều yêu thích mà bạn đã học được là gì?

Mẹo bình luận. 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

Python có hàng đợi ưu tiên không?

Python cung cấp triển khai tích hợp cấu trúc dữ liệu hàng đợi ưu tiên . kể từ khi hàng đợi. Lớp PriorityQueue cần duy trì thứ tự các phần tử của nó, cần có cơ chế sắp xếp mỗi khi một phần tử mới được đưa vào hàng đợi. Python giải quyết vấn đề này bằng cách sử dụng một đống nhị phân để triển khai hàng đợi ưu tiên.

Hàng đợi ưu tiên đa luồng trong Python là gì?

Một khái niệm khoa học máy tính được sử dụng rộng rãi trong cả lập trình không đồng thời và lập trình đồng thời là xếp hàng . Hàng đợi là một cấu trúc dữ liệu trừu tượng, là tập hợp các phần tử khác nhau được duy trì theo một thứ tự cụ thể; .

Hàng đợi Python có phải là FIFO không?

Giống như ngăn xếp, hàng đợi là cấu trúc dữ liệu tuyến tính lưu trữ các mục theo cách Nhập trước Xuất trước [FIFO] . Với một hàng đợi, mục được thêm gần đây nhất sẽ bị xóa trước.

Là chủ đề Heapq

Không, sử dụng thư viện heapq không an toàn cho luồng . Sử dụng khóa để phối hợp truy cập. Lưu ý rằng tài liệu thư viện liên kết với mã nguồn; . Bạn sẽ thấy rằng mô-đun hoạt động trên danh sách Python thông thường và không có mã khóa.

Chủ Đề