English Dictionary CLI
TL;DR
This CLI tool give you the english definition of a word base, also use a Trie to give you predictions as you type your word.
Table of Contents
Table of Contents
CLI App
Normal Funtionality:
Pipe Functionality:
Project Structure
This project return the definition of a word in english and use a Trie to give you predictions as you type your word.
The project is divided in two parts:
cli
: The CLI toolcli.test
: The test project
The CLI tool is divide in two files Program.cs
and Trie.cs
. The Program.cs
is the main file that run the CLI tool and the Trie.cs
is the data structure that hold the dictionary.
The projecr is structure as follow:
.
├── CliApp.sln
├── LICENSE
├── README.md
├── data
│ └── dictionary_compact.json -- Dictionary
├── cli
│ ├── CliApp.csproj
│ ├── Program.cs -- Program
│ └── Trie.cs -- Data Structure
└── cli.test
├── GlobalUsings.cs
├── UnitTest1.cs
└── cli.test.csproj -- Test Project
Prerequisites
- dotnet 8.0
Code
You can find the repository here
Program.cs
using System.Text.Json;
using cli.Structures;
namespace cli.App
{
class Program
{
static int Main(string[] args)
{
CLI app = new CLI();
app.Run();
return 0;
}
}
class CLI
{
private const uint MAX_PRESDICTION_SHOW = 20;
private const string FILE_NAME = "data/dictionary_compact.json";
public EnglishDictionary? myDic;
private int[] wordPosition = [0, 0];
private int lastPosition = 0;
public CLI()
{
this.myDic = new EnglishDictionary(FILE_NAME);
}
public void Run()
{
if (myDic == null)
{
throw new NullReferenceException("Dictionary can not be null");
}
bool flag_continue = true;
string word = "";
Console.Clear();
Console.WriteLine("Please provide a word:");
string[] prediction = [];
do
{
try
{
if (!Console.KeyAvailable)
{
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
if (keyInfo.Key == ConsoleKey.Enter || keyInfo.Key == ConsoleKey.Spacebar || keyInfo.Key == ConsoleKey.Escape)
{
flag_continue = false;
}
else if (keyInfo.Key == ConsoleKey.Backspace)
{
if (word.Length > 0)
{
word = word.Substring(0, word.Length - 1);
}
}
else if (keyInfo.Key == ConsoleKey.Tab)
{
if (prediction.Length > 0)
{
word = prediction[0];
}
}
else
{
word += keyInfo.Key.ToString().ToLower();
}
// clear the console
this.clearPredictions(prediction);
}
Console.WriteLine(word);
this.wordPosition[0] = word.Length;
this.wordPosition[1] = Console.CursorTop - 1;
prediction = myDic.getPrediction(word);
this.printPredictions(prediction);
this.lastPosition = Console.CursorTop;
Console.SetCursorPosition(this.wordPosition[0], this.wordPosition[1]);
}
catch (System.InvalidOperationException e)
{
string? stdIn = Console.ReadLine();
if (stdIn != null)
{
word = stdIn;
}
else
{
throw new NullReferenceException("stdIn can not be null");
}
flag_continue = false;
}
} while (flag_continue);
this.clearPredictions(prediction);
string definition = myDic.getDefinition(word);
Console.WriteLine($"Definition of {word}:");
Console.WriteLine(definition);
}
private void printPredictions(string[] prediction)
{
if (prediction.Length > 0)
{
if (prediction.Length > MAX_PRESDICTION_SHOW)
{
for (int i = 0; i < MAX_PRESDICTION_SHOW; i++)
{
Console.WriteLine("-> " + prediction[i]);
}
}
else
{
for (int i = 0; i < prediction.Length; i++)
{
Console.WriteLine("-> " + prediction[i]);
}
}
}
}
private void clearPredictions(string[] predictions)
{
if (predictions.Length > 0)
{
Console.SetCursorPosition(0, this.lastPosition);
if (predictions.Length > MAX_PRESDICTION_SHOW)
{
for (int i = 0; i < MAX_PRESDICTION_SHOW + 1; i++)
{
Console.SetCursorPosition(0, Console.CursorTop - 1);
Console.WriteLine(new string(' ', Console.WindowWidth));
Console.SetCursorPosition(0, Console.CursorTop - 1);
}
}
else
{
for (int i = 0; i < predictions.Length + 1; i++)
{
Console.SetCursorPosition(0, Console.CursorTop - 1);
Console.WriteLine(new string(' ', Console.WindowWidth));
Console.SetCursorPosition(0, Console.CursorTop - 1);
}
}
}
}
}
class EnglishDictionary
{
public Dictionary<string, string>? dic;
private Trie trie;
public EnglishDictionary(string fileName)
{
string filePath = Path.GetFullPath(fileName);
if (File.Exists(filePath))
{
string text = File.ReadAllText(filePath);
this.dic = JsonSerializer.Deserialize<Dictionary<string, string>>(text);
}
else
{
Console.WriteLine($"File {filePath} does not exist");
}
this.trie = new Trie();
this.addWordsToTrie();
}
public string[] getPrediction(string word)
{
string prediction;
TrieNode node;
if (word != null)
{
(prediction, node) = this.trie.predict(word);
if (prediction != string.Empty)
{
List<Tuple<string, uint>> words = new List<Tuple<string, uint>>();
this.trie.reconstructWords(node, prediction, words);
words.Sort((x, y) => y.Item2.CompareTo(x.Item2));
return words.Select(x => x.Item1).ToArray();
}
}
return [""];
}
public string getDefinition(string word)
{
if (this.dic == null)
{
throw new NullReferenceException(" Dictionary can not be null");
}
if (this.dic.ContainsKey(word))
{
return this.dic[word];
}
else
{
return $"|--- {word} not in the Dictionary ---|";
}
}
private void addWordsToTrie()
{
if (this.dic == null)
{
throw new NullReferenceException(" Dictionary can not be null");
}
List<string> words = new List<string>(this.dic.Keys);
if (words.Count > 0)
{
foreach (string word in words)
{
this.trie.add(word);
}
}
}
}
}
Trie.cs
namespace cli.Structures
{
public class Trie
{
public TrieNode root;
public Trie()
{
this.root = new TrieNode('.');
}
public void add(string word)
{
this.addWord(word, this.root);
}
public (string, TrieNode) predict(string word)
{
string prediction = string.Empty;
TrieNode node = this.root;
foreach (char letter in word)
{
if (node.children.ContainsKey(letter))
{
node = node.children[letter];
prediction += node.letter;
}
else
{
return (string.Empty, new TrieNode('.'));
}
}
return (prediction, node);
}
public void reconstructWords(TrieNode node, string baseWord, List<Tuple<string, uint>> words)
{
if (node.isWordEnd)
{
words.Add(new Tuple<string, uint>(baseWord, node.count));
}
if (node.children.Count == 0)
{
return;
}
foreach (KeyValuePair<char, TrieNode> child in node.children)
{
string newBaseWord = baseWord + child.Value.letter;
this.reconstructWords(child.Value, newBaseWord, words);
}
}
private void addWord(string word, TrieNode node)
{
if (word.Equals(string.Empty))
{
node.isWordEnd = true;
}
else
{
char letter = word[0];
if (node.children.ContainsKey(letter))
{
node.children[letter].count++;
this.addWord(word.Substring(1), node.children[letter]);
}
else
{
TrieNode newNode = new TrieNode(letter);
node.children.Add(letter, newNode);
this.addWord(word.Substring(1), newNode);
}
}
}
}
public class TrieNode
{
public char letter;
public Dictionary<char, TrieNode> children;
public uint count;
public bool isWordEnd;
public TrieNode(char letter)
{
this.letter = letter;
this.children = new Dictionary<char, TrieNode>();
this.count = 1;
this.isWordEnd = false;
}
}
public class MyMath
{
public static int Add(int a, int b)
{
return a + b;
}
}
}
UnitTest1.cs
using cli.Structures;
namespace cli.test;
public class UnitTest1
{
[Fact]
public void TriePrediction()
{
Trie trie = new Trie();
trie.add("car");
trie.add("carbone");
(var pred, var node) = trie.predict("ca");
Assert.Equal("ca", pred);
Assert.Equal((uint)2, node.count);
}
[Fact]
public void TrieReconstructWords()
{
Trie trie = new Trie();
trie.add("car");
trie.add("carbone");
List<Tuple<string, uint>> words = new List<Tuple<string, uint>>();
trie.reconstructWords(trie.root, string.Empty, words);
Assert.Equal(2, words.Count);
Assert.Equal("car", words[0].Item1);
Assert.Equal((uint)2, words[0].Item2);
Assert.Equal("carbone", words[1].Item1);
Assert.Equal((uint)1, words[1].Item2);
}
}
Settings
dotnet run --project cli
Testing
dotnet test