Оригинал: Exercise OnClickListener
Переводчик: Феликс Ли
Лицензия: CC BY-NC-SA 4.0
Гордо использует Google Translate
Первый структурный элемент данных, который вы реализуете, это односвязный список. Я опишу структуру данных, перечислю все операции, которые вам следует реализовать, и предоставлю тест, через который ваша реализация должна пройти. Вы должны сначала попробовать использовать эту структуру данных, а затем посмотреть мою реализацию и видео проверки, чтобы понять процесс.
Предупреждение
Эти не являются эффективными реализациями структур данных. Они намеренно сделаны простыми и медленными, чтобы мы могли объяснить измерения и оптимизации в упражнениях 18 и 19. Если вы будете использовать эти структуры данных в производственной работе, возникнут проблемы с производительностью.
При работе со многими структурами данных в объектно-ориентированных языках программирования (например, Python) вам нужно понимать три общих концепции:
"Ребро", но мы называем его "указатель" или "связь", которая указывает на другие узлы. Все они находятся внутри каждого узла, как правило, в виде экземплярной переменной.
"Контроллер", это некоторый класс, знающий, как использовать указатели в узле правильно для построения данных.В Python мы отображаем эти концепции следующим образом:
Узел просто объект, определённый классом.
Указатель (ребро) просто экземплярная переменная в объекте узла.
Контроллер — это ещё один простой класс, который использует узлы для хранения всего и построения данных. Это место всех операций (push
, pop
, list
и так далее), обычно контроллер используется таким образом, что пользователи никогда не работают непосредственно с узлами или указателями.
В некоторых книгах по алгоритмам вы можете видеть такие реализации, где узел и контроллер объединены в одном классе, но это очень запутано и противоречит принципу разделения проблем в дизайне. Лучше отделить узел от контрольного класса, чтобы делать одно дело хорошо и знать, где ошибки.Представьте себе, что мы хотим хранить серию автомобилей. У нас есть первый автомобиль, за которым следует второй, до последнего. Представьте этот список, и мы можем начать представлять дизайн узел/указатель/контроллер:
Ноды содержат описание каждого автомобиля. Возможно, это просто переменная node.value
класса Car
. Если вы ленивы, мы можем называть этот класс SingleLinkedListNode
или SLLNode
.
Каждый SLLNode
имеет связь с следующим нодом в списке. Доступ к node.next
позволяет получить доступ к следующему автомобилю.
Управление осуществляется контроллером, который называется SingleLinkedList
, и имеет операции, такие как push
, pop
, first
или count
, которые принимают объект типа Car
и используют ноды для внутреннего хранения данных. Когда вы добавляете автомобиль методом push
в контроллер SingleLinkedList
, он обрабатывает его внутри внутреннего списка нод, помещая его в конец списка.
Примечание> Когда в Python есть удобный и быстрый
list
, зачем нам это делать? Это делается исключительно для изучения структур данных. В реальном мире вы можете использоватьlist
Python и продолжать работу.
Для реализации SingleLinkedListNode
нам нужен простой класс, представленный ниже:
class SingleLinkedListNode(object):
def __init__(self, value, nxt, prev):
self.value = value
self.next = nxt
def __repr__(self):
nval = self.next and self.next.value or None
return f"[{self.value}: {repr(nval)}]"
Мы должны использовать слово nxt
, так как next
является зарезервированным словом в Python. Кроме того, это очень простой пример. Самым сложным является метод __repr__
. Когда вы используете %r
формат или вызываете repr()
на узле, он выводит отладочную информацию. Он должен вернуть строку.
Примечание
Теперь потратите время, чтобы понять, как можно создать список вручную с помощью класса
SingleLinkedListNode
, а затем пройтись по нему вручную. Это отличная задача на bcm минут, чтобы попрактиковаться.
Как только мы определили наши узлы в классе SingleLinkedListNode
, мы точно знаем, что должен делать контроллер. У каждой структуры данных есть набор необходимых операций, которые делают её полезной. Разные операции требуют различных объёмов памяти (пространства) и времени; некоторые дорогие, другие быстрые. Структура SingleLinkedListNode
делает некоторые операции очень быстрыми, но многие другие — медленными. В процессе реализации вы узнаете об этом.Самый простой способ просмотреть эти операции — это взглянуть на каркасный вариант класса SingleLinkedList
:
class SingleLinkedList(object):
def __init__(self):
self.begin = None
self.end = None
def push(self, obj):
"""Добавляет новый объект в конец списка."""
def pop(self):
"""Удаляет последний объект и возвращает его."""
def shift(self, obj):
"""Добавляет новый объект в начало списка."""
def unshift(self):
"""Удаляет первый объект и возвращает его."""
def remove(self, obj):
"""Ищет совпадение объекта и удаляет его."""
def first(self):
"""Возвращает ссылку на первый объект, не удаляя его."""
def last(self):
"""Возвращает ссылку на последний объект, не удаляя его."""
def count(self):
"""Подсчитывает количество объектов в списке."""
def get(self, index):
"""Получает значение по указанному индексу."""
def dump(self, mark):
"""Отладочная функция для вывода содержимого связанного списка."""
Для других упражнений я предоставлю вам только указания по реализации этих операций, но в этом упражнении я подробно объясню каждую операцию и как с ними работать, просматривая список функций в SingleLinkedList
.
from sllist import *
def test_push(): colors = SingleLinkedList() colors.push("Птало Синий") assert colors.count() == 1 colors.push("Ультрамариновый Синий") assert colors.count() == 2
def test_pop(): colors = SingleLinkedList() colors.push("Магента") colors.push("Ализарин") assert colors.pop() == "Ализарин" assert colors.pop() == "Магента" assert colors.pop() == None
def test_unshift(): colors = SingleLinkedList() colors.push("Виридиан") colors.push("Сап Грин") colors.push("Ван Дайк") assert colors.unshift() == "Виридиан" assert colors.unshift() == "Сап Грин" assert colors.unshift() == "Ван Дайк" assert colors.unshift() == None
def test_shift(): colors = SingleLinkedList() colors.shift("Кадмий Оранжевый") assert colors.count() == 1
colors.shift("Карбазол Виолет")
assert colors.count() == 2
assert colors.pop() == "Кадмий Оранжевый"
assert colors.count() == 1
assert colors.pop() == "Карбазол Виолет"
assert colors.count() == 0
def test_remove(): colors = SingleLinkedList() colors.push("Кобальт") colors.push("Цинковый Белый") colors.push("Никель Жёлтый") colors.push("Перинон") assert colors.remove("Кобальт") == 0 colors.dump("до перинона") assert colors.remove("Перинон") == 2 colors.dump("после перинона") assert colors.remove("Никель Жёлтый") == 1 assert colors.remove("Цинковый Белый") == 0
def тест_первый():
цвета = ОдносвязныйСписок()
цвета.push("Кадмий Красный Свет")
assert цвета.first() == "Кадмий Красный Свет"
цвета.push("Ханса Желтый")
assert цвета.first() == "Кадмий Красный Свет"
цвета.shift("Птало Зелёный")
assert цвета.first() == "Птало Зелёный"
```
Примечание: В данном контексте использована IT-терминология для перевода названий функций и методов.```markdown
def test_last():
colors = SingleLinkedList()
colors.push("Кадмий красный свет")
assert colors.last() == "Кадмий красный свет"
colors.push("Ханса желтый")
assert colors.last() == "Ханса желтый"
colors.shift("Птало зелёный")
assert colors.last() == "Ханса желтый"
``````python
def test_get():
colors = SingleLinkedList()
colors.push("Вермилион")
assert colors.get(0) == "Вермилион"
colors.push("Сап грин")
assert colors.get(0) == "Вермилион"
assert colors.get(1) == "Сап грин"
colors.push("Кадмий желтый свет")
assert colors.get(0) == "Вермилион"
assert colors.get(1) == "Сап грин"
assert colors.get(2) == "Кадмий желтый свет"
assert colors.pop() == "Кадмий желтый свет"
assert colors.get(0) == "Вермилион"
assert colors.get(1) == "Сап грин"
assert colors.get(2) == None
colors.pop()
assert colors.get(0) == "Вермилион"
colors.pop()
assert colors.get(0) == None
```
Прочитайте этот тест, чтобы лучше понять, как должны работать каждые операции перед тем, как вы начнете писать код. Я бы не рекомендовал вносить все эти изменения за один раз. Вместо этого лучше делать это по частям, создавая небольшие тесты и проверяя, работает ли каждый отдельный участок.
> **Примечание**:
> Здесь, если вы не знакомы с автоматическими тестами, вам может потребоваться просмотреть видео, где я показываю, как это делается.
## Введение в аудит
```Когда вы выполняете каждый тест, вы будете проводить аудит кода для поиска ошибок. В конечном итоге, вы будете отслеживать количество найденных вами ошибок, но сейчас вам нужно выполнить аудит после того, как закончите писать код. Аудит — это процесс, аналогичный тому, который налоговая служба проводит, когда она считает, что вы занижаете свои налоги. Они проходят через каждую сделку, каждый доход, все расходы и анализируют, почему вы так тратите деньги. Аналогично, при код-ревью вы проходите через каждый метод и анализируете все входящие параметры и выходные значения.Для базового аудита вы будете выполнять следующее:
```markdown
- Начните с тестовых случаев. В этом примере мы будем проверять `test_push`.
- Посмотрите первую строчку кода и определите, что вызывается и создаётся. В данном случае это `colors = SingleLinkList()`. Это значит, что мы создаем переменную `colors`, а также вызываем метод `SingleLinkList.__init__`.
- Перейдите в начало метода `__init__`, держа тестовый случай и целевой метод (`__init__`) рядом друг с другом. Подтвердите, что вы сделали это. Затем подтвердите, что вы используете правильные параметры функции с корректными значениями типов данных. В данном случае метод `__init__` принимает только `self`, который должен быть правильного типа.
- Затем войдите внутрь метода `__init__` и проведите проверку строки за строкой, подтверждая каждый вызов функции и каждую переменную аналогичным образом. Количество передаваемых параметров верное? Типы данных верные?
- Для каждого ветвления (инструкций if, циклов for и while) подтвердите, что логика верна и что она правильно обрабатывает все возможные условия внутри себя. Есть ли ошибки в блоках else инструкций if? Может ли цикл завершиться? Затем углубитесь в каждое ветвление, следуя тем же шагам для функций, входящих в ветвление, проверяйте переменные и возвращайтесь назад, чтобы проверить возвращаемое значение.
```+ Когда вы достигнете конца функции или встретите оператор `return`, вернитесь обратно к вызывающему методу `test_push`, чтобы проверить, совпадает ли возвращаемое значение с ожидаемым значением при вызове этого метода. Помните, что вы можете применять этот подход ко всем вызовам внутри метода `__init__`.
+ Наконец, когда вы достигнете конца метода `test_push`, вы завершите процесс и выполните рекурсивную проверку всех вызываемых им методов. Этот процесс может показаться скучным вначале, но вы станете всё быстрее. В видео вы увидите, что я делаю это перед каждым тестом (или хотя бы стараюсь делать это).Процесс состоит из следующих шагов:
* Напишите тестовый код.
* Напишите код, чтобы сделать тесты работоспособными.
* Аудируйте оба этих компонента.
* Выполните тесты, чтобы проверить корректность.
## Упражнение
Мы пришли к этому разделу, и теперь вы готовы попробовать. Прежде всего, просмотрите тесты и исследуйте их работу. Изучите код в `sllist.py`, чтобы понять, что вам нужно сделать. Я рекомендую, когда вы пытаетесь реализовать функцию в `SingleLinkList`, сначала написать комментарии, описывающие её действие, а затем заполнить Python-код, чтобы эти комментарии стали рабочими. Вы увидите, как я это делаю в видео.
После того как вы потратите несколько сессий по 45 минут, чтобы "разобрать" его и попытаться заставить работать, пора смотреть видео. Сначала попробуйте сами, чтобы лучше понять то, что я пытаюсь сделать, что позволит вам лучше воспринять видео. Видео будет просто программированием без слов, но я сделаю голосовое сопровождение, чтобы объяснить происходящее. Видео также будет выполнено быстрее, чтобы экономить время, и я буду урезать любые скучные ошибки или потерянное время.
Как только вы увидите, как я это сделал, сделайте свои заметки (правильно?), а затем попробуйте более строгое испытание и проведите процесс аудита максимально внимательно.## Аудит
После написания кода убедитесь, что вы выполнили аудит согласно процессу, описанному в третьей части. Если вы не уверены, как это сделать, я также проведу аудит этого упражнения в видеоролике.
## Глубокое обучение
Глубокое обучение для этой практики состоит в том, чтобы снова реализовать этот алгоритм полностью, как было описано в третьей части. Также подумайте, какие операции в этой структуре данных наиболее вероятно будут медленными. После завершения работы над этим, проведите аудит над тем, что вы создали.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )