Стек, опашка и свързан списък
Проблем
„Отмени“ отменя не това
Текстовият редактор пази действията в списък. Ctrl+Z трябва да отмени последното, но отменя първото:
actions = []actions.append("Въведе текст")actions.append("Удебели заглавието")actions.append("Вмъкна снимка")
# Ctrl+Z: отмени последното действие.undone = actions.pop(0)print("Отменено:", undone)print("Остава:", actions)Отменено: Въведе текстОстава: ['Удебели заглавието', 'Вмъкна снимка']Поправката е actions.pop(). Но всеки, който пипа този код, трябва да помни от кой край се маха. А при касата в магазина е обратното: обслужва се първият, който е дошъл. Има ли колекция, която сама знае от кой край?
Идеята
Стек: последното най-отгоре
Стекът е като купчина чинии. Слагаш отгоре и взимаш отгоре. Последното сложено излиза първо. На английски това е LIFO: last in, first out.
Бутонът „Назад“ в браузъра е стек:
history = []history.append("Начало")history.append("Лаптопи")history.append("Лаптоп 14 инча")print("Сега:", history[-1])
history.pop()print("Назад:", history[-1])print(history)Сега: Лаптоп 14 инчаНазад: Лаптопи['Начало', 'Лаптопи']Отделен клас за стек в Python няма: списъкът е стек, ако слагаш и взимаш само от края.
appendслага отгоре,pop()без индекс маха и връща горния,history[-1]само поглежда горния.- Отгоре е краят на списъка. И двете операции са бързи, защото в края нищо не се мести.
printпечата стека отдолу нагоре: горният е последен.
По-точно, за любопитните
pop()и[-1]на празен списък спират програмата сIndexError. Затова първо провери.if history:значи „ако списъкът не е празен“: празният списък вifиwhileе катоFalse.
history = []if history: print("Сега:", history[-1])else: print("Няма предишна страница.")try: history.pop()except IndexError as e: print(f"IndexError: {e}")Няма предишна страница.IndexError: pop from empty listПробвай
Какво остава за отмяна?
Какво ще отпечата програмата?
undo = []undo.append("шрифт")undo.append("цвят")undo.append("размер")undo.pop()undo.append("подчертаване")while len(undo) > 0: print(undo.pop())pop маха „размер“, защото е най-отгоре. После „подчертаване“ застава отгоре. Цикълът вади от горе надолу: подчертаване, цвят, шрифт.
Идеята
Опашка: първият дошъл
Опашката е като на касата. Нареждаш се отзад, обслужва се първият. На английски е FIFO: first in, first out.
Заявките към принтера чакат на опашка:
from collections import deque
printer = deque()printer.append("фактура.pdf")printer.append("договор.docx")printer.append("снимка.jpg")print("Следващ:", printer[0])
while len(printer) > 0: print("Печатам", printer.popleft())print("Опашка:", printer)Следващ: фактура.pdfПечатам фактура.pdfПечатам договор.docxПечатам снимка.jpgОпашка: deque([])dequeе в модулаcollections, затова файлът започва сfrom collections import deque. Името идва от double-ended queue, опашка с два края.appendслага отзад,popleftмаха и връща първия,printer[0]поглежда първия.- Защо не списък с
pop(0)?pop(0)мести всички останали с едно място напред, като вмъкването отпред в урока за списъците.dequeмаха отпред, без да мести нищо.
По-точно, за любопитните
popleftна празна опашка спира програмата сIndexError.dequeможе и да е стек:appendиpopработят от десния край.
from collections import deque
clients = deque()clients.append("Клиент 1")clients.append("Клиент 2")print("Последен:", clients.pop())print("Обслужен:", clients.popleft())try: clients.popleft()except IndexError as e: print(f"IndexError: {e}")Последен: Клиент 2Обслужен: Клиент 1IndexError: pop from an empty deque- Има и
queue.Queue, но той е за програми с няколко нишки, които си подават задачи. За обикновена опашка ползвайdeque.
Пробвай
Следващият на касата
Касиерката вика следващия клиент. Кой метод го взима от опашката?
from collections import deque
checkout = deque()
checkout.append("Иван")
checkout.append("Мария")
client = checkout.()
print(f"Заповядай, {client}")popleft маха и връща първия в опашката, тук Иван. checkout[0] само би погледнал, без да го маха, и следващият път пак щеше да е Иван. А pop() щеше да вземе последната, Мария.
Виж го
Свързан списък отвътре
Списъкът пази елементите един до друг в масив. Затова вмъкването отпред мести всички останали. Има и друг начин: всеки елемент е в отделен обект, който пази стойността и стрелка към следващия. Такъв обект се казва възел (node):
class Node: def __init__(self, value, next): self.value = value self.next = nextСписък от възли се казва свързан списък. Достатъчно е да пазиш първия възел, head. Всеки следващ се намира по стрелката next. Последният сочи None.
head = Nonehead = Node("Кафе", head)head = Node("Мляко", head)head = Node("Хляб", head)
n = headwhile n is not None: print(n.value) n = n.nextСтъпка по стъпка, с думи
- Списъкът е празен: head е None.
- Нов възел: next е старият head (None), а head се премества на възела.
- Вторият възел сочи към първия. Нищо не се мести в паметта, сменят се само стрелките.
- Третият възел също влиза отпред: последното добавено е първо.
- n тръгва от head и печата всеки възел.
- n стига None: цикълът спира.
- Вмъкване отпред: нов възел, чийто
nextсочи стария първи, иheadсочи новия. Две стрелки, без да местиш нищо. - Обхождане: тръгваш от
headи минаваш поnext, докато не стигнешNone. - Цената: възлите нямат индекси. До петия елемент се стига, като минеш през първите четири.
Идеята
deque и кое кога
Готов свързан списък като класа Node в Python няма. Има deque: отвътре той е свързан списък, но всеки възел пази не един, а цял блок от елементи. Затова е бърз и в двата края: append и appendleft, pop и popleft, [0] и [-1]:
from collections import deque
route = deque()route.append("Пловдив")route.append("Стара Загора")route.appendleft("София")route.append("Бургас")print(list(route))
route.remove("Стара Загора")print("След промяната:", list(route))print("От", route[0], "до", route[-1])['София', 'Пловдив', 'Стара Загора', 'Бургас']След промяната: ['София', 'Пловдив', 'Бургас']От София до БургасКое кога:
- Списък по номер:
list. Това е обичайният избор. - Стек:
listсappendиpop. - Опашка:
dequeсappendиpopleft.
По-точно, за любопитните
list(route)прави обикновен списък от опашката, за да се отпечата като списък. Самоprint(route)даваdeque([...]).- В CPython всеки блок на
dequeпази 64 елемента, а блоковете са свързани в двете посоки, към следващия и към предишния. Затоваroute[0]иroute[-1]са бързи, а елемент от средата на голяма опашка е бавен: до него се стига през блоковете. - Възелът от тази карта има само
next. Такъв списък се казва едносвързан.
Пробвай
Намери грешката
Функцията трябва да преброи възлите. За списъка Хляб → Мляко → Кафе връща 2 вместо 3. А за празен списък спира с AttributeError. На кой ред е грешката?
Избери реда с грешката.
n.next is not None спира, когато n е на последния възел, преди да го преброи. А ако head е None, n.next няма откъде да се прочете. Условието трябва да е n is not None: продължи, докато има възел.
Съобщението при празен списък е AttributeError: 'NoneType' object has no attribute 'next': None няма next.
Упражнение
Стекове и опашки в работа
_ отпред.1. Влакче. Клас Train с методи add_wagon(wagon) и remove_wagon(). Влакът е стек: откача се последният закачен вагон. remove_wagon() връща откачения вагон или None, ако няма. Добави count(). Закачи три вагона и откачи два.
Откачен: Вагон 3Откачен: Вагон 2Вагони: 12. Маршрут на автобус. Клас BusRoute с опашка от спирки и методи add_stop(stop) и get_next_stop(), който връща следващата спирка или None, ако маршрутът е свършил. Добави „Централна гара“, „НДК“, „Студентски град“ и обходи маршрута.
Следваща спирка: Централна гараСледваща спирка: НДКСледваща спирка: Студентски градКрай на маршрута.3. Борсов тикер. Клас StockTicker пази последните N цени на една акция. Конструкторът получава символа и N. update_price(price) добавя цена, а ако цените станат повече от N, маха най-старата. Добави get_last_price() и average() за пазените цени. За SAP с N = 3 подай цените 150, 152, 149 и 155. Подсказка: deque(maxlen=3) сама маха най-старата, когато станат повече от 3. Цените печатай с два знака след точката.
SAP: последна цена 155.00 €Средно за последните 3: 152.00 €4. Скобите в баланс. Редакторът на код проверява дали скобите (), [] и {} са затворени в правилния ред. Напиши is_balanced(text) със стек: при отваряща скоба я слагаш в стека, при затваряща махаш горната и проверяваш дали е от същия вид. Накрая стекът трябва да е празен. Прочети три реда.
(1 + [2 * 3])(1 + 2]((5)(1 + [2 * 3]) -> да(1 + 2] -> не((5) -> не5. Свой свързан списък. Клас MyLinkedList с възлите от урока и методи add(value) (в края), remove(value), contains(value), size() и __str__(). При remove намери възела преди търсения и пренасочи неговия next. Отделно провери случая, когато се маха първият.
[Фактура, Отчет, Среща]Има ли Отчет: TrueСлед триене: [Фактура, Среща]Размер: 2Още задачи
- Отмени и Повтори. Пази два стека:
undoиredo. Ново действие отива вundoи изчистваredo. „Отмени“ мести горното отundoвredo, а „Повтори“ го връща обратно. - Думите наобратно. Прочети изречение и го отпечатай с думите в обратен ред, като минеш през стек: „Днес е петък“ става „петък е Днес“.
- Две каси. Клиентите идват един по един и отиват на касата с по-къса опашка. Отпечатай кой на коя каса е обслужен.
- Обърни свързания списък. Добави на
MyLinkedListметодreverse(), който обръща стрелките, без да прави нови възли.
Тест