1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
// 5x5 "Boggle" solver.
// Usage: ./boggle_solver dictionary_file board_string
// For example, try executing
// ./boggle_solver wordlist_medium.txt etbtwefetemyfnaececrsldlo
#include <algorithm>
#include <string>
#include <fstream>
#include <vector>
#include <iostream>
#include <chrono>
#include <filesystem>
#include <string_view>
int constexpr board_dim = 5;
int constexpr board_length = board_dim * board_dim;
std::vector<std::string> results;
std::vector<std::string_view> dict_entries;
void search_recursive(std::string b, int search_root, std::string prefix)
{
// if the search_root has already been visited, terminate, otherwise visit it
if (b[search_root] == '\0') return; else prefix.push_back(std::exchange(b[search_root], 0));
auto const lb = std::lower_bound(dict_entries.begin(), dict_entries.end(), prefix);
if (lb == dict_entries.end()) return;
auto const candidate = *lb;
auto const min_size = std::min(candidate.size(), prefix.size());
if (std::equal(candidate.begin(), candidate.begin() + min_size, prefix.begin()))
{ // `prefix` is a prefix of `candidate`
if (candidate.size() == prefix.size())
{ // `prefix` is exactly `candidate`
results.push_back(std::string(candidate));
}
}
else return;
bool const has_left_neighbor = search_root % board_dim != 0;
bool const has_right_neighbor = search_root % board_dim != board_dim - 1;
bool const has_upper_neighbor = search_root >= board_dim;
bool const has_lower_neighbor = search_root < board_length - board_dim;
if (has_upper_neighbor)
{
search_recursive(b, search_root - board_dim, prefix);
if (has_left_neighbor) search_recursive(b, search_root - board_dim - 1, prefix);
if (has_right_neighbor) search_recursive(b, search_root - board_dim + 1, prefix);
}
if (has_lower_neighbor)
{
search_recursive(b, search_root + board_dim, prefix);
if (has_left_neighbor) search_recursive(b, search_root + board_dim - 1, prefix);
if (has_right_neighbor) search_recursive(b, search_root + board_dim + 1, prefix);
}
if (has_left_neighbor) search_recursive(b, search_root - 1, prefix);
if (has_right_neighbor) search_recursive(b, search_root + 1, prefix);
}
int main(int argc, char** argv)
{
if (argc != 3)
{
std::cerr << "usage: " << *argv << " dictionary_file board\n";
return 0;
}
std::string const b = argv[2];
if (b.size() != board_length)
{
std::cerr << "error: board is not " << board_length << " characters in length\n";
return 1;
}
std::chrono::high_resolution_clock::time_point t1, t2, t3, t4, t5, t6;
t1 = std::chrono::high_resolution_clock::now();
////////////////////////////////////////////////////////////////////////
// Perform I/O
std::ifstream dict_stream { argv[1], std::ios::binary };
if (! dict_stream)
{
std::cerr << "error: opening dictionary_file \"" << argv[1] << "\"\n";
return 0;
}
auto const file_size = std::filesystem::file_size(argv[1]);
char* dict_fulltext = new char[file_size];
dict_stream.read(dict_fulltext, file_size);
auto const dict_fulltext_size = dict_stream.gcount();
t2 = std::chrono::high_resolution_clock::now();
////////////////////////////////////////////////////////////////////////
// Parse dictionary contents
dict_entries.reserve(1'000'000); // some reasonable guess
char const* word_start = dict_fulltext;
char const* word_end = dict_fulltext;
char const* dict_end = dict_fulltext + dict_fulltext_size;
auto const is_space = [](char c) { return c == '\r' || c == '\n'; };
while (true)
{
while (word_end != dict_end && is_space(*word_end)) ++word_end;
word_start = word_end;
while (word_end != dict_end && !is_space(*word_end)) ++word_end;
if (word_end == dict_end) break;
dict_entries.emplace_back(std::string_view(std::exchange(word_start, word_end),
word_end - word_start));
}
auto const delta = [](auto start, auto end)
{
return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
};
t3 = std::chrono::high_resolution_clock::now();
////////////////////////////////////////////////////////////////////////
// Make sure the dictionary is sorted.
if (auto it = std::is_sorted_until(dict_entries.begin(), dict_entries.end());
it != dict_entries.end())
{
std::cerr << "warning: dictionary_file \"" << argv[1] << "\" is not properly sorted.\n";
std::cerr << "warning: first out-of-order word is \"" << *it
<< "\", which should have appeared before \"" << *(it - 1) << "\".\n";
std::sort(dict_entries.begin(), dict_entries.end());
dict_entries.erase(std::unique(dict_entries.begin(), dict_entries.end()), dict_entries.end());
}
t4 = std::chrono::high_resolution_clock::now();
////////////////////////////////////////////////////////////////////////
// Search the dictionary for each possible choice on the board
for (int i = 0; i < board_length; ++i)
search_recursive(b, i, "");
t5 = std::chrono::high_resolution_clock::now();
////////////////////////////////////////////////////////////////////////
// Present results
std::sort(results.begin(), results.end());
results.erase(std::unique(results.begin(), results.end()), results.end());
for (std::size_t i = 0; i < results.size();)
{
std::cout << std::setw(18) << results[i++];
if (i % 5 == 0) std::cout << '\n';
}
std::cout << '\n';
t6 = std::chrono::high_resolution_clock::now();
////////////////////////////////////////////////////////////////////////
// Show timings
auto const read_time = delta(t1, t2);
std::clog << "read " << file_size << " bytes from the dictionary file in " << read_time.count()
<< " us (measured " << (file_size * std::micro::den / read_time.count()) << " byte/s)\n";
std::clog << "parsed dictionary in " << delta(t2, t3).count() << " us\n";
std::clog << "ensured dictionary was sorted in " << delta(t3, t4).count() << " us\n";
std::clog << "found all solutions in " << delta(t4, t5).count() << " us\n";
std::clog << "presented results in " << delta(t5, t6).count() << " us\n";
}
|