Skip to content

Commit

Permalink
- change of algorithm for sorting - useQuickSort and useBrowserSort …
Browse files Browse the repository at this point in the history
…now just set the sort mechanism, QuickSort is default - added static Enumerable.sort(arr,comparer) which uses Quicksort to sort an array in place
  • Loading branch information
Siderite committed Feb 6, 2020
1 parent 9c089bc commit 35403ba
Show file tree
Hide file tree
Showing 13 changed files with 268 additions and 152 deletions.
129 changes: 80 additions & 49 deletions LInQer.OrderedEnumerable.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/// <reference path="./LInQer.Slim.ts" />

namespace Linqer {

export interface Enumerable extends Iterable<any> {
Expand Down Expand Up @@ -30,19 +29,25 @@ namespace Linqer {
return new OrderedEnumerable(this, keySelector, false);
};

/// use QuickSort for ordering (default) if take, skip, takeLast, skipLast are used
/// use QuickSort for ordering (default). Recommended when take, skip, takeLast, skipLast are used after orderBy
Enumerable.prototype.useQuickSort = function (): Enumerable {
this._useQuickSort = true;
return this;
};

/// use the default browser sort implementation for ordering at all times
/// removes QuickSort optimization when take, skip, takeLast, skipLast are used
Enumerable.prototype.useBrowserSort = function (): Enumerable {
this._useQuickSort = false;
return this;
};


//static sort: (arr: any[], comparer?: IComparer) => void;
Enumerable.sort = function(arr:any[], comparer:IComparer=_defaultComparer):any[] {
_quicksort(arr,0,arr.length-1,comparer,0,Number.MAX_SAFE_INTEGER);
return arr;
}

enum RestrictionType {
skip,
skipLast,
Expand All @@ -68,9 +73,8 @@ namespace Linqer {
const arr = Array.from(this._src);
const { startIndex, endIndex } = this.getStartAndEndIndexes(self._restrictions, arr.length);
if (startIndex < endIndex) {
// only use QuickSort as an optimization for take, skip, takeLast, skipLast
const sort: (item1: any, item2: any) => void = this._useQuickSort && this._restrictions.length
? (a, c) => _quickSort(a, 0, a.length - 1, c, startIndex, endIndex)
const sort: (item1: any, item2: any) => void = this._useQuickSort
? (a, c) => _quicksort(a, 0, a.length - 1, c, startIndex, endIndex)
: (a, c) => a.sort(c);
const sortFunc = this.generateSortFunc(self._keySelectors);
sort(arr, sortFunc);
Expand All @@ -87,23 +91,23 @@ namespace Linqer {
};
}

private generateSortFunc(selectors: { keySelector: ISelector, ascending: boolean }[]): (i1: any, i2: any)=> number {
const comparers = selectors.map(s=>{
private generateSortFunc(selectors: { keySelector: ISelector, ascending: boolean }[]): (i1: any, i2: any) => number {
const comparers = selectors.map(s => {
const f = s.keySelector;
const comparer = (i1:any,i2:any)=> {
const comparer = (i1: any, i2: any) => {
const k1 = f(i1);
const k2 = f(i2);
if (k1>k2) return 1;
if (k1<k2) return -1;
if (k1 > k2) return 1;
if (k1 < k2) return -1;
return 0;
};
return s.ascending
? comparer
: (i1:any,i2:any)=> -comparer(i1,i2);
: (i1: any, i2: any) => -comparer(i1, i2);
});
return (i1: any, i2: any) => {
for (const comparer of comparers) {
const v = comparer(i1,i2);
for (let i=0; i<comparers.length; i++) {
const v = comparers[i](i1, i2);
if (v) return v;
}
return 0;
Expand Down Expand Up @@ -167,47 +171,74 @@ namespace Linqer {
function _ensureFunction(f: Function): void {
if (!f || typeof f !== 'function') throw new Error('the argument needs to be a function!');
}
function _swapArrayItems(array: any[], leftIndex: number, rightIndex: number): void {
const temp = array[leftIndex];
array[leftIndex] = array[rightIndex];
array[rightIndex] = temp;
}
function _partition(items: any[], left: number, right: number, comparer: IComparer) {
const pivot = items[(right + left) >> 1];
while (left <= right) {
while (comparer(items[left], pivot) < 0) {
left++;


function _insertionsort(arr: any[], leftIndex: number, rightIndex: number, comparer: IComparer) {
for (let j = leftIndex; j <= rightIndex; j++) {
// Invariant: arr[:j] contains the same elements as
// the original slice arr[:j], but in sorted order.
const key = arr[j];
let i = j - 1;
while (i >= leftIndex && comparer(arr[i], key) > 0) {
arr[i + 1] = arr[i];
i--;
}
while (comparer(items[right], pivot) > 0) {
right--;
arr[i + 1] = key;
}
}

/* This QuickSort requires O(Log n) auxiliary space in worst case. */
function _quicksort(arr: any[], leftIndex: number, rightIndex: number, comparer: IComparer = _defaultComparer, minIndex: number, maxIndex: number) {
if (minIndex > rightIndex || maxIndex < leftIndex) return;
const delta = rightIndex - leftIndex;
if (delta > 0 && delta < 30) {
_insertionsort(arr, leftIndex, rightIndex, comparer);
return;
}
while (leftIndex < rightIndex) {
/* pi is partitioning index, arr[p] is now at right place */
if (minIndex > rightIndex || maxIndex < leftIndex) break;
const pi = _partition(arr, leftIndex, rightIndex, comparer);

// If left part is smaller, then recur for left
// part and handle right part iteratively
if (pi - leftIndex < rightIndex - pi) {
_quicksort(arr, leftIndex, pi - 1, comparer, minIndex, maxIndex);
leftIndex = pi + 1;
}
if (left < right) {
_swapArrayItems(items, left, right);
left++;
right--;
} else {
if (left === right) return left + 1;
// Else recur for right part
else {
_quicksort(arr, pi + 1, rightIndex, comparer, minIndex, maxIndex);
rightIndex = pi - 1;
}
}
return left;
}
function _quickSort(items: any[], left: number, right: number, comparer: IComparer = _defaultComparer, minIndex: number = 0, maxIndex: number = Number.MAX_SAFE_INTEGER) {
if (!items.length) return items;

const partitions = [];
partitions.push([left, right]);
let partitionIndex = 0;
while (partitionIndex < partitions.length) {
[left, right] = partitions[partitionIndex];
const index = _partition(items, left, right, comparer); //index returned from partition
if (left < index - 1 && index - 1 >= minIndex) { //more elements on the left side of the pivot
partitions.push([left, index - 1]);
}
if (index < right && index < maxIndex) { //more elements on the right side of the pivot
partitions.push([index, right]);

/* This function takes last element as pivot, places
the pivot element at its correct position in sorted
array, and places all smaller (smaller than pivot)
to left of pivot and all greater elements to right
of pivot */
function _partition(arr: any[], leftIndex: number, rightIndex: number, comparer: IComparer = _defaultComparer) {
const pivot = arr[rightIndex]; // pivot
let i = (leftIndex - 1); // Index of smaller element

for (let j = leftIndex; j <= rightIndex - 1; j++) {
// If current element is smaller than or
// equal to pivot
if (comparer(arr[j], pivot) <= 0) {
i++; // increment index of smaller element
_swapArrayItems(arr, i, j);
}
partitionIndex++;
}
return items;
_swapArrayItems(arr, i + 1, rightIndex);
return (i + 1);
}


function _swapArrayItems(array: any[], leftIndex: number, rightIndex: number): void {
const temp = array[leftIndex];
array[leftIndex] = array[rightIndex];
array[rightIndex] = temp;
}
}
4 changes: 4 additions & 0 deletions LInQer.Slim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ namespace Linqer {
_count: null | (() => number);
_tryGetAt: null | ((index: number) => { value: any } | null);
_wasIterated: boolean;

/// sort an array in place
static sort: (arr: any[], comparer?: IComparer) => any[];

constructor(src: IterableType) {
_ensureIterable(src);
this._src = src;
Expand Down
108 changes: 65 additions & 43 deletions LInQer.all.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion LInQer.all.js.map

Large diffs are not rendered by default.

Loading

0 comments on commit 35403ba

Please sign in to comment.