Repository: marpple/FxTS (ts)

Issue:
Add `zipWith` function
# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## ⭐ Suggestion

<!-- What would you like to be added? -->

Returns Iterable/AsyncIterable out of the two supplied by applying `f` to each same positioned pair in Iterable/AsyncIterable.

## 💻 Use Cases

<!-- What do you want to use this for? -->

```ts

const iter = zipWith((a,b) => [a,b], [1,2,3], ['a','b','c']);
iter.next(); // {value: [1, 'a'] , done: false}
iter.next(); // {value: [2, 'b'] , done: false}
iter.next(); // {value: [3, 'c'] , done: false}

```


Pull request:
feat: add zipWith function
Fixes #31

we do not support `curry` in `zipWith` not yet

Patch:
diff --git a/src/Lazy/index.ts b/src/Lazy/index.ts
index 2bc3fff9..3155da6c 100644
--- a/src/Lazy/index.ts
+++ b/src/Lazy/index.ts
@@ -22,6 +22,7 @@ import toAsync from "./toAsync";
 import uniq from "./uniq";
 import uniqBy from "./uniqBy";
 import zip from "./zip";
+import zipWith from "./zipWith";
 import zipWithIndex from "./zipWithIndex";
 
 export {
@@ -49,5 +50,6 @@ export {
   uniq,
   uniqBy,
   zip,
+  zipWith,
   zipWithIndex,
 };
diff --git a/src/Lazy/zipWith.ts b/src/Lazy/zipWith.ts
new file mode 100644
index 00000000..a730a0f1
--- /dev/null
+++ b/src/Lazy/zipWith.ts
@@ -0,0 +1,61 @@
+import { map, zip } from ".";
+import { isAsyncIterable, isIterable } from "../_internal/utils";
+
+/**
+ * Returns Iterable/AsyncIterable out of the two supplied by applying `f` to each same positioned pair in Iterable/AsyncIterable.
+ *
+ * @example
+ * const iter = zipWith((a,b) => [a,b], [1,2,3], ['a','b','c']);
+ * iter.next(); // {value: [1, 'a'] , done: false}
+ * iter.next(); // {value: [2, 'b'] , done: false}
+ * iter.next(); // {value: [3, 'c'] , done: false}
+ * iter.next(); // {value: undefined , done: true}
+ */
+function zipWith<A, B, C>(
+  f: (a: A, b: B) => C,
+  iterable1: Iterable<A>,
+  iterable2: Iterable<B>,
+): IterableIterator<C>;
+
+function zipWith<A, B, C>(
+  f: (a: A, b: B) => C,
+  iterable1: Iterable<A>,
+  iterable2: AsyncIterable<B>,
+): AsyncIterableIterator<C>;
+
+function zipWith<A, B, C>(
+  f: (a: A, b: B) => C,
+  iterable1: AsyncIterable<A>,
+  iterable2: Iterable<B>,
+): AsyncIterableIterator<C>;
+
+function zipWith<A, B, C>(
+  f: (a: A, b: B) => C,
+  iterable1: AsyncIterable<A>,
+  iterable2: AsyncIterable<B>,
+): AsyncIterableIterator<C>;
+
+function zipWith<A, B, C>(
+  f: (a: A, b: B) => C,
+  iterable1: Iterable<A> | AsyncIterable<A>,
+  iterable2: Iterable<B> | AsyncIterable<B>,
+): IterableIterator<C> | AsyncIterableIterator<C> {
+  if (isIterable(iterable1) && isIterable(iterable2)) {
+    return map(([a, b]) => f(a, b), zip(iterable1, iterable2));
+  }
+  if (isIterable(iterable1) && isAsyncIterable(iterable2)) {
+    return map(([a, b]) => f(a, b), zip(iterable1, iterable2));
+  }
+  if (isAsyncIterable(iterable1) && isIterable(iterable2)) {
+    return map(([a, b]) => f(a, b), zip(iterable1, iterable2));
+  }
+  if (isAsyncIterable(iterable1) && isAsyncIterable(iterable2)) {
+    return map(([a, b]) => f(a, b), zip(iterable1, iterable2));
+  }
+
+  throw new TypeError(
+    "'iterable1' and 'iterable2' must be type of Iterable or AsyncIterable",
+  );
+}
+
+export default zipWith;
diff --git a/src/index.ts b/src/index.ts
index f0f62157..07424ef7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -12,9 +12,7 @@ import isString from "./isString";
 import join from "./join";
 import last from "./last";
 import not from "./not";
-import omit from "./omit";
 import partition from "./partition";
-import pick from "./pick";
 import pipe from "./pipe";
 import pipe1 from "./pipe1";
 import reduce from "./reduce";
@@ -38,9 +36,7 @@ export {
   join,
   last,
   not,
-  omit,
   partition,
-  pick,
   pipe,
   pipe1,
   reduce,
diff --git a/src/omit.ts b/src/omit.ts
deleted file mode 100644
index d28163c2..00000000
--- a/src/omit.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import IterableInfer from "./types/IterableInfer";
-import { isAsyncIterable, isIterable } from "./_internal/utils";
-import toArray from "./toArray";
-
-function inner<T>(obj: T, keys: Set<keyof T>) {
-  return Object.fromEntries(
-    Object.entries(obj).filter(([k]) => !keys.has(k as keyof T)),
-  );
-}
-
-function sync<T extends object, U extends Iterable<keyof T>>(
-  iterable: U,
-  obj: T,
-): Omit<T, IterableInfer<U>> {
-  const keys = new Set(iterable);
-  return inner(obj, keys) as Omit<T, IterableInfer<U>>;
-}
-
-async function async<T extends object, U extends AsyncIterable<keyof T>>(
-  iterable: U,
-  obj: T,
-): Promise<Omit<T, IterableInfer<U>>> {
-  const keys = new Set(await toArray(iterable));
-  return inner(obj, keys) as Omit<T, IterableInfer<U>>;
-}
-
-/**
- * omit
- *
- * Returns a partial copy of an object omitting the keys specified.
- *
- * @example
- * ```ts
- * const person = {
- *   name: "james",
- *   age: 40,
- *   numberOfKids: 2,
- *   team: "Software Development",
- *   preferredLanguage: "Rust",
- * };
- *
- * const dad = omit(["team", "preferredLanguage"], person);
- * // { name: "james", age: 40, numberOfKids: 2 }
- *
- * const developer = omit(["age", "numberOfKids"], person);
- * // { name: "james", team: "Software Development", preferredLanguage: "Rust" }
- *
- * // with pipe
- * pipe(
- *  person,
- *  omit(["team", "preferredLanguage"]),
- * );
- *
- * // if you want to use AsyncIterable as the list of property names
- * const anonymous = await omit(toAsync(["name"] as const), person);
- * ```
- * see {@link https://fxts.dev/docs/pipe | pipe}, {@link https://fxts.dev/docs/toAsync | toAsync},
- * {@link https://fxts.dev/docs/pick | pick},
- */
-
-function omit<T extends object, U extends Iterable<keyof T>>(
-  iterable: U,
-  obj: T,
-): Omit<T, IterableInfer<U>>;
-
-function omit<T extends object, U extends AsyncIterable<keyof T>>(
-  iterable: U,
-  obj: T,
-): Promise<Omit<T, IterableInfer<U>>>;
-
-function omit<T extends object, U extends Iterable<keyof T>>(
-  iterable: U,
-): (obj: T) => Omit<T, IterableInfer<U>>;
-
-function omit<T extends object, U extends AsyncIterable<keyof T>>(
-  iterable: U,
-): (obj: T) => Promise<Omit<T, IterableInfer<U>>>;
-
-function omit<
-  T extends object,
-  U extends AsyncIterable<keyof T> | Iterable<keyof T>,
->(
-  iterable: U,
-  obj?: T,
-):
-  | Promise<Omit<T, IterableInfer<U>>>
-  | Omit<T, IterableInfer<U>>
-  | ((
-      obj: T,
-    ) => Promise<Omit<T, IterableInfer<U>>> | Omit<T, IterableInfer<U>>) {
-  if (obj === undefined) {
-    return (obj: T) => omit(iterable as any, obj) as any;
-  }
-  if (isIterable(iterable)) {
-    return sync(iterable, obj) as Omit<T, IterableInfer<U>>;
-  } else if (isAsyncIterable(iterable)) {
-    return async(iterable, obj) as Promise<Omit<T, IterableInfer<U>>>;
-  }
-  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
-}
-
-export default omit;
diff --git a/src/pick.ts b/src/pick.ts
deleted file mode 100644
index cad24631..00000000
--- a/src/pick.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import IterableInfer from "./types/IterableInfer";
-import { isAsyncIterable, isIterable } from "./_internal/utils";
-import toArray from "./toArray";
-
-function inner<T>(obj: T, keys: Set<keyof T>) {
-  return Object.fromEntries(
-    Object.entries(obj).filter(([k]) => keys.has(k as keyof T)),
-  );
-}
-
-function sync<T extends object, U extends Iterable<keyof T>>(
-  iterable: U,
-  obj: T,
-): Pick<T, IterableInfer<U>> {
-  const keys = new Set(iterable);
-  return inner(obj, keys) as Pick<T, IterableInfer<U>>;
-}
-
-async function async<T extends object, U extends AsyncIterable<keyof T>>(
-  iterable: U,
-  obj: T,
-): Promise<Pick<T, IterableInfer<U>>> {
-  const keys = new Set(await toArray(iterable));
-  return inner(obj, keys) as Pick<T, IterableInfer<U>>;
-}
-
-/**
- * pick
- *
- * Returns a partial copy of an object containing given keys.
- *
- * @example
- * ```ts
- * const person = {
- *   name: "james",
- *   age: 40,
- *   numberOfKids: 2,
- *   team: "Software Development",
- *   preferredLanguage: "Rust",
- * };
- *
- * const dad = pick(["name", "age", "numberOfKids"], person);
- * // { name: "james", age: 40, numberOfKids: 2 }
- *
- * const developer = pick(["name", "team", "preferredLanguage"], person);
- * // { name: "james", team: "Software Development", preferredLanguage: "Rust" }
- *
- * // with pipe
- * pipe(
- *  person,
- *  pick(["name", "age", "numberOfKids"]),
- * );
- *
- * // if you want to use AsyncIterable as the list of property names
- * const anonymousDeveloper = await pick(toAsync(["preferredLanguage"] as const), person);
- * ```
- * see {@link https://fxts.dev/docs/pipe | pipe}, {@link https://fxts.dev/docs/toAsync | toAsync},
- * {@link https://fxts.dev/docs/omit | omit},
- */
-
-function pick<T extends object, U extends readonly []>(
-  iterable: U,
-  obj: T,
-): Record<string, never>;
-
-function pick<T extends object, U extends Iterable<keyof T>>(
-  iterable: U,
-  obj: T,
-): Pick<T, IterableInfer<U>>;
-
-function pick<T extends object, U extends AsyncIterable<keyof T>>(
-  iterable: U,
-  obj: T,
-): Promise<Pick<T, IterableInfer<U>>>;
-
-function pick<T extends object, U extends Iterable<keyof T>>(
-  iterable: U,
-): (obj: T) => Pick<T, IterableInfer<U>>;
-
-function pick<T extends object, U extends AsyncIterable<keyof T>>(
-  iterable: U,
-): (obj: T) => Promise<Pick<T, IterableInfer<U>>>;
-
-function pick<
-  T extends object,
-  U extends AsyncIterable<keyof T> | Iterable<keyof T>,
->(
-  iterable: U,
-  obj?: T,
-):
-  | Promise<Pick<T, IterableInfer<U>>>
-  | Pick<T, IterableInfer<U>>
-  | ((
-      obj: T,
-    ) => Promise<Pick<T, IterableInfer<U>>> | Pick<T, IterableInfer<U>>) {
-  if (obj === undefined) {
-    return (obj: T) => pick(iterable as any, obj) as any;
-  }
-  if (isIterable(iterable)) {
-    return sync(iterable, obj);
-  } else if (isAsyncIterable(iterable)) {
-    return async(iterable, obj);
-  }
-  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
-}
-
-export default pick;


Test patch:
diff --git a/test/Lazy/zipWith.spec.ts b/test/Lazy/zipWith.spec.ts
new file mode 100644
index 00000000..726de3a1
--- /dev/null
+++ b/test/Lazy/zipWith.spec.ts
@@ -0,0 +1,57 @@
+import { toAsync } from "../../src";
+import zipWith from "../../src/Lazy/zipWith";
+
+describe("zipWith", function () {
+  const iter1 = ["foo", "bar", "ha"];
+  const iter2 = [1, 2, 3];
+  const then = [{ foo: 1 }, { bar: 2 }, { ha: 3 }];
+
+  describe("sync", function () {
+    it("should apply `f` to each same positioned pair", function () {
+      const acc = [];
+      const iter = zipWith((a, b) => ({ [a]: b }), iter1, iter2);
+      for (const a of iter) {
+        acc.push(a);
+      }
+      expect(acc).toEqual(then);
+    });
+  });
+
+  describe("async", function () {
+    it("should apply `f` to each same positioned pair [iterable/AsyncIterable]", async function () {
+      const acc = [];
+      const iter = zipWith((a, b) => ({ [a]: b }), toAsync(iter1), iter2);
+      for await (const a of iter) {
+        acc.push(a);
+      }
+
+      expect(acc).toEqual(then);
+    });
+
+    it("should apply `f` to each same positioned pair [AsyncIterable/iterable]", async function () {
+      const acc = [];
+      const iter = zipWith((a, b) => ({ [a]: b }), toAsync(iter1), iter2);
+
+      for await (const a of iter) {
+        acc.push(a);
+      }
+
+      expect(acc).toEqual(then);
+    });
+
+    it("should apply `f` to each same positioned pair [AsyncIterable/AsyncIterable]", async function () {
+      const acc = [];
+      const iter = zipWith(
+        (a, b) => ({ [a]: b }),
+        toAsync(iter1),
+        toAsync(iter2),
+      );
+
+      for await (const a of iter) {
+        acc.push(a);
+      }
+
+      expect(acc).toEqual(then);
+    });
+  });
+});
diff --git a/test/omit.spec.ts b/test/omit.spec.ts
deleted file mode 100644
index c019401f..00000000
--- a/test/omit.spec.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { omit, pipe, toAsync } from "../src/index";
-
-describe("omit", function () {
-  describe("sync", function () {
-    it("should be omitted properties as given keys", function () {
-      const obj = { a: 1, b: 2, c: "3" };
-      expect(omit(["a", "c"], obj)).toEqual({ b: 2 });
-    });
-    it("should be able to be used as a curried function in the pipeline", function () {
-      const res = pipe(
-        { a: 1, b: "2", c: true },
-        omit(["a", "b"]),
-        Object.entries,
-      );
-      expect(res).toEqual([["c", true]]);
-    });
-  });
-
-  describe("async", function () {
-    it("should be omitted properties as given keys", async function () {
-      const obj = { a: 1, b: 2, c: "3" };
-      const iter = toAsync(["a", "c"] as const);
-      const res = await omit(iter, obj);
-      expect(res).toEqual({ b: 2 });
-    });
-    it("should be able to be used as a curried function in the pipeline", async function () {
-      const res = await pipe(
-        { a: 1, b: "2", c: true },
-        omit(toAsync(["a", "b"] as const)),
-        Object.entries,
-      );
-      expect(res).toEqual([["c", true]]);
-    });
-  });
-});
diff --git a/test/pick.spec.ts b/test/pick.spec.ts
deleted file mode 100644
index 64e7cdb0..00000000
--- a/test/pick.spec.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { pick, pipe, toAsync } from "../src/index";
-
-describe("pick", function () {
-  describe("sync", function () {
-    it("should be picked properties as given keys", function () {
-      const obj = { a: 1, b: 2, c: "3" };
-      expect(pick(["a", "c"], obj)).toEqual({ a: 1, c: "3" });
-    });
-    it("should be able to be used as a curried function in the pipeline", function () {
-      const res = pipe(
-        { a: 1, b: "2", c: true },
-        pick(["a", "b"]),
-        Object.entries,
-      );
-      expect(res).toEqual([
-        ["a", 1],
-        ["b", "2"],
-      ]);
-    });
-  });
-
-  describe("async", function () {
-    it("should be picked properties as given keys", async function () {
-      const obj = { a: 1, b: 2, c: "3" };
-      const iter = toAsync(["a", "c"] as const);
-      const res = await pick(iter, obj);
-      expect(res).toEqual({ a: 1, c: "3" });
-    });
-    it("should be able to be used as a curried function in the pipeline", async function () {
-      const res = await pipe(
-        { a: 1, b: "2", c: true },
-        pick(toAsync(["a", "b"] as const)),
-        Object.entries,
-      );
-      expect(res).toEqual([
-        ["a", 1],
-        ["b", "2"],
-      ]);
-    });
-  });
-});
diff --git a/type-check/Lazy/zipWith.test.ts b/type-check/Lazy/zipWith.test.ts
new file mode 100644
index 00000000..d19235a0
--- /dev/null
+++ b/type-check/Lazy/zipWith.test.ts
@@ -0,0 +1,47 @@
+import * as Test from "../../src/types/Test";
+import { toAsync, zipWith } from "../../src";
+
+const { checks, check } = Test;
+
+const iter1 = ["foo", "bar", "ha"];
+const iter2 = [1, 2, 3];
+
+const res1 = zipWith((a, b) => ({ [a]: b }), iter1, iter2);
+const res2 = zipWith((a, b) => ({ [a]: b }), toAsync(iter1), iter2);
+const res3 = zipWith((a, b) => ({ [a]: b }), iter1, toAsync(iter2));
+const res4 = zipWith((a, b) => ({ [a]: b }), toAsync(iter1), toAsync(iter2));
+const res5 = zipWith((a, b) => [a, b], iter1, iter2);
+const res6 = zipWith((a, b) => [a, b] as [string, number], iter1, iter2);
+
+checks([
+  check<
+    typeof res1,
+    IterableIterator<{
+      [x: string]: number;
+    }>,
+    Test.Pass
+  >(),
+  check<
+    typeof res2,
+    AsyncIterableIterator<{
+      [x: string]: number;
+    }>,
+    Test.Pass
+  >(),
+  check<
+    typeof res3,
+    AsyncIterableIterator<{
+      [x: string]: number;
+    }>,
+    Test.Pass
+  >(),
+  check<
+    typeof res4,
+    AsyncIterableIterator<{
+      [x: string]: number;
+    }>,
+    Test.Pass
+  >(),
+  check<typeof res5, IterableIterator<(string | number)[]>, Test.Pass>(),
+  check<typeof res6, IterableIterator<[string, number]>, Test.Pass>(),
+]);
diff --git a/type-check/omit.test.ts b/type-check/omit.test.ts
deleted file mode 100644
index 2283f122..00000000
--- a/type-check/omit.test.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import * as Test from "../src/types/Test";
-import { omit, toAsync } from "../src";
-
-const { checks, check } = Test;
-
-const obj1 = {
-  a: 1,
-  b: "2",
-  c: true,
-  d: Symbol("d"),
-};
-
-type OptionalObj = {
-  a: number;
-  b?: string;
-};
-
-const res1 = omit([], obj1);
-const res2 = omit(["a", "b"], obj1);
-const res3 = omit(["a", "c"], obj1);
-const res4 = omit(toAsync(["a", "c"] as const), obj1);
-const res5 = omit(["a"], { a: 1 } as OptionalObj);
-
-checks([
-  check<typeof res1, typeof obj1, Test.Pass>(),
-  check<typeof res2, { c: boolean; d: symbol }, Test.Pass>(),
-  check<typeof res3, { b: string; d: symbol }, Test.Pass>(),
-  check<typeof res4, Promise<{ b: string; d: symbol }>, Test.Pass>(),
-  check<typeof res5, { b?: string }, Test.Pass>(),
-]);
diff --git a/type-check/pick.test.ts b/type-check/pick.test.ts
deleted file mode 100644
index d024b827..00000000
--- a/type-check/pick.test.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import * as Test from "../src/types/Test";
-import { pick, toAsync } from "../src";
-
-const { checks, check } = Test;
-
-const obj1 = {
-  a: 1,
-  b: "2",
-  c: true,
-  d: Symbol("d"),
-};
-
-type OptionalObj = {
-  a: number;
-  b?: string;
-};
-
-const res1 = pick([], obj1);
-const res2 = pick(["a", "b", "d"], obj1);
-const res3 = pick(["a", "c"], obj1);
-const res4 = pick(toAsync(["a", "b"] as const), obj1);
-const res5 = pick(["a", "b"], { a: 1 } as OptionalObj);
-
-checks([
-  check<typeof res1, Record<string, never>, Test.Pass>(),
-  check<typeof res2, { a: number; b: string; d: symbol }, Test.Pass>(),
-  check<typeof res3, { a: number; c: boolean }, Test.Pass>(),
-  check<typeof res4, Promise<{ a: number; b: string }>, Test.Pass>(),
-  check<typeof res5, { a: number; b?: string }, Test.Pass>(),
-]);