• בלוג
  • חידת גרמלין - Coalesce

חידת גרמלין - Coalesce

09/12/2023

הקוד הבא היה אמור לבדוק אם קיים ורקטקס עם הערך bob במאפיין name, ואם לא קיים ליצור חדש, אבל הוא לא עושה בדיוק את מה שרצינו. מה עושה הקוד, מה הבעיה ואיך נתקן אותו?

g
  .V()
  .coalesce(
    __.has('person', 'name', 'bob'),
    __.addV('person').property('name', 'bob')
  )
  .iterate()

1. הבעיה בקוד

הפקודה coalesce של גרמלין מקבלת מספר אפשרויות להתקדמות בגרף ומבצעת אותן לפי הסדר עד שמגיעה לאפשרות שמחזירה צומת תקין. לכן לכאורה הקוד היה אמור להתחיל מכל הצמתים בגרף, למצוא צומת שיש לו את המאפיין name עם הערך bob, אם קיים להחזיר אותו ואם לא קיים להגיע ל addV. אבל לא ככה זה עובד.

הבעיה בקוד היא ש coalesce מופיע אחרי V() ולכן הוא יופעל על כל הצמתים בגרף, כלומר לכל צומת נבדוק בנפרד אם הוא מתאים ל has ואם לא ננסה להוסיף צומת כזה עם addV, מה שיגרום להוספה של המון צמתים. דוגמת הרצה בסקאלה עם הקוד הזה:

@main def main() =
  val graph = TinkerGraph.open
  val g = traversal.withEmbedded(graph)

  println(g.addV("person").property("name", "a").next())
  println(g.addV("person").property("name", "b").next())

  println("--- 1")
  println(g.V().elementMap().toList)

  g.V()
    .coalesce(
      has("person", "name", "bob"),
      addV("person").property("name", "bob"))
    .iterate()

  println("--- 2")
  println(g.V().elementMap().toList)

מדפיסה את הפלט:

--- 1
[{id=0, label=person, name=a}, {id=2, label=person, name=b}]
--- 2
[{id=0, label=person, name=a}, {id=2, label=person, name=b}, {id=4, label=person, name=bob}, {id=6, label=person, name=bob}]

כלומר נוצרו שני צמתים חדשים עם השם bob.

2. פיתרון בעזרת fold

איך מתקנים? הכי פשוט זה קודם לחפש את הצומת ואז עליו להפעיל את coalesce. הבעיה היא שניסיון כזה עלול להיכשל אם הצומת לא קיים ולעצור את כל השאילתה. לכן יש להוסיף צעד fold שאוסף את כל הצמתים המתאימים לרשימה ולפתוח את אותה רשימה בתוך ה coalesce. או בקיצור בקוד:

@main def main() =
  val graph = TinkerGraph.open
  val g = traversal.withEmbedded(graph)

  println(g.addV("person").property("name", "a").next())
  println(g.addV("person").property("name", "b").next())

  println("--- 1")
  println(g.V().elementMap().toList)

  g.V()
    .has("person", "name", "bob")
    .fold()
    .coalesce(
      unfold(),
      addV("person").property("name", "bob"))
    .iterate()

  println("--- 2")
  println(g.V().elementMap().toList)

מה שיוצר רק צומת אחד עם השם bob.

3. פיתרון בעזרת merge

בגירסאות מספיק חדשות של טינקרפופ אפשר להשתמש ב merge כדי לחסוך את כל הבלאגן הזה כשאנחנו מנסים למזג צומת לפי מאפיין כמו name. הקוד נראה ככה:

  g.mergeV(Map[Object, Object](
    T.label -> "person",
    "name" -> "bob"
  ).asJava)
  .iterate()

כל עוד המיזוג הוא לפי מאפיינים שאנחנו מכירים זה יהיה הפיתרון הכי טוב. כשצריך למזג לפי יחסים לצמתים אחרים (לדוגמה אם קיים צומת person שהוא friend של bob) אז נצטרך לחזור לטריק של ה coalesce וה fold.