Add naive voting logic
This commit is contained in:
+132
@@ -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");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user