/* * Copyright 2012 Future Systems * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.araqne.logdb.sort; import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; public class Partitioner { private Comparator<Item> comparator; public Partitioner(Comparator<Item> comparator) { this.comparator = comparator; } public List<Partition> partition(int count, List<SortedRun> runs) { if ((count & (count - 1)) > 0) throw new IllegalArgumentException("count should be power of 2, count=" + count); List<SortedRunStatus> sortedRuns = new LinkedList<SortedRunStatus>(); for (SortedRun run : runs) sortedRuns.add(new SortedRunStatus(run)); List<Partition> partitions = new ArrayList<Partition>(); divide(count, sortedRuns, partitions); return partitions; } private void divide(int count, List<SortedRunStatus> sortedRuns, List<Partition> partitions) { count /= 2; // find median of each runs PriorityQueue<Median> q = new PriorityQueue<Median>(); for (SortedRunStatus status : sortedRuns) { try { status.run.open(); status.boundary = status.left + (status.right - status.left) / 2; status.median = status.run.get(status.boundary); q.add(new Median(status, status.boundary, status.median)); } catch (IOException e) { throw new IllegalStateException("IOException while finding median of each runs in divide()", e); } finally { status.run.close(); } } // sort by median Item medianOfMedian = null; int runCount = sortedRuns.size(); int half = (runCount - 1) / 2; int i = 0; while (true) { Median m = q.poll(); if (m == null) break; if (i <= half) { m.runStatus.medianLeft = Math.min(m.offset, m.runStatus.medianRight); if (i == half) { medianOfMedian = m.value; } } else { m.runStatus.medianRight = Math.max(m.offset - 1, m.runStatus.medianLeft); } i++; } // ensure median of median boundary for each run for (SortedRunStatus r : sortedRuns) findBoundary(medianOfMedian, r); if (count == 1) { Partition p1 = new Partition(); Partition p2 = new Partition(); for (SortedRunStatus status : sortedRuns) { if (status.left < status.boundary) p1.getRunRanges().add(new SortedRunRange(status.run, status.left, status.boundary - 1)); if (status.boundary <= status.right) p2.getRunRanges().add(new SortedRunRange(status.run, status.boundary, status.right)); } partitions.add(p1); partitions.add(p2); } else { List<SortedRunStatus> leftRuns = new LinkedList<SortedRunStatus>(); List<SortedRunStatus> rightRuns = new LinkedList<SortedRunStatus>(); for (SortedRunStatus status : sortedRuns) { if (status.left < status.boundary) leftRuns.add(new SortedRunStatus(status.run, status.left, status.boundary - 1)); if (status.boundary <= status.right) rightRuns.add(new SortedRunStatus(status.run, status.boundary, status.right)); } divide(count, leftRuns, partitions); divide(count, rightRuns, partitions); } } private void findBoundary(Item medianOfMedian, SortedRunStatus runStatus) { int left = runStatus.medianLeft; int right = runStatus.medianRight; int mid = 0; Item value = null; try { runStatus.run.open(); while (left <= right) { mid = left + ((right - left) / 2); value = runStatus.run.get(mid); int ret = comparator.compare(medianOfMedian, value); if (ret < 0) { right = mid - 1; } else if (ret > 0) { left = mid + 1; } else { break; } } // check predicate while (left <= mid) { value = runStatus.run.get(mid); int ret = comparator.compare(medianOfMedian, value); if (ret < 0) mid--; else break; } } catch (IOException e) { throw new IllegalStateException("IOException in findBoundary", e); } finally { runStatus.run.close(); } runStatus.boundary = mid + 1; } private class Median implements Comparable<Median> { private SortedRunStatus runStatus; private int offset; private Item value; public Median(SortedRunStatus runStatus, int offset, Item value) { this.runStatus = runStatus; this.offset = offset; this.value = value; } @Override public int compareTo(Median o) { return comparator.compare(value, o.value); } @Override public String toString() { return "#" + runStatus.run + ", offset=" + offset + ", median=" + value; } } private static class SortedRunStatus { private SortedRun run; private int left; private int right; private int boundary; private Item median; // median binary search range private int medianLeft; private int medianRight; public SortedRunStatus(SortedRun run) { this.run = run; this.left = 0; this.right = run.length() - 1; this.medianLeft = 0; this.medianRight = this.right; } public SortedRunStatus(SortedRun run, int left, int right) { this.run = run; this.left = left; this.right = right; this.medianLeft = left; this.medianRight = right; } @Override public String toString() { return "#" + run + ", boundary=" + boundary + ", median=" + median; } } }