Статьи

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

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

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

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

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

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

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

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

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

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

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

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

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

// 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

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

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

График 11

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

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)
)

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

 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, чтобы визуализировать зависимости, теперь мы можем также запрашивать данные. Например, скажем, я знаю, как ездить на велосипеде вперед, сидя на велосипеде. Какие шаги есть между мной и возможностью кататься на велосипеде вокруг парка?

 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

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

 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

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