Дизайн списка на функциональных языках - PullRequest
5 голосов
/ 07 апреля 2011

Я заметил, что в функциональных языках, таких как Haskell и OCaml, вы можете выполнять 2 действия со списками.Сначала вы можете сделать x:xs, где x - это элемент, а xs - это список, и в результате мы получим новый список, где x добавляется в начало xs в постоянное время.Вторым является x++y, где и x, и y являются списками, и в результате мы получаем новый список, в котором y добавляется в конец x за линейное время по отношению к числу элементов в x.Сейчас я не разбираюсь в том, как спроектированы языки и построены компиляторы, но мне это очень похоже на простую реализацию связанного списка с одним указателем на первый элемент.Если бы я реализовал эту структуру данных на языке, подобном C ++, я бы нашел тривиальным добавить указатель на последний элемент.В этом случае, если эти языки были реализованы таким образом (при условии, что они используют связанные списки, как описано), добавление «указателя» на последний элемент сделало бы намного более эффективным добавление элементов в конец списка и позволило бы сопоставить шаблон споследний элемент.

Мой вопрос заключается в том, действительно ли эти структуры данных реализованы в виде связанных списков, и если да, то почему они не добавляют ссылку на последний элемент?

Ответы [ 5 ]

11 голосов
/ 07 апреля 2011

Да, они действительно являются связанными списками. Но они неизменны. Преимущество неизменности заключается в том, что вам не нужно беспокоиться о том, у кого еще есть указатель на тот же список. Вы могли бы написать x++y, но где-то еще в программе можно полагаться на x, оставаясь неизменным.

Люди, которые работают над компиляторами для таких языков (из которых я один), не беспокоятся об этой стоимости, потому что существует множество других структур данных, обеспечивающих эффективный доступ:

  • Функциональная очередь, представленная в виде двух списков, обеспечивает постоянный доступ к обоим концам и амортизированное постоянное время для операций put и get.

  • Более сложная структура данных, такая как дерево пальцев , может обеспечить несколько видов доступа к списку при очень низкой цене.

  • Если вы просто хотите добавить постоянное время, Джон Хьюз разработал превосходное, простое представление списков как функций, которое обеспечивает именно это. (В библиотеке Haskell они называются DList.)

Если вас интересуют такие вопросы, вы можете получить хорошую информацию из книги Криса Окасаки Чисто функциональные структуры данных и из некоторых менее пугающих статей Ральфа Хинзе.

4 голосов
/ 07 апреля 2011

Вы сказали:

Второй - x++y, где x и y списки и итоговое действие добавляется в конце х в линейное время по отношению к числу элементов в х.

Это не совсем так в функциональном языке, таком как Haskell; y добавляется к копии из x, поскольку все, что удерживает x, зависит от того, не изменяется ли оно.

Если вы все равно собираетесь скопировать все x, удержание последнего узла на самом деле ничего вам не даст.

4 голосов
/ 07 апреля 2011

Да, это связанные списки.В таких языках, как Haskell и OCaml, вы не добавляете элементы в конец списка, точка.Списки неизменны.Существует одна операция для создания новых списков - минусы, оператор :, на который вы ссылались ранее.Он берет элемент и список и создает новый список с элементом в качестве заголовка и списком в качестве хвоста.Причина, по которой x++y принимает линейное время, состоит в том, что он должен содержать последний элемент x с y, а затем объединяет второй-последний элемент x с в этом списке , итак с каждым элементом x.Ни одна из cons-ячеек в x не может быть использована повторно, потому что это также приведет к изменению исходного списка.Указатель на последний элемент x не будет здесь очень полезен - нам еще предстоит пройти весь список.

1 голос
/ 07 апреля 2011

++ - это лишь одна из десятков «вещей, которые вы можете делать со списками». Реальность такова, что списки настолько универсальны, что редко используются другие коллекции. Кроме того, мы, функциональные программисты, почти никогда не чувствуем необходимости смотреть на последний элемент списка - если нам нужно, есть функция last.

Однако, если списки удобны, это не означает, что у нас нет других структур данных. Если вам действительно интересно, взгляните на эту книгу http://www.cs.cmu.edu/~rwh/theses/okasaki.pdf (Чисто функциональные структуры данных). Вы найдете деревья, очереди, списки с O (1) добавлением элемента в хвост и т. Д.

0 голосов
/ 07 апреля 2011

Вот небольшое объяснение того, как все делается в Clojure :

Самый простой способ избежать изменения состояния - это использовать неизменяемые структуры данных. Clojure предоставляет набор неизменяемых списков, векторов, наборов и карт. Поскольку их нельзя изменить, «добавление» или «удаление» чего-либо из неизменяемой коллекции означает создание новой коллекции, такой же, как старая, но с необходимыми изменениями. Постоянство - это термин, используемый для описания свойства, в котором старая версия коллекции все еще доступна после «изменения», и что коллекция поддерживает свои гарантии производительности для большинства операций. В частности, это означает, что новая версия не может быть создана с использованием полной копии, так как это потребует линейного времени. Неизбежно, постоянные коллекции реализуются с использованием связанных структур данных , так что новые версии могут делиться структурой с предыдущей версией. Односвязные списки и деревья являются основными функциональными структурами данных , к которым Clojure добавляет хэш-карту, набор и вектор, основанные на попытках хэширования в массиве.

(акцент мой)

Так что, в основном, похоже, вы в основном правы, по крайней мере, в отношении Clojure.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...