private static Result FindSplit(ArrayList dataArray, Interval bounds, int intervals, float optimum, ResultCache cache) { #if PERFCOUNTERS recursionCount++; #endif #if SCOREGRAPH bool topLevel = (bounds.Left == 0) && (bounds.Right == dataArray.Count); #endif // check if there is enough split points Debug.Assert(bounds.Right - bounds.Left >= intervals); // test the end of recursion (no splitting) if (intervals == 1) { Result result = new Result(); result.Intervals.Add(bounds); int count = 0; for (int i = bounds.Left; i < bounds.Right; i++) count += ((Data) dataArray[i]).Count; result.Cost = ResultPenalty(count, optimum); return result; } // test the end of recursion (exact splitting, no choice) if (intervals == bounds.Right - bounds.Left) { Result result = new Result(); result.Cost = 0.0f; for (int i = bounds.Left; i < bounds.Right; i++) { result.Intervals.Add(new Interval(i, i + 1)); result.Cost += ResultPenalty(((Data) dataArray[i]).Count, optimum); } return result; } // cache lookup { Result result = cache.GetResult(intervals, bounds); if (result != null) return result; } // count objects that must be in the left part int leftIntervals = intervals / 2; int leftSum = 0; for (int i = 0; i < leftIntervals; i++) leftSum += ((Data) dataArray[bounds.Left + i]).Count; // add some more intervals until optimal point is reached int bestSplit; int leftOptimalSum = (int) Math.Round(optimum * leftIntervals); for (bestSplit = bounds.Left + leftIntervals; bestSplit < bounds.Right - (intervals - leftIntervals); bestSplit++) { if (leftSum + ((Data) dataArray[bestSplit]).Count > leftOptimalSum) break; leftSum += ((Data) dataArray[bestSplit]).Count; } // start testing these split points (spreading to left and right) int leftSplit = bestSplit; int rightSplit = bestSplit + 1; bool leftStop = false; // there's always at least one solution bool rightStop = (rightSplit > bounds.Right - (intervals - leftIntervals)); // go right only if there is another possible split point // spread to both sides and search for better solutions Result bestResult = new Result(), leftTmpResult, rightTmpResult; bestResult.Cost = initPenalty; float leftLastScore = initPenalty, rightLastScore = initPenalty; int leftGrowCount = 0, rightGrowCount = 0; while (!leftStop || !rightStop) { if (!leftStop) { // find solution for left and right part leftTmpResult = FindSplit(dataArray, new Interval(bounds.Left, leftSplit), leftIntervals, optimum, cache); rightTmpResult = FindSplit(dataArray, new Interval(leftSplit, bounds.Right), intervals - leftIntervals, optimum, cache); // sum the costs of partial results float sum = leftTmpResult.Cost + rightTmpResult.Cost; #if SCOREGRAPH if (topLevel) { // top level in the recursion Trace.WriteLine(String.Format("{0};{1}", leftSplit, sum)); } #endif // first solution is propagated to the right side if (rightLastScore == initPenalty) { // save to right last value rightLastScore = sum; } // compare this result to what we have so far if (sum < bestResult.Cost) { // merge two partial solution to one bestResult.Merge(leftTmpResult, rightTmpResult); // absolute stop criterium (perfect result) if (sum == 0.0f) break; } #if SCOREGRAPH if (!topLevel) { #endif // check stop criterium (result penalty is too big) if (sum > stopLimit * bestResult.Cost) { // stop spreading to the left leftStop = true; } // check stop criterium (result penalty is constantly growing, so there is // probably no hope of getting better result than we have...) if (sum < leftLastScore) { // not growing, reset the counter leftGrowCount = 0; } else { // growing, increase leftGrowCount++; if (leftGrowCount == growLimit) leftStop = true; } leftLastScore = sum; #if SCOREGRAPH } #endif // check if there is possibility to spread further to the left if (leftSplit <= bounds.Left + leftIntervals) { // stop testing spreading to the left leftStop = true; } else { // shift the left split to the next position leftSplit--; } } if (!rightStop) { // find solution for left and right part leftTmpResult = FindSplit(dataArray, new Interval(bounds.Left, rightSplit), leftIntervals, optimum, cache); rightTmpResult = FindSplit(dataArray, new Interval(rightSplit, bounds.Right), intervals - leftIntervals, optimum, cache); // sum the costs of partial results float sum = leftTmpResult.Cost + rightTmpResult.Cost; #if SCOREGRAPH if (topLevel) { // top level in the recursion Trace.WriteLine(String.Format("{0};{1}", rightSplit, sum)); } #endif // compare this result to what we have so far if (sum < bestResult.Cost) { // merge two partial solution to one bestResult.Merge(leftTmpResult, rightTmpResult); } #if SCOREGRAPH if (!topLevel) { #endif // check stop criterium (result penalty is too big) if (sum > stopLimit * bestResult.Cost) { // stop testing spreading to the right rightStop = true; } // check stop criterium (result penalty is constantly growing, so there is // probably no hope of getting better result than we have...) if (sum < rightLastScore) { // not growing, reset the counter rightGrowCount = 0; } else { // growing, increase rightGrowCount++; if (rightGrowCount == growLimit) rightStop = true; } rightLastScore = sum; #if SCOREGRAPH } #endif // check if there is possibility to spread further to the right if (rightSplit >= bounds.Right - (intervals - leftIntervals)) { // stop testing spreading to the right rightStop = true; } else { // shift the right split to the next position rightSplit++; } } } // check the solution Debug.Assert(bestResult.Cost < initPenalty); // add the best result to cache cache.SetResult(intervals, bounds, bestResult); // ...and return it return bestResult; }