namespace WhiteRabbit { using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Numerics; 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 IEnumerable[]> GenerateSequences(IEnumerable> vectors) { var filteredVectors = this.FilterVectors(vectors); var dictionary = ImmutableStack.Create(filteredVectors.ToArray()); var unorderedSequences = this.GenerateUnorderedSequences(this.Target, ImmutableStack.Create>(), dictionary); var allSequences = unorderedSequences.SelectMany(this.GeneratePermutations); return allSequences; } private IEnumerable> FilterVectors(IEnumerable> vectors) { return vectors .Where(vector => ((this.Target - vector) & Negative) == Vector.Zero); } [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(); } } } }