Skip to content

Commit 6daece8

Browse files
authored
[New Exercise] Add word-search exercise (#867)
* Add word search exercise * Fix build error? * Review fixes * Fix typo in .meta/config.json * Some more fixes and cleaning
1 parent be98a1b commit 6daece8

File tree

13 files changed

+851
-0
lines changed

13 files changed

+851
-0
lines changed

config.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,17 @@
12191219
"loops"
12201220
]
12211221
},
1222+
{
1223+
"slug": "word-search",
1224+
"name": "Word Search",
1225+
"uuid": "e88da49b-37d7-4f84-ad64-765fb464a721",
1226+
"practices": [],
1227+
"prerequisites": [],
1228+
"difficulty": 5,
1229+
"topics": [
1230+
"algorithms"
1231+
]
1232+
},
12221233
{
12231234
"slug": "bowling",
12241235
"name": "Bowling",
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Instructions
2+
3+
In word search puzzles you get a square of letters and have to find specific words in them.
4+
5+
For example:
6+
7+
```text
8+
jefblpepre
9+
camdcimgtc
10+
oivokprjsm
11+
pbwasqroua
12+
rixilelhrs
13+
wolcqlirpc
14+
screeaumgr
15+
alxhpburyi
16+
jalaycalmp
17+
clojurermt
18+
```
19+
20+
There are several programming languages hidden in the above square.
21+
22+
Words can be hidden in all kinds of directions: left-to-right, right-to-left, vertical and diagonal.
23+
24+
Given a puzzle and a list of words return the location of the first and last letter of each word.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
2+
class TrieNode {
3+
var children: [Character: TrieNode] = [:]
4+
var word: String? = nil
5+
}
6+
7+
extension TrieNode {
8+
9+
static func buildTrie(_ words: [String]) -> TrieNode {
10+
let root = TrieNode()
11+
for word in words {
12+
var node = root
13+
for char in word {
14+
if node.children[char] == nil {
15+
node.children[char] = TrieNode()
16+
}
17+
node = node.children[char]!
18+
}
19+
node.word = word
20+
}
21+
return root
22+
}
23+
24+
static func dfs(
25+
board: [[Character]],
26+
initialRow: Int,
27+
initialColumn: Int,
28+
row: Int,
29+
column: Int,
30+
direction: (Int, Int),
31+
node: TrieNode,
32+
result: inout [String: WordLocation?]
33+
) {
34+
guard row >= 0, row < board.count, column >= 0, column < board[0].count else { return }
35+
36+
let char = board[row][column]
37+
guard let nextNode = node.children[char] else { return }
38+
39+
if let word = nextNode.word {
40+
let start = WordLocation.Location(row: initialRow + 1, column: initialColumn + 1)
41+
let end = WordLocation.Location(row: row + 1, column: column + 1)
42+
result[word] = WordLocation(start: start, end: end)
43+
}
44+
45+
dfs(
46+
board: board,
47+
initialRow: initialRow,
48+
initialColumn: initialColumn,
49+
row: row + direction.0,
50+
column: column + direction.1,
51+
direction: direction,
52+
node: nextNode,
53+
result: &result
54+
)
55+
}
56+
57+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
struct WordLocation: Equatable {
3+
4+
struct Location: Equatable {
5+
let row: Int
6+
let column: Int
7+
}
8+
9+
let start: Location
10+
let end: Location
11+
12+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Foundation
2+
3+
func search(words: [String], in grid: [String]) -> [String: WordLocation?] {
4+
let board: [[Character]] = grid.map { Array($0) }
5+
let trie = TrieNode.buildTrie(words)
6+
var result = [String: WordLocation?]()
7+
8+
let directions = [
9+
(-1,0), (1,0), (0,-1), (0,1),
10+
(-1,-1), (1,1), (1,-1), (-1,1)
11+
]
12+
for row in 0..<board.count {
13+
for column in 0..<board[0].count {
14+
for direction in directions {
15+
TrieNode.dfs(
16+
board: board,
17+
initialRow: row,
18+
initialColumn: column,
19+
row: row,
20+
column: column,
21+
direction: direction,
22+
node: trie,
23+
result: &result
24+
)
25+
}
26+
}
27+
}
28+
29+
return result
30+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"authors": [
3+
"Sencudra"
4+
],
5+
"files": {
6+
"solution": [
7+
"Sources/WordSearch/WordSearch.swift"
8+
],
9+
"test": [
10+
"Tests/WordSearchTests/WordSearchTests.swift"
11+
],
12+
"example": [
13+
".meta/Sources/WordSearch/WordSearchExample.swift",
14+
".meta/Sources/WordSearch/WordLocation.swift",
15+
".meta/Sources/WordSearch/TrieNode.swift"
16+
],
17+
"editor": [
18+
"Sources/WordSearch/WordLocation.swift"
19+
]
20+
},
21+
"blurb": "Create a program to solve a word search puzzle."
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Testing
2+
import Foundation
3+
@testable import {{exercise | camelCase}}
4+
5+
let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false
6+
7+
@Suite struct {{exercise | camelCase}}Tests {
8+
{% for case in cases %}
9+
{% if forloop.first -%}
10+
@Test("{{case.description}}")
11+
{% else -%}
12+
@Test("{{case.description}}", .enabled(if: RUNALL))
13+
{% endif -%}
14+
func test{{case.description | camelCase}}() {
15+
let grid = {{case.input.grid | toStringArray}}
16+
let words = {{case.input.wordsToSearchFor | toStringArray}}
17+
18+
var expected = [String: WordLocation?]()
19+
{%- for word in case.expected %}
20+
expected["{{word}}"] =
21+
{%- if case.expected[word] | isNull -%}
22+
nil
23+
{%- else -%}
24+
WordLocation(
25+
start: {{case.expected[word]["start"] | toStringDictionary}},
26+
end: {{case.expected[word]["end"] | toStringDictionary}}
27+
)
28+
{%- endif %}
29+
{%- endfor %}
30+
31+
#expect({{case.property}}(words: words, in: grid) == expected)
32+
}
33+
{% endfor -%}
34+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
[b4057815-0d01-41f0-9119-6a91f54b2a0a]
13+
description = "Should accept an initial game grid and a target search word"
14+
15+
[6b22bcc5-6cbf-4674-931b-d2edbff73132]
16+
description = "Should locate one word written left to right"
17+
18+
[ff462410-434b-442d-9bc3-3360c75f34a8]
19+
description = "Should locate the same word written left to right in a different position"
20+
21+
[a02febae-6347-443e-b99c-ab0afb0b8fca]
22+
description = "Should locate a different left to right word"
23+
24+
[e42e9987-6304-4e13-8232-fa07d5280130]
25+
description = "Should locate that different left to right word in a different position"
26+
27+
[9bff3cee-49b9-4775-bdfb-d55b43a70b2f]
28+
description = "Should locate a left to right word in two line grid"
29+
30+
[851a35fb-f499-4ec1-9581-395a87903a22]
31+
description = "Should locate a left to right word in three line grid"
32+
33+
[2f3dcf84-ba7d-4b75-8b8d-a3672b32c035]
34+
description = "Should locate a left to right word in ten line grid"
35+
36+
[006d4856-f365-4e84-a18c-7d129ce9eefb]
37+
description = "Should locate that left to right word in a different position in a ten line grid"
38+
39+
[eff7ac9f-ff11-443e-9747-40850c12ab60]
40+
description = "Should locate a different left to right word in a ten line grid"
41+
42+
[dea39f86-8c67-4164-8884-13bfc48bd13b]
43+
description = "Should locate multiple words"
44+
45+
[29e6a6a5-f80c-48a6-8e68-05bbbe187a09]
46+
description = "Should locate a single word written right to left"
47+
48+
[3cf34428-b43f-48b6-b332-ea0b8836011d]
49+
description = "Should locate multiple words written in different horizontal directions"
50+
51+
[2c8cd344-a02f-464b-93b6-8bf1bd890003]
52+
description = "Should locate words written top to bottom"
53+
54+
[9ee1e43d-e59d-4c32-9a5f-6a22d4a1550f]
55+
description = "Should locate words written bottom to top"
56+
57+
[6a21a676-f59e-4238-8e88-9f81015afae9]
58+
description = "Should locate words written top left to bottom right"
59+
60+
[c9125189-1861-4b0d-a14e-ba5dab29ca7c]
61+
description = "Should locate words written bottom right to top left"
62+
63+
[b19e2149-7fc5-41ec-a8a9-9bc6c6c38c40]
64+
description = "Should locate words written bottom left to top right"
65+
66+
[69e1d994-a6d7-4e24-9b5a-db76751c2ef8]
67+
description = "Should locate words written top right to bottom left"
68+
69+
[695531db-69eb-463f-8bad-8de3bf5ef198]
70+
description = "Should fail to locate a word that is not in the puzzle"
71+
72+
[fda5b937-6774-4a52-8f89-f64ed833b175]
73+
description = "Should fail to locate words that are not on horizontal, vertical, or diagonal lines"
74+
75+
[5b6198eb-2847-4e2f-8efe-65045df16bd3]
76+
description = "Should not concatenate different lines to find a horizontal word"
77+
78+
[eba44139-a34f-4a92-98e1-bd5f259e5769]
79+
description = "Should not wrap around horizontally to find a word"
80+
81+
[cd1f0fa8-76af-4167-b105-935f78364dac]
82+
description = "Should not wrap around vertically to find a word"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// swift-tools-version:6.0
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "WordSearch",
7+
products: [
8+
.library(
9+
name: "WordSearch",
10+
targets: ["WordSearch"])
11+
],
12+
dependencies: [],
13+
targets: [
14+
.target(
15+
name: "WordSearch",
16+
dependencies: []),
17+
.testTarget(
18+
name: "WordSearchTests",
19+
dependencies: ["WordSearch"]),
20+
]
21+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Foundation
2+
3+
struct WordLocation: Equatable {
4+
5+
struct Location: Equatable {
6+
let row: Int
7+
let column: Int
8+
}
9+
10+
let start: Location
11+
let end: Location
12+
13+
}

0 commit comments

Comments
 (0)