Converting Array of Pairs into 2 Separate Arrays Without Iteration - javascript

I have an array of pairs that looks like this [[x,y], [x,y] ... ]. I want to format it into an Object where the values are arrays of x and y values like so {keys: [x1, x2, x3 ...], values: [y1, y2, y3 ... ]}.
Are there any array/object operations to complete this operation without iterating through the original list?

The easiest and safest way is to reduce the array to an object, although it requires a loop:
const input = [[3, 300], [2, 200], [1, 100]];
const result = input.reduce((acc, [key, val]) => {
acc.keys.push(key);
acc.values.push(val);
return acc;
}, { keys: [], values: [] });
console.log(result);
I wouldn't actually use the convert to object / Map method (under Original Answer), because it has a serious caveat - duplicate entries the has the same keys would be overridden.
For example:
const input = [[3, 300], [3, 200], [3, 100]];
const obj = Object.fromEntries(input);
const result = { keys: Object.keys(obj), values: Object.values(obj) };
console.log(result);
Original Answer
Building on top of pilchard's answer, I would convert the array to a Map, and then take the Map's keys, and values. I would use a Map, and not an object, because object's keys are always strings, and if the values are integers, the object would also be sorted by their value. A Map would preserve the original type, and won't reorder them.
const input = [[3, 300], [4, 200], [1, 100]];
const map = new Map(input);
const result = { keys: [...map.keys()], values: [...map.values()] };
console.log(result);
An example of converting the same structure to an object:
const input = [[3, 300], [4, 200], [1, 100]];
const obj = Object.fromEntries(input);
const result = { keys: Object.keys(obj), values: Object.values(obj) };
console.log(result);

The process is iterative, but you can hide it by using existing Object methods: Object.fromEntries(), Object.keys(), Object.values()
const input = [['a', 1], ['b', 2], ['c', 3]];
const obj = Object.fromEntries(input);
const result = { keys: Object.keys(obj), values: Object.values(obj) };
console.log(result);
Ori Drori's refinement using a Map is more robust not only for numeric values but for any type.
const input = [[new Date(), { y: 1 }], [new Date(), { y: 2 }], [new Date(), { y: 3 }]];
const map = new Map(input);
const result = { keys: [...map.keys()], values: [...map.values()] };
console.log(result);
console.log(result.keys[0].valueOf());

No space left for losers, but just another way
const input = [[3, 300], [4, 200], [1, 100]];
const result = { keys: Array.from(input, x => x[0]), values: Array.from(input, x => x[1]) };
console.log(result);

Related

Sort an object by key names specified in an array

I have an object with simple key value pairs and an array with just key names. I would like to sort the objects keys by the order of the keys in the array.
A simplified example:
const obj = {
"bread": 11,
"butter": 6,
"milk": 40,
}
const orderedKeys = ["milk", "butter", "bread"]
const expectedResult = {
"milk": 40,
"butter": 6,
"bread": 11,
}
In this example I know the amount of keys in both the array and the object, however in reality I don't know the amount and in reality the object can also have more keys than specified in the array. In this case I just want the unknown keys at the end of the object.
What would be the cleanest way to solve this problem?
You can take advantage of Array.prototype.reduce which allow to perform different manipulation on array and return another kind of object or array
const obj = {
"bread": 11,
"butter": 6,
"milk": 40,
}
const orderedKeys = ["milk", "butter", "bread"]
const expectedResult = orderedKeys.reduce((accumulator, current) => {
return {...accumulator, [current]: obj[current]};
}, {})
console.log(expectedResult);
If you want to add key which aren't present in the orderedKey but are present inside of the obj you can perform it like this
const obj = {
"bread": 11,
"butter": 6,
"milk": 40,
"cheese": 5
}
const orderedKeys = ["milk", "butter", "bread"]
let expectedResult = orderedKeys.reduce((accumulator, current) => {
return {...accumulator, [current]: obj[current]};
}, {});
expectedResult = Object.assign({}, expectedResult, obj);
console.log(expectedResult);
The simplest way is probably this:
map the ordered keys to [key, value] tuples
Reconstruct the object from those using Object.fromEntries
...spread the remaining object properties
const obj = {
unknown: 15,
bread: 11,
butter: 6,
milk: 40,
}
const orderedKeys = ['milk', 'butter', 'bread']
const orderedObj = {
...Object.fromEntries(orderedKeys.map(k => [k, obj[k]])),
...obj,
}
console.log(orderedObj)
However, relying on the order of keys in an object is generally a bad idea, because JavaScript objects are typically considered to be unordered ({ a: 1, b: 2 } is usually considered equivalent to { b: 2, a: 1 }). Whether or not they're ordered in practice depends somewhat on implementation. For this reason, and depending on your use case, you could consider using a Map instead:
new Map(orderedObj)

append static elements in object to each object in array of objects javascript

How do I take this object and array.
const data = {
type: "hello",
point: 1.8
};
const raw = [
{
x: [1, 2],
y: [-1.1, -1.2]
},
{
x: [14, 24],
y: [-1.14, 1.24]
}
];
Then "append" the items in the data object to each object in the raw array. The desired end result is;
const result = [
{
x: [1, 2],
y: [-1.1, -1.2],
type: "hello",
point: 1.8
},
{
x: [14, 24],
y: [-1.14, 1.24],
type: "hello",
point: 1.8
}
];
I tried using map but this object works with arrays, then I looked at using Object.keys but am having no luck.
Use map with spreading:
const data = {type:"hello",point:1.8};
const raw = [{x:[1,2],y:[-1.1,-1.2]},{x:[14,24],y:[-1.14,1.24]}];
const result = raw.map(e => ({ ...e, ...data }));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: auto; }
ES5 syntax:
var data = {type:"hello",point:1.8};
var raw = [{x:[1,2],y:[-1.1,-1.2]},{x:[14,24],y:[-1.14,1.24]}];
var result = raw.map(function(e) {
return Object.assign({}, e, data);
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: auto; }
map is indeed the tool you want. I'd probably combine it with destructuring in the map callback parameter list and property spread in the result value:
const result = raw.map(({x, y}) => ({x, y, ...data}));
Live Copy:
const data = {
type: "hello",
point: 1.8
};
const raw = [
{
x: [1, 2],
y: [-1.1, -1.2]
},
{
x: [14, 24],
y: [-1.14, 1.24]
}
];
const result = raw.map(({x, y}) => ({x, y, ...data}));
console.log(result);
Note that if data had any properties whose values were objects (data in your example doesn't), using spread will copy only the references to the objects, it won't make deep copies. So all of your result objects should share them. You could deep copy if that were relevant.
I found a solution;
const data = {type:"hello",point:1.8};
const raw = [{x:[1,2],y:[-1.1,-1.2]},{x:[14,24],y:[-1.14,1.24]}];
const result = raw.map(r => Object.assign(r, data));
console.log(result);
Some feedback on this approach would be appreciated. Looking a the solutions provided now. Thank you all.

multidimensional default array

I've got two multidimensional arrays:
const defaultData = [
["ad", 0],
["ae", 0],
["af", 0]
]
const data = [
["az", 20],
["ad", 50]
]
The desired result is:
const expected = [
["ad", 50],
["ae", 0],
["af", 0]
]
Here az is ignored because its not valid because its not in the defaultData.
ad is valid and therefore the 0 is overwritten with the value of 50.
How would I best produce this result. Thanks?
Please assume neither array is sorted. The expected result need not be sorted either.
Either vanilla JS or Lodash is fine.
Convert the data to an object (dataByKey) with _.fromPairs(). Map the defaultData, and check if the "key" (index 0) exists in dataByKey. If it is, create a new array with the value from dataByKey. If not return the original array.
const defaultData = [
["ad", 0],
["ae", 0],
["af", 0]
]
const data = [
["az", 20],
["ad", 50]
]
const dataByKey = _.fromPairs(data)
const result = defaultData.map(o => o[0] in dataByKey ? [o[0], dataByKey[o[0]]] : o)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
Use map to iterate over the defaultData, and find to match each iterated array against data:
const defaultData = [['ad', 0],['ae', 0],['af', 0]];
const data = [['az', 20],['ad', 50]];
const expected = defaultData.map(arr => {
// Destructure the first element from `arr`
// and assign it to `code`
const [ code ] = arr;
// `find` the array in `data` where the first element
// matches `code`
const found = data.find(el => el[0] === code);
// If `found` is undefined return the whole array
// otherwise return a new array with the code and the value
// from the found array
return found ? [code, found[1]] : arr;
});
console.log(expected);
You could first create one object from data and then use map method on defaultData to create new array and get value from data by key.
const defaultData = [
["ad", 0],
["ae", 0],
["af", 0]
]
const data = [
["az", 20],
["ad", 50]
].reduce((r, [key, value]) => {
r[key] = (r[key] || 0) + value;
return r;
}, {})
const expected = defaultData.map(([key, value]) => {
return [key, key in data ? data[key] : value]
})
console.log(expected)
Try this out, I am using map() to create a new Array and find() to find elements from the second array.
I compare the first index item of each sub array from the default array, if any of the items from second array has the same element in its first index then I return it, if none matches, nothing is returned.
If I have a matched item then I create a new array using its elements.
If I don't have a matched item I create a new using the default elements of the default item.
const defaultData = [
["ad", 0],
["ae", 0],
["af", 0]
]
const data = [
["az", 20],
["ad", 50]
]
let newData = defaultData.map(eachDefault => {
let found = data.find(eachData => {
if (eachData[0] == eachDefault[0]) {
return true
}
})
if (found) {
return [...found]
}
return [...eachDefault]
})
console.log(newData)
You can use map/reduce like this:
const defaultData = [
["ad", 0],
["ae", 0],
["af", 0]
]
const data = [
["az", 20],
["ad", 50]
]
const result = defaultData.map(arr => data.reduce(([dKey, dVal], [key, val]) => [ dKey, dKey === key ? val : dVal ], arr));
console.log(result);

How can I turn an array into an object with the name being the first value in the array and the properties an array of the subarrays?

What is the best way to take a multidimensional array with an unknown list of elements and group it into an object to remove repeated values in the first element of the subarray:
For example, I'd like to turn this:
const arr = [[a, 1, 4], [b, 3, 4], [c, 1, 7], [a, 2, 5], [c, 3, 5]]
Into this:
arrResult = {a:[[1, 4],[2, 5]], b:[[3, 4]], c:[[1, 7],[3, 5]]}
I thought about sorting this and then splitting it or running some kind of reduce operation but couldn't figure out exactly how to accomplish it.
You only need to use reduce (and slice), no need for sorting or splitting
var arr = [['a', 1, 4], ['b', 3, 4], ['c', 1, 7], ['a', 2, 5], ['c', 3, 5]];
var arrResult = arr.reduce((result, item) => {
var obj = result[item[0]] = result[item[0]] || [];
obj.push(item.slice(1));
return result;
}, {});
console.log(JSON.stringify(arrResult));
You can use reduce like this:
const arr = [["a", 1, 4], ["b", 3, 4], ["c", 1, 7], ["a", 2, 5], ["c", 3, 5]];
var result = arr.reduce((obj, sub) => {
var key = sub[0]; // key is the first item of the sub-array
if(obj[key]) obj[key].push(sub.slice(1)); // if the there is already an array for that key then push this sub-array (sliced from the index 1) to it
else obj[key] = [sub.slice(1)]; // otherwise create a new array that initially contain the sliced sub-array
return obj;
}, {});
console.log(result);
you could use reduce and destructuring like this:
const arr = [['a', 1, 4],['b', 3, 4],['c', 1, 7],['a', 2, 5],['c', 3, 5]]
function sub(arr) {
return arr.reduce((obj, [key, ...value]) => {
obj[key] ? obj[key].push(value) : obj[key] = [value]
return obj
}, {})
}
console.log(sub(arr));
I like this solution better because it abstracts away the collation but allows you to control how items are collated using a higher-order function.
Notice how we don't talk about the kind or structure of data at all in the collateBy function – this keeps our function generic and allows for it to work on data of any shape.
// generic collation procedure
const collateBy = f => g => xs => {
return xs.reduce((m,x) => {
let v = f(x)
return m.set(v, g(m.get(v), x))
}, new Map())
}
// generic head/tail functions
const head = ([x,...xs]) => x
const tail = ([x,...xs]) => xs
// collate by first element in an array
const collateByFirst = collateBy (head)
// your custom function, using the collateByFirst collator
// this works much like Array.prototype.reduce
// the first argument is your accumulator, the second argument is your array value
// note the acc=[] seed value used for the initial empty collation
const foo = collateByFirst ((acc=[], xs) => [...acc, tail(xs)])
const arr = [['a', 1, 4], ['b', 3, 4], ['c', 1, 7], ['a', 2, 5], ['c', 3, 5]]
let collation = foo(arr);
console.log(collation.get('a')) // [ [1,4], [2,5] ]
console.log(collation.get('b')) // [ [3,4] ]
console.log(collation.get('c')) // [ [1,7], [3,5] ]
Of course you could write it all in one line if you didn't want to give names to the intermediate functions
let collation = collateBy (head) ((acc=[], xs) => [...acc, tail(xs)]) (arr)
console.log(collation.get('a')) // [ [1,4], [2,5] ]
Lastly, if you want the object, simply convert the Map type to an Object
let obj = Array.from(collation).reduce((acc, [k,v]) =>
Object.assign(acc, { [k]: v }), {})
console.log(obj)
// { a: [ [1,4], [2,5] ],
// b: [ [3,4] ],
// c: [ [1,7], [3,5] ] }
Higher order functions demonstrate how powerful generic procedures likes collateBy can be. Here's another example using the exact same collateBy procedure but performing a very different collation
const collateBy = f => g => xs => {
return xs.reduce((m,x) => {
let v = f(x)
return m.set(v, g(m.get(v), x))
}, new Map())
}
const collateEvenOdd = collateBy (x => x % 2 === 0 ? 'even' : 'odd')
const sumEvenOdd = collateEvenOdd ((a=0, b) => a + b)
let data = [2,3,4,5,6,7]
let collation = sumEvenOdd (data)
let even = collation.get('even')
let odd = collation.get('odd')
console.log('even sum', even) // 2 + 4 + 6 === 12
console.log('odd sum', odd) // 3 + 5 + 7 === 15

Create one array from 2 Arrays with keys x & y

I have 2 Arrays xDates and yMentions
xDates
[1453766400000, 1453852800000, 1453939200000...
yMentions
[5160, 5240, 7090...
Goal is an Array like so:
[
{
x: 1453766400000,
y: 5160
},
...
]
Trying to use Ramda Zip thought zipObj would be what I need, but the following produces just 1 object:
R.zipObj(['x', 'x', 'x'], [1, 2, 3]); => {"x": 3}
Figured perhaps I run R.zipObj on the x then the y arrays, then zip them together then set that as the Array for mentionsPointsArray below:
const createMentionPoints = (frequencyPoints, termsData) => {
const yMentions = termsData.mentions;
const propX = R.prop('x');
const xPointsFromFrequency = R.map(propX, frequencyPoints);
console.log('xDates', xPointsFromFrequency)
console.log('yMentions', yMentions)
const mentionsPointsArray = []
return frequencyPoints;
};
You should use Array#map function.
The map() method creates a new array with the results of calling a provided function on every element in this array.The provided function is a callback.
The elements from the result array are objects, like this: {"x":item, "y":yMentions[i]}.
var xDates=[1453766400000, 1453852800000, 1453939200000];
var yMentions=[5160, 5240, 7090];
console.log(xDates.map(function(elem,i){
return {"x":elem,"y":yMentions[i]}
}));
The ramda solution http://ramdajs.com/docs/#zipWith
var createPoints = (x, y) => {
return { x: x, y: y }
};
R.zipWith(createPoints, [1, 2, 3], ['a', 'b', 'c']);
// returns: [{"x": 1, "y": "a"}, {"x": 2, "y": "b"}, {"x": 3, "y": "c"}]
I think the cleanest point-free version would be:
const data1 = ['a', 'b', 'c']
const data2 = [1, 2, 3]
R.zipWith(R.objOf, data1, data2)
Please have a look at a working REPL here

Categories

Resources