Source: list/zip.js

/**
 * maryamyriameliamurphies.js
 * A library of Haskell-style morphisms ported to ES2015 JavaScript.
 *
 * list/zip.js
 *
 * @file Functions for zipping and unzipping lists.
 * @license ISC
 */

/** @module list/zip */

import {partial} from '../base';

import {tuple} from '../tuple';

import {
  emptyList,
  cons,
  head,
  tail,
  isList,
  isEmpty
} from '../list';

import {error} from '../error';

/**
 * Take two `List` objects and return a `List` of corresponding pairs. If one input list is shorter,
 * excess elements of the longer list are discarded.
 * <br>`Haskell> zip :: [a] -> [b] -> [(a, b)]`
 * @param {List} as - The first `List`
 * @param {List} bs - The second `List`
 * @returns {List} The zipped `List` of `Tuple` objects
 * @kind function
 * @example
 * const lst1 = list(1,2,3,4,5);
 * const lst2 = list(5,4,3,2,1);
 * zip(lst1, lst2);              // => [(1,5):(2,4):(3,3):(4,2):(5,1):[]]
 */
export const zip = (as, bs) => {
  const zip_ = (as, bs) => {
    if (isList(as) === false) { return error.listError(as, zip); }
    if (isList(bs) === false) { return error.listError(bs, zip); }
    if (isEmpty(as)) { return emptyList; }
    if (isEmpty(bs)) { return emptyList; }
    const x = head(as);
    const xs = tail(as);
    const y = head(bs);
    const ys = tail(bs);
    return cons(tuple(x, y))(zip(xs)(ys));
  }
  return partial(zip_, as, bs);
}

/**
 * Take three `List` objects and return a `List` of triples (`Tuple` objects with three values).
 * Analogous to the `zip` function.
 * <br>`Haskell> zip3 :: [a] -> [b] -> [c] -> [(a, b, c)]`
 * @param {List} as - The first `List`
 * @param {List} bs - The second `List`
 * @param {List} cs - The third `List`
 * @returns {List} The zipped `List` of `Tuple` objects
 * @kind function
 * @example
 * const lst1 = list(1,2,3,4,5);
 * const lst2 = list(5,4,3,2,1);
 * const lst3 = list(6,7,8,9,10);
 * zip3(lst1, lst2, lst3);        // => [(1,5,6):(2,4,7):(3,3,8):(4,2,9):(5,1,10):[]]
 */
export const zip3 = (as, bs, cs) => {
  const zip3_ = (as, bs, cs) => {
    if (isList(as) === false) { return error.listError(as, zip3); }
    if (isList(bs) === false) { return error.listError(bs, zip3); }
    if (isList(cs) === false) { return error.listError(cs, zip3); }
    if (isEmpty(as) || isEmpty(bs) || isEmpty(cs)) { return emptyList; }
    const x = head(as);
    const xs = tail(as);
    const y = head(bs);
    const ys = tail(bs);
    const z = head(cs);
    const zs = tail(cs);
    return cons(tuple(x, y, z))(zip3(xs, ys, zs));
  }
  return partial(zip3_, as, bs, cs);
}

/**
 * A generalization of the `zip` function. Zip two `List` objects using a provided function.
 * <br>`Haskell> zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]`
 * @param {Function} f - The zipping function
 * @param {List} as - The first `List`
 * @param {List} bs - The second `List`
 * @returns {List} The zipped `List`
 * @kind function
 * @example
 * const lst1 = list(1,2,3,4,5);
 * const lst2 = list(5,4,3,2,1);
 * const f = (x, y) => tuple(x * 3, y ** 2);
 * const g = (x, y) => x + y;
 * zipWith(f, lst1, lst2);                   // => [(3,25):(6,16):(9,9):(12,4):(15,1):[]]
 * zipWith(g, lst1, lst2);                   // => [6:6:6:6:6:[]]
 */
export const zipWith = (f, as, bs) => {
  const zipWith_ = (f, as, bs) => {
    if (isList(as) === false) { return error.listError(as, zipWith); }
    if (isList(bs) === false) { return error.listError(bs, zipWith); }
    if (isEmpty(as) || isEmpty(bs)) { return emptyList; }
    const x = head(as);
    const xs = tail(as);
    const y = head(bs);
    const ys = tail(bs);
    return cons(f(x, y))(zipWith(f, xs, ys));
  }
  return partial(zipWith_, f, as, bs);
}

/**
 * A generalization of the `zip3` function. Zip three `List` objects using a provided function.
 * <br>`Haskell> zipWith3 :: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d]`
 * @param {Function} f - The zipping function
 * @param {List} as - The first `List`
 * @param {List} bs - The second `List`
 * @param {List} cs - The third `List`
 * @returns {List} The zipped `List`
 * @kind function
 * @example
 * const lst1 = list(1,2,3,4,5);
 * const lst2 = list(5,4,3,2,1);
 * const lst3 = list(6,7,8,9,10);
 * const f = (x, y, z) => tuple(x * 3, y * y, z % 2);
 * const g = (x, y, z) => x + y + z;
 * zipWith3(f, lst1, lst2, lst3);    // => [(3,25,0):(6,16,1):(9,9,0):(12,4,1):(15,1,0):[]]
 * zipWith3(g, lst1, lst2, lst3);    // => [12:13:14:15:16:[]]
 */
export const zipWith3 = (f, as, bs, cs) => {
  const zipWith3_ = (f, as, bs, cs) => {
    if (isList(as) === false) { return error.listError(as, zipWith3); }
    if (isList(bs) === false) { return error.listError(bs, zipWith3); }
    if (isList(cs) === false) { return error.listError(cs, zipWith3); }
    if (isEmpty(as) || isEmpty(bs) || isEmpty(cs)) { return emptyList; }
    const x = head(as);
    const xs = tail(as);
    const y = head(bs);
    const ys = tail(bs);
    const z = head(cs);
    const zs = tail(cs);
    return cons(f(x, y, z))(zipWith3(f, xs, ys, zs));
  }
  return partial(zipWith3_, f, as, bs, cs);
}