diff --git a/WhiteRabbit/PermutationsGenerator.cs b/WhiteRabbit/PermutationsGenerator.cs new file mode 100644 index 0000000..4033214 --- /dev/null +++ b/WhiteRabbit/PermutationsGenerator.cs @@ -0,0 +1,118 @@ +namespace WhiteRabbit +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + /// + /// Code taken from https://ericlippert.com/2013/04/22/producing-permutations-part-three/ + /// + internal class PermutationsGenerator + { + public static IEnumerable HamiltonianPermutations(int n) + { + if (n < 0) + { + throw new ArgumentOutOfRangeException("n"); + } + + return Permutation.HamiltonianPermutationsIterator(n); + } + + public struct Permutation : IEnumerable + { + private Permutation(int[] permutation) + { + this.PermutationData = permutation; + } + + private Permutation(IEnumerable permutation) + : this(permutation.ToArray()) + { + } + + public static Permutation Empty { get; } = new Permutation(new int[] { }); + + private int[] PermutationData { get; } + + public int this[int index] => this.PermutationData[index]; + + public static IEnumerable HamiltonianPermutationsIterator(int n) + { + if (n == 0) + { + yield return Empty; + yield break; + } + + bool forwards = false; + foreach (Permutation permutation in HamiltonianPermutationsIterator(n - 1)) + { + for (int index = 0; index < n; index += 1) + { + yield return new Permutation(Extensions.InsertAt(permutation, forwards ? index : n - index - 1, n - 1)); + } + + forwards = !forwards; + } + } + + public IEnumerator GetEnumerator() + { + foreach (int item in this.PermutationData) + { + yield return item; + } + } + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } + + public static class Extensions + { + public static IEnumerable InsertAt(IEnumerable items, int position, T newItem) + { + if (items == null) + { + throw new ArgumentNullException("items"); + } + + if (position < 0) + { + throw new ArgumentOutOfRangeException("position"); + } + + return InsertAtIterator(items, position, newItem); + } + + private static IEnumerable InsertAtIterator(IEnumerable items, int position, T newItem) + { + int index = 0; + bool yieldedNew = false; + foreach (T item in items) + { + if (index == position) + { + yield return newItem; + yieldedNew = true; + } + + yield return item; + index += 1; + } + + if (index == position) + { + yield return newItem; + yieldedNew = true; + } + + if (!yieldedNew) + { + throw new ArgumentOutOfRangeException("position"); + } + } + } + } +} diff --git a/WhiteRabbit/Processor.cs b/WhiteRabbit/Processor.cs new file mode 100644 index 0000000..d655387 --- /dev/null +++ b/WhiteRabbit/Processor.cs @@ -0,0 +1,115 @@ +namespace WhiteRabbit +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + + internal class Processor + { + private const int DifferentChars = 12; + private const int ArraySize = DifferentChars * sizeof(int); + + public Processor(string sourceString, int maxWordsCount) + { + var rawNumberOfOccurrences = sourceString.Where(ch => ch != ' ').GroupBy(ch => ch).ToDictionary(group => group.Key, group => group.Count()); + this.IntToChar = rawNumberOfOccurrences.Select(kvp => kvp.Key).OrderBy(ch => ch).ToArray(); + if (this.IntToChar.Length != DifferentChars) + { + throw new ArgumentException("Unsupported phrase", nameof(sourceString)); + } + this.CharToInt = Enumerable.Range(0, DifferentChars).ToDictionary(i => this.IntToChar[i], i => i); + this.NumberOfOccurrences = Enumerable.Range(0, DifferentChars).Select(i => this.IntToChar[i]).Select(ch => rawNumberOfOccurrences.ContainsKey(ch) ? rawNumberOfOccurrences[ch] : 0).ToArray(); + this.MaxWordsCount = maxWordsCount; + } + + private Dictionary CharToInt { get; } + + private char[] IntToChar { get; } + + private int[] NumberOfOccurrences { get; } + + private int TotalCharsNumber { get; } + + private int MaxWordsCount { get; } + + private long Iterations { get; set; } = 0; + + public IEnumerable GeneratePhrases(IEnumerable words) + { + var filtered = FilterWords(words); + var dictionary = ImmutableStack.Create(filtered.Reverse().ToArray()); + var anagrams = GenerateOrderedPhrases(this.NumberOfOccurrences, ImmutableStack.Create(), dictionary) + .Select(list => list.Select(word => new string(word.Select(i => this.IntToChar[i]).ToArray())).ToArray()); + var anagramsWithPermutations = anagrams.SelectMany(GeneratePermutations).Select(list => string.Join(" ", list)); + return anagramsWithPermutations; + } + + private IEnumerable FilterWords(IEnumerable words) + { + return words + .Where(word => word.All(this.CharToInt.ContainsKey)) + .OrderBy(word => word) + .Distinct() + .Select(word => word.Select(ch => this.CharToInt[ch]).ToArray()) + .Where(word => word.GroupBy(ch => ch).All(group => group.Count() <= this.NumberOfOccurrences[group.Key])); + } + + private int[] GetStatus(int[] originalState, int[] newWord, out int status) + { + var tmpArray = new int[DifferentChars]; + Buffer.BlockCopy(originalState, 0, tmpArray, 0, ArraySize); + + foreach (var ch in newWord) + { + --tmpArray[ch]; + } + + // Negative if at least one element is negative; zero if all elements are zero; positive if all elements are non-negative and at least one element is positive + status = tmpArray[0] | tmpArray[1] | tmpArray[2] | tmpArray[3] | tmpArray[4] | tmpArray[5] | tmpArray[6] | tmpArray[7] | tmpArray[8] | tmpArray[9] | tmpArray[10] | tmpArray[11]; + return tmpArray; + } + + // This method takes most of the time, so everything related to it must be optimized + private IEnumerable GenerateOrderedPhrases(int[] currentState, ImmutableStack phraseStack, ImmutableStack dictionaryStack) + { + var remainder = dictionaryStack; + var count = phraseStack.Count() + 1; + while (!remainder.IsEmpty) + { + int[] currentWord; + var nextRemainder = remainder.Pop(out currentWord); + + this.Iterations++; + if (this.Iterations % 1000000 == 0) + { + Console.WriteLine($"Iteration #{this.Iterations}: {string.Join(" ", phraseStack.Push(currentWord).Reverse().Select(word => new string(word.Select(ch => this.IntToChar[ch]).ToArray())))}"); + } + + int status; + var state = GetStatus(currentState, currentWord, out status); + if (status > 0 && count < this.MaxWordsCount) + { + foreach (var result in GenerateOrderedPhrases(state, phraseStack.Push(currentWord), remainder)) + { + yield return result; + } + } + else if (status == 0) + { + yield return phraseStack.Push(currentWord).Reverse().ToArray(); + } + + remainder = nextRemainder; + } + } + + private IEnumerable GeneratePermutations(string[] original) + { + foreach (var permutation in PermutationsGenerator.HamiltonianPermutations(original.Length)) + { + yield return permutation.Select(i => original[i]).ToArray(); + } + } + } +} diff --git a/WhiteRabbit/Program.cs b/WhiteRabbit/Program.cs index 7990474..3886a0b 100644 --- a/WhiteRabbit/Program.cs +++ b/WhiteRabbit/Program.cs @@ -1,5 +1,11 @@ namespace WhiteRabbit { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + /// /// Main class /// @@ -10,6 +16,30 @@ /// public static void Main() { + var processor = new Processor("poultry outwits ants", 3); + foreach (var phrase in processor.GeneratePhrases(ReadInput())) + { + Console.WriteLine(GetMd5Hash(phrase) + ": " + phrase); + } + } + + private static string GetMd5Hash(string input) + { + using (MD5 md5Hash = MD5.Create()) + { + // Convert the input string to a byte array and compute the hash. + byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input)); + return string.Concat(data.Select(b => b.ToString("x2"))); + } + } + + private static IEnumerable ReadInput() + { + string line; + while ((line = Console.ReadLine()) != null) + { + yield return line; + } } } } diff --git a/WhiteRabbit/WhiteRabbit.csproj b/WhiteRabbit/WhiteRabbit.csproj index 92e0b87..9fd51c2 100644 --- a/WhiteRabbit/WhiteRabbit.csproj +++ b/WhiteRabbit/WhiteRabbit.csproj @@ -35,6 +35,10 @@ + + ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + True + @@ -44,11 +48,14 @@ + + +