Skip to content

Commit 6913e3e

Browse files
authored
Add relative-distance (#1588)
* Add `relative-distance` * Reformat the stub
1 parent 3d72d6c commit 6913e3e

18 files changed

+7275
-0
lines changed

config.json

+13
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,19 @@
627627
"math"
628628
]
629629
},
630+
{
631+
"slug": "relative-distance",
632+
"name": "Relative Distance",
633+
"uuid": "bbdf7688-65cb-4291-9a4a-0ab89d122a31",
634+
"practices": [],
635+
"prerequisites": [],
636+
"difficulty": 5,
637+
"topics": [
638+
"algorithms",
639+
"data_structures",
640+
"maps"
641+
]
642+
},
630643
{
631644
"slug": "sum-of-multiples",
632645
"name": "Sum of Multiples",
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+
5+
- You will be given an input, with all parent names and their children.
6+
- Each name is unique, a child _can_ have one or two parents.
7+
- The degree of separation is defined as the shortest number of connections from one person to another.
8+
- If two individuals are not connected, return a value that represents "no known relationship."
9+
Please see the test cases for the actual implementation.
10+
11+
## Example
12+
13+
Given the following family tree:
14+
15+
```text
16+
┌──────────┐ ┌──────────┐ ┌───────────┐
17+
│ Helena │ │ Erdős │ │ Shusaku │
18+
└───┬───┬──┘ └─────┬────┘ └──────┬────┘
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 3 (Tariq → Helena → Isla → Uma).
31+
There's no known relationship between Isla and [Kevin][six-bacons], 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
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]: http://www.islendingaapp.is/information-in-english/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"authors": [
3+
"BNAndras"
4+
],
5+
"files": {
6+
"solution": [
7+
"relative-distance.ts"
8+
],
9+
"test": [
10+
"relative-distance.test.ts"
11+
],
12+
"example": [
13+
".meta/proof.ci.ts"
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+
"custom": {
20+
"version.tests.compatibility": "jest-29",
21+
"flag.tests.task-per-describe": false,
22+
"flag.tests.may-run-long": false,
23+
"flag.tests.includes-optional": false,
24+
"flag.tests.jest": true,
25+
"flag.tests.tstyche": false
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
export const degreesOfSeparation = (
2+
familyTree: Record<string, string[]>,
3+
personA: string,
4+
personB: string
5+
) => {
6+
const neighbors = new Map<string, Set<string>>()
7+
8+
// Build adjacency list
9+
for (const [parent, children] of Object.entries(familyTree)) {
10+
const parentNeighbors = neighbors.get(parent) ?? new Set()
11+
neighbors.set(parent, parentNeighbors)
12+
13+
for (const child of children) {
14+
const childNeighbors = neighbors.get(child) ?? new Set()
15+
neighbors.set(child, childNeighbors)
16+
17+
parentNeighbors.add(child)
18+
childNeighbors.add(parent)
19+
20+
// Connect siblings
21+
for (const sibling of children) {
22+
if (child !== sibling) {
23+
childNeighbors.add(sibling)
24+
}
25+
}
26+
}
27+
}
28+
29+
if (!neighbors.has(personA) || !neighbors.has(personB)) return -1
30+
31+
// BFS setup
32+
const queue: [string, number][] = [[personA, 0]]
33+
const visited = new Set([personA])
34+
35+
while (queue.length > 0) {
36+
const [current, degree] = queue.shift()!
37+
38+
if (current === personB) return degree
39+
40+
for (const neighbor of neighbors.get(current)!) {
41+
if (!visited.has(neighbor)) {
42+
visited.add(neighbor)
43+
queue.push([neighbor, degree + 1])
44+
}
45+
}
46+
}
47+
48+
return -1
49+
}
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"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"recommendations": [
3+
"arcanis.vscode-zipfs",
4+
"dbaeumer.vscode-eslint",
5+
"esbenp.prettier-vscode"
6+
]
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"cSpell.words": ["exercism"],
3+
"search.exclude": {
4+
"**/.yarn": true,
5+
"**/.pnp.*": true
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
compressionLevel: mixed
2+
3+
enableGlobalCache: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
// eslint-disable-next-line @typescript-eslint/no-require-imports
3+
presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]],
4+
plugins: [],
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// @ts-check
2+
3+
import tsEslint from 'typescript-eslint'
4+
import config from '@exercism/eslint-config-typescript'
5+
import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs'
6+
7+
export default [
8+
...tsEslint.config(...config, {
9+
files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'],
10+
extends: maintainersConfig,
11+
}),
12+
{
13+
ignores: [
14+
// # Protected or generated
15+
'.git/**/*',
16+
'.vscode/**/*',
17+
18+
//# When using npm
19+
'node_modules/**/*',
20+
21+
// # Configuration files
22+
'babel.config.cjs',
23+
'jest.config.cjs',
24+
],
25+
},
26+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module.exports = {
2+
verbose: true,
3+
projects: ['<rootDir>'],
4+
testMatch: [
5+
'**/__tests__/**/*.[jt]s?(x)',
6+
'**/test/**/*.[jt]s?(x)',
7+
'**/?(*.)+(spec|test).[jt]s?(x)',
8+
],
9+
testPathIgnorePatterns: [
10+
'/(?:production_)?node_modules/',
11+
'.d.ts$',
12+
'<rootDir>/test/fixtures',
13+
'<rootDir>/test/helpers',
14+
'__mocks__',
15+
],
16+
transform: {
17+
'^.+\\.[jt]sx?$': 'babel-jest',
18+
},
19+
moduleNameMapper: {
20+
'^(\\.\\/.+)\\.js$': '$1',
21+
},
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "@exercism/typescript-relative-distance",
3+
"version": "1.0.0",
4+
"description": "Exercism exercises in Typescript.",
5+
"private": true,
6+
"repository": {
7+
"type": "git",
8+
"url": "https://github.com/exercism/typescript"
9+
},
10+
"type": "module",
11+
"engines": {
12+
"node": "^18.16.0 || >=20.0.0"
13+
},
14+
"devDependencies": {
15+
"@exercism/babel-preset-typescript": "^0.6.0",
16+
"@exercism/eslint-config-typescript": "^0.8.0",
17+
"@jest/globals": "^29.7.0",
18+
"@types/node": "~22.7.6",
19+
"babel-jest": "^29.7.0",
20+
"core-js": "~3.38.1",
21+
"eslint": "^9.12.0",
22+
"expect": "^29.7.0",
23+
"jest": "^29.7.0",
24+
"prettier": "^3.3.3",
25+
"tstyche": "^2.1.1",
26+
"typescript": "~5.6.3",
27+
"typescript-eslint": "^8.10.0"
28+
},
29+
"scripts": {
30+
"test": "corepack yarn node test-runner.mjs",
31+
"test:types": "corepack yarn tstyche",
32+
"test:implementation": "corepack yarn jest --no-cache --passWithNoTests",
33+
"lint": "corepack yarn lint:types && corepack yarn lint:ci",
34+
"lint:types": "corepack yarn tsc --noEmit -p .",
35+
"lint:ci": "corepack yarn eslint . --ext .tsx,.ts"
36+
},
37+
"packageManager": "[email protected]"
38+
}

0 commit comments

Comments
 (0)