I (roughly) defined (almost) every array method using recursion 😂
YCM Jason
Posted on April 30, 2021
So... I have decided to define every array methods using recursion. (I haven't really tested all of them... so there might be some errors.)
Also, I only defined the "essence" of most methods. Didn't follow the complete spec for most.
Why?
Why not?
How is this useful?
It is not.
Array.from
Array.from
takes in two kinds of objects.
- Array-like objects that have a
length
property with zero-indexed elements - Iterable objects that have an iterator at
[Symbol.iterator]
const arrayFrom = (o) => {
if ('length' in o) return arrayFromArrayLike(o)
if (Symbol.iterator in o) return arrayFromIterator(o[Symbol.iterator]())
return []
}
const arrayFromArrayLike = (arrayLikeObject) => {
if (arrayLikeObject.length <= 0) return []
return [
...arrayFromArrayLike({
...arrayLikeObject,
length: arrayLikeObject.length - 1,
}),
arrayLikeObject[arrayLikeObject.length - 1],
]
}
const arrayFromIterator = (iterator) => {
const { value, done } = iterator.next()
if (done) return []
return [value, ...arrayFromIterator(iterator)]
}
Note: we ignore the 2nd and 3rd arguments of Array.from
. (see docs)
Array.of
const arrayOf = (...xs) => {
if (xs.length <= 0) return []
const [head, ...tail] = xs
return [head, ...arrayOf(...tail)]
}
Array.prototype.concat
const concat = (xs, ...arrays) => {
if (arrays.length <= 0) return xs
const [ys, ...restArrays] = arrays
if (ys.length <= 0) return concat(xs, ...restArrays)
const [head, ...tail] = ys
return concat([...xs, head], tail, ...restArrays)
}
Note: assuming concat only takes in 2 parameters
Array.prototype.entries
function* entries(xs, i = 0) {
if (xs.length <= 0) return
const [head, ...tail] = xs
yield [i, head]
yield* entries(tail, i + 1)
}
note: i
does not exist in Array.prototype.entries
Array.prototype.every
const every = (xs, predicate) => {
if (xs.length <= 0) return true
const [head, ...tail] = xs
return predicate(head) && every(tail, predicate)
}
Array.prototype.fill
const fill = (xs, k, start = 0, end = xs.length + 1) => {
if (xs.length <= 0) return []
const [head, ...tail] = xs
if (start > 0) return [head, ...fill(tail, k, start - 1, end - 1)]
return fillFromStart([head, ...tail], k, end)
}
const fillFromStart = (xs, k, end = xs.length + 1) => {
if (xs.length <= 0) return []
if (end <= 0) return xs
const [_, ...tail] = xs
return [k, ...fillFromStart(tail, k, end - 1)]
}
Array.prototype.filter
const filter = (xs, predicate) => {
if (xs.length <= 0) return []
const [head, ...tail] = xs
return [
...(predicate(head) ? [head] : []),
...filter(tail, predicate)
]
}
Array.prototype.find
const find = (xs, predicate) => {
if (xs.length <= 0) return undefined
const [head, ...tail] = xs
if (predicate(head)) return head
return find(tail, predicate)
}
Array.prototype.findIndex
const findIndex - (xs, predicate) => {
if (xs.length <= 0) return -1
const [head, ...tail] = xs
if (predicate(head)) return 0
return findIndex(tail, predicate) + 1
}
Array.prototype.forEach
const forEach = (xs, fn) => {
if (xs.length <= 0) return
const [head, ...tail] = xs
fn(head)
forEach(tail, fn)
}
notes: ignoring index
Array.prototype.includes
const includes = (xs, predicate) => {
if (xs.length <= 0) return false
const [head, ...tail] = xs
const predicate(head) || includes(tail, predicate)
}
Array.prototype.indexOf
const indexOf = (xs, x) => {
if (xs.length <= 0) return -1
const [head, ...tail] = xs
if (head === x) return 0
return indexOf(tail, x) + 1
}
Array.prototype.join
const join = (xs, separator = ',') => {
if (xs.length <= 0) return ''
const [head, ...tail] = xs
return `${head}${separator}${join(tail, separator)}`
}
Array.prototype.map
const map = (xs, fn) => {
if (xs.length <= 0) return []
const [head, ...tail] = xs
return [fn(head), ...map(tail, fn)]
}
Array.prototype.reduce
const reduce = (xs, fn, acc) => {
if (xs.length <= 0) {
if (typeof acc === 'undefined') {
throw new TypeError('Reduce of empty array with no initial value')
} else {
return acc
}
}
const [head, ...tail] = xs
if (typeof acc === 'undefined') return reduce(tail, fn, head)
return reduce(tail, fn, fn(acc, head))
}
Array.prototype.reverse
const reverse = (xs) => {
if (xs.length <= 0) return []
const [head, ...tail] = xs
return [...reverse(xs), head]
}
Array.prototype.slice
Slice is a surprisingly annoying one to define. It needs to deal with negative indices, but you can't simply "mod" the numbers...
const slice = (xs, start = 0, end = xs.length) => {
if (xs.length <= 0) return []
if (start < 0) return slice(xs, Math.max(0, start + xs.length), end)
if (end < 0) return slice(xs, start, Math.max(0, end + xs.length))
const [head, ...tail] = xs
if (end <= start) return []
if (start > 0) return slice(tail, start - 1, end - 1)
return [head, ...slice(tail, 0, end - 1)]
}
Array.prototype.some
const some = (xs, predicate) => {
if (xs.length <= 0) return false
const [head, ...tail] = xs
return predicate(head) || some(tail, predicate)
}
💖 💪 🙅 🚩
YCM Jason
Posted on April 30, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.