Last active
August 3, 2020 03:39
-
-
Save glossawy/2d622f2eb32b1429539aab566eb3d80b to your computer and use it in GitHub Desktop.
Vanilla JS implementation of QuickSort that attempts to mimick ramda somewhat
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Vanilla js equivalent of a ramda quicksort (using ES6 features) | |
Note: | |
1. I use arrays and not lists | |
2. I do not implement the placeholder helper, not difficult to add but requires more thought | |
3. Opted for a curried ifThenElse function | |
4. In some places I use ES6 features to avoid further recursion, though its possible to encompass | |
the recursion in a foldl/foldr | |
5. I implement pipe to match the ramda example | |
*/ | |
const length = () => (xs) => xs.length | |
const slice = (a,b) => (xs) => xs.slice(a,b) | |
const lt = (n) => (x) => x < n | |
const eq = (n) => (x) => x === n | |
const isEmpty = (xs) => xs.length === 0 | |
// If-Expression as a Function | |
const ifThenElse = (cond, ifTrue, ifFalse) => (x) => cond(x) ? ifTrue(x) : ifFalse(x) | |
const head = (xs) => xs[0] | |
const tail = ifThenElse((xs) => xs.length === 1, () => [], slice(1)) | |
const identity = (x) => x // I combinator | |
const constant = (x) => () => x // K combinator | |
// Being lazy here and using ES6 syntax instead of proper recursion | |
const concat = (x, xs) => [x, ...xs] | |
const append = (xs, ys) => [...xs, ...ys] | |
// Fold Left and Fold Right reductions, with variants curried on the final parameter | |
const foldl = (op, b, xs) => ifThenElse(isEmpty, constant(b), (xs) => foldl(op, op(head(xs), b), tail(xs)))(xs) | |
const foldr = (op, b, xs) => ifThenElse(isEmpty, constant(b), (xs) => op(head(xs), foldr(op, b, tail(xs))))(xs) | |
const _foldl = (op, b) => (xs) => foldl(op, b, xs) | |
const _foldr = (op, b) => (xs) => foldr(op, b, xs) | |
// Select only elements matching the predicate `p` | |
const filter = (p, xs) => foldl( | |
(e, a) => ifThenElse(p, () => concat(e, a), constant(a))(e), | |
[], | |
xs | |
) | |
// Reverse by folding | |
const reverse = _foldl(concat, []) | |
// Composition and Pipeline (pipeline is reversed composition) | |
const _compose = (f, g) => (...args) => f(g(...args)) | |
const compose = (...fns) => foldr(_compose, identity, fns) | |
const pipe = (...fns) => compose(...reverse(fns)) | |
// Vanilla JS quicksort that attempts to mimick ramda | |
const qsort = (xs) => { | |
const pivot = head(xs) | |
const lesser = filter((x) => x < pivot, tail(xs)) | |
const greater = filter((x) => x >= pivot, tail(xs)) | |
return ifThenElse( | |
pipe(length(), lt(2)), | |
identity, | |
() => append(qsort(lesser), concat(pivot, qsort(greater))) | |
)(xs) | |
} | |
console.log(qsort([ | |
11, | |
6, | |
15, | |
5, | |
12, | |
4, | |
3, | |
1, | |
2, | |
13, | |
7, | |
14, | |
9, | |
8, | |
10, | |
])) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Vanilla js equivalent of a ramda quicksort (using ES6 features) + placeholder helper | |
Note: | |
1. I use arrays and not lists | |
2. I implement the placheholder helper a bit sloppily (ramdas is much better, perf and semantics wise) | |
3. In some places I use ES6 features to avoid further recursion, though its possible to encompass | |
the recursion in a foldl/foldr | |
4. I implement pipe to match the ramda example | |
*/ | |
const placeholder = () => ({ '@@functional/placeholder': true }) | |
const unit = () => ({ '@@functional/unit': true }) | |
const __ = placeholder | |
const _ = placeholder() | |
const unitval = unit() | |
const isUnit = (x) => x != null && typeof x === 'object' && x['@@functional/unit'] === true | |
const isPlaceholder = (x) => x != null && typeof x === 'object' && x['@@functional/placeholder'] === true | |
/* | |
Quick and dirty N-arity currying using placeholders or partial application | |
*/ | |
const curryN = (arity, received, fn) => { | |
const composite = [].concat(received) | |
const placeholderIxs = [] | |
received.forEach((x, i) => { | |
if (isPlaceholder(x)) placeholderIxs.push(i) | |
}) | |
const remaining = () => arity - composite.reduce((a, e, i) => isPlaceholder(e) || i >= arity ? a : a + 1, 0) | |
return (...args) => { | |
let current = [].concat(composite) | |
let placeholders = [].concat(placeholderIxs) | |
args.forEach((x) => { | |
if (placeholderIxs.length > 0) { | |
current[placeholders.shift()] = x | |
} else { | |
current.push(x) | |
} | |
}, []) | |
const remaining = arity - current.reduce((a, e, i) => isPlaceholder(e) || i >= arity ? a : a + 1, 0) | |
return remaining <= 0 ? fn.apply(this, current) : curryN(arity, current, fn) | |
} | |
} | |
const curry1 = (fn) => curryN(1, [], fn) /* Placeholder unary */ | |
const curry2 = (fn) => curryN(2, [], fn) /* Binary currying */ | |
const curry3 = (fn) => curryN(3, [], fn) /* Ternary currying */ | |
const identity = curry1((x) => x) // I combinator | |
const constant = curry1((x) => () => x) // K combinator | |
// If-Expression as a Function | |
const ifThenElse = curry3((cond, ifTrue, ifFalse) => curry1((x) => cond(x) ? ifTrue(x) : ifFalse(x))) | |
const _ifThenElse = curry3((c, t, f) => ifThenElse(constant(c), constant(t), constant(f))(unitval)) | |
const not = _ifThenElse(_, false, true) | |
const and = _ifThenElse(_, _, false) | |
const or = _ifThenElse(_, true, _) | |
const lt = curry2((x, n) => x < n) | |
const eq = curry2((x, n) => x === n) | |
const gt = curry2((x, n) => not(or(lt(x, n), eq(x, n)))) | |
const gte = curry2((x, n) => or(gt(x, n), eq(x, n))) | |
const lte = curry2((x, n) => or(lt(x, n), eq(x, n))) | |
const length = curry1((xs) => xs.length) | |
const slice = curry3((a, b, xs) => xs.slice(a, b)) | |
const sliceFrom = slice(_, undefined, _) | |
const sliceTo = slice(0, _, _) | |
const isEmpty = curry1((xs) => xs.length === 0) | |
const head = curry1((xs) => xs[0]) | |
const tail = ifThenElse((xs) => xs.length === 1, () => [], sliceFrom(1)) | |
// Being lazy here and using ES6 syntax instead of proper recursion | |
const concat = curry2((x, xs) => [x, ...xs]) | |
const append = curry2((xs, ys) => [...xs, ...ys]) | |
// Fold Left and Fold Right reductions, with variants curried on the final parameter | |
const foldl = curry3((op, b, xs) => ifThenElse(isEmpty, constant(b), (xs) => foldl(op, op(head(xs), b), tail(xs)))(xs)) | |
const foldr = curry3((op, b, xs) => ifThenElse(isEmpty, constant(b), (xs) => op(head(xs), foldr(op, b, tail(xs))))(xs)) | |
// Select only elements matching the predicate `p` | |
const filter = (p, xs) => foldl( | |
(e, a) => ifThenElse(p, concat(_, a), constant(a))(e), | |
[], | |
xs | |
) | |
// Reverse by folding | |
const reverse = foldl(concat, [], _) | |
// Composition and Pipeline (pipeline is reversed composition) | |
const _compose = curry2((f, g) => (...args) => f(g(...args))) | |
const compose = (...fns) => foldr(_compose, identity, fns) | |
const pipe = (...fns) => compose(...reverse(fns)) | |
const partition = curry2((p, xs) => [filter(p, xs), filter(compose(not, p), xs)]) | |
// Vanilla JS quicksort that attempts to mimick ramda | |
const qsort = (xs) => { | |
const pivot = head(xs) | |
const [lesser, greater] = partition(lt(_, pivot), tail(xs)) | |
return ifThenElse( | |
pipe(length(_), lt(_, 2)), | |
identity, | |
() => append(qsort(lesser), concat(pivot, qsort(greater))) | |
)(xs) | |
} | |
console.log(qsort([ | |
11, | |
6, | |
15, | |
5, | |
12, | |
4, | |
3, | |
1, | |
2, | |
13, | |
7, | |
14, | |
9, | |
8, | |
10, | |
])) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment