Стиль прохождения продолжения (CPS) при построении графа

Я работаю над библиотекой для поверхностей подразделения. Чтобы представить топологию сетки, я использую своего рода реечную структуру данных с разделенными вершинами (см. диаграмму слева).

Структура данных сетки

Во время построения сетки, которую также можно рассматривать как граф, создаются узлы, которые должны указывать на другие, которых еще не существует (см. диаграмму справа — пунктирная стрелка представляет будущие связи). Классическое решение — создать узел с пустым указателем, а затем обновить его при создании другого узла. Так как я работаю на Haskell :) и не хочу переходить на темную сторону кода (примеси) мне интересно, можно ли построить сетку (граф) без обновления данных. Я предполагаю, что CPS (стиль непрерывного прохождения) может справиться с этой задачей, но я не могу найти способ.

Это просто сон?

ОБНОВЛЕНИЕ

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

data Mesh = Edge Vertex Mesh Mesh Vertex | Ground

Если я не ошибаюсь и если это выполнимо, CPS позволит эффективно создавать (без обновлений узлов) и эффективно пересекать (без поиска на картах) графа. С другой стороны, граф станет полностью неизменным, т.е. одно изменение необходимо распространить по всему графу, например. изменение хвоста списка.

Я ошибся? Если нет, то как это сделать?


person LambdaStaal    schedule 15.09.2011    source источник
comment
Вы должны быть осторожны с ограничениями вашего представительства. Принципы Haskell запрещают вам определять интенсиональную идентичность Mesh; т. е. вы хотите связать себя узами брака, но вы не сможете определить, что, если вы идете куда-то и обратно, вы на самом деле возвращаетесь, а не куда-то еще в бесконечной циклической структуре. В математике графы определяются над произвольным набором вершин-носителей; Я предлагаю сделать то же самое для чистых функциональных графов.   -  person luqui    schedule 16.09.2011


Ответы (3)


Вам нужна техника, известная как завязывание узла. Он использует ленивую оценку, чтобы выполнить работу. ЦПС не нужен.

Предположим, вы можете идентифицировать каждый узел по некоторому уникальному идентификатору (строка, целое число или что-то еще). Предположим также, что при создании узла вы уже знаете идентификаторы всех узлов, на которые он указывает, независимо от того, созданы они уже или нет. Тогда вы можете использовать эту технику.

Вы вводите nodes :: Data.Map NodeID Node через свои функции создания графа (используйте монаду состояния для дополнительного удобства). Когда вы создаете узел, вы добавляете его на карту. Когда вы создаете ребро, которое должно указывать на узел с именем x, вы используете fromMaybe $ lookup nodes x. Не имеет значения, создан ли узел с именем x уже или будет создан в будущем. Пока он создается в какой-то момент, все готово. Он будет извлекаться с карты только тогда, когда вам это нужно.

Именно так я создавал график из его текстового описания. Возможно, есть другие, лучшие способы.

Если при создании узла вы не знаете идентификаторы всех узлов, на которые будет указывать ваш узел, вам необходимо немного изменить эту технику, например. передать карту от идентификатора узла к списку его соседей и построить каждый список постепенно.

Вы должны быть осторожны и избегать оценки ленивых значений до того, как закончите построение графика.

person n. 1.8e9-where's-my-share m.    schedule 15.09.2011
comment
Это не сработает, так как при вставке заметки создается новая карта, поэтому вы можете иметь в виду версию карты, в которой узел никогда не будет существовать. Вместо этого создайте карту сразу, используя fromList или подобное. Таким образом, вы можете легко сослаться на окончательную карту, чтобы связать себя узами брака. РЕДАКТИРОВАТЬ: На самом деле, вы все еще можете сделать это таким образом, просто убедитесь, что карта, используемая при поиске, является окончательной картой, а не текущей. - person hammar; 15.09.2011
comment
@hammar: да, хороший момент. Я забыл, что на самом деле столкнулся с этой проблемой. Я закончил тем, что использовал окончательную карту в поиске. - person n. 1.8e9-where's-my-share m.; 15.09.2011
comment
используется для создания графика? Как вы это делаете сейчас? - person John L; 15.09.2011
comment
Это было бы хорошим решением в случае однородной сетки, где предсказание метки было бы простым. Как вы сказали, в общем случае некоторая работа должна быть сделана правильно. Мой вопрос здесь больше о возможном использовании CPS (см. ОБНОВЛЕНИЕ по моему вопросу). Если CPS не сработает, я приму ваше предложение. Спасибо - person LambdaStaal; 15.09.2011

Он не использует CPS ... но я работаю над библиотекой планарных графов для Haskell, используя схему, аналогичную той, что вы описали выше. Ребра добавляются путем указания того, какое существующее ребро идет до или после него.

Фактическая реализация графа завершена, осталось сделать двоичную сериализацию работающей и производительной (используя PLANAR_CODE для начала, возможно также Graph6 и Sparse6) и несколько других дополнительных вещей.

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

Код можно получить у darcs get http://code.haskell.org/~ivanm/planar-graph/; пример использования (именно для этого я разрабатываю эту библиотеку) находится в http://code.haskell.org/~ivanm/dangd/.


Взято из документации Haddock в качестве примера его использования:

Например, пусть g ссылается на следующий график (где n1 и т. д. — это и метки, и имена переменных):

     ====                    ====
    ( n1 )                  ( n2 )
     ====                    ====





                             ====
                            ( n3 )
                             ====

Мы можем добавить ребро между n1 и n2 (используя Anywhere в качестве EdgePos, поскольку в настоящее время ни на одном узле нет ребер):

 ((e1,e2),g') = addEdge n1 Anywhere n2 Anywhere "e1" "e2" g

В результате получится следующий график:

                  e2
     ====  <---------------  ====
    ( n1 )                  ( n2 )
     ====  --------------->  ====
                  e1




                             ====
                            ( n3 )
                             ====

Если мы хотим добавить ребра между n2 и n3, у нас есть три варианта расположения на n2:

  • Используйте Anywhere: поскольку есть только одно другое ребро, не имеет значения с точки зрения встраивания, куда идет второе ребро.

  • Поместите новое ребро BeforeEdge e2 (по часовой стрелке вокруг n2).

  • Поместите новое ребро AfterEdge e2 (по часовой стрелке вокруг n2).

Поскольку n2 в настоящее время имеет только одно ребро, все три значения EdgePos дадут один и тот же график, поэтому мы можем произвольно выбрать одно из них:

 ((e3,e4),g'') = addEdge n2 (BeforeEdge e2) n3 Anywhere "e3" "e4" g'

Однако при наличии большего количества ребер необходимо соблюдать осторожность при использовании значения EdgePos. Результирующий график:

                  e2
     ====  <---------------  ====
    ( n1 )                  ( n2 )
     ====  --------------->  ====
                  e1         |  ^
                             |  |
                          e3 |  | e4
                             |  |
                             v  |
                             ====
                            ( n3 )
                             ====

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

 ((e4,e3), g'') = addEdge n3 Anywhere n2 (BeforeEdge e2) "e4" "e3" g'
person ivanm    schedule 15.09.2011
comment
Я просмотрел ваш код, и вы используете функции вставки и поиска карты для построения графика. Моя идея использования CPS состоит в том, чтобы избежать поиска или обновления любой карты. Смотрите ОБНОВЛЕНИЕ в моем вопросе. Спасибо - person LambdaStaal; 15.09.2011
comment
@LambdaStaal: я просто представил это как альтернативу :) Хотя могу я спросить, почему вы так против поиска или обновлений на базовых Картах? - person ivanm; 16.09.2011
comment
Пожалуйста, не поймите меня неправильно. Я не против их! Более того, я буду использовать их. Даже если CPS здесь работает, это кажется слишком сложной идеей, чтобы оставить его в производственном коде. Мне просто интересно, можно ли создать график без обновления или базовых таблиц. Я представляю, что это было бы более эффективно, и, что еще более странно, я понял, что это более естественное решение. Спасибо за уделенное время! - person LambdaStaal; 16.09.2011
comment
@LambdaStaal: Хорошо, я думал, что у вас есть особая причина, по которой вы хотите реализовать здесь графики без них, а не просто интересно, можно ли это сделать. Я предполагаю, что в моем случае использования мне нужно иметь возможность обновлять/изменять график, что, я полагаю, будет общим случаем (в отличие от статического графика). - person ivanm; 16.09.2011

Кажется, вам не нужно хранить ссылку на ребро NextA и NextB внутри ребра. Поскольку это то, что можно вычислить, пройдя от текущего ребра, почему бы не написать функцию, которая берет ребро и возвращает его ребро NextA/NextB, которые соответствуют диаграмме, основанной на направлении по часовой стрелке частей ребра A и B.

person Ankur    schedule 15.09.2011
comment
Извините, но я не понял. Вы говорите рассчитать NextA/B, а затем сохранить его в таблице/карте и использовать для будущих поисков. Не могли бы вы пояснить свой ответ? Для меня NextA или NextB являются частью этого процесса. - person LambdaStaal; 15.09.2011