Repository: marpple/FxTS (ts)

Issue:
Add `min` and `max` functions
# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## ⭐ Suggestion

<!-- What would you like to be added? -->

## 💻 Use Cases

```ts
min([5, 3, 1]); // 1;
min([5, NaN, 1]); // NaN;

max([1, 3, 5]); // 5;
max([1, NaN, 1]); // NaN;
max([1, Infinity, 1]); // Infinity;

await min(toAsync([5, 3, 1])); // 1;
await max(toAsync([1, 3, 5])); // 5;
```


Pull request description:
feat: add max/min function
Fixes #79 


Patch:
diff --git a/src/index.ts b/src/index.ts
index 2054f2be..cd0c266a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -19,6 +19,8 @@ import isObject from "./isObject";
 import isString from "./isString";
 import join from "./join";
 import last from "./last";
+import max from "./max";
+import min from "./min";
 import noop from "./noop";
 import not from "./not";
 import nth from "./nth";
@@ -59,6 +61,8 @@ export {
   isString,
   join,
   last,
+  max,
+  min,
   noop,
   not,
   nth,
diff --git a/src/max.ts b/src/max.ts
new file mode 100644
index 00000000..467326a6
--- /dev/null
+++ b/src/max.ts
@@ -0,0 +1,62 @@
+import ReturnValueType from "./types/ReturnValueType";
+import { isAsyncIterable, isIterable } from "./_internal/utils";
+
+function sync(iterable: Iterable<number>) {
+  let n = NaN;
+  for (const a of iterable) {
+    if (Number.isNaN(a)) {
+      return a;
+    } else if (a > n || Number.isNaN(n)) {
+      n = a;
+    }
+  }
+
+  if (Number.isNaN(n)) {
+    return -Infinity;
+  }
+
+  return n;
+}
+
+async function async(iterable: AsyncIterable<number>) {
+  let n = NaN;
+  for await (const a of iterable) {
+    if (Number.isNaN(a)) {
+      return a;
+    } else if (a > n || Number.isNaN(n)) {
+      n = a;
+    }
+  }
+
+  if (Number.isNaN(n)) {
+    return -Infinity;
+  }
+
+  return n;
+}
+
+/**
+ * Returns the largest of the given iterable/AsyncIterable
+ *
+ * @example
+ * ```ts
+ * max([1, 3, 5]); // 5;
+ * max([1, NaN, 2]); // NaN;
+ * max([1, Infinity, 2]); // Infinity;
+ * max([]); // 0
+ * ```
+ */
+function max<A extends Iterable<number> | AsyncIterable<number>>(
+  iterable: A,
+): ReturnValueType<A, number>;
+
+function max(iterable: Iterable<number> | AsyncIterable<number>) {
+  if (isIterable(iterable)) {
+    return sync(iterable as Iterable<number>);
+  } else if (isAsyncIterable(iterable)) {
+    return async(iterable as AsyncIterable<number>);
+  }
+  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
+}
+
+export default max;
diff --git a/src/min.ts b/src/min.ts
new file mode 100644
index 00000000..bdf481fd
--- /dev/null
+++ b/src/min.ts
@@ -0,0 +1,63 @@
+import ReturnValueType from "./types/ReturnValueType";
+import { isAsyncIterable, isIterable } from "./_internal/utils";
+
+function sync(iterable: Iterable<number>) {
+  let n = NaN;
+  for (const a of iterable) {
+    if (Number.isNaN(a)) {
+      return a;
+    } else if (a < n || Number.isNaN(n)) {
+      n = a;
+    }
+  }
+
+  if (Number.isNaN(n)) {
+    return Infinity;
+  }
+
+  return n;
+}
+
+async function async(iterable: AsyncIterable<number>) {
+  let n = NaN;
+  for await (const a of iterable) {
+    if (Number.isNaN(a)) {
+      return a;
+    } else if (a < n || Number.isNaN(n)) {
+      n = a;
+    }
+  }
+
+  if (Number.isNaN(n)) {
+    return Infinity;
+  }
+
+  return n;
+}
+
+/**
+ * Returns the smallest of the given iterable/AsyncIterable
+ *
+ * @example
+ * ```ts
+ * min([1, 3, 5]); // 1;
+ * min([1, NaN, 2]); // NaN;
+ * min([1, Infinity, 3]); // 1;
+ * min([1, -Infinity, 3]); // -Infinity;
+ * min([]); // 0
+ * ```
+ */
+function min<A extends Iterable<number> | AsyncIterable<number>>(
+  iterable: A,
+): ReturnValueType<A, number>;
+
+function min(iterable: Iterable<number> | AsyncIterable<number>) {
+  if (isIterable(iterable)) {
+    return sync(iterable as Iterable<number>);
+  } else if (isAsyncIterable(iterable)) {
+    return async(iterable as AsyncIterable<number>);
+  }
+  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
+}
+
+export default min;


Test patch:
diff --git a/test/max.spec.ts b/test/max.spec.ts
new file mode 100644
index 00000000..20e6b3e2
--- /dev/null
+++ b/test/max.spec.ts
@@ -0,0 +1,30 @@
+import { max, toAsync } from "../src";
+
+describe("max", function () {
+  describe("sync", function () {
+    it.each([
+      [[3, 4, 5, 1, 6], 6],
+      [[0, Infinity, 3], Infinity],
+      [[0, Infinity, 3, NaN], NaN],
+      [[0, 2, 3, NaN], NaN],
+      [[], -Infinity],
+    ])("should the largest of given iterable %s %s", function (input, result) {
+      expect(max(input)).toEqual(result);
+    });
+  });
+
+  describe("async", function () {
+    it.each([
+      [[3, 4, 5, 1, 6], 6],
+      [[0, Infinity, 3], Infinity],
+      [[0, Infinity, 3, NaN], NaN],
+      [[0, 2, 3, NaN], NaN],
+      [[], -Infinity],
+    ])(
+      "should the largest of given asyncIterable %s %s",
+      async function (input, result) {
+        expect(await max(toAsync(input))).toEqual(result);
+      },
+    );
+  });
+});
diff --git a/test/min.spec.ts b/test/min.spec.ts
new file mode 100644
index 00000000..3dd30656
--- /dev/null
+++ b/test/min.spec.ts
@@ -0,0 +1,32 @@
+import { min, toAsync } from "../src";
+
+describe("min", function () {
+  describe("sync", function () {
+    it.each([
+      [[3, 4, 5, 1, 6], 1],
+      [[0, Infinity, 3], 0],
+      [[0, -Infinity, 3], -Infinity],
+      [[0, Infinity, 3, NaN], NaN],
+      [[0, 2, 3, NaN], NaN],
+      [[], Infinity],
+    ])("should the smallest of given iterable %s %s", function (input, result) {
+      expect(min(input)).toEqual(result);
+    });
+  });
+
+  describe("async", function () {
+    it.each([
+      [[3, 4, 5, 1, 6], 1],
+      [[0, Infinity, 3], 0],
+      [[0, -Infinity, 3], -Infinity],
+      [[0, Infinity, 3, NaN], NaN],
+      [[0, 2, 3, NaN], NaN],
+      [[], Infinity],
+    ])(
+      "should the smallest of given asyncIterable %s %s",
+      async function (input, result) {
+        expect(await min(toAsync(input))).toEqual(result);
+      },
+    );
+  });
+});
diff --git a/type-check/max.test.ts b/type-check/max.test.ts
new file mode 100644
index 00000000..9707c57a
--- /dev/null
+++ b/type-check/max.test.ts
@@ -0,0 +1,22 @@
+import * as Test from "../src/types/Test";
+import { max, pipe, toAsync } from "../src";
+
+const { checks, check } = Test;
+
+const res1 = max([]);
+const res2 = max([2]);
+const res3 = max([1, NaN]);
+const res4 = max(toAsync([1]));
+
+// pipeline
+const res5 = pipe([1, 2, 3], max);
+const res6 = pipe([1, 2, 3], toAsync, max);
+
+checks([
+  check<typeof res1, number, Test.Pass>(),
+  check<typeof res2, number, Test.Pass>(),
+  check<typeof res3, number, Test.Pass>(),
+  check<typeof res4, Promise<number>, Test.Pass>(),
+  check<typeof res5, number, Test.Pass>(),
+  check<typeof res6, Promise<number>, Test.Pass>(),
+]);
diff --git a/type-check/min.test.ts b/type-check/min.test.ts
new file mode 100644
index 00000000..66aa328b
--- /dev/null
+++ b/type-check/min.test.ts
@@ -0,0 +1,22 @@
+import * as Test from "../src/types/Test";
+import { min, pipe, toAsync } from "../src";
+
+const { checks, check } = Test;
+
+const res1 = min([]);
+const res2 = min([2]);
+const res3 = min([1, NaN]);
+const res4 = min(toAsync([1]));
+
+// pipeline
+const res5 = pipe([1, 2, 3], min);
+const res6 = pipe([1, 2, 3], toAsync, min);
+
+checks([
+  check<typeof res1, number, Test.Pass>(),
+  check<typeof res2, number, Test.Pass>(),
+  check<typeof res3, number, Test.Pass>(),
+  check<typeof res4, Promise<number>, Test.Pass>(),
+  check<typeof res5, number, Test.Pass>(),
+  check<typeof res6, Promise<number>, Test.Pass>(),
+]);