From 89a1b5f97ee3509c01ebad23a47f104385d05113 Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Sun, 17 May 2026 10:39:13 -0400 Subject: [PATCH 1/2] Add relative-distance --- config.json | 8 + exercises/practice/relative-distance/.busted | 5 + .../relative-distance/.docs/instructions.md | 39 ++++ .../relative-distance/.docs/introduction.md | 12 + .../relative-distance/.meta/config.json | 19 ++ .../relative-distance/.meta/example.moon | 45 ++++ .../.meta/spec_generator.moon | 16 ++ .../relative-distance/.meta/tests.toml | 31 +++ .../relative-distance/relative_distance.moon | 4 + .../relative_distance_spec.moon | 213 ++++++++++++++++++ lib/spec_helpers.moon | 2 +- 11 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 exercises/practice/relative-distance/.busted create mode 100644 exercises/practice/relative-distance/.docs/instructions.md create mode 100644 exercises/practice/relative-distance/.docs/introduction.md create mode 100644 exercises/practice/relative-distance/.meta/config.json create mode 100644 exercises/practice/relative-distance/.meta/example.moon create mode 100644 exercises/practice/relative-distance/.meta/spec_generator.moon create mode 100644 exercises/practice/relative-distance/.meta/tests.toml create mode 100644 exercises/practice/relative-distance/relative_distance.moon create mode 100644 exercises/practice/relative-distance/relative_distance_spec.moon diff --git a/config.json b/config.json index 0480036..eb88c4a 100644 --- a/config.json +++ b/config.json @@ -778,6 +778,14 @@ "prerequisites": [], "difficulty": 5 }, + { + "slug": "relative-distance", + "name": "Relative Distance", + "uuid": "d978e2b2-5647-4309-8e64-330fd1f95484", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "simple-cipher", "name": "Simple Cipher", diff --git a/exercises/practice/relative-distance/.busted b/exercises/practice/relative-distance/.busted new file mode 100644 index 0000000..86b84e7 --- /dev/null +++ b/exercises/practice/relative-distance/.busted @@ -0,0 +1,5 @@ +return { + default = { + ROOT = { '.' } + } +} diff --git a/exercises/practice/relative-distance/.docs/instructions.md b/exercises/practice/relative-distance/.docs/instructions.md new file mode 100644 index 0000000..64ca4e4 --- /dev/null +++ b/exercises/practice/relative-distance/.docs/instructions.md @@ -0,0 +1,39 @@ +# Instructions + +Your task is to determine the degree of separation between two individuals in a family tree. +This is similar to the pop culture idea that every Hollywood actor is [within six degrees of Kevin Bacon][six-bacons]. + +- You will be given an input, with all parent names and their children. +- Each name is unique, a child _can_ have one or two parents. +- The degree of separation is defined as the shortest number of connections from one person to another. +- If two individuals are not connected, return a value that represents "no known relationship." + Please see the test cases for the actual implementation. + +## Example + +Given the following family tree: + +```text + ┌──────────┐ ┌──────────┐ ┌───────────┐ + │ Helena │ │ Erdős ├─────┤ Shusaku │ + └───┬───┬──┘ └─────┬────┘ └────┬──────┘ + ┌───┘ └───────┐ └───────┬───────┘ +┌─────┴────┐ ┌────┴───┐ ┌─────┴────┐ +│ Isla ├─────┤ Tariq │ │ Kevin │ +└────┬─────┘ └────┬───┘ └──────────┘ + │ │ +┌────┴────┐ ┌────┴───┐ +│ Uma │ │ Morphy │ +└─────────┘ └────────┘ +``` + +The degree of separation between Tariq and Uma is 2 (Tariq → Isla → Uma). +There's no known relationship between Isla and Kevin, as there is no connection in the given data. +The degree of separation between Uma and Isla is 1. + +~~~~exercism/note +Isla and Tariq are siblings and have a separation of 1. +Similarly, this implementation would report a separation of 2 from you to your father's brother. +~~~~ + +[six-bacons]: https://en.wikipedia.org/wiki/Six_Degrees_of_Kevin_Bacon diff --git a/exercises/practice/relative-distance/.docs/introduction.md b/exercises/practice/relative-distance/.docs/introduction.md new file mode 100644 index 0000000..34073b4 --- /dev/null +++ b/exercises/practice/relative-distance/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +You've been hired to develop **Noble Knots**, the hottest new dating app for nobility! +With centuries of royal intermarriage, things have gotten… _complicated_. +To avoid any _oops-we're-twins_ situations, your job is to build a system that checks how closely two people are related. + +Noble Knots is inspired by Iceland's "[Islendinga-App][islendiga-app]," which is backed up by a database that traces all known family connections between Icelanders from the time of the settlement of Iceland. +Your algorithm will determine the **degree of separation** between two individuals in the royal family tree. + +Will your app help crown a perfect match? + +[islendiga-app]: https://web.archive.org/web/20250816223614/http://www.islendingaapp.is/information-in-english/ diff --git a/exercises/practice/relative-distance/.meta/config.json b/exercises/practice/relative-distance/.meta/config.json new file mode 100644 index 0000000..4f79b0d --- /dev/null +++ b/exercises/practice/relative-distance/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "relative_distance.moon" + ], + "test": [ + "relative_distance_spec.moon" + ], + "example": [ + ".meta/example.moon" + ] + }, + "blurb": "Given a family tree, calculate the degree of separation.", + "source": "vaeng", + "source_url": "https://github.com/exercism/problem-specifications/pull/2537" +} diff --git a/exercises/practice/relative-distance/.meta/example.moon b/exercises/practice/relative-distance/.meta/example.moon new file mode 100644 index 0000000..9578acd --- /dev/null +++ b/exercises/practice/relative-distance/.meta/example.moon @@ -0,0 +1,45 @@ +buildTree = (tree) -> + t = {} + for name, children in pairs tree + if not t[name] + t[name] = { + parent: nil + siblings: {} + children: {} + } + for i, child in ipairs children + table.insert t[name].children, child + if t[child] + t[child].parent = name + else + t[child] = {parent: name, children: {}} + t[child].siblings = [c for j, c in ipairs children when i != j] + t + +ancestry = (t, person) -> + parentage = {person} + while t[person].parent + table.insert parentage, 1, t[person].parent + person = t[person].parent + parentage + +removeCommonAncestors = (list1, list2) -> + while #list1 > 0 and #list2 > 0 and list1[1] == list2[1] + table.remove list1, 1 + table.remove list2, 1 + +{ + degreeOfSeparation: (tree, a, b) -> + t = buildTree tree + anc = {who, ancestry(t, who) for who in *{a, b}} + + -- different roots, no relation + return nil if anc[a][1] != anc[b][1] + + removeCommonAncestors anc[a], anc[b] + return #anc[b] if #anc[a] == 0 -- a is ancestor of b + return #anc[a] if #anc[b] == 0 -- b is ancestor of a + + -- if we get here, they are siblings or cousins + #anc[a] + #anc[b] - 1 +} diff --git a/exercises/practice/relative-distance/.meta/spec_generator.moon b/exercises/practice/relative-distance/.meta/spec_generator.moon new file mode 100644 index 0000000..7ab3c29 --- /dev/null +++ b/exercises/practice/relative-distance/.meta/spec_generator.moon @@ -0,0 +1,16 @@ +import indent, quote, is_json_null, table_dump from require 'spec_helpers' + +{ + module_imports: {'degreeOfSeparation'}, + + generate_test: (case, level) -> + local lines + -- you may want to "switch case.property" here + lines = { + "familyTree = #{table_dump case.input.familyTree, level}", + "result = #{case.property} familyTree, #{quote case.input.personA}, #{quote case.input.personB}", + "expected = #{if is_json_null case.expected then nil else case.expected}", + "assert.are.equal expected, result" + } + table.concat [indent line, level for line in *lines], '\n' +} diff --git a/exercises/practice/relative-distance/.meta/tests.toml b/exercises/practice/relative-distance/.meta/tests.toml new file mode 100644 index 0000000..66c91ba --- /dev/null +++ b/exercises/practice/relative-distance/.meta/tests.toml @@ -0,0 +1,31 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[4a1ded74-5d32-47fb-8ae5-321f51d06b5b] +description = "Direct parent-child relation" + +[30d17269-83e9-4f82-a0d7-8ef9656d8dce] +description = "Sibling relationship" + +[8dffa27d-a8ab-496d-80b3-2f21c77648b5] +description = "Two degrees of separation, grandchild" + +[34e56ec1-d528-4a42-908e-020a4606ee60] +description = "Unrelated individuals" + +[93ffe989-bad2-48c4-878f-3acb1ce2611b] +description = "Complex graph, cousins" + +[2cc2e76b-013a-433c-9486-1dbe29bf06e5] +description = "Complex graph, no shortcut, far removed nephew" + +[46c9fbcb-e464-455f-a718-049ea3c7400a] +description = "Complex graph, some shortcuts, cross-down and cross-up, cousins several times removed, with unrelated family tree" diff --git a/exercises/practice/relative-distance/relative_distance.moon b/exercises/practice/relative-distance/relative_distance.moon new file mode 100644 index 0000000..dad29f2 --- /dev/null +++ b/exercises/practice/relative-distance/relative_distance.moon @@ -0,0 +1,4 @@ +{ + degreeOfSeparation: (tree, a, b) -> + error 'Implement me!' +} diff --git a/exercises/practice/relative-distance/relative_distance_spec.moon b/exercises/practice/relative-distance/relative_distance_spec.moon new file mode 100644 index 0000000..273bc5c --- /dev/null +++ b/exercises/practice/relative-distance/relative_distance_spec.moon @@ -0,0 +1,213 @@ +import degreeOfSeparation from require 'relative_distance' + +describe 'relative-distance:', -> + it 'Direct parent-child relation', -> + familyTree = { + Vera: {'Tomoko'} + Tomoko: {'Aditi'} + } + result = degreeOfSeparation familyTree, 'Vera', 'Tomoko' + expected = 1 + assert.are.equal expected, result + + pending 'Sibling relationship', -> + familyTree = { + Dalia: {'Olga', 'Yassin'} + } + result = degreeOfSeparation familyTree, 'Olga', 'Yassin' + expected = 1 + assert.are.equal expected, result + + pending 'Two degrees of separation, grandchild', -> + familyTree = { + Mateo: {'Rami'} + Khadija: {'Mateo'} + } + result = degreeOfSeparation familyTree, 'Khadija', 'Rami' + expected = 2 + assert.are.equal expected, result + + pending 'Unrelated individuals', -> + familyTree = { + Kaito: {'Elif'} + Priya: {'Rami'} + } + result = degreeOfSeparation familyTree, 'Priya', 'Kaito' + expected = nil + assert.are.equal expected, result + + pending 'Complex graph, cousins', -> + familyTree = { + Tomoko: {'Gabriela'} + Elif: {'Rami'} + Umar: {'Helena'} + Vera: {'Igor'} + Ian: {'Vera'} + Javier: {'Quynh', 'Ravi'} + Bao: {'Dalia', 'Elias'} + Hana: {'Umar'} + Kaito: {'Xia'} + Ravi: {'Celine'} + Dalia: {'Hassan', 'Isla'} + Isla: {'Pedro'} + Wang: {'Jing'} + Nia: {'Antonio'} + Aiko: {'Bao', 'Carlos'} + Gustavo: {'Mina'} + Hassan: {'Noah', 'Olga'} + Viktor: {'Hana', 'Ian'} + Priya: {'Cai'} + Fatima: {'Khadija', 'Liam'} + Xiomara: {'Kaito'} + Diego: {'Qi'} + Tariq: {'Farah'} + Celine: {'Priya'} + Zara: {'Mohammed'} + Giorgio: {'Tomoko'} + Aditi: {'Nia'} + Yuki: {'Leila'} + Yassin: {'Lucia'} + Xia: {'Kim'} + Noah: {'Xiomara'} + Wyatt: {'Jun'} + Quynh: {'Boris'} + Sven: {'Fabio'} + Rami: {'Ewa'} + Qi: {'Dimitri'} + Uma: {'Giorgio'} + Elias: {'Javier'} + Oscar: {'Bianca'} + Mina: {'Viktor', 'Wang'} + Carlos: {'Fatima', 'Gustavo'} + Liam: {'Tariq', 'Uma'} + Farah: {'Sven'} + Leila: {'Yassin'} + Jing: {'Wyatt'} + Pedro: {'Zane', 'Aditi'} + Sofia: {'Diego', 'Elif'} + Mateo: {'Zara'} + Khadija: {'Sofia'} + Boris: {'Oscar'} + Zane: {'Mateo'} + Olga: {'Yuki'} + } + result = degreeOfSeparation familyTree, 'Dimitri', 'Fabio' + expected = 9 + assert.are.equal expected, result + + pending 'Complex graph, no shortcut, far removed nephew', -> + familyTree = { + Tomoko: {'Gabriela'} + Elif: {'Rami'} + Umar: {'Helena'} + Vera: {'Igor'} + Ian: {'Vera'} + Javier: {'Quynh', 'Ravi'} + Bao: {'Dalia', 'Elias'} + Hana: {'Umar'} + Kaito: {'Xia'} + Ravi: {'Celine'} + Dalia: {'Hassan', 'Isla'} + Isla: {'Pedro'} + Wang: {'Jing'} + Nia: {'Antonio'} + Aiko: {'Bao', 'Carlos'} + Gustavo: {'Mina'} + Hassan: {'Noah', 'Olga'} + Viktor: {'Hana', 'Ian'} + Priya: {'Cai'} + Fatima: {'Khadija', 'Liam'} + Xiomara: {'Kaito'} + Diego: {'Qi'} + Tariq: {'Farah'} + Celine: {'Priya'} + Zara: {'Mohammed'} + Giorgio: {'Tomoko'} + Aditi: {'Nia'} + Yuki: {'Leila'} + Yassin: {'Lucia'} + Xia: {'Kim'} + Noah: {'Xiomara'} + Wyatt: {'Jun'} + Quynh: {'Boris'} + Sven: {'Fabio'} + Rami: {'Ewa'} + Qi: {'Dimitri'} + Uma: {'Giorgio'} + Elias: {'Javier'} + Oscar: {'Bianca'} + Mina: {'Viktor', 'Wang'} + Carlos: {'Fatima', 'Gustavo'} + Liam: {'Tariq', 'Uma'} + Farah: {'Sven'} + Leila: {'Yassin'} + Jing: {'Wyatt'} + Pedro: {'Zane', 'Aditi'} + Sofia: {'Diego', 'Elif'} + Mateo: {'Zara'} + Khadija: {'Sofia'} + Boris: {'Oscar'} + Zane: {'Mateo'} + Olga: {'Yuki'} + } + result = degreeOfSeparation familyTree, 'Lucia', 'Jun' + expected = 14 + assert.are.equal expected, result + + pending 'Complex graph, some shortcuts, cross-down and cross-up, cousins several times removed, with unrelated family tree', -> + familyTree = { + Tomoko: {'Gabriela'} + Elif: {'Rami'} + Umar: {'Helena'} + Vera: {'Igor'} + Ian: {'Vera'} + Javier: {'Quynh', 'Ravi'} + Bao: {'Dalia'} + Hana: {'Umar'} + Kaito: {'Xia'} + Ravi: {'Celine'} + Dalia: {'Hassan', 'Isla'} + Isla: {'Pedro'} + Wang: {'Jing'} + Nia: {'Antonio'} + Aiko: {'Bao', 'Carlos'} + Gustavo: {'Mina'} + Hassan: {'Noah', 'Olga'} + Viktor: {'Hana', 'Ian'} + Priya: {'Cai'} + Fatima: {'Khadija', 'Liam'} + Xiomara: {'Kaito'} + Diego: {'Qi'} + Tariq: {'Farah'} + Celine: {'Priya'} + Giorgio: {'Tomoko'} + Aditi: {'Nia'} + Yuki: {'Leila'} + Zara: {'Mohammed'} + Yassin: {'Lucia'} + Olga: {'Yuki'} + Xia: {'Kim'} + Quynh: {'Boris'} + Wyatt: {'Jun'} + Sven: {'Fabio'} + Rami: {'Ewa'} + Uma: {'Giorgio'} + Qi: {'Dimitri'} + Oscar: {'Bianca'} + Mina: {'Viktor', 'Wang'} + Carlos: {'Fatima', 'Gustavo'} + Liam: {'Tariq', 'Uma'} + Farah: {'Sven'} + Leila: {'Yassin'} + Jing: {'Wyatt'} + Pedro: {'Zane', 'Aditi'} + Sofia: {'Diego', 'Elif'} + Mateo: {'Zara'} + Khadija: {'Sofia'} + Boris: {'Oscar'} + Zane: {'Mateo'} + Noah: {'Xiomara'} + } + result = degreeOfSeparation familyTree, 'Wyatt', 'Xia' + expected = 12 + assert.are.equal expected, result diff --git a/lib/spec_helpers.moon b/lib/spec_helpers.moon index c9479bd..da0211f 100644 --- a/lib/spec_helpers.moon +++ b/lib/spec_helpers.moon @@ -165,7 +165,7 @@ table_dump = (what, level = 0) -> _dump = (what, depth = 0) -> t = type what if t == 'string' then - json_string what + if what\find "[\t\r\n\\]" then json_string what else quote what elseif t != 'table' then tostring what else From 9fd2122494a170e78e5ae9534145ec5459d4907d Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Sun, 17 May 2026 10:50:02 -0400 Subject: [PATCH 2/2] tweaks --- .../relative-distance/.meta/example.moon | 22 ++++++------------- .../relative-distance/relative_distance.moon | 2 +- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/exercises/practice/relative-distance/.meta/example.moon b/exercises/practice/relative-distance/.meta/example.moon index 9578acd..366b786 100644 --- a/exercises/practice/relative-distance/.meta/example.moon +++ b/exercises/practice/relative-distance/.meta/example.moon @@ -1,19 +1,11 @@ buildTree = (tree) -> + -- given the algorithm I'm using, all we need to know is the parent of each person t = {} for name, children in pairs tree if not t[name] - t[name] = { - parent: nil - siblings: {} - children: {} - } - for i, child in ipairs children - table.insert t[name].children, child - if t[child] - t[child].parent = name - else - t[child] = {parent: name, children: {}} - t[child].siblings = [c for j, c in ipairs children when i != j] + t[name] = {parent: nil} + for child in *children + t[child] = {parent: name} t ancestry = (t, person) -> @@ -34,11 +26,11 @@ removeCommonAncestors = (list1, list2) -> anc = {who, ancestry(t, who) for who in *{a, b}} -- different roots, no relation - return nil if anc[a][1] != anc[b][1] + return nil if anc[a][1] != anc[b][1] removeCommonAncestors anc[a], anc[b] - return #anc[b] if #anc[a] == 0 -- a is ancestor of b - return #anc[a] if #anc[b] == 0 -- b is ancestor of a + return #anc[b] if #anc[a] == 0 -- a is ancestor of b + return #anc[a] if #anc[b] == 0 -- b is ancestor of a -- if we get here, they are siblings or cousins #anc[a] + #anc[b] - 1 diff --git a/exercises/practice/relative-distance/relative_distance.moon b/exercises/practice/relative-distance/relative_distance.moon index dad29f2..328f2ca 100644 --- a/exercises/practice/relative-distance/relative_distance.moon +++ b/exercises/practice/relative-distance/relative_distance.moon @@ -1,4 +1,4 @@ { - degreeOfSeparation: (tree, a, b) -> + degreeOfSeparation: (familyTree, personA, personB) -> error 'Implement me!' }