Skip to content

Instantly share code, notes, and snippets.

@glossawy
Last active August 3, 2020 03:39
Show Gist options
  • Save glossawy/2d622f2eb32b1429539aab566eb3d80b to your computer and use it in GitHub Desktop.
Save glossawy/2d622f2eb32b1429539aab566eb3d80b to your computer and use it in GitHub Desktop.
Vanilla JS implementation of QuickSort that attempts to mimick ramda somewhat
/*
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,
]))
/*
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