Статьи

Neo4j: обучение циклическому графу зависимостей

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

Сначала я набросал схему развития навыка, но быстро понял, что нарисовал график зависимости, и подумал, что его включение в Neo4j упростит ситуацию.

Я начал с общей цели катания на велосипеде, которая заключалась в том, чтобы «иметь возможность ездить на велосипеде через общественный парк»:

1
MERGE (:Goal:Task {name: "Be able to cycle through a public park"})

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

Мини-алгоритм, который мы собираемся использовать для разбивки задач:

  1. Можем ли мы выполнить данную задачу сейчас?
  2. Разбейте задачу на что-то более простое и вернитесь к 1.

Одна из вещей, которую нужно иметь в виду, это то, что мы не получим идеальный разрыв в первый раз, поэтому нам, возможно, придется его изменить. Для диаграммы, нарисованной на листе бумаги, это будет раздражать, но в Neo4j это просто более простой рефакторинг.

Возвращаясь к велоспорту. Поскольку цель еще не достижима, нам нужно разбить ее на что-то более простое. Давайте начнем с чего-то действительно простого:

1
2
3
4
MERGE (task:Task {name: "Take a few steps forward while standing over the bike"})
WITH task
MATCH (goal:Goal:Task {name: "Be able to cycle through a public park"})
MERGE (goal)-[:DEPENDS_ON]->(task)

В первой строке мы создаем нашу новую задачу, а затем связываем ее с нашей целью, которую мы создали ранее.

граф-9

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

Давайте обновим наш график:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// First let's get rid of the relationship between our initial task and the goal
MATCH (initialTask:Task {name: "Take a few steps forward while standing over the bike"})
MATCH (goal:Goal {name: "Be able to cycle through a public park"})
MATCH (goal)-[rel:DEPENDS_ON]->(initialTask)
DELETE rel
  
WITH initialTask, goal, ["Get bike moving from standing start", "Cycle forward while standing", "Cycle forward while sitting"] AS newTasks
  
// Create some nodes for our new tasks
UNWIND newTasks AS newTask
MERGE (t:Task {name: newTask})
WITH initialTask, goal, COLLECT(t) AS newTasks
WITH initialTask, goal, newTasks, newTasks[0] AS firstTask, newTasks[-1] AS lastTask
  
// Connect the last task to the goal
MERGE (goal)-[:DEPENDS_ON]->(lastTask)
  
// And the first task to our initial task
MERGE (firstTask)-[:DEPENDS_ON]->(initialTask)
  
// And all the tasks to each other
FOREACH(i in RANGE(0, length(newTasks) - 2) |
  FOREACH(t1 in [newTasks[i]] | FOREACH(t2 in [newTasks[i+1]] |
    MERGE (t2)-[:DEPENDS_ON]->(t1)
)))

граф-10

Строго говоря, нам не нужно учиться ездить на велосипеде, стоя — мы могли бы просто перейти от движения велосипеда к велосипеду вперед, сидя. Давайте обновим график, чтобы отразить это:

1
2
3
MATCH (sitting:Task {name: "Cycle forward while sitting"})
MATCH (moving:Task {name: "Get bike moving from standing start"})
MERGE (sitting)-[:DEPENDS_ON]->(moving)

граф-11

Как только мы разберемся с этими задачами, давайте добавим еще несколько, чтобы приблизить нас к нашей цели:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
WITH [
  {skill: "Controlled stop using brakes/feet", dependsOn: "Cycle forward while sitting"},
  {skill: "Steer around stationary objects", dependsOn: "Controlled stop using brakes/feet"},
  {skill: "Steer around people", dependsOn: "Steer around stationary objects"},
  {skill: "Navigate a small circular circuit", dependsOn: "Steer around stationary objects"},
  {skill: "Navigate a loop of a section of the park", dependsOn: "Navigate a small circular circuit"},
  {skill: "Navigate a loop of a section of the park", dependsOn: "Steer around people"},
  {skill: "Be able to cycle through a public park", dependsOn: "Navigate a loop of a section of the park"}
  
] AS newTasks
  
FOREACH(newTask in newTasks |
  MERGE (t1:Task {name: newTask.skill})  
  MERGE (t2:Task {name: newTask.dependsOn})
  MERGE (t1)-[:DEPENDS_ON]->(t2)
)

Наконец, давайте избавимся от отношения с нашей целью «Цикл вперед, сидя», так как мы заменили это на несколько промежуточных шагов:

1
2
3
4
5
MATCH (task:Task {name: "Cycle forward while sitting"})
WITH task
MATCH (goal:Goal:Task {name: "Be able to cycle through a public park"})
MERGE (goal)-[rel:DEPENDS_ON]->(task)
DELETE rel

И вот как выглядит окончательный граф зависимостей:

граф-13

Хотя я поместил это в Neo4j, чтобы визуализировать зависимости, теперь мы можем также запрашивать данные. Например, скажем, я знаю, как ездить на велосипеде вперед, сидя на велосипеде. Какие шаги есть между мной и возможностью кататься на велосипеде вокруг парка?

1
2
3
4
MATCH (t:Task {name: "Cycle forward while sitting"}),
      (g:Goal {name: "Be able to cycle through a public park"}),
      path = shortestpath((g)-[:DEPENDS_ON*]->(t))
RETURN path

граф-14

Или, если мы хотим получить список задач, которые нам нужно выполнить, мы могли бы немного реструктурировать запрос:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
MATCH (t:Task {name: "Cycle forward while sitting"}),
      (g:Goal {name: "Be able to cycle through a public park"}),
      path = shortestpath((t)<-[:DEPENDS_ON*]->(g))
WITH [n in nodes(path) | n.name] AS tasks
UNWIND tasks AS task
RETURN task
  
==> +--------------------------------------------+
==> | task                                       |
==> +--------------------------------------------+
==> | "Cycle forward while sitting"              |
==> | "Controlled stop using brakes/feet"        |
==> | "Steer around stationary objects"          |
==> | "Steer around people"                      |
==> | "Navigate a loop of a section of the park" |
==> | "Be able to cycle through a public park"   |
==> +--------------------------------------------+
==> 6 rows

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