Add naive voting logic

This commit is contained in:
2026-06-10 02:53:54 -06:00
parent 38760693c2
commit a916a31c69
+132
View File
@@ -0,0 +1,132 @@
const std = @import("std");
const Wordle = @import("wordle.zig");
const Dict = @import("dict.zig");
/// A "pattern": a word where nulls signify wildcards.
pub const Pattern = Wordle.Word;
/// A list of candidates: indexes of words in the dictionary.
pub const Candidates = std.ArrayList(u16);
/// A single vote.
pub const Vote = struct {
/// The pattern to match this vote to.
pattern: Pattern,
/// The number of votes placed on this pattern.
score: usize,
};
/// A collection of votes.
pub const Election = []const Vote;
/// Calculates the total number of votes in an election.
pub fn electionVoteTotal(election: Election) usize {
var total: usize = 0;
for (election) |vote| total += vote.score;
return total;
}
/// Tests if a pattern is satisfied by a given word.
pub fn patternSatisfied(word: Wordle.Word, pat: Pattern) bool {
// find mismatches
for (word, pat) |wordLetter, patLetter| {
if (patLetter != 0 and patLetter != wordLetter) {
return false;
}
}
// if no mismatches were found, the pattern is satisfied
return true;
}
/// Naively calculates how many votes a word scores in an election.
pub fn wordScore(word: Wordle.Word, election: Election) usize {
var total: usize = 0;
for (election) |vote| {
if (patternSatisfied(word, vote.pattern)) {
total += vote.score;
}
}
return total;
}
/// Elects candidates using brute-force scoring on the whole dictionary.
///
/// The result must be deinitialized after use.
pub fn naiveElect(alloc: std.mem.Allocator, election: Election) !Candidates {
// track the maximum known score
var maxScore: usize = 0;
// keep a list of all highest-scoring candidates
var candidates: Candidates = .empty;
// evaluate all words in the dictionary
for (Dict.getDictionary(), 0..) |word, idx| {
// score this word
const score = wordScore(word, election);
// if score is lower than current known score, skip
if (score < maxScore) continue;
// if score exceeds known score, clear candidates and accommodate
if (score > maxScore) {
maxScore = score;
try candidates.resize(alloc, 0);
}
// add this candidate's index
try candidates.append(alloc, @intCast(idx));
}
// return the complete list of candidates
return candidates;
}
/// Formats a list of the words referred to by candidate indices.
fn formatCandidates(alloc: std.mem.Allocator, candidates: Candidates) !std.ArrayList(u8) {
// cache dictionary for repeated lookup
const dict = Dict.getDictionary();
// dynamically allocate formatted words
var formatted: std.ArrayList(u8) = .empty;
for (candidates.items, 0..) |wordIdx, idx| {
// prefix word with space unless it's the first one
if (idx != 0) {
try formatted.append(alloc, ' ');
}
// append the word in the dictionary
try formatted.appendSlice(alloc, &dict[wordIdx]);
}
return formatted;
}
test "simple naive score" {
const election = [_]Vote{.{
.pattern = Wordle.strToWord("abas\x00"),
.score = 1,
}};
try std.testing.expectEqual(wordScore(Wordle.strToWord("abash"), &election), 1);
try std.testing.expectEqual(wordScore(Wordle.strToWord("abask"), &election), 1);
try std.testing.expectEqual(wordScore(Wordle.strToWord("abear"), &election), 0);
}
test "simple naive election" {
const election = [_]Vote{.{
.pattern = Wordle.strToWord("abas\x00"),
.score = 1,
}};
const alloc = std.heap.smp_allocator;
var candidates = try naiveElect(alloc, &election);
defer candidates.deinit(alloc);
var candidatesFmt = try formatCandidates(alloc, candidates);
defer candidatesFmt.deinit(alloc);
try std.testing.expectEqualStrings(candidatesFmt.items, "abash abask");
}