Я играю с Neo4j, и до сих пор у меня есть географический график, где AIRPORT
соединяется с CITY
, CITY
COUNTRY
и COUNTRY
с CONTINENT
, как показано на рисунке
Ярлыки на стрелках переводят в org.neo4j.graphdb.RelationshipType
в мой код. Пока я могу построить путь между стартовым узлом MXP
до конечного узла LTN
используя следующий однонаправленный обход.
Traverser traverse = database.traversalDescription().depthFirst()
.relationships(CITY, BOTH)
.relationships(CONTINENT, BOTH)
.relationships(COUNTRY, BOTH)
.relationships(REGION, BOTH)
.evaluator(Evaluators.includeWhereEndNodeIs(endNode)).traverse(startNode);
С этим я получаю единственный путь MXP → Milan → Italy → Europe <- England <- London <- LTN
, что верно, учитывая описание графика, описание обхода и, конечно, мое понимание моего понимания такого описания.
Я пытаюсь изменить этот код, чтобы выполнить двунаправленный обход, то есть я хочу начать с MXP
и LTN
и остановиться в точке столкновения. Я попробовал следующий фрагмент, где комментарии означают мое понимание, поэтому легче указать проблему.
TraversalDescription startSide = database.traversalDescription().depthFirst() //Depth first algorithm
.relationships(CITY, OUTGOING) //consider CITY relationship, only outgoing
.relationships(REGION, OUTGOING) //consider REGION relationship, only outgoing
.relationships(COUNTRY, OUTGOING) //consider COUNTRY relationship, only outgoing
.relationships(CONTINENT, OUTGOING) //consider CONTINENT relationship, only outgoing
.evaluator(Evaluators.excludeStartPosition()); //do not consider the starting point.
//Here I tried also with all, with the same result
//with includeWhereEndNodeIs(endNode), again with same result
//and combining includeWhereEndNodeIs and excludeStartPosition, once more with same result.
//All tries I mirrored for the endSide description, changing endNode to startNode where I feel it was needed
TraversalDescription endSide = database.traversalDescription().depthFirst()
.relationships(CITY, OUTGOING)
.relationships(REGION, OUTGOING)
.relationships(COUNTRY, OUTGOING)
.relationships(CONTINENT, OUTGOING)
.evaluator(Evaluators.excludeStartPosition());
List<Node> asList = Arrays.asList(startNode, endNode);
Traverser traverse = database.bidirectionalTraversalDescription().endSide(endSide).startSide(startSide).traverse(asList, asList);
Здесь вместо пути, который я получаю с попыткой однонаправленного обхода, я получаю два пути: один только с MXP
и один с LTN
.
В этот момент я всерьез полагаю, что полностью недопонимаю двунаправленный обход и, возможно, даже его цель. Где моя ошибка? Почему я не получаю такой же результат?
Наконец-то я получил рабочее решение. Проблема в моем коде была связана с понятием уникальности. Интересные моменты для моей проблемы:
Устанавливает правила того, как позиции могут быть просмотрены во время обхода, как указано в Uniqueness. Значение по умолчанию, если не установлено, - NODE_GLOBAL.
Уникальность NODE_GLOBAL: ни один узел на всем графике не может быть посещен более одного раза. Это может потенциально потреблять много памяти, поскольку для этого требуется сохранить структуру данных в памяти, запоминающую все посещенные узлы.
Уникальность NODE_PATH: узел может не встречаться ранее в пути, достигающем его.
Эти описания каким-то образом отличаются от официального API, поэтому я играл в разные комбинации и получал следующий код:
TraversalDescription bothSide = database.traversalDescription().depthFirst()
.relationships(CITY, OUTGOING)
.relationships(REGION, OUTGOING)
.relationships(COUNTRY, OUTGOING)
.relationships(CONTINENT, OUTGOING)
.uniqueness(NODE_PATH);
Traverser traverser = database
.bidirectionalTraversalDescription()
.startSide(bothSide)
.endSide(bothSide)
.traverse(node, endNode);
В принципе, я определил общее TraversalDescription
как для конца, так и для начала, где я хочу следовать только отношениям OUTGOING, и я хочу рассматривать пути только там, где узлы уникальны внутри самого пути.
Затем я определил bidirectional traverser
который просто устанавливает конец и стартовую сторону и пересекает график от узла начального node
до конечного узла endNode
(ну, фактически он проходит от начала до конца И от конца до начала в то же время и останавливается, когда сталкиваются два обхода, объединяя результирующие пути в один путь, ведущий от start
до end
).
ПРИМЕЧАНИЕ. Я не совсем уверен в значении NODE_GLOBAL
, поскольку в моей базе данных каждый узел представляет собой географический объект, поэтому каждый узел на пути MXP → Milan → Italy → Europe <- England <- London <- LTN
должен можно посещать только один раз, и поэтому в этом контексте не должно быть разницы между NODE_GLOBAL
и NODE_PATH
.