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..366b786 --- /dev/null +++ b/exercises/practice/relative-distance/.meta/example.moon @@ -0,0 +1,37 @@ +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} + for child in *children + t[child] = {parent: name} + 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..328f2ca --- /dev/null +++ b/exercises/practice/relative-distance/relative_distance.moon @@ -0,0 +1,4 @@ +{ + degreeOfSeparation: (familyTree, personA, personB) -> + 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