The requirements:
The following was an interview test for a junior position.
You’ve been asked to write a program that will take a dictionary file (csv list of words) and output the number of words containing the same letters grouped by the length in a separate txt file.
Eg west and stew are both 4 letter words containing the same letters and as such count a match.
My understanding on the requirements was to read the .CSV file and find anagrams of the words, in the file.
My Solution:
First off you can find the entire solution on GitHub, but here's the code:
Program.cs (CLI application to start the program)
namespace Syzygy.App
{
class Program
{
static void Main(string[] args)
{
// Read file
CSVReader reader = new CSVReader("../../../CsvTest.csv");
var words = reader.Read();
// Create a list of dictionaries (length:list<string>)
// Makes the string comparisson a lot quicker as we don't have to
// iterate over the entire list multiple times.
var dictionaryList = new List<WordDictionary>();
CreateDictionaries(words, ref dictionaryList);
var wordHandler = new WordHandler();
List<string> anagrams = wordHandler.FindAnagrams(dictionaryList);
ShowAndWrite(anagrams);
}
/// <summary>
/// Prints and writes to file.
/// </summary>
/// <param name="Anagrams"></param>
private static void ShowAndWrite(List<string> Anagrams)
{
Console.WriteLine("Amount of anagrams: " + Anagrams.Count + "\n");
foreach (var word in Anagrams)
{
Console.WriteLine($"{word.Length}:{word}");
}
var fw = new FileWriter(Anagrams);
}
/// <summary>
/// Uses the CSV-parsed words to create a list of dictionaries
/// where each dictionary holds an integer (length) and a list of words.
/// </summary>
/// <param name="words"></param> The words from our CSV-file
/// <param name="dictionaryList"></param> A reference to the memory-address of the list of dictionaries.
private static void CreateDictionaries(IEnumerable<string> words, ref List<WordDictionary> dictionaryList)
{
var dictionary = new WordDictionary(1);
foreach (var word in words.OrderBy(w => w.Length))
{
if (word.Length != dictionary.Length)
{
dictionaryList.Add(dictionary);
dictionary = new WordDictionary(word.Length);
}
dictionary.AddWord(word);
}
}
}
}
CSVReader.cs
namespace Syzygy.BL
{
public class CSVReader
{
public string Path { get; set; }
public CSVReader(string path)
{
Path = @""+path;
}
public IEnumerable<string> Read()
{
return File.ReadAllLines(Path)
.SelectMany(w => w.Split(',')).Where(w => !String.IsNullOrEmpty(w));
}
}
}
WordHandler.cs
namespace Syzygy.BL
{
public class WordHandler
{
/// <summary>
/// Simply iterates over the list of words looking for
/// any occurrences of the scrambled word-parameter.
/// Since each dictionary will at least hold ONE
/// word it will match with (itself) we need a count
/// variable to ensure that we only return matches
/// on other words.
/// </summary>
/// <param name="word"></param>
/// <param name="words"></param>
/// <returns></returns>
public bool CompareWords (string word, List<string> words)
{
int count = 0;
foreach(var dictionaryWord in words)
{
var sortedDictionaryWord = String.Concat(dictionaryWord.ToLower()
.OrderBy(c => c));
var sortedWord = String.Concat(word.ToLower()
.OrderBy(c => c));
if (sortedDictionaryWord == sortedWord)
{
if(count > 0)
{
return true;
}
count++;
}
}
return false;
}
public List<string> FindAnagrams(List<WordDictionary> dictionaryList)
{
var Anagrams = new List<string>();
foreach (var dictionary in dictionaryList)
{
foreach (var word in dictionary.Words)
{
if (CompareWords(word, dictionary.Words))
{
Anagrams.Add(word);
}
}
}
return Anagrams;
}
}
}
WordDictionary.cs
namespace Syzygy.BL
{
public class WordDictionary
{
public int Length { get; set; }
public List<string> Words { get; set; }
public WordDictionary(int length)
{
Length = length;
Words = new List<string>();
}
public void AddWord(string word)
{
Words.Add(word);
}
}
}
FileWriter.cs
namespace Syzygy.BL
{
public class FileWriter
{
public FileWriter(List<string> listToWrite)
{
using (StreamWriter writeText = new StreamWriter("Anagrams.txt"))
{
writeText.WriteLine($"Anagram Count:{listToWrite.Count}");
listToWrite.ForEach(w =>
writeText.WriteLine($"{w.Length}:{w}"));
}
}
}
}
@""+path
does the empty string has any purpose? It's a rather strange operation. \$\endgroup\$@
concatenation creates an absolute path? \$\endgroup\$string
is an enumerable collection ofchar
. Would that have influenced your design? If so How? \$\endgroup\$