Task: Word wheel


C++: #include <array>
#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <vector>
#include <boost/program_options.hpp>



class letterset {
public:
    letterset() {
        count_.fill(0);
    }
    explicit letterset(const std::string& str) {
        count_.fill(0);
        for (char c : str)
            add(c);
    }
    bool contains(const letterset& set) const {
        for (size_t i = 0; i < count_.size(); ++i) {
            if (set.count_[i] > count_[i])
                return false;
        }
        return true;
    }
    unsigned int count(char c) const {
        return count_[index(c)];
    }
    bool is_valid() const {
        return count_[0] == 0;
    }
    void add(char c) {
        ++count_[index(c)];
    }
private:
    static bool is_letter(char c) { return c >= 'a' && c <= 'z'; }
    static int index(char c) { return is_letter(c) ? c - 'a' + 1 : 0; }
    
    
    
    std::array<unsigned int, 27> count_;
};

template <typename iterator, typename separator>
std::string join(iterator begin, iterator end, separator sep) {
    std::string result;
    if (begin != end) {
        result += *begin++;
        for (; begin != end; ++begin) {
            result += sep;
            result += *begin;
        }
    }
    return result;
}

using dictionary = std::vector<std::pair<std::string, letterset>>;

dictionary load_dictionary(const std::string& filename, int min_length,
                           int max_length) {
    std::ifstream in(filename);
    if (!in)
        throw std::runtime_error("Cannot open file " + filename);
    std::string word;
    dictionary result;
    while (getline(in, word)) {
        if (word.size() < min_length)
            continue;
        if (word.size() > max_length)
            continue;
        letterset set(word);
        if (set.is_valid())
            result.emplace_back(word, set);
    }
    return result;
}

void word_wheel(const dictionary& dict, const std::string& letters,
                char central_letter)  {
    letterset set(letters);
    if (central_letter == 0 && !letters.empty())
        central_letter = letters.at(letters.size()/2);
    std::map<size_t, std::vector<std::string>> words;
    for (const auto& pair : dict) {
        const auto& word = pair.first;
        const auto& subset = pair.second;
        if (subset.count(central_letter) > 0 && set.contains(subset))
            words[word.size()].push_back(word);
    }
    size_t total = 0;
    for (const auto& p : words) {
        const auto& v = p.second;
        auto n = v.size();
        total += n;
        std::cout << "Found " << n << " " << (n == 1 ? "word" : "words")
            << " of length " << p.first << ": "
            << join(v.begin(), v.end(), ", ") << '\n';
    }
    std::cout << "Number of words found: " << total << '\n';
}

void find_max_word_count(const dictionary& dict, int word_length) {
    size_t max_count = 0;
    std::vector<std::pair<std::string, char>> max_words;
    for (const auto& pair : dict) {
        const auto& word = pair.first;
        if (word.size() != word_length)
            continue;
        const auto& set = pair.second;
        dictionary subsets;
        for (const auto& p : dict) {
            if (set.contains(p.second))
                subsets.push_back(p);
        }
        letterset done;
        for (size_t index = 0; index < word_length; ++index) {
            char central_letter = word[index];
            if (done.count(central_letter) > 0)
                continue;
            done.add(central_letter);
            size_t count = 0;
            for (const auto& p : subsets) {
                const auto& subset = p.second;
                if (subset.count(central_letter) > 0)
                    ++count;
            }
            if (count > max_count) {
                max_words.clear();
                max_count = count;
            }
            if (count == max_count)
                max_words.emplace_back(word, central_letter);
        }
    }
    std::cout << "Maximum word count: " << max_count << '\n';
    std::cout << "Words of " << word_length << " letters producing this count:\n";
    for (const auto& pair : max_words)
        std::cout << pair.first << " with central letter " << pair.second << '\n';
}

constexpr const char* option_filename = "filename";
constexpr const char* option_wheel = "wheel";
constexpr const char* option_central = "central";
constexpr const char* option_min_length = "min-length";
constexpr const char* option_part2 = "part2";

int main(int argc, char** argv) {
    const int word_length = 9;
    int min_length = 3;
    std::string letters = "ndeokgelw";
    std::string filename = "unixdict.txt";
    char central_letter = 0;
    bool do_part2 = false;
    
    namespace po = boost::program_options;
    po::options_description desc("Allowed options");
    desc.add_options()
        (option_filename, po::value<std::string>(), "name of dictionary file")
        (option_wheel, po::value<std::string>(), "word wheel letters")
        (option_central, po::value<char>(), "central letter (defaults to middle letter of word)")
        (option_min_length, po::value<int>(), "minimum word length")
        (option_part2, "include part 2");

    try {
        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);

        if (vm.count(option_filename))
            filename = vm[option_filename].as<std::string>();
        if (vm.count(option_wheel))
            letters = vm[option_wheel].as<std::string>();
        if (vm.count(option_central))
            central_letter = vm[option_central].as<char>();
        if (vm.count(option_min_length))
            min_length = vm[option_min_length].as<int>();
        if (vm.count(option_part2))
            do_part2 = true;

        auto dict = load_dictionary(filename, min_length, word_length);
        
        word_wheel(dict, letters, central_letter);
        
        if (do_part2) {
            std::cout << '\n';
            find_max_word_count(dict, word_length);
        }
    } catch (const std::exception& ex) {
        std::cerr << ex.what() << '\n';
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}



Go: package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "log"
    "sort"
    "strings"
)

func main() {
    b, err := ioutil.ReadFile("unixdict.txt")
    if err != nil {
        log.Fatal("Error reading file")
    }
    letters := "deegklnow"
    wordsAll := bytes.Split(b, []byte{'\n'})
    
    var words [][]byte
    for _, word := range wordsAll {
        word = bytes.TrimSpace(word)
        le := len(word)
        if le > 2 && le < 10 {
            words = append(words, word)
        }
    }
    var found []string
    for _, word := range words {
        le := len(word)
        if bytes.IndexByte(word, 'k') >= 0 {
            lets := letters
            ok := true
            for i := 0; i < le; i++ {
                c := word[i]
                ix := sort.Search(len(lets), func(i int) bool { return lets[i] >= c })
                if ix < len(lets) && lets[ix] == c {
                    lets = lets[0:ix] + lets[ix+1:]
                } else {
                    ok = false
                    break
                }
            }
            if ok {
                found = append(found, string(word))
            }
        }
    }
    fmt.Println("The following", len(found), "words are the solutions to the puzzle:")
    fmt.Println(strings.Join(found, "\n"))

    
    mostFound := 0
    var mostWords9 []string
    var mostLetters []byte
    
    var words9 [][]byte
    for _, word := range words {
        if len(word) == 9 {
            words9 = append(words9, word)
        }
    }
    
    for _, word9 := range words9 {
        letterBytes := make([]byte, len(word9))
        copy(letterBytes, word9)
        sort.Slice(letterBytes, func(i, j int) bool { return letterBytes[i] < letterBytes[j] })
        
        distinctBytes := []byte{letterBytes[0]}
        for _, b := range letterBytes[1:] {
            if b != distinctBytes[len(distinctBytes)-1] {
                distinctBytes = append(distinctBytes, b)
            }
        }
        distinctLetters := string(distinctBytes)
        for _, letter := range distinctLetters {
            found := 0
            letterByte := byte(letter)
            for _, word := range words {
                le := len(word)
                if bytes.IndexByte(word, letterByte) >= 0 {
                    lets := string(letterBytes)
                    ok := true
                    for i := 0; i < le; i++ {
                        c := word[i]
                        ix := sort.Search(len(lets), func(i int) bool { return lets[i] >= c })
                        if ix < len(lets) && lets[ix] == c {
                            lets = lets[0:ix] + lets[ix+1:]
                        } else {
                            ok = false
                            break
                        }
                    }
                    if ok {
                        found = found + 1
                    }
                }
            }
            if found > mostFound {
                mostFound = found
                mostWords9 = []string{string(word9)}
                mostLetters = []byte{letterByte}
            } else if found == mostFound {
                mostWords9 = append(mostWords9, string(word9))
                mostLetters = append(mostLetters, letterByte)
            }
        }
    }
    fmt.Println("\nMost words found =", mostFound)
    fmt.Println("Nine letter words producing this total:")
    for i := 0; i < len(mostWords9); i++ {
        fmt.Println(mostWords9[i], "with central letter", string(mostLetters[i]))
    }
}