-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add CLI infrastructure and an option to dump stats
- Loading branch information
1 parent
16fec39
commit a6af8f0
Showing
6 changed files
with
237 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import * as process from 'process'; | ||
import * as readline from 'readline'; | ||
import CliUtils from '../lib/cli-utils'; | ||
import dumpStats from "./dump-stats"; | ||
|
||
const menuOptions:Array<{name:string, handler:Function}> = [ | ||
{ | ||
name: 'Dump wordlist stats.', | ||
handler: dumpStats, | ||
} | ||
]; | ||
|
||
/** | ||
* Run the command line interface to this app. | ||
*/ | ||
const runCli = async() => { | ||
const rl = readline.createInterface(process.stdin, process.stdout); | ||
console.log(' *- Welcome to the Wordle Solver! -* '); | ||
console.log('-------------------------------------'); | ||
console.log(''); | ||
|
||
const cliPrompts = new CliUtils(rl); | ||
const selection = await cliPrompts.menuPrompt(menuOptions.map(o => o.name)); | ||
|
||
await menuOptions[selection].handler(); | ||
|
||
rl.close(); | ||
} | ||
|
||
|
||
export default runCli; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { FrequencyTable } from "../lib/frequency-table"; | ||
import { getWordListStats } from "../lib/word-list"; | ||
|
||
const printSortedTable = (frequencyTable:FrequencyTable) => { | ||
const sortedTable = frequencyTable.getSortedFrequencyTable(); | ||
for(const entry of sortedTable) | ||
console.log(`${entry.letter}: ${(100 * entry.percent).toFixed(3).padStart(7)} %`); | ||
} | ||
|
||
const dumpStats = async ():Promise<void> => { | ||
const stats = getWordListStats(); | ||
|
||
console.log('-----------------------------'); | ||
|
||
console.log('Frequencies for ALL positions:'); | ||
printSortedTable(stats.overallFrequencies); | ||
|
||
for(let i = 0; i < stats.characterFrequencies.length; i++) { | ||
console.log(); | ||
console.log(`Frequencies in character ${i}:`); | ||
printSortedTable(stats.characterFrequencies[i]); | ||
} | ||
} | ||
|
||
export default dumpStats; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import * as readline from 'readline'; | ||
|
||
/** | ||
* A few utilities for command line interfaces. | ||
*/ | ||
class CliUtils { | ||
readonly rl:readline.Interface; | ||
|
||
/** | ||
* @param rl Instance of node readline interface. This is *not* closed by this app. | ||
*/ | ||
constructor(rl:readline.Interface) { | ||
this.rl = rl; | ||
} | ||
|
||
/** | ||
* Async version of readline.question() | ||
*/ | ||
ask(question: string):Promise<string> { | ||
return new Promise(resolve => { | ||
this.rl.question(question, answer => resolve(answer)); | ||
}) | ||
} | ||
|
||
/** | ||
* Prompts the user with a menu of options on the terminal, and waits for | ||
* them to enter the number of the option. Returns the index of the selected | ||
* option. If user enters invalid option, They are prompted to enter again. | ||
*/ | ||
async menuPrompt (options: string[], prompt?:string):Promise<number> { | ||
prompt = prompt || 'What would you like to do?'; | ||
|
||
//we exit loop by returning, when user enters a valid selection | ||
while(true) { | ||
console.log(); | ||
console.log(prompt); | ||
for(let i = 0; i < options.length; i++) | ||
console.log(`${i+1}: ${options[i]}`); | ||
|
||
console.log(); | ||
let answer = await this.ask(`Enter your selection (1-${options.length}): `); | ||
answer = answer.trim(); | ||
if(/^\d+$/.test(answer)) { | ||
let selection = Number(answer); | ||
if(0 < selection && selection <= options.length) | ||
return selection - 1; | ||
} | ||
|
||
//if we get here, user has made an invalid selection | ||
console.log(); | ||
console.log(`Sorry, that's not a valid option. Try again!`); | ||
} | ||
}; | ||
} | ||
|
||
export default CliUtils; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
|
||
const ALL_LETTERS = 'abcdefghijklmnopqrstuvwxyz'.split(''); | ||
|
||
export class FrequencyTableEntry { | ||
count: number = 0; | ||
percent: number = NaN; | ||
} | ||
|
||
export type SortedFrequencyTableEntry = { | ||
letter: string; | ||
count: number; | ||
percent: number; | ||
} | ||
|
||
export type SortedFrequencyTable = Array<SortedFrequencyTableEntry>; | ||
|
||
/** | ||
* Represents the frequency of each letter in a given data set. | ||
*/ | ||
export class FrequencyTable { | ||
/** Total characters in frequency table. */ | ||
count: number = 0; | ||
|
||
/** Flag to indicate if the percentages are accurate or not. */ | ||
private percentagesAccurate:boolean = true; | ||
|
||
/** Counts for each letter of the alphabet. */ | ||
private entries: Record<string, FrequencyTableEntry> = {}; | ||
|
||
constructor() { | ||
ALL_LETTERS.forEach(c => this.entries[c] = new FrequencyTableEntry()); | ||
} | ||
|
||
/** | ||
* Increments the count for the given letter. | ||
*/ | ||
incrementLetter(letter:string):void { | ||
this.entries[letter].count++; | ||
this.count++; | ||
this.percentagesAccurate = false; | ||
} | ||
|
||
/** | ||
* Gets the number of times the given letter was encountered. | ||
*/ | ||
getLetterCount(letter:string):number { | ||
return this.entries[letter].count; | ||
} | ||
|
||
/** | ||
* Gets the percentage of letters seen that were the given letter. | ||
*/ | ||
getLetterPercent(letter:string):number { | ||
if(!this.percentagesAccurate) | ||
this.updatePercentages(); | ||
return this.entries[letter].percent; | ||
} | ||
|
||
/** | ||
* Updates the percentage field for all numbers in the table. | ||
*/ | ||
private updatePercentages():void { | ||
if(this.percentagesAccurate) | ||
return; | ||
ALL_LETTERS.forEach(c => this.entries[c].percent = this.entries[c].count / this.count); | ||
this.percentagesAccurate = true; | ||
} | ||
|
||
/** | ||
* Converts this table to a sorted frequency table, with the most common letters | ||
* first and least common letters last. | ||
*/ | ||
getSortedFrequencyTable():SortedFrequencyTable { | ||
const ret:SortedFrequencyTable = ALL_LETTERS.map(letter => ({ | ||
letter, | ||
count: this.getLetterCount(letter), | ||
percent: this.getLetterPercent(letter), | ||
})); | ||
ret.sort((a,b) => b.percent - a.percent); | ||
return ret; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import * as fs from 'fs'; | ||
import { FrequencyTable } from './frequency-table'; | ||
|
||
type WordListStats = { | ||
overallFrequencies: FrequencyTable; | ||
characterFrequencies: Array<FrequencyTable>; | ||
} | ||
|
||
const VALID_WORD_REGEX = /^[a-z]{5}$/; | ||
|
||
export const wordList:string[] = fs.readFileSync('app/resources/word-list.txt').toString() | ||
.split(/\s+/) | ||
.filter(s => VALID_WORD_REGEX.test(s)); | ||
|
||
/** | ||
* Parse the word list to calculate frequencies of each character overall and per-character. | ||
*/ | ||
export const getWordListStats = ():WordListStats => { | ||
const stats:WordListStats = { | ||
overallFrequencies: new FrequencyTable(), | ||
characterFrequencies: [ | ||
new FrequencyTable(), | ||
new FrequencyTable(), | ||
new FrequencyTable(), | ||
new FrequencyTable(), | ||
new FrequencyTable(), | ||
] | ||
}; | ||
|
||
for(const word of wordList) { | ||
for(let i = 0; i < 5; i++) { | ||
const letter = word[i]; | ||
stats.overallFrequencies.incrementLetter(letter); | ||
stats.characterFrequencies[i].incrementLetter(letter); | ||
} | ||
} | ||
|
||
return stats; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
console.log('coming soon!'); | ||
import runCli from "./app/cli/cli"; | ||
|
||
runCli(); |