Skip to content

Instantly share code, notes, and snippets.

@moatorres
Last active June 27, 2021 13:51
Show Gist options
  • Save moatorres/69a0242067934f64e78e5e264b0eb6f2 to your computer and use it in GitHub Desktop.
Save moatorres/69a0242067934f64e78e5e264b0eb6f2 to your computer and use it in GitHub Desktop.
Lazy-list in JS/Node (with ES6 classes)
import { List } from './List'
// generator #1
function* idMaker() {
let index = 1
while (true && index < this.length) yield index++
}
// generator #2
function* fibonacci() {
let x = 1
let y = 1
yield* [0, x, y]
while (true) {
let next = x + y
yield next
x = y
y = next
}
}
let lista = new List(idMaker, 10)
let outra = new List(fibonacci, 10)
// console.log(lista)
console.log('toArray:', lista.toArray())
console.log('head:', lista.head())
console.log('headOrUndefined:', lista.headOrUndefined())
console.log('headOrCompute:', lista.headOrCompute(() => 'bla'))
console.log('headOr:', lista.take(5).headOr('Huhuhu'))
console.log('sum:', lista.take(5).sum())
console.log('toDictionary:', lista.take(5).toDictionary())
console.log('ofType:', lista.take(5).ofType('number').toArray())
console.log('instanceOf:', lista.take(5).instanceOf(List).toArray())
console.log('some:', lista.take(5).some((a) => a > 3))
console.log('some:', lista.take(5).some((a) => a > 6))
console.log('all:', lista.take(5).all((a) => typeof a !== 'number'))
console.log('where:', lista.take(5).where((a) => a > 2).toArray())
console.log('filter:',lista.take(5).filter((a) => a < 3 && a > 0).toArray())
console.log('tail:', lista.take(5).tail().toArray())
console.log('empty:', lista.take(5).empty())
console.log('map:', lista.take(5).map((a) => a * 100).toArray())
console.log('integers:', lista.integers().take(5).toArray())
console.log('drop:', lista.take(5).drop(2).toArray())
console.log('range:', lista.range(1, 10).toArray())
console.log('from:', lista.from([{ nome: 'Moka' }, { nome: 'Floca' }]).toArray())
console.log('concat:', lista.concat([50, 25]).toArray())
console.log('toString:', lista.toString())
console.log('inspect:', lista.inspect())
console.log('reduce:', lista.reduce((acc, v) => acc + v * 2, 0))
console.log('scan:', lista.take(3).scan((v) => v * 2, 2).toArray())
console.log(
'toDictionary(key):',
lista
.from([
{ id: '123abc', nome: 'Moka' },
{ id: '123def', nome: 'Floca' },
])
.toDictionary('id')
)
console.log(
'of:',
lista
.of({ id: 1, nome: 'John' }, { id: 2, nome: 'Jane' })
.map((v) => {
return { ...v, nome: v.nome + ' Doe' }
})
.toArray()
)
// a Haskell-like lazy-list
// adapted from this awesome gist: https://gist.github.com/gvergnaud/6e9de8e06ef65e65f18dbd05523c7ca9
export class List {
constructor(generator, length) {
this[Symbol.iterator] = generator
this.length = length
}
// should be private
generator() {
return this[Symbol.iterator]()
}
// should be private
flattenArgs(args) {
return args.reduce((acc, curr) => {
return Array.isArray(curr) ? [...acc, ...curr] : [...acc, curr]
}, [])
}
static of(...args) {
return new List(function* () {
return yield* args
}, args.length)
}
static from(iterable) {
return iterable
? new List(function* () {
yield* iterable
}, iterable.length)
: this.empty()
}
static range(start, end, step = 1) {
return new List(function* () {
let i = start
while (i <= end) {
yield i
i += step
}
}, Math.floor((end - start + 1) / step))
}
static empty() {
return new List(function* () {}, 0)
}
static get integers() {
return this.range(0, Infinity)
}
map(fn) {
const generator = this.generator()
return new List(function* () {
for (const value of generator) yield fn(value)
}, this.length)
}
reduce(reducer, seed) {
return this.toArray().reduce(reducer, seed)
}
ap(list) {
const generator = this[Symbol.iterator]
return new List(function* () {
for (const f of generator()) {
yield* list.map(f)
}
}, this.length)
}
// delete the first N elements from a list
drop(count) {
const generator = this.generator()
return new List(function* () {
let next = generator.next()
let n = 1
while (!next.done) {
if (n > count) yield next.value
n++
next = generator.next()
}
}, this.length - count)
}
// deletes the first element from a list
tail() {
return this.drop(1)
}
scan(fn, seed) {
const generator = this.generator()
return new List(function* () {
let acc = seed
for (const value of generator) yield (acc = fn(acc, value))
}, this.length)
}
filter(fn) {
const generator = this.generator()
return new List(function* () {
for (const value of generator) if (fn(value)) yield value
}, this.length)
}
where(fn) {
return this.filter(fn)
}
concat(...args) {
const generator = this.generator()
const toAdd = this.flattenArgs(args)
return new List(function* () {
yield* generator
yield* toAdd
}, this.length + toAdd.length)
}
take(count) {
const generator = this.generator()
return new List(
function* () {
let next = generator.next()
let n = 0
while (!next.done && count > n) {
yield next.value
n++
next = generator.next()
}
},
this.length > count ? count : this.length
)
}
all(fn) {
const generator = this.generator()
const newList = new List(function* () {
for (const value of generator) {
if (fn(value)) yield value
else return yield value
}
}, this.length)
return newList.toArray().length === this.length
}
any(fn) {
const generator = this.generator()
const newList = new List(function* () {
for (const value of generator) if (fn(value)) return yield value
}, this.length)
return newList.toArray().length >= 1
}
some(fn) {
return this.any(fn)
}
ofType(type) {
return this.filter((a) => typeof a === type)
}
instanceOf(type) {
return this.filter((a) => a instanceof type)
}
toDictionary(key) {
return this.reduce((acc, curr, idx) => {
return key
? curr[key]
? { ...acc, [curr[key]]: curr }
: acc
: { ...acc, [idx]: curr }
}, {})
}
zipWith (lazyList, zipper) {
const generator1 = this[Symbol.iterator]
const generator2 = lazyList[Symbol.iterator]
return new List(function* () {
const iterator1 = generator1()
const iterator2 = generator2()
let next1 = iterator1.next()
let next2 = iterator2.next()
let i = 0
while (!next1.done && !next2.done) {
yield zipper(next1.value, next2.value)
next1 = iterator1.next()
next2 = iterator2.next()
}
}, this.length < lazyList.length ? this.length : lazyList.length)
}
sum() {
return this.toArray().reduce((acc, curr) => {
return typeof curr === 'number' ? acc + curr : 0
}, 0)
}
head() {
return this[Symbol.iterator]().next().value
}
headOr(valueWhenUndefined) {
return this.headOrUndefined() || valueWhenUndefined
}
headOrUndefined() {
return this.generator().next().value
}
headOrCompute(fn) {
return this.headOrUndefined() || fn()
}
headOrThrow(msg) {
return (
this.headOrUndefined() ||
(() => {
throw new Error(msg)
})()
)
}
toArray() {
return [...this]
}
toIterable() {
return this.toArray()
}
toString() {
const displayedCount = 100
return `List [ ${this.take(displayedCount).toArray().join(', ')}${
this.length > displayedCount ? ' ...' : ''
} ]`
}
inspect() {
return this.toString()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment