Skip to content

Commit d48f7dc

Browse files
Add relative distance exercise (#748)
* Fixes to concept tree * Add Relative Distance exercise with instructions, examples, and tests * regenerate test file * Update exercises/practice/relative-distance/.meta/src/example.cr Co-authored-by: Ryan Hartlage <2488333+ryanplusplus@users.noreply.github.com> * Update test file with the change aswell --------- Co-authored-by: Ryan Hartlage <2488333+ryanplusplus@users.noreply.github.com>
1 parent bbcb2e6 commit d48f7dc

9 files changed

Lines changed: 272 additions & 0 deletions

File tree

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,14 @@
10941094
"prerequisites": [],
10951095
"difficulty": 4
10961096
},
1097+
{
1098+
"slug": "relative-distance",
1099+
"name": "Relative Distance",
1100+
"uuid": "6d5cdbb3-f592-4b18-9cd3-23a28723e9cf",
1101+
"practices": [],
1102+
"prerequisites": [],
1103+
"difficulty": 4
1104+
},
10971105
{
10981106
"slug": "flower-field",
10991107
"name": "Flower Field",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Instructions
2+
3+
Your task is to determine the degree of separation between two individuals in a family tree.
4+
This is similar to the pop culture idea that every Hollywood actor is [within six degrees of Kevin Bacon][six-bacons].
5+
6+
- You will be given an input, with all parent names and their children.
7+
- Each name is unique, a child _can_ have one or two parents.
8+
- The degree of separation is defined as the shortest number of connections from one person to another.
9+
- If two individuals are not connected, return a value that represents "no known relationship."
10+
Please see the test cases for the actual implementation.
11+
12+
## Example
13+
14+
Given the following family tree:
15+
16+
```text
17+
┌──────────┐ ┌──────────┐ ┌───────────┐
18+
│ Helena │ │ Erdős ├─────┤ Shusaku │
19+
└───┬───┬──┘ └─────┬────┘ └────┬──────┘
20+
┌───┘ └───────┐ └───────┬───────┘
21+
┌─────┴────┐ ┌────┴───┐ ┌─────┴────┐
22+
│ Isla ├─────┤ Tariq │ │ Kevin │
23+
└────┬─────┘ └────┬───┘ └──────────┘
24+
│ │
25+
┌────┴────┐ ┌────┴───┐
26+
│ Uma │ │ Morphy │
27+
└─────────┘ └────────┘
28+
```
29+
30+
The degree of separation between Tariq and Uma is 2 (Tariq → Isla → Uma).
31+
There's no known relationship between Isla and Kevin, as there is no connection in the given data.
32+
The degree of separation between Uma and Isla is 1.
33+
34+
~~~~exercism/note
35+
Isla and Tariq are siblings and have a separation of 1.
36+
Similarly, this implementation would report a separation of 2 from you to your father's brother.
37+
~~~~
38+
39+
[six-bacons]: https://en.m.wikipedia.org/wiki/Six_Degrees_of_Kevin_Bacon
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Introduction
2+
3+
You've been hired to develop **Noble Knots**, the hottest new dating app for nobility!
4+
With centuries of royal intermarriage, things have gotten… _complicated_.
5+
To avoid any _oops-we're-twins_ situations, your job is to build a system that checks how closely two people are related.
6+
7+
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.
8+
Your algorithm will determine the **degree of separation** between two individuals in the royal family tree.
9+
10+
Will your app help crown a perfect match?
11+
12+
[islendiga-app]: https://web.archive.org/web/20250816223614/http://www.islendingaapp.is/information-in-english/
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"meatball133"
4+
],
5+
"files": {
6+
"solution": [
7+
"src/relative_distance.cr"
8+
],
9+
"test": [
10+
"spec/relative_distance_spec.cr"
11+
],
12+
"example": [
13+
".meta/src/example.cr"
14+
]
15+
},
16+
"blurb": "Given a family tree, calculate the degree of separation.",
17+
"source": "vaeng",
18+
"source_url": "https://github.com/exercism/problem-specifications/pull/2537"
19+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
require "set"
2+
3+
class RelativeDistance
4+
@graph : Hash(String, Set(String))
5+
6+
def initialize(family_tree : Hash(String, Array(String)))
7+
@family_tree = family_tree
8+
@graph = build_graph(@family_tree)
9+
end
10+
11+
def degree_of_separation(person_a : String, person_b : String) : Int32?
12+
return 0 if person_a == person_b
13+
return nil unless @graph.has_key?(person_a) && @graph.has_key?(person_b)
14+
15+
visited = Set(String).new
16+
visited << person_a
17+
18+
queue = [{person_a, 0}] of Tuple(String, Int32)
19+
head = 0
20+
21+
while head < queue.size
22+
current, distance = queue[head]
23+
head += 1
24+
25+
@graph[current].each do |neighbor|
26+
next if visited.includes?(neighbor)
27+
28+
next_distance = distance + 1
29+
return next_distance if neighbor == person_b
30+
31+
visited << neighbor
32+
queue << {neighbor, next_distance}
33+
end
34+
end
35+
36+
nil
37+
end
38+
39+
private def build_graph(family_tree : Hash(String, Array(String))) : Hash(String, Set(String))
40+
graph = Hash(String, Set(String)).new { |hash, key| hash[key] = Set(String).new }
41+
42+
family_tree.each do |parent, children|
43+
graph[parent]
44+
45+
children.each do |child|
46+
graph[child]
47+
graph[parent] << child
48+
graph[child] << parent
49+
end
50+
51+
# Siblings are considered 1 degree apart.
52+
children.each_with_index do |child, i|
53+
(i + 1...children.size).each do |j|
54+
sibling = children[j]
55+
graph[child] << sibling
56+
graph[sibling] << child
57+
end
58+
end
59+
end
60+
61+
graph
62+
end
63+
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
require "spec"
2+
require "../src/*"
3+
4+
describe "<%-= to_capitalized(@json["exercise"].to_s) %>" do
5+
<%- @json["cases"].as_a.each do |cases| %>
6+
<%= status()%> "<%-= cases["description"] %>" do
7+
family_tree = <%= cases["input"]["familyTree"].to_s == "{}" ? "{} of String => Array(String)" : to_s_deep(cases["input"]["familyTree"]) %>
8+
relative_distance = <%= to_capitalized(@json["exercise"].to_s) %>.new(family_tree)
9+
relative_distance.<%= cases["property"].to_s.underscore %>("<%= cases["input"]["personA"] %>", "<%= cases["input"]["personB"] %>").should eq(<%= cases["expected"].as_i? ? cases["expected"] : "nil" %>)
10+
end
11+
<% end %>
12+
end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[4a1ded74-5d32-47fb-8ae5-321f51d06b5b]
13+
description = "Direct parent-child relation"
14+
15+
[30d17269-83e9-4f82-a0d7-8ef9656d8dce]
16+
description = "Sibling relationship"
17+
18+
[8dffa27d-a8ab-496d-80b3-2f21c77648b5]
19+
description = "Two degrees of separation, grandchild"
20+
21+
[34e56ec1-d528-4a42-908e-020a4606ee60]
22+
description = "Unrelated individuals"
23+
24+
[93ffe989-bad2-48c4-878f-3acb1ce2611b]
25+
description = "Complex graph, cousins"
26+
27+
[2cc2e76b-013a-433c-9486-1dbe29bf06e5]
28+
description = "Complex graph, no shortcut, far removed nephew"
29+
30+
[46c9fbcb-e464-455f-a718-049ea3c7400a]
31+
description = "Complex graph, some shortcuts, cross-down and cross-up, cousins several times removed, with unrelated family tree"
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
require "spec"
2+
require "../src/*"
3+
4+
describe "RelativeDistance" do
5+
it "Direct parent-child relation" do
6+
family_tree = {"Vera" => ["Tomoko"], "Tomoko" => ["Aditi"]}
7+
relative_distance = RelativeDistance.new(family_tree)
8+
relative_distance.degree_of_separation("Vera", "Tomoko").should eq(1)
9+
end
10+
11+
pending "Sibling relationship" do
12+
family_tree = {"Dalia" => ["Olga", "Yassin"]}
13+
relative_distance = RelativeDistance.new(family_tree)
14+
relative_distance.degree_of_separation("Olga", "Yassin").should eq(1)
15+
end
16+
17+
pending "Two degrees of separation, grandchild" do
18+
family_tree = {"Khadija" => ["Mateo"], "Mateo" => ["Rami"]}
19+
relative_distance = RelativeDistance.new(family_tree)
20+
relative_distance.degree_of_separation("Khadija", "Rami").should eq(2)
21+
end
22+
23+
pending "Unrelated individuals" do
24+
family_tree = {"Priya" => ["Rami"], "Kaito" => ["Elif"]}
25+
relative_distance = RelativeDistance.new(family_tree)
26+
relative_distance.degree_of_separation("Priya", "Kaito").should eq(nil)
27+
end
28+
29+
pending "Complex graph, cousins" do
30+
family_tree = {
31+
"Aiko" => ["Bao", "Carlos"], "Bao" => ["Dalia", "Elias"], "Carlos" => ["Fatima", "Gustavo"],
32+
"Dalia" => ["Hassan", "Isla"], "Elias" => ["Javier"], "Fatima" => ["Khadija", "Liam"], "Gustavo" => ["Mina"],
33+
"Hassan" => ["Noah", "Olga"], "Isla" => ["Pedro"], "Javier" => ["Quynh", "Ravi"], "Khadija" => ["Sofia"],
34+
"Liam" => ["Tariq", "Uma"], "Mina" => ["Viktor", "Wang"], "Noah" => ["Xiomara"], "Olga" => ["Yuki"],
35+
"Pedro" => ["Zane", "Aditi"], "Quynh" => ["Boris"], "Ravi" => ["Celine"], "Sofia" => ["Diego", "Elif"],
36+
"Tariq" => ["Farah"], "Uma" => ["Giorgio"], "Viktor" => ["Hana", "Ian"], "Wang" => ["Jing"],
37+
"Xiomara" => ["Kaito"], "Yuki" => ["Leila"], "Zane" => ["Mateo"], "Aditi" => ["Nia"], "Boris" => ["Oscar"],
38+
"Celine" => ["Priya"], "Diego" => ["Qi"], "Elif" => ["Rami"], "Farah" => ["Sven"], "Giorgio" => ["Tomoko"],
39+
"Hana" => ["Umar"], "Ian" => ["Vera"], "Jing" => ["Wyatt"], "Kaito" => ["Xia"], "Leila" => ["Yassin"],
40+
"Mateo" => ["Zara"], "Nia" => ["Antonio"], "Oscar" => ["Bianca"], "Priya" => ["Cai"], "Qi" => ["Dimitri"],
41+
"Rami" => ["Ewa"], "Sven" => ["Fabio"], "Tomoko" => ["Gabriela"], "Umar" => ["Helena"], "Vera" => ["Igor"],
42+
"Wyatt" => ["Jun"], "Xia" => ["Kim"], "Yassin" => ["Lucia"], "Zara" => ["Mohammed"],
43+
}
44+
relative_distance = RelativeDistance.new(family_tree)
45+
relative_distance.degree_of_separation("Dimitri", "Fabio").should eq(9)
46+
end
47+
48+
pending "Complex graph, no shortcut, far removed nephew" do
49+
family_tree = {
50+
"Aiko" => ["Bao", "Carlos"], "Bao" => ["Dalia", "Elias"], "Carlos" => ["Fatima", "Gustavo"],
51+
"Dalia" => ["Hassan", "Isla"], "Elias" => ["Javier"], "Fatima" => ["Khadija", "Liam"], "Gustavo" => ["Mina"],
52+
"Hassan" => ["Noah", "Olga"], "Isla" => ["Pedro"], "Javier" => ["Quynh", "Ravi"], "Khadija" => ["Sofia"],
53+
"Liam" => ["Tariq", "Uma"], "Mina" => ["Viktor", "Wang"], "Noah" => ["Xiomara"], "Olga" => ["Yuki"],
54+
"Pedro" => ["Zane", "Aditi"], "Quynh" => ["Boris"], "Ravi" => ["Celine"], "Sofia" => ["Diego", "Elif"],
55+
"Tariq" => ["Farah"], "Uma" => ["Giorgio"], "Viktor" => ["Hana", "Ian"], "Wang" => ["Jing"],
56+
"Xiomara" => ["Kaito"], "Yuki" => ["Leila"], "Zane" => ["Mateo"], "Aditi" => ["Nia"], "Boris" => ["Oscar"],
57+
"Celine" => ["Priya"], "Diego" => ["Qi"], "Elif" => ["Rami"], "Farah" => ["Sven"], "Giorgio" => ["Tomoko"],
58+
"Hana" => ["Umar"], "Ian" => ["Vera"], "Jing" => ["Wyatt"], "Kaito" => ["Xia"], "Leila" => ["Yassin"],
59+
"Mateo" => ["Zara"], "Nia" => ["Antonio"], "Oscar" => ["Bianca"], "Priya" => ["Cai"], "Qi" => ["Dimitri"],
60+
"Rami" => ["Ewa"], "Sven" => ["Fabio"], "Tomoko" => ["Gabriela"], "Umar" => ["Helena"], "Vera" => ["Igor"],
61+
"Wyatt" => ["Jun"], "Xia" => ["Kim"], "Yassin" => ["Lucia"], "Zara" => ["Mohammed"],
62+
}
63+
relative_distance = RelativeDistance.new(family_tree)
64+
relative_distance.degree_of_separation("Lucia", "Jun").should eq(14)
65+
end
66+
67+
pending "Complex graph, some shortcuts, cross-down and cross-up, cousins several times removed, with unrelated family tree" do
68+
family_tree = {
69+
"Aiko" => ["Bao", "Carlos"], "Bao" => ["Dalia"], "Carlos" => ["Fatima", "Gustavo"], "Dalia" => ["Hassan", "Isla"],
70+
"Fatima" => ["Khadija", "Liam"], "Gustavo" => ["Mina"], "Hassan" => ["Noah", "Olga"], "Isla" => ["Pedro"],
71+
"Javier" => ["Quynh", "Ravi"], "Khadija" => ["Sofia"], "Liam" => ["Tariq", "Uma"], "Mina" => ["Viktor", "Wang"],
72+
"Noah" => ["Xiomara"], "Olga" => ["Yuki"], "Pedro" => ["Zane", "Aditi"], "Quynh" => ["Boris"],
73+
"Ravi" => ["Celine"], "Sofia" => ["Diego", "Elif"], "Tariq" => ["Farah"], "Uma" => ["Giorgio"],
74+
"Viktor" => ["Hana", "Ian"], "Wang" => ["Jing"], "Xiomara" => ["Kaito"], "Yuki" => ["Leila"],
75+
"Zane" => ["Mateo"], "Aditi" => ["Nia"], "Boris" => ["Oscar"], "Celine" => ["Priya"], "Diego" => ["Qi"],
76+
"Elif" => ["Rami"], "Farah" => ["Sven"], "Giorgio" => ["Tomoko"], "Hana" => ["Umar"], "Ian" => ["Vera"],
77+
"Jing" => ["Wyatt"], "Kaito" => ["Xia"], "Leila" => ["Yassin"], "Mateo" => ["Zara"], "Nia" => ["Antonio"],
78+
"Oscar" => ["Bianca"], "Priya" => ["Cai"], "Qi" => ["Dimitri"], "Rami" => ["Ewa"], "Sven" => ["Fabio"],
79+
"Tomoko" => ["Gabriela"], "Umar" => ["Helena"], "Vera" => ["Igor"], "Wyatt" => ["Jun"], "Xia" => ["Kim"],
80+
"Yassin" => ["Lucia"], "Zara" => ["Mohammed"],
81+
}
82+
relative_distance = RelativeDistance.new(family_tree)
83+
relative_distance.degree_of_separation("Wyatt", "Xia").should eq(12)
84+
end
85+
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class RelativeDistance
2+
# Write your code for the 'RelativeDistance' exercise in this file.
3+
end

0 commit comments

Comments
 (0)