diff --git a/config.json b/config.json index 4a708598..3fbad120 100644 --- a/config.json +++ b/config.json @@ -1060,6 +1060,14 @@ "prerequisites": [], "difficulty": 4 }, + { + "slug": "relative-distance", + "name": "Relative Distance", + "uuid": "6d5cdbb3-f592-4b18-9cd3-23a28723e9cf", + "practices": [], + "prerequisites": [], + "difficulty": 4 + }, { "slug": "flower-field", "name": "Flower Field", diff --git a/exercises/practice/relative-distance/.docs/instructions.md b/exercises/practice/relative-distance/.docs/instructions.md new file mode 100644 index 00000000..9046aee7 --- /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.m.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 00000000..34073b40 --- /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 00000000..433ea8cd --- /dev/null +++ b/exercises/practice/relative-distance/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "meatball133" + ], + "files": { + "solution": [ + "src/relative_distance.cr" + ], + "test": [ + "spec/relative_distance_spec.cr" + ], + "example": [ + ".meta/src/example.cr" + ] + }, + "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/src/example.cr b/exercises/practice/relative-distance/.meta/src/example.cr new file mode 100644 index 00000000..694e5a2c --- /dev/null +++ b/exercises/practice/relative-distance/.meta/src/example.cr @@ -0,0 +1,63 @@ +require "set" + +class RelativeDistance + @graph : Hash(String, Set(String)) + + def initialize(family_tree : Hash(String, Array(String))) + @family_tree = family_tree + @graph = build_graph(@family_tree) + end + + def degree_of_separation?(person_a : String, person_b : String) : Int32? + return 0 if person_a == person_b + return nil unless @graph.has_key?(person_a) && @graph.has_key?(person_b) + + visited = Set(String).new + visited << person_a + + queue = [{person_a, 0}] of Tuple(String, Int32) + head = 0 + + while head < queue.size + current, distance = queue[head] + head += 1 + + @graph[current].each do |neighbor| + next if visited.includes?(neighbor) + + next_distance = distance + 1 + return next_distance if neighbor == person_b + + visited << neighbor + queue << {neighbor, next_distance} + end + end + + nil + end + + private def build_graph(family_tree : Hash(String, Array(String))) : Hash(String, Set(String)) + graph = Hash(String, Set(String)).new { |hash, key| hash[key] = Set(String).new } + + family_tree.each do |parent, children| + graph[parent] + + children.each do |child| + graph[child] + graph[parent] << child + graph[child] << parent + end + + # Siblings are considered 1 degree apart. + children.each_with_index do |child, i| + (i + 1...children.size).each do |j| + sibling = children[j] + graph[child] << sibling + graph[sibling] << child + end + end + end + + graph + end +end diff --git a/exercises/practice/relative-distance/.meta/test_template.ecr b/exercises/practice/relative-distance/.meta/test_template.ecr new file mode 100644 index 00000000..b07e7561 --- /dev/null +++ b/exercises/practice/relative-distance/.meta/test_template.ecr @@ -0,0 +1,12 @@ +require "spec" +require "../src/*" + +describe "<%-= to_capitalized(@json["exercise"].to_s) %>" do +<%- @json["cases"].as_a.each do |cases| %> + <%= status()%> "<%-= cases["description"] %>" do + family_tree = <%= cases["input"]["familyTree"].to_s == "{}" ? "{} of String => Array(String)" : to_s_deep(cases["input"]["familyTree"]) %> + relative_distance = <%= to_capitalized(@json["exercise"].to_s) %>.new(family_tree) + relative_distance.<%= cases["property"].to_s.underscore %>?("<%= cases["input"]["personA"] %>", "<%= cases["input"]["personB"] %>").should eq(<%= cases["expected"].as_i? ? cases["expected"] : "nil" %>) + end +<% end %> +end diff --git a/exercises/practice/relative-distance/.meta/tests.toml b/exercises/practice/relative-distance/.meta/tests.toml new file mode 100644 index 00000000..66c91ba0 --- /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/spec/relative_distance_spec.cr b/exercises/practice/relative-distance/spec/relative_distance_spec.cr new file mode 100644 index 00000000..075a9063 --- /dev/null +++ b/exercises/practice/relative-distance/spec/relative_distance_spec.cr @@ -0,0 +1,85 @@ +require "spec" +require "../src/*" + +describe "RelativeDistance" do + it "Direct parent-child relation" do + family_tree = {"Vera" => ["Tomoko"], "Tomoko" => ["Aditi"]} + relative_distance = RelativeDistance.new(family_tree) + relative_distance.degree_of_separation?("Vera", "Tomoko").should eq(1) + end + + pending "Sibling relationship" do + family_tree = {"Dalia" => ["Olga", "Yassin"]} + relative_distance = RelativeDistance.new(family_tree) + relative_distance.degree_of_separation?("Olga", "Yassin").should eq(1) + end + + pending "Two degrees of separation, grandchild" do + family_tree = {"Khadija" => ["Mateo"], "Mateo" => ["Rami"]} + relative_distance = RelativeDistance.new(family_tree) + relative_distance.degree_of_separation?("Khadija", "Rami").should eq(2) + end + + pending "Unrelated individuals" do + family_tree = {"Priya" => ["Rami"], "Kaito" => ["Elif"]} + relative_distance = RelativeDistance.new(family_tree) + relative_distance.degree_of_separation?("Priya", "Kaito").should eq(nil) + end + + pending "Complex graph, cousins" do + family_tree = { + "Aiko" => ["Bao", "Carlos"], "Bao" => ["Dalia", "Elias"], "Carlos" => ["Fatima", "Gustavo"], + "Dalia" => ["Hassan", "Isla"], "Elias" => ["Javier"], "Fatima" => ["Khadija", "Liam"], "Gustavo" => ["Mina"], + "Hassan" => ["Noah", "Olga"], "Isla" => ["Pedro"], "Javier" => ["Quynh", "Ravi"], "Khadija" => ["Sofia"], + "Liam" => ["Tariq", "Uma"], "Mina" => ["Viktor", "Wang"], "Noah" => ["Xiomara"], "Olga" => ["Yuki"], + "Pedro" => ["Zane", "Aditi"], "Quynh" => ["Boris"], "Ravi" => ["Celine"], "Sofia" => ["Diego", "Elif"], + "Tariq" => ["Farah"], "Uma" => ["Giorgio"], "Viktor" => ["Hana", "Ian"], "Wang" => ["Jing"], + "Xiomara" => ["Kaito"], "Yuki" => ["Leila"], "Zane" => ["Mateo"], "Aditi" => ["Nia"], "Boris" => ["Oscar"], + "Celine" => ["Priya"], "Diego" => ["Qi"], "Elif" => ["Rami"], "Farah" => ["Sven"], "Giorgio" => ["Tomoko"], + "Hana" => ["Umar"], "Ian" => ["Vera"], "Jing" => ["Wyatt"], "Kaito" => ["Xia"], "Leila" => ["Yassin"], + "Mateo" => ["Zara"], "Nia" => ["Antonio"], "Oscar" => ["Bianca"], "Priya" => ["Cai"], "Qi" => ["Dimitri"], + "Rami" => ["Ewa"], "Sven" => ["Fabio"], "Tomoko" => ["Gabriela"], "Umar" => ["Helena"], "Vera" => ["Igor"], + "Wyatt" => ["Jun"], "Xia" => ["Kim"], "Yassin" => ["Lucia"], "Zara" => ["Mohammed"], + } + relative_distance = RelativeDistance.new(family_tree) + relative_distance.degree_of_separation?("Dimitri", "Fabio").should eq(9) + end + + pending "Complex graph, no shortcut, far removed nephew" do + family_tree = { + "Aiko" => ["Bao", "Carlos"], "Bao" => ["Dalia", "Elias"], "Carlos" => ["Fatima", "Gustavo"], + "Dalia" => ["Hassan", "Isla"], "Elias" => ["Javier"], "Fatima" => ["Khadija", "Liam"], "Gustavo" => ["Mina"], + "Hassan" => ["Noah", "Olga"], "Isla" => ["Pedro"], "Javier" => ["Quynh", "Ravi"], "Khadija" => ["Sofia"], + "Liam" => ["Tariq", "Uma"], "Mina" => ["Viktor", "Wang"], "Noah" => ["Xiomara"], "Olga" => ["Yuki"], + "Pedro" => ["Zane", "Aditi"], "Quynh" => ["Boris"], "Ravi" => ["Celine"], "Sofia" => ["Diego", "Elif"], + "Tariq" => ["Farah"], "Uma" => ["Giorgio"], "Viktor" => ["Hana", "Ian"], "Wang" => ["Jing"], + "Xiomara" => ["Kaito"], "Yuki" => ["Leila"], "Zane" => ["Mateo"], "Aditi" => ["Nia"], "Boris" => ["Oscar"], + "Celine" => ["Priya"], "Diego" => ["Qi"], "Elif" => ["Rami"], "Farah" => ["Sven"], "Giorgio" => ["Tomoko"], + "Hana" => ["Umar"], "Ian" => ["Vera"], "Jing" => ["Wyatt"], "Kaito" => ["Xia"], "Leila" => ["Yassin"], + "Mateo" => ["Zara"], "Nia" => ["Antonio"], "Oscar" => ["Bianca"], "Priya" => ["Cai"], "Qi" => ["Dimitri"], + "Rami" => ["Ewa"], "Sven" => ["Fabio"], "Tomoko" => ["Gabriela"], "Umar" => ["Helena"], "Vera" => ["Igor"], + "Wyatt" => ["Jun"], "Xia" => ["Kim"], "Yassin" => ["Lucia"], "Zara" => ["Mohammed"], + } + relative_distance = RelativeDistance.new(family_tree) + relative_distance.degree_of_separation?("Lucia", "Jun").should eq(14) + end + + pending "Complex graph, some shortcuts, cross-down and cross-up, cousins several times removed, with unrelated family tree" do + family_tree = { + "Aiko" => ["Bao", "Carlos"], "Bao" => ["Dalia"], "Carlos" => ["Fatima", "Gustavo"], "Dalia" => ["Hassan", "Isla"], + "Fatima" => ["Khadija", "Liam"], "Gustavo" => ["Mina"], "Hassan" => ["Noah", "Olga"], "Isla" => ["Pedro"], + "Javier" => ["Quynh", "Ravi"], "Khadija" => ["Sofia"], "Liam" => ["Tariq", "Uma"], "Mina" => ["Viktor", "Wang"], + "Noah" => ["Xiomara"], "Olga" => ["Yuki"], "Pedro" => ["Zane", "Aditi"], "Quynh" => ["Boris"], + "Ravi" => ["Celine"], "Sofia" => ["Diego", "Elif"], "Tariq" => ["Farah"], "Uma" => ["Giorgio"], + "Viktor" => ["Hana", "Ian"], "Wang" => ["Jing"], "Xiomara" => ["Kaito"], "Yuki" => ["Leila"], + "Zane" => ["Mateo"], "Aditi" => ["Nia"], "Boris" => ["Oscar"], "Celine" => ["Priya"], "Diego" => ["Qi"], + "Elif" => ["Rami"], "Farah" => ["Sven"], "Giorgio" => ["Tomoko"], "Hana" => ["Umar"], "Ian" => ["Vera"], + "Jing" => ["Wyatt"], "Kaito" => ["Xia"], "Leila" => ["Yassin"], "Mateo" => ["Zara"], "Nia" => ["Antonio"], + "Oscar" => ["Bianca"], "Priya" => ["Cai"], "Qi" => ["Dimitri"], "Rami" => ["Ewa"], "Sven" => ["Fabio"], + "Tomoko" => ["Gabriela"], "Umar" => ["Helena"], "Vera" => ["Igor"], "Wyatt" => ["Jun"], "Xia" => ["Kim"], + "Yassin" => ["Lucia"], "Zara" => ["Mohammed"], + } + relative_distance = RelativeDistance.new(family_tree) + relative_distance.degree_of_separation?("Wyatt", "Xia").should eq(12) + end +end diff --git a/exercises/practice/relative-distance/src/relative_distance.cr b/exercises/practice/relative-distance/src/relative_distance.cr new file mode 100644 index 00000000..1e5a66e4 --- /dev/null +++ b/exercises/practice/relative-distance/src/relative_distance.cr @@ -0,0 +1,3 @@ +class RelativeDistance + # Write your code for the 'RelativeDistance' exercise in this file. +end