Repository: char0n/ramda-adjunct (js)

Issue:
filterIndexed
**Is your feature request related to a problem? Please describe.**

[R.filter](https://ramdajs.com/docs/#filter) doesn't expose index to the predicate function. Let's create a function that does and call it `filterIndexed`.

**Describe the solution you'd like**
```js
filterIndexed((v, idx) => idx === 0, [1, 2, 3); // => [1]
```
Possible implementation
```js
const filterIndexed = R.addIndex(R.filter);
```
**Describe alternatives you've considered**

--

**Additional context**

--


Pull request description:
feat(list): Add filterIndexed
Closes #1346

This took longer than I wanted it to due to prioritization on my end, appreciate the patience. Updated the docs example from the previous iteration to a function that uses both the value & index for didactic purposes.

Patch:
diff --git a/src/filterIndexed.js b/src/filterIndexed.js
new file mode 100644
index 0000000000..ce8dd57573
--- /dev/null
+++ b/src/filterIndexed.js
@@ -0,0 +1,28 @@
+import { addIndex, filter } from 'ramda';
+
+/**
+ * {@link http://ramdajs.com/docs/#filter|R.filter} function that more closely resembles `Array.prototype.filter`.
+ * It takes two new parameters to its callback function: the current index, and the entire list.
+ *
+ * `filterIndexed` implementation is simple: `
+ *  const filterIndexed = R.addIndex(R.filter);
+ * `
+ *
+ * @func filterIndexed
+ * @memberOf RA
+ * @since {@link https://char0n.github.io/ramda-adjunct/2.31.0|v2.31.0}
+ * @category List
+ * @typedef Idx = Number
+ * @sig Filterable f => ((a, Idx, f a) -> Boolean) -> f a -> f a
+ * @param {Function} pred The predicate function
+ * @param {Array} list The collection to filter
+ * @return {Array} Filterable
+ * @see {@link http://ramdajs.com/docs/#addIndex|R.addIndex}, {@link http://ramdajs.com/docs/#filter|R.filter}
+ * @example
+ *
+ * const isValueGtIndex = (val, idx) => val > idx;
+ * RA.filterIndexed(isValueGtIndex, [5, 4, 3, 2, 1, 0]); //=> [5, 4, 3]
+ */
+const filterIndexed = addIndex(filter);
+
+export default filterIndexed;
diff --git a/src/index.js b/src/index.js
index ad6c38ebc2..7a7b6acd94 100644
--- a/src/index.js
+++ b/src/index.js
@@ -123,6 +123,7 @@ export { default as fnull } from './fnull';
 // List
 export { default as mapIndexed } from './mapIndexed';
 export { default as reduceIndexed } from './reduceIndexed';
+export { default as filterIndexed } from './filterIndexed';
 export { default as pickIndexes } from './pickIndexes';
 export { default as list } from './list';
 export { default as ensureArray } from './ensureArray';
diff --git a/types/index.d.ts b/types/index.d.ts
index 910ff03f07..57710ee8b5 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -14,6 +14,10 @@ declare namespace RamdaAdjunct {
         reduce<Acc>(fn: (acc: Acc, val: T) => Acc, initAcc: Acc): Acc;
     }
 
+    interface Filterable<T> {
+        filter(fn: (t: T) => Boolean): Filterable<T>;
+    }
+
     interface Semigroup {
         // https://www.typescriptlang.org/docs/handbook/advanced-types.html#polymorphic-this-types
         concat(other: this): this;
@@ -705,6 +709,28 @@ declare namespace RamdaAdjunct {
             (acc: TResult, list: R): TResult
         };
 
+        /**
+         * {@link http://ramdajs.com/docs/#filter|R.filter} function that more closely resembles `Array.prototype.filter`.
+         * It takes two new parameters to its callback function: the current index, and the entire list.
+         *
+         * `filterIndexed` implementation is simple: `
+         *  const filterIndexed = R.addIndex(R.filter);
+         * `
+         */
+        filterIndexed<T>(iterator: (elem: T, idx: number, list: T[]) => Boolean, list: ReadonlyArray<T>): T[];
+        filterIndexed<T>(iterator: (elem: T, idx: number, list: T[]) => Boolean): (list: ReadonlyArray<T>) => T[];
+        filterIndexed<T>(
+            iterator: (elem: T, idx: number, list: Dictionary<T>) => Boolean,
+            list: Dictionary<T>,
+        ): Dictionary<T>;
+        filterIndexed<T>(
+            iterator: (elem: T, idx: number, list: Dictionary<T>) => Boolean,
+        ): (list: Dictionary<T>) => Dictionary<T>;
+        filterIndexed<T>(iterator: (elem: T, idx: number, list: Filterable<T>) => Boolean, list: Filterable<T>): Filterable<T>;
+        filterIndexed<T>(iterator: (elem: T, idx: number, list: Filterable<T>) => Boolean): (list: Filterable<T>) => Filterable<Boolean>;
+        filterIndexed(iterator: (char: string, idx: number, str: string) => Boolean, str: string): string[];
+        filterIndexed(iterator: (char: string, idx: number, str: string) => Boolean): (str: string) => string[];
+
         /**
          * Given an `Iterable`(arrays are `Iterable`), or a promise of an `Iterable`,
          * which produces promises (or a mix of promises and values),


Test patch:
diff --git a/test/filterIndexed.js b/test/filterIndexed.js
new file mode 100644
index 0000000000..d7d0b0b6b2
--- /dev/null
+++ b/test/filterIndexed.js
@@ -0,0 +1,106 @@
+import { assert } from 'chai';
+
+import * as RA from '../src';
+
+describe('filterIndexed', function () {
+  const listXf = {
+    '@@transducer/init': () => [],
+    '@@transducer/step': (acc, x) => acc.concat([x]),
+    '@@transducer/result': (x) => x,
+  };
+
+  const isEven = (x) => x % 2 === 0;
+  const isValueGtIndex = (val, index) => val > index;
+
+  context('R.filter', function () {
+    it('should filter arrays based on a simple predicate', function () {
+      assert.deepEqual(RA.filterIndexed(isEven, []), []);
+      assert.deepEqual(RA.filterIndexed(isEven, [0, 1, 2, 3, 4]), [0, 2, 4]);
+    });
+
+    it('should filter objects based on a simple predicate', function () {
+      assert.deepEqual(RA.filterIndexed(isEven, {}), {});
+      assert.deepEqual(
+        RA.filterIndexed(isEven, { a: 0, b: 1, c: 2, d: 3, e: 4 }),
+        { a: 0, c: 2, e: 4 }
+      );
+    });
+
+    it('should filter strings based on a simple predicate', function () {
+      const isVowel = (v) => 'aeiou'.includes(v.toLowerCase());
+
+      assert.deepEqual(RA.filterIndexed(isVowel, ''), []);
+      assert.deepEqual(
+        RA.filterIndexed(
+          isVowel,
+          'The quick brown fox jumps over the lazy dog'
+        ),
+        ['e', 'u', 'i', 'o', 'o', 'u', 'o', 'e', 'e', 'a', 'o']
+      );
+    });
+
+    it('should dispatch to objects that implement `filter`', function () {
+      const obj = {
+        content: [0, 1, 2, 3, 4],
+        filter(f) {
+          return this.content.filter(f);
+        },
+      };
+
+      assert.deepEqual(RA.filterIndexed(isEven, obj), [0, 2, 4]);
+    });
+
+    it('should dispatch to transformer objects', function () {
+      const result = RA.filterIndexed(isEven, listXf);
+
+      assert.deepEqual(result.xf, listXf);
+      assert.strictEqual(result.f(0), true);
+    });
+
+    it('should throw a TypeError on null and undefined', function () {
+      assert.throws(() => RA.filterIndexed(isEven, null), TypeError);
+
+      assert.throws(() => RA.filterIndexed(isEven, undefined), TypeError);
+    });
+
+    it('should support transducer-style composition', function () {
+      const fvalGtIndex = RA.filterIndexed(isValueGtIndex);
+      const feven = RA.filterIndexed(isEven);
+      const xcomp = feven(fvalGtIndex(listXf));
+
+      assert.deepEqual(xcomp.xf.xf, listXf);
+      assert.strictEqual(xcomp.xf.f(11, 12), false);
+      assert.strictEqual(xcomp.f(2), true);
+    });
+  });
+
+  it('should correctly use val and idx arguments', function () {
+    const list = [5, 4, 3, 2, 1, 0];
+
+    assert.deepEqual(RA.filterIndexed(isValueGtIndex, list), [5, 4, 3]);
+  });
+
+  context('given third argument', function () {
+    const initialList = ['f', 'o', 'o', 'b', 'a', 'r'];
+
+    specify(
+      'the third argument should be the same reference that original list',
+      function () {
+        RA.filterIndexed((_, __, list) => {
+          assert.strictEqual(list, initialList);
+
+          return true;
+        }, initialList);
+      }
+    );
+  });
+
+  it('should be curried', function () {
+    const list = [1, 2, 3];
+
+    assert.sameOrderedMembers(
+      RA.filterIndexed(isEven)(list),
+      RA.filterIndexed(isEven, list)
+    );
+  });
+});
diff --git a/types/test/filterIndexed.ts b/types/test/filterIndexed.ts
new file mode 100644
index 0000000000..985d62313f
--- /dev/null
+++ b/types/test/filterIndexed.ts
@@ -0,0 +1,23 @@
+import * as RA from 'ramda-adjunct';
+
+interface Dictionary<T> { [key: string]: T; }
+
+const isValueGtIndex = (value: number, index: number, _: number[]): Boolean => value > index;
+const isEven = (n: number, _: number, __: Dictionary<number>): Boolean => n % 2 === 0;
+const isVowel = (char: string, _: number, __: string) => 'aeiou'.includes(char.toLowerCase());
+
+RA.filterIndexed<number>(isValueGtIndex)([1, 2, 3, 4]); // $ExpectType number[]
+RA.filterIndexed<number>(isValueGtIndex, [1, 2, 3, 4]); // $ExpectType number[]
+
+RA.filterIndexed<number>(isEven)({ a: 0, b: 1, c: 2, d: 3, e: 4 }); // $ExpectType Dictionary<number>
+RA.filterIndexed<number>(isEven, { a: 0, b: 1, c: 2, d: 3, e: 4 }); // $ExpectType Dictionary<number>
+
+RA.filterIndexed(isVowel)('The quick brown fox jumps over the lazy dog'); // $ExpectType string[]
+RA.filterIndexed(isVowel, 'The quick brown fox jumps over the lazy dog'); // $ExpectType string[]
+
+RA.filterIndexed<number>(isVowel)([1, 2, 3, 4]); // $ExpectError
+RA.filterIndexed<number>(isVowel, [1, 2, 3, 4]); // $ExpectError
+RA.filterIndexed<number>(isValueGtIndex)({ a: 0, b: 1, c: 2, d: 3, e: 4 }); // $ExpectError
+RA.filterIndexed<number>(isValueGtIndex, { a: 0, b: 1, c: 2, d: 3, e: 4 }); // $ExpectError
+RA.filterIndexed(isEven)('The quick brown fox jumps over the lazy dog'); // $ExpectError
+RA.filterIndexed(isEven, 'The quick brown fox jumps over the lazy dog'); // $ExpectError