танцующие ссылки
Jul. 18th, 2015 12:28 amСЯУ из комментария в Hacker News, что проблему решения судоку можно легко представить в виде частного случая проблемы exact cover. Заодно решил прочитать уже ставшую классической статью Кнута о его алгоритме "танцующих ссылок" для решения проблемы exact cover.
Помню, что мне много лет назад это показалось слишком сложным, и я отложил статью, но теперь не понимаю, почему. На самом деле эти танцующие ссылки не сложны, и действительно очень красивы. Рекомендую к прочтению.
Если в двух словах, то Кнут описывает эффективный подход к написанию поиска с backtracking. Любой, кто писал в том или ином виде backtracking search, знает, что сложность обычно заключается в том, чтобы выбрать правильную репрезентацию данных, так, чтобы добавить следующий выбор, а потом его "откатить назад", было легко. Часто оказывается, что есть трейдофф между быстрым доступом к данным и быстрым изменению/откатыванию их части. Алгоритм Кнута основывается на следующем наблюдении. Если мы удаляем элемент из двойного связанного списка, то сам элемент, вырванный из списка, естественным образом сохраняет в себе - в своих указателях влево/вправо - информацию о том, где он был в списке; и за O(1) времени его можно обратно вставить. Значит, если в процессе backtracking search можно представить "следующий выбор" как удаление элементов из связанных списков, то "откат" - возвращение их в эти списки - получается и очень быстрым, и удобным с точки зрения промежуточных данных: элементы сами помнят, куда их вставлять, не нужно это отдельно никуда записывать.
Это еще не все, есть важные подробности конкретно в случае exact cover о том, в каком порядке удалять/возвращать элементы из списков, но это все можно прочитать в статье. Отличная статья, легко читается.
Мета-замечание: "алгоритм танцующих ссылок" Кнута требует свободного владения указателями! Не в каком-то конкретном языке, а самим понятием указателя. Его можно считать примером того, почему все-таки важно программистам иметь опыт работы с языками, в которых указатели не спрятаны от программиста: не только Питон, но и C++ или хотя бы Джава. Не то чтобы на Питоне невозможно было имплементировать этот алгоритм; нет, можно, но странно и неудобно, потому что нормальный способ работы со списком в Питоне - это, как ни странно, питоновский список, а в нем удаленный элемент не хранит при себе свое место и не может за O(1) вернуть себя на него.
Кто-то написал элегантный код алгоритма Кнута на Питоне, но его можно считать только кодом алгоритма X для решения exact cover, а не питоновским вариантом "танцующих ссылок", вопреки тому, что говорит автор кода. В его коде никакие ссылки не танцуют, а удаление элемента и возвращение его воплощено как удаление/возвращение из питоновского множества, т.е. операция O(logN) на каком-то сбалансированном дереве (полагаю - не знаю точно, как в Питоне внутри устроены множества). С другой стороны, важно отметить, что если это работает для проблем нужного нам масштаба, то и ладно. Еще в начале 90-х, не говоря уж о более ранних эпохах, делать отдельное сбалансированное дерево для каждой строки матрицы было бы безнадежно медленным. Закон Мура быстро, но верно сделал свое дело. Задачи с малым N теперь обычно не требуют элегантных оптимизаций, подобных "танцующим ссылкам"; а задачам с огромным N они все равно часто не помогают, потому что задачи NP-полные и перебор растет экспоненциально. Остается область посредине, где такие техники все еще важны и останутся важными. Нередко интересные задачи оказываются именно там, так что "танцующие ссылки" еще потанцуют, думаю.
Помню, что мне много лет назад это показалось слишком сложным, и я отложил статью, но теперь не понимаю, почему. На самом деле эти танцующие ссылки не сложны, и действительно очень красивы. Рекомендую к прочтению.
Если в двух словах, то Кнут описывает эффективный подход к написанию поиска с backtracking. Любой, кто писал в том или ином виде backtracking search, знает, что сложность обычно заключается в том, чтобы выбрать правильную репрезентацию данных, так, чтобы добавить следующий выбор, а потом его "откатить назад", было легко. Часто оказывается, что есть трейдофф между быстрым доступом к данным и быстрым изменению/откатыванию их части. Алгоритм Кнута основывается на следующем наблюдении. Если мы удаляем элемент из двойного связанного списка, то сам элемент, вырванный из списка, естественным образом сохраняет в себе - в своих указателях влево/вправо - информацию о том, где он был в списке; и за O(1) времени его можно обратно вставить. Значит, если в процессе backtracking search можно представить "следующий выбор" как удаление элементов из связанных списков, то "откат" - возвращение их в эти списки - получается и очень быстрым, и удобным с точки зрения промежуточных данных: элементы сами помнят, куда их вставлять, не нужно это отдельно никуда записывать.
Это еще не все, есть важные подробности конкретно в случае exact cover о том, в каком порядке удалять/возвращать элементы из списков, но это все можно прочитать в статье. Отличная статья, легко читается.
Мета-замечание: "алгоритм танцующих ссылок" Кнута требует свободного владения указателями! Не в каком-то конкретном языке, а самим понятием указателя. Его можно считать примером того, почему все-таки важно программистам иметь опыт работы с языками, в которых указатели не спрятаны от программиста: не только Питон, но и C++ или хотя бы Джава. Не то чтобы на Питоне невозможно было имплементировать этот алгоритм; нет, можно, но странно и неудобно, потому что нормальный способ работы со списком в Питоне - это, как ни странно, питоновский список, а в нем удаленный элемент не хранит при себе свое место и не может за O(1) вернуть себя на него.
Кто-то написал элегантный код алгоритма Кнута на Питоне, но его можно считать только кодом алгоритма X для решения exact cover, а не питоновским вариантом "танцующих ссылок", вопреки тому, что говорит автор кода. В его коде никакие ссылки не танцуют, а удаление элемента и возвращение его воплощено как удаление/возвращение из питоновского множества, т.е. операция O(logN) на каком-то сбалансированном дереве (полагаю - не знаю точно, как в Питоне внутри устроены множества). С другой стороны, важно отметить, что если это работает для проблем нужного нам масштаба, то и ладно. Еще в начале 90-х, не говоря уж о более ранних эпохах, делать отдельное сбалансированное дерево для каждой строки матрицы было бы безнадежно медленным. Закон Мура быстро, но верно сделал свое дело. Задачи с малым N теперь обычно не требуют элегантных оптимизаций, подобных "танцующим ссылкам"; а задачам с огромным N они все равно часто не помогают, потому что задачи NP-полные и перебор растет экспоненциально. Остается область посредине, где такие техники все еще важны и останутся важными. Нередко интересные задачи оказываются именно там, так что "танцующие ссылки" еще потанцуют, думаю.
no subject
Date: 2015-07-18 12:40 am (UTC)Да в том-то и дело, что при наличии Питона за понятия указателя и двунаправленного списка нужно бороться, т.е. обосновывать их абсолютно неочевидную ценность для каждого конкретного случая.
no subject
Date: 2015-07-18 03:04 am (UTC)https://github.com/python-git/python/blob/master/Objects/dictobject.c
2) А чем лучше всего читать ps-файлы? Я скачал ghostscript, проблевался (от жуткого рендера шрифтов и я не понял как посмотреть что-то кроме первой страницы, но я не буду узнавать как листать документ командами в консоли!!!), нашёл онлайн-конвертилку ps в pdf, но вдруг какая-то программа может нормально показывать ps после дабл-клика по нему. Может какой-то плагин к Foxit/Acrobat reader.
no subject
Date: 2015-07-18 03:10 am (UTC)no subject
Date: 2015-07-18 03:33 am (UTC)hash(1) = 1
hash(2) = 2
hash(3) = 3
А вот таким значениям хеш-функции не поможет даже перехеширование (пользователи 64-битных питонов - могут добавить 8 нулей в середине для того же эффекта):
>>> hash(0x100000009)
10
>>> hash(0x200000008)
10
>>> hash(0x300000007)
10
>>> hash(0x400000006)
10
no subject
Date: 2015-07-18 03:43 am (UTC)Что касется перехэширования, то если я правильно понимаю смысл этого термина, то оно происходит всякий раз когда достигается определенное число коллизий в хэш-таблице.
no subject
Date: 2015-07-18 03:51 am (UTC)Под "перехешированием" я понимал как применение дополнительной второй хеш-функции к тому, что пришло из hash()
no subject
Date: 2015-07-18 04:17 am (UTC)Понятно, а зачем это нужно? Если из первого hash() пришло два одинаковых значения, то очевидно второй hash() каким бы он ни был не сделает их разными, разве нет?
В применении же к конкретной специфике построения словаря на хэш-таблице, хэширование просто меняется в зависимости от количества данных - мы периодически меняем действительную адресацию по корзинам. Да, при этом все что хэшится к небольшим значениям будет продолжать хэшиться к ним же, однако не думаю что в практических задачах реально может встретиться слишком много подобных данных (хотя на этом можно построить DDoS атаку: https://events.ccc.de/congress/2011/Fahrplan/attachments/2007_28C3_Effective_DoS_on_web_application_platforms.pdf, но кажется они что-то изменили в алгоритме хеширования в 2012м: http://bugs.python.org/issue13703).
Наконец, можно заимплементить свой собственный __hash__() если встроенный не нравится - ибо питон динамический.
Подробнее:
http://stackoverflow.com/questions/13514716/overriding-pythons-hashing-function-in-dictionary
и еще подробнее:
http://www.laurentluce.com/posts/python-dictionary-implementation/
no subject
Date: 2015-07-18 03:14 am (UTC)no subject
Date: 2015-07-18 03:25 am (UTC)Получается вот такое юзабилити:
Скачиваем файл. Даблкликаем по файлу в списке скачанного. Открывается WinRar. Разархивируем документ в какую-нибудь папку. Ищем в гугле конвертилку. Закачиваем в конвертилку файл, указывая тот путь, по которому его разархивировали.
pdf: кликаем на ссылку, сразу читаем (вариант: кликаем на ссылку и скачиваем, кликаем по скачанному и читаем в установленной читалке pdf).
http://dobrokot.ru/dump/donald-knuth-dancing-links-colors.pdf
no subject
Date: 2015-07-18 03:36 am (UTC)Под линукс рендерит вполне годно
no subject
Date: 2015-07-18 03:44 am (UTC)no subject
Date: 2015-07-18 05:39 am (UTC)Клик на ссылку -файл скачался. Клик на скачанный файл в панели хрома - открывается статься.
no subject
Date: 2015-07-18 08:01 am (UTC)ps
Date: 2015-07-18 03:28 am (UTC)no subject
Date: 2015-07-18 03:47 am (UTC)но вдруг какая-то программа может нормально показывать ps после дабл-клика по нему
Вроде Acrobat Reader умеет, но я им уже давно не пользуюсь, поэтому на 100% не уверен.
no subject
Date: 2015-07-18 10:02 am (UTC)no subject
Date: 2015-07-18 10:03 am (UTC)no subject
Date: 2015-07-19 11:21 am (UTC)( gsview замÑÐ»Ð¸Ð²Ð°ÐµÑ Ð±ÑÐºÐ²Ñ Ð¸Ð·-за пÑоблем ÑовмеÑÑимоÑÑи ÑÑаÑÑÑ Ð¿ÑогÑамм Ñ Ð²ÑÑоким DPI мониÑоÑа, но ÑÑо не меÑÐ°ÐµÑ ÑиÑаÑÑ ÑложнÑÑ ÑÑаÑÑÑ )
no subject
Date: 2015-07-19 09:50 am (UTC)no subject
Date: 2015-07-24 03:43 pm (UTC)no subject
Date: 2015-07-18 05:09 am (UTC)"Сначала они тебя не замечают, потом смеются над тобой, затем борются с тобой. А потом ты побеждаешь."
Так победим.
По сути.
На питоне прекрасно можно работать со ссылками. Собственно в нем почти нельзя работать по значению. Из того что человек в примере поленился сделать двухсвязный список, делать вывод, что в питоне это делать странно и неудобно - это может быть неудобно только для тех кто привык к тому что можно работать по значению, и в общем то просто вопрос вкуса.
ибо простейший двухсвязный список на питоне
l=[]
r=[]
c=[l,r]
l.extend([None,c])
r.extend([c,None])
И все.
no subject
Date: 2015-07-18 06:04 am (UTC)no subject
Date: 2015-07-18 06:36 am (UTC)ну интерпретатор говорит, что переменная с говорит занимает 88 байт.
Tuple можно зафигачить на 72 байта.
В C/C++ это два 8 байтовых указателя. Т.е. оверхед где то в 4-6 раз если грубо
Оверхед конечно является аргументом, против Питона, но я то отвечал не на это. Я отвечал на утверждение, что ссылки на питоне выглядят странно. Как указывает автор поста - оверхед уже не так важен.
Сейчас, часто для интересных задач данные в оперативную память в принципе уже не влезают и надо уже использовать кластеры и там задержка по сети уже часто нивелирует любое преимущество в скорости. Хоть на Ruby пиши.
А скорость в разработке,часто нивелирует требование по памяти ибо железо дешевле времени программистов.
no subject
Date: 2015-07-18 07:41 am (UTC)no subject
Date: 2015-07-19 11:08 am (UTC)no subject
Date: 2015-07-19 02:27 pm (UTC)no subject
Date: 2015-07-19 03:27 pm (UTC)no subject
Date: 2015-07-19 04:10 pm (UTC)no subject
Date: 2015-07-20 04:50 am (UTC)ÐÑли да. То ÑпоÑÑÑе Ñ Ð°Ð²ÑоÑом поÑÑа, не Ñо мной.
> возможно менее ÑÑÑекÑивно
ÐÑевидно, неÑ. Ðа доÑÑаÑоÑно болÑÑÐ¸Ñ Ð´Ð°Ð½Ð½ÑÑ , ÑанÑÑÑÑие ÑÑÑлки победÑÑ. Ðа и по опÑÑÑ, set доÑÑаÑоÑно ÑоÑмознÑÑÑй Ñип даннÑÑ Ð½Ð° болÑÑÐ¸Ñ Ð¾Ð±ÑÐµÐ¼Ð°Ñ .
Я вообÑе не обÑÑÐ¶Ð´Ð°Ñ ÐºÐ°ÑеÑÑво иÑÑ Ð¾Ð´Ð½Ð¾Ð³Ð¾ пÑимеÑа. ÐÐµÐ½Ñ - ÑÑо же не вÑегда Ð¿Ð»Ð¾Ñ Ð¾. ÐÑÐ¸Ð¼ÐµÑ Ð´ÐµÐ¹ÑÑвиÑелÑно полÑÑилÑÑ ÐºÐ¾ÑоÑе и понÑÑнее. Ðо как Ñказано - ÑÑо не алгоÑиÑм ÑанÑÑÑÑÐ¸Ñ ÑÑÑлок.
Сам Ñ Ñоже ÑаÑе вÑего Ð·Ð°Ð±Ð¸Ð²Ð°Ñ Ð½Ð° излиÑнÑÑ Ð¾Ð¿ÑимизаÑÐ¸Ñ ÐµÑли знаÑ, ÑÑо колиÑеÑÑво даннÑÑ Ð½ÐµÐ²ÐµÐ»Ð¸ÐºÐ¾, или вообÑе пиÑÑ, Ñо ÑÑо должно ÑабоÑаÑÑ Ð±ÑÑÑÑо на С.
Ðо Ñ Ð¶Ðµ вообÑе говоÑил не пÑо ÑÑо.
no subject
Date: 2015-07-18 10:27 am (UTC)0: [None, 1, "who"],
1: [0, 2, "cares"],
2: [1, 3, "about"],
3: [2, None, "https://en.wikipedia.org/wiki/MMIX"]
}
Потом это сериализируется в Json и читается на Фортране :)
no subject
Date: 2015-07-18 06:11 am (UTC)К сожалению, решение получилось слишком медленным... выдавало пару десятков решений в час, тогда как их общее количество явно немногим меньше гугола...
no subject
Date: 2015-07-18 10:47 am (UTC)no subject
Date: 2015-07-19 08:30 pm (UTC)no subject
Date: 2015-07-19 12:47 pm (UTC)no subject
Date: 2015-07-21 09:49 am (UTC)