public void TestPartition() { var fixtures = new [] { Tuple.Create(new int[] { }, 1), Tuple.Create((int[])null, 1), Tuple.Create(new[] { 5, 5, 5, 5, 5, 5, 5 }, 5), Tuple.Create(new[] { 3, 5, 8, 5, 10, 2, 1 }, 5), Tuple.Create(new[] { 7, 5, 8, 5, 10, 6, 11 }, 5), Tuple.Create(new[] { 5, 5, 10, 15, 7, 3, 5, 5, 2, 1, 9, 9, 0, 3, 5 }, 5), }; var methods = new PartitioningMethod <int>[] { PartitionReorderNode, PatitionReorderValues }; foreach (var method in methods) { foreach (var fixture in fixtures) { var pivot = fixture.Item2; var result = method(CreateListFromArray(fixture.Item1), pivot); var greaterFound = false; for (var node = result; node != null; node = node.Next) { if (node.Value.CompareTo(pivot) < 0) { if (greaterFound) { Assert.Fail(); } } else { greaterFound = true; } } } } }
/// <summary> /// 분할 방법에 따라 총액(<paramref name="totalAmount"/>)을 분할 갯수(<paramref name="partitionCount"/>)만큼 분할합니다. /// </summary> /// <param name="method"></param> /// <param name="partitionCount"></param> /// <param name="totalAmount"></param> /// <returns></returns> public static double[] Partitioning(this PartitioningMethod method, int partitionCount = DefaultPartitionCount, double totalAmount = DefaultPartitionAmount) { partitionCount.ShouldBePositive("partitionCouunt"); totalAmount.ShouldBePositive("totalAmount"); if (IsDebugEnabled) { log.Debug("총액을 분배합니다. PartitioningMethod=[{0}], partitionCount=[{1}], totalAmount=[{2}]", method, partitionCount, totalAmount); } if (partitionCount == DefaultPartitionCount && Math.Abs(totalAmount - DefaultPartitionAmount) < double.Epsilon) { return(DefaultPartitioningArray[method]); } Guard.Assert(() => partitionCount % 2 == 0, "PartitionCount는 짝수여야 합니다. PartitionCount=[{0}]", partitionCount); double[] result; double a, b; var half = partitionCount / 2; var uniformParition = totalAmount / partitionCount; switch (method) { case PartitioningMethod.HorizontaolUniform: result = Generate(partitionCount, x => uniformParition).ToArray <double>(); break; case PartitioningMethod.VerticalBegin: result = new double[partitionCount]; result[0] = totalAmount; break; case PartitioningMethod.VerticalEnd: result = new double[partitionCount]; result[partitionCount - 1] = totalAmount; break; case PartitioningMethod.TriangleBegin: a = -16.0 / 9.0; b = 18.0 * totalAmount / DefaultPartitionAmount; result = Generate <double>(partitionCount, x => a * x + b).Select(y => Math.Round(y, 0)).ToArray <double>(); break; case PartitioningMethod.TriangleMiddle: result = new double[partitionCount]; b = 2.0 * totalAmount / DefaultPartitionAmount; var triangleMiddle = Generate <double>(half, x => 4 * x + b).Select(y => Math.Round(y, 0)).ToArray <double>(); Array.Copy(triangleMiddle, result, half); Array.Copy(triangleMiddle.Reverse().ToArray <double>(), 0, result, half, half); break; case PartitioningMethod.TriangleEnd: a = 16.0 / 9.0; b = 2.0 * totalAmount / DefaultPartitionAmount; result = Generate <double>(partitionCount, x => a * x + b).Select(y => Math.Round(y, 0)).ToArray <double>(); break; case PartitioningMethod.TwoStepBegin: Func <int, double> @twostepBegin = x => (x < half) ? 1.3 * uniformParition : 0.7 * uniformParition; result = Generate <double>(partitionCount, x => @twostepBegin(x)).Select(y => Math.Round(y, 0)).ToArray <double>(); break; case PartitioningMethod.TwoStepEnd: Func <int, double> @twostepEnd = x => (x < half) ? 0.7 * uniformParition : 1.3 * uniformParition; result = Generate <double>(partitionCount, x => @twostepEnd(x)).Select(y => Math.Round(y, 0)).ToArray <double>(); break; case PartitioningMethod.TrapezoidMiddle: result = new double[partitionCount]; b = 2.0 * totalAmount / DefaultPartitionAmount; var trapezoid = Generate <double>(half - 1, x => 13.0 / 3.0 * x + b).Select(y => Math.Round(y, 0)).ToArray <double>(); Array.Copy(trapezoid, result, half - 1); Array.Copy(trapezoid.Reverse().ToArray(), 0, result, half + 1, half - 1); result[half] = result[half - 1]; break; case PartitioningMethod.Normal: result = Generate <double>(partitionCount, x => totalAmount * NormalDistribution(4.0, half, x + 1)).Select( y => Math.Round(y, 0)).ToArray <double>(); break; case PartitioningMethod.ThreeMiddle: var countByThree = 0.3 * partitionCount; var countBySeven = 0.7 * partitionCount; Func <int, double> @treeMiddle = x => (x <= countByThree || x >= countBySeven) ? 0.8 * uniformParition : 1.3 * uniformParition; result = Generate <double>(partitionCount, x => @treeMiddle(x)).Select(y => Math.Round(y, 0)).ToArray <double>(); break; default: throw new NotSupportedException(string.Format("지원하지 않는 분할 방식입니다. method=[{0}]", method)); } return(result); }
public static IEnumerable <IEnumerable <T> > SequentialPartition <T>(this IEnumerable <T> source, Func <T, bool> partitioningPredicate, PartitioningMethod partitioningMethod = PartitioningMethod.Ignore) { T? previousBoundary = default; var previousBoundarySet = false; while (source.Any()) { var partitionIterator = source.TakeWhile(i => !partitioningPredicate(i)); source = source.Skip(partitionIterator.Count()); if (previousBoundarySet) { partitionIterator = partitionIterator.Prepend(previousBoundary !); previousBoundarySet = false; } var partition = partitionIterator.ToList().AsReadOnly(); if (!source.TryGetFirst(out var boundary)) { yield return(partition.Any() ? partition : throw new InvalidOperationException("Invalid partitioning state.")); break; } source = source.Skip(1); if (!partitioningPredicate(boundary)) { throw new InvalidOperationException("Invalid partitioning state."); } switch (partitioningMethod) { case PartitioningMethod.Ignore: if (partition.Count > 0) { yield return(partition); } break; case PartitioningMethod.KeepWithLast: yield return(partition.Append(boundary)); break; case PartitioningMethod.KeepSingle: if (partition.Count > 0) { yield return(partition); } yield return(new[] { boundary }.ToList().AsReadOnly()); break; case PartitioningMethod.KeepWithNext: if (partition.Count > 0) { yield return(partition); } previousBoundary = boundary; previousBoundarySet = true; break; default: throw new InvalidOperationException(); } } }