Darkhogg/polyethylene

View on GitHub
src/lib/sync/generators.ts

Summary

Maintainability
F
1 wk
Test Coverage
import {
  ChunkingPredicate,
  Comparator,
  IndexedMapping,
  IndexedPredicate,
  IndexedRunnable,
  IndexedTypePredicate,
} from '../types.js'

export function * appendGen<T, U> (iterA: Iterable<T>, iterB: Iterable<U>): Iterable<T | U> {
  yield * iterA
  yield * iterB
}

export function * prependGen<T, U> (iterA: Iterable<T>, iterB: Iterable<U>): Iterable<T | U> {
  yield * iterB
  yield * iterA
}

export function * dropGen<T> (iter: Iterable<T>, num: number): Iterable<T> {
  let rem = num
  for (const elem of iter) {
    if (rem-- <= 0) {
      yield elem
    }
  }
}

export function * takeGen<T> (iter: Iterable<T>, num: number): Iterable<T> {
  let rem = num
  for (const elem of iter) {
    if (rem-- <= 0) {
      return
    }
    yield elem
  }
}

export function * dropLastGen<T> (iter: Iterable<T>, num: number): Iterable<T> {
  const buf = Array(num)
  let pos = 0
  let size = 0
  for (const elem of iter) {
    if (size >= num) {
      yield num ? buf[pos] : elem
    } else {
      size++
    }
    if (num) {
      buf[pos] = elem
      pos = ((pos + 1) % num)
    }
  }
}

export function * takeLastGen<T> (iter: Iterable<T>, num: number): Iterable<T> {
  const buf = Array(num)
  let pos = 0
  let size = 0
  for (const elem of iter) {
    size = size >= num ? num : size + 1
    if (num) {
      buf[pos] = elem
      pos = ((pos + 1) % num)
    }
  }
  pos = size >= num ? pos : 0
  for (let i = 0; i < size; i++) {
    yield buf[pos]
    pos = ((pos + 1) % num)
  }
}

function * sliceNegPosGen<T> (iter: Iterable<T>, start: number, end: number): Iterable<T> {
  const buf = Array(start)
  let pos = 0
  let size = 0
  let num = end
  for (const elem of iter) {
    if (size >= start && end) {
      num--
    }
    size = size >= start ? start : size + 1
    if (start) {
      buf[pos] = elem
      pos = ((pos + 1) % start)
    }
  }
  pos = size >= start ? pos : 0
  for (let i = 0; i < Math.min(size, num); i++) {
    yield buf[pos]
    pos = ((pos + 1) % start)
  }
}

export function sliceGen<T> (iter: Iterable<T>, start: number, end?: number): Iterable<T> {
  if (start >= 0 && end == null) {
    return dropGen(iter, start)
  } else if (end == null) { // && start < 0
    return takeLastGen(iter, -start)
  } else if (start >= 0 && end >= 0) {
    return takeGen(dropGen(iter, start), end - start)
  } else if (start < 0 && end >= 0) {
    return sliceNegPosGen(iter, -start, end)
  } else if (start >= 0) { // && end < 0
    return dropLastGen(dropGen(iter, start), -end)
  } else { // start < 0 && end < 0
    return dropLastGen(takeLastGen(iter, -start), -end)
  }
}

export function * dropWhileGen<T> (iter: Iterable<T>, func: IndexedPredicate<T>): Iterable<T> {
  let idx = 0
  let yielding = false
  for (const elem of iter) {
    if (yielding || !func(elem, idx++)) {
      yielding = true
      yield elem
    }
  }
}

export function * takeWhileGen<T> (iter: Iterable<T>, func: IndexedPredicate<T>): Iterable<T> {
  let idx = 0
  for (const elem of iter) {
    if (!func(elem, idx++)) {
      return
    }
    yield elem
  }
}

export function filterGen<T, U extends T> (iter: Iterable<T>, func: IndexedTypePredicate<T, U>): Iterable<U>
export function filterGen<T> (iter: Iterable<T>, func: IndexedPredicate<T>): Iterable<T>

export function * filterGen<T, U extends T> (
  iter: Iterable<T>,
  func: IndexedPredicate<T> | IndexedTypePredicate<T, U>,
): Iterable<U> {
  let idx = 0
  for (const elem of iter) {
    if (func(elem, idx++)) {
      yield elem
    }
  }
}

export function * mapGen<T, U> (iter: Iterable<T>, func: IndexedMapping<T, U>): Iterable<U> {
  let idx = 0
  for (const elem of iter) {
    yield func(elem, idx++)
  }
}

export function * tapGen<T> (iter: Iterable<T>, func: IndexedRunnable<T>): Iterable<T> {
  let idx = 0
  for (const elem of iter) {
    func(elem, idx++)
    yield elem
  }
}

export function * flattenGen<T> (iter: Iterable<Iterable<T>>): Iterable<T> {
  for (const elem of iter) {
    yield * elem
  }
}

export function * chunkGen<T> (iter: Iterable<T>, num: number): Iterable<Array<T>> {
  let chunk = []

  for (const elem of iter) {
    chunk.push(elem)
    if (chunk.length === num) {
      yield chunk
      chunk = []
    }
  }

  if (chunk.length) {
    yield chunk
  }
}

export function * chunkWhileGen<T> (iter: Iterable<T>, func: ChunkingPredicate<T>): Iterable<Array<T>> {
  let chunk: Array<T> = []

  for (const elem of iter) {
    if (chunk.length === 0 || func(elem, chunk[chunk.length - 1], chunk[0])) {
      chunk.push(elem)
    } else {
      yield chunk
      chunk = [elem]
    }
  }

  if (chunk.length) {
    yield chunk
  }
}

export function * groupByGen<K, T> (iter: Iterable<T>, func: IndexedMapping<T, K>): Iterable<[K, Array<T>]> {
  const groups: Map<K, Array<T>> = new Map()

  let idx = 0
  for (const elem of iter) {
    const key = func(elem, idx++)

    const group = groups.get(key) ?? []
    group.push(elem)

    groups.set(key, group)
  }

  yield * groups.entries()
}

export function * uniqueGen<T> (iter: Iterable<T>, func: IndexedMapping<T, unknown>): Iterable<T> {
  const seen = new Set()

  let idx = 0
  for (const elem of iter) {
    const key = func(elem, idx++)
    if (!seen.has(key)) {
      yield elem
      seen.add(key)
    }
  }
}

export function * reverseGen<T> (iter: Iterable<T>): Iterable<T> {
  const buf = Array.from(iter)
  for (let i = buf.length - 1; i >= 0; i--) {
    yield buf[i]
  }
}

export function * sortGen<T> (iter: Iterable<T>, func: Comparator<T>): Iterable<T> {
  const buf = Array.from(iter)
  for (const elem of buf.sort(func)) {
    yield elem
  }
}