Queue é uma estrutura de dados de tabela linear comum.As filas geralmente são usadas em cenários com recursos limitados, como pools de threads, pools de conexão de banco de dados e assim por diante. Quando não há encadeamentos ociosos no conjunto de encadeamentos, quando uma nova tarefa solicita recursos de encadeamento, as tarefas são enfileiradas e quando há encadeamentos ociosos, as tarefas enfileiradas são retiradas para continuar o processamento.
26.1 Operações Básicas da Fila
Sua característica típica é o primeiro a entrar, o primeiro a sair. Ou seja, aqueles que entram na fila primeiro são desenfileirados e aqueles que entram na fila por último são desenfileirados. As filas têm duas operações básicas: enqueue, que coloca uma parte dos dados no final da fila, e dequeque, que remove uma parte dos dados do início da fila. A figura a seguir é um diagrama esquemático da operação de uma fila unidirecional:
26.2 Filas Limitadas e Não Limitadas
As filas podem ser divididas em filas ilimitadas e filas limitadas de acordo com o número de dados contidos nelas. Uma fila ilimitada pode armazenar qualquer quantidade de dados, enquanto uma fila limitada pode armazenar um número limitado de dados. O tamanho da fila deve ser definido de forma razoável. Se a fila for muito grande, haverá muitas tarefas em espera. Se a fila for muito pequena, os recursos do sistema não poderão ser totalmente utilizados.
26.3 Filas fornecidas pelo Python
O Python fornece uma fila de módulo relacionada à fila, que fornece três filas comumente usadas, a saber:
- Fila do primeiro a entrar, primeiro a sair (FIFO) Fila, fila comum, ou seja, o primeiro inserido é excluído primeiro.
- Fila de prioridade PriorityQueue, de acordo com a ordem de prioridade para determinar a ordem da fila.
- Fila Last in, first out (LIFO) LifoQueue, semelhante a uma pilha, os itens adicionados mais recentemente são recuperados primeiro.
Além disso, o módulo de coleções do Python fornece um deque de fila de extremidade dupla, que pode ser enfileirado e desenfileirado em ambas as extremidades da fila, como uma fila ou como uma pilha.
26.3.1 Fila de fila de primeiro a entrar, primeiro a sair (FIFO).
Esta é a fila mais básica em Python. Pressione shift duas vezes no Pycharm, insira queue na caixa de pesquisa, pressione Enter para inserir o arquivo queue.py e clique em Navigate-File Structure no Pycharm. Você pode ver a estrutura de arquivos do módulo de fila. Tanto o LifoQueue quanto o PriorityQueue herdam de Queue.
A figura acima lista todos os métodos da classe Queue. Vamos dar uma olhada em alguns dos métodos mais usados:
import queue
q = queue.Queue() # 没有传入maxsize,表示这是个无界队列。
for i in range(5):
q.put(i) # 放入元素到队列,在队列满时会阻塞
while not q.empty(): # 不为空
print(q.get(), end=" ") # 从队列中取元素,在队列为空时会阻塞
Além do enfileiramento e desenfileiramento das operações de bloqueio acima, existem dois métodos de enfileiramento e desenfileiramento sem bloqueio:
- get_nowait() não bloqueia quando a fila está vazia, ele lançará uma fila de exceção.Empty
- put_nowait(1) não bloqueia quando a fila está cheia, neste momento uma fila de exceção.Full será lançada
import queue
q = queue.Queue(5) # 传入maxsize=5,表示这队列长度为5。
for i in range(5):
try:
q.put_nowait(i) # 放入元素到队列,在队列满时会阻塞
except queue.Full as e:
print("队列满了")
while True:
try:
print(q.get_nowait()) # 在队列为空的时候也不阻塞,这时候会抛异常queue.Empty
except queue.Empty:
print('队列为空')
break
Essa fila é mais comumente usada no modelo produtor-consumidor. Nesse modelo, os produtores colocam os dados na fila e os consumidores leem os dados da fila. Este modelo realiza o desacoplamento entre sistemas, produtores e consumidores são sistemas independentes que transmitem dados através de filas, podendo ser expandidos e otimizados horizontalmente sem afetar um ao outro.
Aqui está um exemplo de um produtor e consumidor completo (original):
import concurrent.futures
import os
import queue
import random
import threading
import time
# 生产者,定义生产逻辑,将生产的数据放入Queue中
def produce(q):
production = '{}-{}'.format(threading.current_thread().name, int(time.time()))
time.sleep(random.randint(1, 2))
production_date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print('时间:%s, %s 生产了%s, 库存是%d' % (production_date, threading.current_thread().name, production, q.qsize()))
q.put(production)
# 消费者,定义消费数据的逻辑,从队列中取数据
def consume(q):
production = q.get()
time.sleep(random.randint(1, 2))
consume_date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print('时间:%s, %s 购买了%s, 库存是%d' % (consume_date, threading.current_thread().name, production, q.qsize()))
if __name__ == '__main__':
# 生产者线程池,实现多个生产者同时生产
def produce_pool(workers, q):
with concurrent.futures.ThreadPoolExecutor(max_workers=workers,
thread_name_prefix='producer') as executor:
while True:
executor.submit(produce, q)
# 消费者线程池,实现多个消费者同时消费
def consume_pool(workers, q):
with concurrent.futures.ThreadPoolExecutor(max_workers=workers,
thread_name_prefix='consumer') as executor:
while True:
executor.submit(consume, q)
qq = queue.Queue(20)
produce_workers = os.cpu_count() # 生产者线程数量
consume_workers = os.cpu_count() # 消费者线程数量
producer_thread = threading.Thread(target=produce_pool, args=(produce_workers, qq))
consumer_thread = threading.Thread(target=consume_pool, args=(consume_workers, qq))
producer_thread.start()
consumer_thread.start()
producer_thread.join()
consumer_thread.join()
Neste exemplo, o produtor e o consumidor têm seus próprios conjuntos de encadeamentos. Eles podem melhorar seus respectivos recursos ajustando o número de seus próprios conjuntos de encadeamentos. Por exemplo, se os dados na fila atingirem o valor máximo da fila por um longo tempo, isso significa que o consumidor está lento no consumo.Se o número de threads do consumidor for muito pequeno, você pode expandir o valor de comsume_workers e melhorar a capacidade de consumo. Se for constatado que os dados na fila estão vazios por muito tempo, isso significa que o produtor está cheio ou o número de encadeamentos do produtor é muito pequeno. Você pode expandir o valor de Produce_workder para fornecer capacidade de produção.
O exemplo acima cria threads independentes para o pool de threads do produtor e do pool de threads do consumidor.Após executar o código, você verá vários produtores e consumidores trabalhando ao mesmo tempo.
时间:2020-06-07 12:26:28, producer_0 生产了producer_0-1591503986, 库存是2
时间:2020-06-07 12:26:28, producer_3 生产了producer_3-1591503986, 库存是3
时间:2020-06-07 12:26:28, consumer_1 购买了producer_2-1591503984, 库存是4
时间:2020-06-07 12:26:28, consumer_3 购买了producer_5-1591503984, 库存是2
时间:2020-06-07 12:26:29, consumer_7 购买了producer_0-1591503984, 库存是1
O produtor-consumidor no exemplo acima apenas imprime algum conteúdo no Console. No trabalho real, os consumidores geralmente retornam os dados processados ao chamador. Vamos transformar, se o consumidor tem um valor de retorno, como lidar com isso.
Primeiro, a função comsume adiciona uma instrução return que retorna a avaliação da produção de mercadorias. Então, na função de pool de threads consumidor comsume_pool, o resultado da execução da função de consumo é obtido através do método result da instância futura.
def consume(q): # 消费者改进版,对消费的产品给出评价
production = q.get()
return "{} 很棒~,好评".format(production)
def consume_pool(workers, q):
with concurrent.futures.ThreadPoolExecutor(max_workers=workers,
thread_name_prefix='consumer') as executor:
to_do = [] # 存放future实例
results = [] # 存储future实例的结果
while True:
to_do.append(executor.submit(consume, q)) # submit返回创建好的 future 实例,放入to_do列表
if len(to_do) >= 5:
for done_future in concurrent.futures.as_completed(to_do): # 返回任务的对象,这里不耗时
results.append(done_future.result()) # 耗时,注意取得的结果是乱序的
print(results)
to_do = []
26.3.2 Fila da fila Last In, First Out (LIFO).LifoQueue
Esta é uma estrutura de dados semelhante a uma pilha, e os dados que chegam na última fila saem primeiro. Por meio de Navigate - Type Hierachy do Pycharm, você pode ver que o LifoQueue herda de Queue.
Ao olhar para o código fonte do LifoQueue, verifica-se que não existe um método especial em si. Sua operação ainda está usando o método de sua classe pai Queue, mas para o método get, é definida aqui sua própria lógica, que é obter dados do final da fila.
class LifoQueue(Queue):
'''Variant of Queue that retrieves most recently added entries first.'''
def _init(self, maxsize):
self.queue = []
def _qsize(self):
return len(self.queue)
def _put(self, item):
self.queue.append(item)
def _get(self):
return self.queue.pop() # 特殊点,从队尾删除
A partir do código-fonte de Queue, você pode ver que o método get obtém dados do início da fila:
# Get an item from the queue
def _get(self):
return self.queue.popleft()
Esta é toda a diferença entre LifoQueue e Queue. Por exemplo, sinta:
import queue
q = queue.LifoQueue()
for i in range(5):
q.put(i)
while not q.empty():
print(q.get(), end=" ")
26.3.3 Fila de fila de prioridade.PriorityQueue
PrirotiyQueue também é herdado da classe Queue. Ao buscar dados dela, eles são buscados por prioridade. Ao colocar dados, uma prioridade deve ser atribuída aos dados. A prioridade é um número, quanto menor o número, maior a prioridade e mais preferencial deve ser retirado.
class PriorityQueue(Queue):
'''Variant of Queue that retrieves open entries in priority order (lowest first).
Entries are typically tuples of the form: (priority number, data).
'''
def _init(self, maxsize):
self.queue = []
def _qsize(self):
return len(self.queue)
def _put(self, item):
heappush(self.queue, item)
def _get(self):
return heappop(self.queue)
Pode-se ver que o método get de dados e o método put de dados são implementados pelo módulo heap heapq em Python. Ao colocar dados no método put, o formato é uma tupla, o primeiro elemento é o número de prioridade e o segundo elemento são os dados.
Dê um exemplo para ver:
from queue import PriorityQueue
pq = PriorityQueue()
student_1 = {
'name': 'lisi', 'age': 30}
student_2 = {
'name': 'wangwu', 'age': 29}
pq.put((20, student_1)) # 第一个参数指定一个优先级
pq.put((9, student_2))
# 越小的数字代表越高的优先级
while not pq.empty():
print(pq.get()) # 按照优先级取 ,先取出student_2再取出student_1
26.3.4 Deque coleções.deque
Deque é a abreviação de double-ended queue.Deque pode operar em ambas as extremidades, então deque suporta métodos de operação avançados.
Digite o seguinte código no Pycharm para visualizar todos os métodos da classe deque.
import collections
print(dir(collections.deque))
# ['__add__', '__bool__', '__class__', '__contains__', '__copy__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'appendleft', 'clear', 'copy', 'count', 'extend', 'extendleft', 'index', 'insert', 'maxlen', 'pop', 'popleft', 'remove', 'reverse', 'rotate']
Mantenha pressionada a tecla de comando e clique com o botão esquerdo do mouse no deque. Você pode inserir o arquivo de origem da classe deque. Há instruções para usar cada método no código-fonte. Aqui estão alguns exemplos de métodos comumente usados:
import collections
d1 = collections.deque(maxlen=5)
# 右边操作
d1.extend('abcdefg')
print('extend right:', d1)
d1.append('h')
print('append right:', d1)
d1.rotate(2)
print('Rotate the deque 2 steps to the right ', d1)
print(d1.pop())
print(d1)
# 左边操作
d2 = collections.deque()
d2.extendleft(range(6))
print('extend left:', d2)
d2.appendleft(6)
print('append left:', d2)
print(d2.popleft())
print(d2)
# 像列表一样操作
print(d2[0], d2[-1])
d2.remove(3)
print(d2)
print(d2.count(0))
O deque é thread-safe, o que significa que as operações dos lados esquerdo e direito do deque não afetarão umas às outras ao mesmo tempo. Veja o código a seguir:
import collections
import threading
import time
candle = collections.deque(maxlen=20)
candle.extend("abcdefghijklmn")
def burn(direction, nextSource):
while True:
try:
next = nextSource()
except IndexError:
break
else:
print('%s : %s' % (direction, next))
time.sleep(0.1)
print("done %s" % direction)
return
left = threading.Thread(target=burn, args=('left', candle.popleft))
right = threading.Thread(target=burn, args=('right', candle.pop))
left.start()
right.start()
left.join()
right.join()
O parâmetro maxlen em deque é muito útil. Use maxlen em deque para criar uma fila de tamanho fixo e adicione elementos a ela por meio de append. Quando o número de elementos atinge maxlen, o primeiro elemento adicionado a deque é removido automaticamente e novos elementos são adicionados. Assim, sempre mantendo os elementos maxlen mais recentes no deque.
Vejamos um exemplo, em uma lista de strings, procure uma string correspondente e, quando a primeira pesquisa for encontrada, imprima as n strings atuais e anteriores. Nesse cenário, precisamos salvar um número limitado de elementos. Vejamos o código abaixo:
from collections import deque
def search(lst, pattern, history): # 当编写搜索某一项记录的代码时,通常会用到含有yield关键字的生成器。这将处理搜索过程的代码和使用搜索结果的代码解耦
previous_elements = deque(maxlen=history)
for l in lst:
if pattern in l:
yield l, previous_elements
break
previous_elements.append(l)
if __name__ == '__main__':
persons = ['I', 'am', 'learning', 'python', 'by', 'coding']
for item, records in search(persons, 'b', 2):
for r in records:
print(r, end=' ')
print(item)
deque(maxlen=history)
Uma fila de comprimento fixo de histórico de comprimento é criada. Quando um novo elemento é adicionado à fila, mas a fila está cheia neste momento, o registro mais antigo é removido automaticamente.
26.4 Perguntas da entrevista
- Corresponde a cada linha no arquivo, quando combinada, gera a linha correspondente e as N linhas que acabaram de ser correspondidas
from collections import deque
def search(text, pattern, history):
previous_lines = deque(maxlen=history)
for line in text:
if pattern in line:
yield line, previous_lines
previous_lines.append(line)
if __name__ == '__main__':
with open("java.txt") as f:
for line, previous in search(f, 'javascript', 2):
for p in previous:
print(p, end='')
print(line)
Dado um array não vazio de inteiros, retorne os k elementos mais frequentes nele.
Exemplo 1:
Entrada: nums = [1,1,1,2,2,3], k = 2
Saída: [1,2]
Exemplo 2:
Entrada: nums = [1], k = 1
Saída: [1]
Explicação:
Você pode assumir que um dado k é sempre razoável e que 1 ≤ k ≤ o número de elementos distintos na matriz.
A complexidade de tempo do seu algoritmo deve ser melhor que O(n log n) , onde n é o tamanho do array.
class Solution:
def topKFrequent(self, nums, k: int):
if k<0 or k>len(nums): return []
from queue import PriorityQueue
from collections import defaultdict
queue = PriorityQueue() # 优先级队列
d = defaultdict(int) # 值是int的字典
res = []
for i in nums:
d[i]+=1
d = list(d.items())
print(d)
for i in range(len(d)):
queue.put([-d[i][1],d[i][0]]) # 这里第一个参数要用,要用负数,优先级队列中越小优先级越高
for i in range(k):
res.append(queue.get()[1])
return res