Skip to content

Commit 6c41ca0

Browse files
word-count - checkAllAllocationFailures
1 parent 85d6868 commit 6c41ca0

File tree

3 files changed

+119
-31
lines changed

3 files changed

+119
-31
lines changed

exercises/practice/word-count/.meta/config.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
"authors": [
33
"ee7"
44
],
5+
"contributors": [
6+
"keiravillekode"
7+
],
58
"files": {
69
"solution": [
710
"word_count.zig"
Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,54 @@
11
const std = @import("std");
2+
const ascii = std.ascii;
23
const mem = std.mem;
3-
const StringMap = std.StringHashMap(u32);
4-
const Word = std.array_list.Managed(u8);
54

6-
/// If `self` contains the key `word`, increments its value by 1.
7-
/// Otherwise, dupes `word` and puts it into `self`, setting its value to 1.
8-
/// Clears `word`.
9-
fn incOrPut(self: *StringMap, word: *Word) mem.Allocator.Error!void {
10-
const res = try self.getOrPut(word.items);
11-
if (res.found_existing) {
12-
res.value_ptr.* += 1;
13-
} else {
14-
const dupe = try self.allocator.dupe(u8, word.items);
15-
errdefer self.allocator.free(dupe);
16-
res.key_ptr.* = dupe;
17-
res.value_ptr.* = 1;
5+
fn freeKeysAndDeinit(self: *std.StringHashMap(u32)) void {
6+
var iter = self.keyIterator();
7+
while (iter.next()) |key_ptr| {
8+
self.allocator.free(key_ptr.*);
189
}
19-
word.clearRetainingCapacity();
10+
self.deinit();
11+
}
12+
13+
fn insert(allocator: mem.Allocator, result: *std.StringHashMap(u32), word: []const u8) !void {
14+
const key = try allocator.dupe(u8, word);
15+
errdefer allocator.free(key);
16+
try result.*.put(key, 1);
2017
}
2118

2219
/// Returns the counts of the words in `s`.
2320
/// Caller owns the returned memory.
24-
pub fn countWords(allocator: mem.Allocator, s: []const u8) mem.Allocator.Error!StringMap {
25-
var result = StringMap.init(allocator);
26-
errdefer result.deinit();
27-
28-
var word = Word.init(allocator);
29-
defer word.deinit();
21+
pub fn countWords(allocator: mem.Allocator, s: []const u8) !std.StringHashMap(u32) {
22+
var lower = try allocator.alloc(u8, s.len);
23+
defer allocator.free(lower);
24+
for (s, 0..) |ch, i| {
25+
lower[i] = ascii.toLower(ch);
26+
}
27+
var result = std.StringHashMap(u32).init(allocator);
28+
errdefer freeKeysAndDeinit(&result);
3029

31-
for (s, 0..) |c, i| {
32-
switch (c) {
33-
'0'...'9' => try word.append(c),
34-
'A'...'Z' => try word.append('a' + c - 'A'),
35-
'a'...'z' => try word.append(c),
36-
'\'' => if (word.items.len > 0 and i + 1 < s.len and std.ascii.isAlphabetic(s[i + 1])) {
37-
try word.append(c);
38-
},
39-
else => if (word.items.len > 0) try incOrPut(&result, &word),
30+
var first: usize = 0;
31+
outer: while (first < lower.len) {
32+
while (!ascii.isAlphanumeric(lower[first])) {
33+
first += 1;
34+
if (first == lower.len) {
35+
break :outer;
36+
}
37+
}
38+
var last = first + 1;
39+
while (last < lower.len and (lower[last] == '\'' or ascii.isAlphanumeric(lower[last]))) {
40+
last += 1;
41+
}
42+
while (lower[last - 1] == '\'') {
43+
last -= 1;
44+
}
45+
const word = lower[first..last];
46+
if (result.getEntry(word)) |entry| {
47+
entry.value_ptr.* = entry.value_ptr.* + 1;
48+
} else {
49+
try insert(allocator, &result, word);
4050
}
51+
first = last + 1;
4152
}
42-
if (word.items.len > 0) try incOrPut(&result, &word);
4353
return result;
4454
}

exercises/practice/word-count/test_word_count.zig

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,22 @@ fn freeKeysAndDeinit(self: *std.StringHashMap(u32)) void {
1111
self.deinit();
1212
}
1313

14+
fn countWordsTest(allocator: std.mem.Allocator, s: []const u8) anyerror!void {
15+
var map = try countWords(allocator, s);
16+
defer freeKeysAndDeinit(&map);
17+
}
18+
1419
test "count one word" {
1520
const s = "word";
1621
var map = try countWords(testing.allocator, s);
1722
defer freeKeysAndDeinit(&map);
1823
try testing.expectEqual(@as(u32, 1), map.count());
1924
try testing.expectEqual(@as(?u32, 1), map.get("word"));
25+
try std.testing.checkAllAllocationFailures(
26+
std.testing.allocator,
27+
countWordsTest,
28+
.{s},
29+
);
2030
}
2131

2232
test "count one of each word" {
@@ -27,6 +37,11 @@ test "count one of each word" {
2737
try testing.expectEqual(@as(?u32, 1), map.get("one"));
2838
try testing.expectEqual(@as(?u32, 1), map.get("of"));
2939
try testing.expectEqual(@as(?u32, 1), map.get("each"));
40+
try std.testing.checkAllAllocationFailures(
41+
std.testing.allocator,
42+
countWordsTest,
43+
.{s},
44+
);
3045
}
3146

3247
test "multiple occurrences of a word" {
@@ -39,6 +54,11 @@ test "multiple occurrences of a word" {
3954
try testing.expectEqual(@as(?u32, 1), map.get("two"));
4055
try testing.expectEqual(@as(?u32, 1), map.get("red"));
4156
try testing.expectEqual(@as(?u32, 1), map.get("blue"));
57+
try std.testing.checkAllAllocationFailures(
58+
std.testing.allocator,
59+
countWordsTest,
60+
.{s},
61+
);
4262
}
4363

4464
test "handles cramped lists" {
@@ -49,6 +69,11 @@ test "handles cramped lists" {
4969
try testing.expectEqual(@as(?u32, 1), map.get("one"));
5070
try testing.expectEqual(@as(?u32, 1), map.get("two"));
5171
try testing.expectEqual(@as(?u32, 1), map.get("three"));
72+
try std.testing.checkAllAllocationFailures(
73+
std.testing.allocator,
74+
countWordsTest,
75+
.{s},
76+
);
5277
}
5378

5479
test "handles expanded lists" {
@@ -59,6 +84,11 @@ test "handles expanded lists" {
5984
try testing.expectEqual(@as(?u32, 1), map.get("one"));
6085
try testing.expectEqual(@as(?u32, 1), map.get("two"));
6186
try testing.expectEqual(@as(?u32, 1), map.get("three"));
87+
try std.testing.checkAllAllocationFailures(
88+
std.testing.allocator,
89+
countWordsTest,
90+
.{s},
91+
);
6292
}
6393

6494
test "ignore punctuation" {
@@ -71,6 +101,11 @@ test "ignore punctuation" {
71101
try testing.expectEqual(@as(?u32, 1), map.get("as"));
72102
try testing.expectEqual(@as(?u32, 1), map.get("java"));
73103
try testing.expectEqual(@as(?u32, 1), map.get("javascript"));
104+
try std.testing.checkAllAllocationFailures(
105+
std.testing.allocator,
106+
countWordsTest,
107+
.{s},
108+
);
74109
}
75110

76111
test "include numbers" {
@@ -81,6 +116,11 @@ test "include numbers" {
81116
try testing.expectEqual(@as(?u32, 2), map.get("testing"));
82117
try testing.expectEqual(@as(?u32, 1), map.get("1"));
83118
try testing.expectEqual(@as(?u32, 1), map.get("2"));
119+
try std.testing.checkAllAllocationFailures(
120+
std.testing.allocator,
121+
countWordsTest,
122+
.{s},
123+
);
84124
}
85125

86126
test "normalize case" {
@@ -90,6 +130,11 @@ test "normalize case" {
90130
try testing.expectEqual(@as(u32, 2), map.count());
91131
try testing.expectEqual(@as(?u32, 3), map.get("go"));
92132
try testing.expectEqual(@as(?u32, 2), map.get("stop"));
133+
try std.testing.checkAllAllocationFailures(
134+
std.testing.allocator,
135+
countWordsTest,
136+
.{s},
137+
);
93138
}
94139

95140
test "with apostrophes" {
@@ -105,6 +150,11 @@ test "with apostrophes" {
105150
try testing.expectEqual(@as(?u32, 1), map.get("you're"));
106151
try testing.expectEqual(@as(?u32, 1), map.get("getting"));
107152
try testing.expectEqual(@as(?u32, 1), map.get("it"));
153+
try std.testing.checkAllAllocationFailures(
154+
std.testing.allocator,
155+
countWordsTest,
156+
.{s},
157+
);
108158
}
109159

110160
test "with quotations" {
@@ -118,6 +168,11 @@ test "with quotations" {
118168
try testing.expectEqual(@as(?u32, 1), map.get("between"));
119169
try testing.expectEqual(@as(?u32, 2), map.get("large"));
120170
try testing.expectEqual(@as(?u32, 1), map.get("and"));
171+
try std.testing.checkAllAllocationFailures(
172+
std.testing.allocator,
173+
countWordsTest,
174+
.{s},
175+
);
121176
}
122177

123178
test "substrings from the beginning" {
@@ -133,6 +188,11 @@ test "substrings from the beginning" {
133188
try testing.expectEqual(@as(?u32, 1), map.get("apple"));
134189
try testing.expectEqual(@as(?u32, 1), map.get("and"));
135190
try testing.expectEqual(@as(?u32, 1), map.get("a"));
191+
try std.testing.checkAllAllocationFailures(
192+
std.testing.allocator,
193+
countWordsTest,
194+
.{s},
195+
);
136196
}
137197

138198
test "multiple spaces not detected as a word" {
@@ -142,6 +202,11 @@ test "multiple spaces not detected as a word" {
142202
try testing.expectEqual(@as(u32, 2), map.count());
143203
try testing.expectEqual(@as(?u32, 1), map.get("multiple"));
144204
try testing.expectEqual(@as(?u32, 1), map.get("whitespaces"));
205+
try std.testing.checkAllAllocationFailures(
206+
std.testing.allocator,
207+
countWordsTest,
208+
.{s},
209+
);
145210
}
146211

147212
test "alternating word separators not detected as a word" {
@@ -152,6 +217,11 @@ test "alternating word separators not detected as a word" {
152217
try testing.expectEqual(@as(?u32, 1), map.get("one"));
153218
try testing.expectEqual(@as(?u32, 1), map.get("two"));
154219
try testing.expectEqual(@as(?u32, 1), map.get("three"));
220+
try std.testing.checkAllAllocationFailures(
221+
std.testing.allocator,
222+
countWordsTest,
223+
.{s},
224+
);
155225
}
156226

157227
test "quotation for word with apostrophe" {
@@ -161,4 +231,9 @@ test "quotation for word with apostrophe" {
161231
try testing.expectEqual(@as(u32, 2), map.count());
162232
try testing.expectEqual(@as(?u32, 1), map.get("can"));
163233
try testing.expectEqual(@as(?u32, 2), map.get("can't"));
234+
try std.testing.checkAllAllocationFailures(
235+
std.testing.allocator,
236+
countWordsTest,
237+
.{s},
238+
);
164239
}

0 commit comments

Comments
 (0)