namespace WhiteRabbit { using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Numerics; using System.Reactive.Concurrency; using System.Reactive.Linq; internal class VectorsProcessor { public VectorsProcessor(Vector target, int maxVectorsCount, Func, string> vectorToString) { this.Target = target; this.MaxVectorsCount = maxVectorsCount; this.VectorToString = vectorToString; } /// /// Negative sign bit. /// (byte)b & (byte)128 equals zero for non-negative (0..127) bytes and equals (byte)128 for negative (128..255) bytes. /// Similarly, vector & Negative equals zero if all bytes are non-negative, and does not equal zero if some bytes are negative. /// Use (vector & Negative) == Vector<byte>.Zero to determine if all components are non-negative. /// private static Vector Negative { get; } = new Vector(Enumerable.Repeat((byte)128, 16).ToArray()); private Vector Target { get; } private int MaxVectorsCount { get; } private Func, string> VectorToString { get; } private long Iterations { get; set; } = 0; // Produces all sequences of vectors with the target sum public IObservable[]> GenerateSequences(IReadOnlyCollection> vectors) { var filteredVectors = this.FilterVectors(vectors); var dictionary = ImmutableStack.Create(filteredVectors.ToArray()); var unorderedSequences = this.GenerateUnorderedSequences(this.Target, ImmutableStack.Create>(), dictionary) .ToObservable() .SubscribeOn(NewThreadScheduler.Default); var allSequences = unorderedSequences .SelectMany(this.GeneratePermutations) .SubscribeOn(NewThreadScheduler.Default); return allSequences; } // We want words with more letters (and among these, words with more "rare" letters) to appear first, to reduce the searching time somewhat. // Applying such a sort, we reduce the total number of triplets to check for anagrams from ~62M to ~29M. // Total number of quadruplets is reduced from 1468M to mere 311M. // Also, it produces the intended results faster (as these are more likely to contain longer words - e.g. "poultry outwits ants" is more likely than "p o u l t r y o u t w i t s a n t s"). // This method basically gives us the 1-norm of the vector in the space rescaled so that the target is [1, 1, ..., 1]. private int GetVectorWeight(Vector vector) { var weight = 0; for (var i = 0; this.Target[i] != 0; i++) { weight += (720 * vector[i]) / this.Target[i]; // 720 = 6!, so that the result will be a whole number (unless Target[i] > 6) } return weight; } private IEnumerable> FilterVectors(IReadOnlyCollection> vectors) { return vectors .Where(vector => ((this.Target - vector) & Negative) == Vector.Zero) .OrderBy(GetVectorWeight); } [Conditional("DEBUG")] private void DebugState(ImmutableStack> partialSumStack, Vector currentVector) { this.Iterations++; if (this.Iterations % 1000000 == 0) { Console.WriteLine($"Iteration #{this.Iterations}: {string.Join(" ", partialSumStack.Push(currentVector).Reverse().Select(vector => this.VectorToString(vector)))}"); } } // This method takes most of the time, so everything related to it must be optimized. // In every sequence, next vector always goes after the previous one from dictionary. // E.g. if dictionary is [x, y, z], then only [x, y] sequence could be generated, and [y, x] will never be generated. // That way, the complexity of search goes down by a factor of MaxVectorsCount! (as if [x, y] does not add up to a required target, there is no point in checking [y, x]) private IEnumerable[]> GenerateUnorderedSequences(Vector remainder, ImmutableStack> partialSumStack, ImmutableStack> dictionaryStack) { var count = partialSumStack.Count() + 1; if (count < this.MaxVectorsCount) { var dictionaryTail = dictionaryStack; while (!dictionaryTail.IsEmpty) { Vector currentVector; var nextDictionaryTail = dictionaryTail.Pop(out currentVector); this.DebugState(partialSumStack, currentVector); var newRemainder = remainder - currentVector; if (newRemainder == Vector.Zero) { yield return partialSumStack.Push(currentVector).Reverse().ToArray(); } else if ((newRemainder & Negative) == Vector.Zero) { foreach (var result in this.GenerateUnorderedSequences(newRemainder, partialSumStack.Push(currentVector), dictionaryTail)) { yield return result; } } dictionaryTail = nextDictionaryTail; } } else if (count == this.MaxVectorsCount) { var dictionaryTail = dictionaryStack; while (!dictionaryTail.IsEmpty) { Vector currentVector; dictionaryTail = dictionaryTail.Pop(out currentVector); this.DebugState(partialSumStack, currentVector); var newRemainder = remainder - currentVector; if (newRemainder == Vector.Zero) { yield return partialSumStack.Push(currentVector).Reverse().ToArray(); } } } } private IEnumerable GeneratePermutations(T[] original) { foreach (var permutation in PrecomputedPermutationsGenerator.HamiltonianPermutations(original.Length)) { yield return permutation.Select(i => original[i]).ToArray(); } } } }