Repository: char0n/ramda-adjunct (js)

Issue:
zipObjWith
**Is your feature request related to a problem? Please describe.**

Same as https://ramdajs.com/docs/#zipObj but accepts a binary function as the first argument. This binary function returns an object which gets merged into final resulting object.

**Describe the solution you'd like**

```js
RA.zipObjWith((value, key) => [key, `${key}${value + 1}`]), ['a', 'b', 'c'], [1, 2, 3]); // => {a: 'a2', b: 'b3', c: 'c4'}
```

**Describe alternatives you've considered**

```js
RA.zipObjWith((value, key) => ({[key]: value}), ['a', 'b', 'c'], [1, 2, 3]); // => {'a': 1, 'b': 2, 'c': 3}
RA.zipObjWith((value, key) => `key${value + 1}`, ['a', 'b', 'c'], [1, 2, 3]); // => {'a': a2, 'b': b3, 'c': c4}
```

Before we go for implementation we have to decide which implementation makes more sense and have higher value.


Pull request description:
feat(object): add zipObjWith
Closes #1129


Patch:
diff --git a/RELEASE.md b/RELEASE.md
index 4d3a37e03f..26822979de 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -48,6 +48,25 @@ github release, please refer to this [page](https://help.github.com/articles/cre
  $ npm publish
 ```
 
+## Publish to Github Package Registry
+
+Read [this document](https://help.github.com/en/github/managing-packages-with-github-package-registry/configuring-npm-for-use-with-github-package-registry) to understand how to configure your npm to publish to Github Package Registry.
+
+Temporarily add the following entry into the `package.json` file. This modifications will set tell npm that the primary 
+registry is Github Package Registry.
+
+```json
+"publishConfig": {
+  "registry": "https://npm.pkg.github.com/"
+}
+```
+
+Now publish to GitHub Package Registry:
+
+```sh
+ $ npm publish
+```
+
 ## Bump the version
 
 Bump the `package.json` and `package-lock.json` version by incrementing **MAJOR**, **MINOR** or **PATCH** part of the semver version.
diff --git a/src/index.js b/src/index.js
index b99ab646ae..5ae0648f2c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -163,6 +163,7 @@ export { default as spreadPath } from './spreadPath';
 export { default as flattenProp } from './flattenProp';
 export { default as flattenPath } from './flattenPath';
 export { default as unzipObjWith } from './unzipObjWith';
+export { default as zipObjWith } from './zipObjWith';
 // Relation
 export { default as lensEq } from './lensEq';
 export { default as lensNotEq } from './lensNotEq';
diff --git a/src/zipObjWith.js b/src/zipObjWith.js
new file mode 100644
index 0000000000..ba48062c0e
--- /dev/null
+++ b/src/zipObjWith.js
@@ -0,0 +1,31 @@
+import { apply, curryN, fromPairs, map, pipe, zip } from 'ramda';
+
+/**
+ * Creates a new object out of a list of keys and a list of values by applying the function
+ * to each equally-positioned pair in the lists.
+ * Key/value pairing is truncated to the length of the shorter of the two lists.
+ *
+ * @func zipObjWith
+ * @memberOf RA
+ * @category Object
+ * @since {@link https://char0n.github.io/ramda-adjunct/2.22.0|v2.22.0}
+ * @sig (b, a) -> [k, v] -> [a] -> [b] -> { k: v }
+ * @param {Function} fn The function to transform each value-key pair
+ * @param {Array} keys Array to transform into the properties on the output object
+ * @param {Array} values Array to transform into the values on the output object
+ * @return {Object}  The object made by pairing up and transforming same-indexed elements of `keys` and `values`.
+ * @see {@link https://ramdajs.com/docs/#zipObj|zipObj}, {@link RA.unzipObjWith|unzipObjWith}
+ * @example
+ *
+ * RA.zipObjWith((value, key) => [key, `${key}${value + 1}`]), ['a', 'b', 'c'], [1, 2, 3]);
+ *  // => { a: 'a2', b: 'b3', c: 'c4' }
+ */
+const zipObjWith = curryN(3, (fn, keys, values) =>
+  pipe(
+    zip,
+    map(apply(fn)),
+    fromPairs
+  )(values, keys)
+);
+
+export default zipObjWith;
diff --git a/types/index.d.ts b/types/index.d.ts
index 191c3f73de..cb34f10307 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -820,6 +820,18 @@ declare namespace RamdaAdjunct {
         flattenProp(prop: string | number, obj: object): object;
         flattenProp(prop: string | number): (obj: object) => object;
 
+        /**
+         * Creates a new object out of a list of keys and a list of values by applying the function
+         * to each equally-positioned pair in the lists.
+         * Key/value pairing is truncated to the length of the shorter of the two lists.
+         */
+        zipObjWith<T, U, V>(fn: (value: T, key: U) => [string, V], keys: U[], values: T[]): { [k: string]: V };
+        zipObjWith<T, U, V>(fn: (value: T, key: U) => [string, V]): (keys: U[], values: T[]) => { [k: string]: V };
+        zipObjWith<T, U, V>(fn: (value: T, key: U) => [string, V]): {
+            (keys: U[], values: T[]): { [k: string]: V };
+            (keys: U[]): (values: T[]) => { [k: string]: V };
+        };
+
         /**
          * Creates a new list out of the supplied object by applying the function to each key/value pairing.
          */


Test patch:
diff --git a/test/zipObjWith.js b/test/zipObjWith.js
new file mode 100644
index 0000000000..4dc570a210
--- /dev/null
+++ b/test/zipObjWith.js
@@ -0,0 +1,61 @@
+import { assert } from 'chai';
+import * as R from 'ramda';
+
+import * as RA from '../src';
+
+describe('zipObjWith', function() {
+  it('should zip a pair of lists into an object', function() {
+    const keys = ['packageA', 'packageB', 'packageC'];
+    const values = [
+      { name: 'packageA', version: '1.0.9' },
+      { name: 'packageB', version: '1.6.1' },
+      { name: 'packageC', version: '1.6.0' },
+    ];
+    const fn = (v, k) => [k, { version: v.version }];
+
+    const actual = RA.zipObjWith(fn, keys, values);
+
+    const expected = {
+      packageA: { version: '1.0.9' },
+      packageB: { version: '1.6.1' },
+      packageC: { version: '1.6.0' },
+    };
+
+    assert.deepEqual(actual, expected);
+  });
+
+  context('given empty key/value lists', function() {
+    specify('should return an empty object', function() {
+      const fn = (v, k) => [k, v];
+      const keys = [];
+      const values = [];
+      const expected = {};
+
+      assert.deepEqual(RA.zipObjWith(fn, keys, values), expected);
+    });
+  });
+
+  context('given key/value lists of unequal length', function() {
+    specify('should truncate to the length of the shortest list', function() {
+      const fn = (v, k) => [k, v];
+      const long = ['a', 'b', 'c'];
+      const short = ['x', 'y'];
+
+      assert.deepEqual(RA.zipObjWith(fn, long, short), { a: 'x', b: 'y' });
+      assert.deepEqual(RA.zipObjWith(fn, short, long), { x: 'a', y: 'b' });
+    });
+  });
+
+  it('should curry', function() {
+    const fn = (v, k) => [k, `${k}${v + 1}`];
+    const keys = ['a', 'b', 'c', 'd'];
+    const values = [1, 2, 3];
+    const expected = { a: 'a2', b: 'b3', c: 'c4' };
+
+    assert.deepEqual(RA.zipObjWith(fn, keys, values), expected);
+    assert.deepEqual(RA.zipObjWith(fn, keys)(values), expected);
+    assert.deepEqual(RA.zipObjWith(fn)(keys)(values), expected);
+    assert.deepEqual(RA.zipObjWith()(fn)(keys)(values), expected);
+    assert.deepEqual(RA.zipObjWith(fn)(R.__, values)(keys), expected);
+  });
+});
diff --git a/types/test/zipObjWith.ts b/types/test/zipObjWith.ts
new file mode 100644
index 0000000000..ed674514bc
--- /dev/null
+++ b/types/test/zipObjWith.ts
@@ -0,0 +1,10 @@
+import * as RA from 'ramda-adjunct';
+
+RA.zipObjWith((v, t) => [t, 1], ['a'], [2]); // $ExpectType { [k: string]: number; }
+RA.zipObjWith((v, k) => ['a', 'b'])([1], ['b']); // $ExpectType { [k: string]: string; }
+RA.zipObjWith<string, boolean, number>((v, k) => ['a', 1], [true], ['b']); // $ExpectType { [k: string]: number; }
+
+RA.zipObjWith((v, k) => true, ['a'], [2]); // $ExpectError
+RA.zipObjWith((v, k) => [k, v], {}, false); // $ExpectError
+RA.zipObjWith<boolean, boolean, string>((v, k) => ['a', 1], [true], ['b']); // $ExpectError
+RA.zipObjWith((v, k) => [k, v], undefined, undefined); // $ExpectError