Dart API Referencedart:coreimplDualPivotQuicksort

DualPivotQuicksort class

Dual-Pivot Quicksort algorithm.

This class implements the dual-pivot quicksort algorithm as presented in Vladimir Yaroslavskiy's paper.

Some improvements have been copied from Android's implementation.

class DualPivotQuicksort {
  // When a list has less then [:_INSERTION_SORT_THRESHOLD:] elements it will
  // be sorted by an insertion sort.
  static const int _INSERTION_SORT_THRESHOLD = 32;

  /**
   * Sorts all elements of the given list [:a:] according to the given
   * [:compare:] function.
   *
   * The [:compare:] function takes two arguments [:x:] and [:y:] and returns
   *  -1 if [:x < y:],
   *   0 if [:x == y:], and
   *   1 if [:x > y:].
   *
   * The function's behavior must be consistent. It must not return different
   * results for the same values.
   */
  static void sort(List a, int compare(a, b)) {
    _doSort(a, 0, a.length - 1, compare);
  }

  /**
   * Sorts all elements in the range [:from:] (inclusive) to [:to:] (exclusive)
   * of the given list [:a:].
   *
   * If the given range is invalid an "OutOfRange" error is raised.
   * TODO(floitsch): do we want an error?
   *
   * See [:sort:] for requirements of the [:compare:] function.
   */
  static void sortRange(List a, int from, int to, int compare(a, b)) {
    if ((from < 0) || (to > a.length) || (to < from)) {
      throw "OutOfRange";
    }
    _doSort(a, from, to - 1, compare);
  }

  /**
   * Sorts the list in the interval [:left:] to [:right:] (both inclusive).
   */
  static void _doSort(List a, int left, int right, int compare(a, b)) {
    if ((right - left) <= _INSERTION_SORT_THRESHOLD) {
      insertionSort_(a, left, right, compare);
    } else {
      _dualPivotQuicksort(a, left, right, compare);
    }
  }

  static void insertionSort_(List a, int left, int right, int compare(a, b)) {
    for (int i = left + 1; i <= right; i++) {
      var el = a[i];
      int j = i;
      while ((j > left) && (compare(a[j - 1], el) > 0)) {
        a[j] = a[j - 1];
        j--;
      }
      a[j] = el;
    }
  }

  static void _dualPivotQuicksort(List a,
                                  int left, int right,
                                  int compare(a, b)) {
    assert(right - left > _INSERTION_SORT_THRESHOLD);

    // Compute the two pivots by looking at 5 elements.
    int sixth = (right - left + 1) ~/ 6;
    int index1 = left + sixth;
    int index5 = right - sixth;
    int index3 = (left + right) ~/ 2;  // The midpoint.
    int index2 = index3 - sixth;
    int index4 = index3 + sixth;

    var el1 = a[index1];
    var el2 = a[index2];
    var el3 = a[index3];
    var el4 = a[index4];
    var el5 = a[index5];

    // Sort the selected 5 elements using a sorting network.
    if (compare(el1, el2) > 0) { var t = el1; el1 = el2; el2 = t; }
    if (compare(el4, el5) > 0) { var t = el4; el4 = el5; el5 = t; }
    if (compare(el1, el3) > 0) { var t = el1; el1 = el3; el3 = t; }
    if (compare(el2, el3) > 0) { var t = el2; el2 = el3; el3 = t; }
    if (compare(el1, el4) > 0) { var t = el1; el1 = el4; el4 = t; }
    if (compare(el3, el4) > 0) { var t = el3; el3 = el4; el4 = t; }
    if (compare(el2, el5) > 0) { var t = el2; el2 = el5; el5 = t; }
    if (compare(el2, el3) > 0) { var t = el2; el2 = el3; el3 = t; }
    if (compare(el4, el5) > 0) { var t = el4; el4 = el5; el5 = t; }

    var pivot1 = el2;
    var pivot2 = el4;

    // el2 and el4 have been saved in the pivot variables. They will be written
    // back, once the partioning is finished.
    a[index1] = el1;
    a[index3] = el3;
    a[index5] = el5;

    a[index2] = a[left];
    a[index4] = a[right];

    int less = left + 1;    // First element in the middle partition.
    int great = right - 1;  // Last element in the middle partition.

    bool pivots_are_equal = (compare(pivot1, pivot2) == 0);
    if (pivots_are_equal) {
      var pivot = pivot1;
      // Degenerated case where the partioning becomes a dutch national flag
      // problem.
      //
      // [ |  < pivot  | == pivot | unpartitioned | > pivot  | ]
      //  ^             ^          ^             ^            ^
      // left         less         k           great         right
      //
      // a[left] and a[right] are undefined and are filled after the
      // partitioning.
      //
      // Invariants:
      //   1) for x in ]left, less[ : x < pivot.
      //   2) for x in [less, k[ : x == pivot.
      //   3) for x in ]great, right[ : x > pivot.
      for (int k = less; k <= great; k++) {
        var ak = a[k];
        int comp = compare(ak, pivot);
        if (comp == 0) continue;
        if (comp < 0) {
          if (k != less) {
            a[k] = a[less];
            a[less] = ak;
          }
          less++;
        } else {
          // comp > 0.
          //
          // Find the first element <= pivot in the range [k - 1, great] and
          // put [:ak:] there. We know that such an element must exist:
          // When k == less, then el3 (which is equal to pivot) lies in the
          // interval. Otherwise a[k - 1] == pivot and the search stops at k-1.
          // Note that in the latter case invariant 2 will be violated for a
          // short amount of time. The invariant will be restored when the
          // pivots are put into their final positions.
          while (true) {
            comp = compare(a[great], pivot);
            if (comp > 0) {
              great--;
              // This is the only location in the while-loop where a new
              // iteration is started.
              continue;
            } else if (comp < 0) {
              // Triple exchange.
              a[k] = a[less];
              a[less++] = a[great];
              a[great--] = ak;
              break;
            } else {
              // comp == 0;
              a[k] = a[great];
              a[great--] = ak;
              // Note: if great < k then we will exit the outer loop and fix
              // invariant 2 (which we just violated).
              break;
            }
          }
        }
      }
    } else {
      // We partition the list into three parts:
      //  1. < pivot1
      //  2. >= pivot1 && <= pivot2
      //  3. > pivot2
      //
      // During the loop we have:
      // [ | < pivot1 | >= pivot1 && <= pivot2 | unpartitioned  | > pivot2  | ]
      //  ^            ^                        ^              ^             ^
      // left         less                     k              great        right
      //
      // a[left] and a[right] are undefined and are filled after the
      // partitioning.
      //
      // Invariants:
      //   1. for x in ]left, less[ : x < pivot1
      //   2. for x in [less, k[ : pivot1 <= x && x <= pivot2
      //   3. for x in ]great, right[ : x > pivot2
      for (int k = less; k <= great; k++) {
        var ak = a[k];
        int comp_pivot1 = compare(ak, pivot1);
        if (comp_pivot1 < 0) {
          if (k != less) {
            a[k] = a[less];
            a[less] = ak;
          }
          less++;
        } else {
          int comp_pivot2 = compare(ak, pivot2);
          if (comp_pivot2 > 0) {
            while (true) {
              int comp = compare(a[great], pivot2);
              if (comp > 0) {
                great--;
                if (great < k) break;
                // This is the only location inside the loop where a new
                // iteration is started.
                continue;
              } else {
                // a[great] <= pivot2.
                comp = compare(a[great], pivot1);
                if (comp < 0) {
                  // Triple exchange.
                  a[k] = a[less];
                  a[less++] = a[great];
                  a[great--] = ak;
                } else {
                  // a[great] >= pivot1.
                  a[k] = a[great];
                  a[great--] = ak;
                }
                break;
              }
            }
          }
        }
      }
    }

    // Move pivots into their final positions.
    // We shrunk the list from both sides (a[left] and a[right] have
    // meaningless values in them) and now we move elements from the first
    // and third partition into these locations so that we can store the
    // pivots.
    a[left] = a[less - 1];
    a[less - 1] = pivot1;
    a[right] = a[great + 1];
    a[great + 1] = pivot2;

    // The list is now partitioned into three partitions:
    // [ < pivot1   | >= pivot1 && <= pivot2   |  > pivot2   ]
    //  ^            ^                        ^             ^
    // left         less                     great        right

    // Recursive descent. (Don't include the pivot values.)
    _doSort(a, left, less - 2, compare);
    _doSort(a, great + 2, right, compare);

    if (pivots_are_equal) {
      // All elements in the second partition are equal to the pivot. No
      // need to sort them.
      return;
    }

    // In theory it should be enough to call _doSort recursively on the second
    // partition.
    // The Android source however removes the pivot elements from the recursive
    // call if the second partition is too large (more than 2/3 of the list).
    if (less < index1 && great > index5) {
      while (compare(a[less], pivot1) == 0) { less++; }
      while (compare(a[great], pivot2) == 0) { great--; }

      // Copy paste of the previous 3-way partitioning with adaptions.
      //
      // We partition the list into three parts:
      //  1. == pivot1
      //  2. > pivot1 && < pivot2
      //  3. == pivot2
      //
      // During the loop we have:
      // [ == pivot1 | > pivot1 && < pivot2 | unpartitioned  | == pivot2 ]
      //              ^                      ^              ^
      //            less                     k              great
      //
      // Invariants:
      //   1. for x in [ *, less[ : x == pivot1
      //   2. for x in [less, k[ : pivot1 < x && x < pivot2
      //   3. for x in ]great, * ] : x == pivot2
      for (int k = less; k <= great; k++) {
        var ak = a[k];
        int comp_pivot1 = compare(ak, pivot1);
        if (comp_pivot1 == 0) {
          if (k != less) {
            a[k] = a[less];
            a[less] = ak;
          }
          less++;
        } else {
          int comp_pivot2 = compare(ak, pivot2);
          if (comp_pivot2 == 0) {
            while (true) {
              int comp = compare(a[great], pivot2);
              if (comp == 0) {
                great--;
                if (great < k) break;
                // This is the only location inside the loop where a new
                // iteration is started.
                continue;
              } else {
                // a[great] < pivot2.
                comp = compare(a[great], pivot1);
                if (comp < 0) {
                  // Triple exchange.
                  a[k] = a[less];
                  a[less++] = a[great];
                  a[great--] = ak;
                } else {
                  // a[great] == pivot1.
                  a[k] = a[great];
                  a[great--] = ak;
                }
                break;
              }
            }
          }
        }
      }
      // The second partition has now been cleared of pivot elements and looks
      // as follows:
      // [  *  |  > pivot1 && < pivot2  | * ]
      //        ^                      ^
      //       less                  great
      // Sort the second partition using recursive descent.
      _doSort(a, less, great, compare);
    } else {
      // The second partition looks as follows:
      // [  *  |  >= pivot1 && <= pivot2  | * ]
      //        ^                        ^
      //       less                    great
      // Simply sort it by recursive descent.
      _doSort(a, less, great, compare);
    }
  }
}

Static Methods

void sort(List a, int compare(a, b)) #

Sorts all elements of the given list a according to the given compare function.

The compare function takes two arguments x and y and returns -1 if x < y, 0 if x == y, and 1 if x > y.

The function's behavior must be consistent. It must not return different results for the same values.

static void sort(List a, int compare(a, b)) {
  _doSort(a, 0, a.length - 1, compare);
}

void sortRange(List a, int from, int to, int compare(a, b)) #

Sorts all elements in the range from (inclusive) to to (exclusive) of the given list a.

If the given range is invalid an "OutOfRange" error is raised. TODO(floitsch): do we want an error?

See sort for requirements of the compare function.

static void sortRange(List a, int from, int to, int compare(a, b)) {
  if ((from < 0) || (to > a.length) || (to < from)) {
    throw "OutOfRange";
  }
  _doSort(a, from, to - 1, compare);
}

void insertionSort_(List a, int left, int right, int compare(a, b)) #

static void insertionSort_(List a, int left, int right, int compare(a, b)) {
  for (int i = left + 1; i <= right; i++) {
    var el = a[i];
    int j = i;
    while ((j > left) && (compare(a[j - 1], el) > 0)) {
      a[j] = a[j - 1];
      j--;
    }
    a[j] = el;
  }
}