I have a bunch of arrays in this form:
var myRows = [
[{idx: 0, val: 90}, {idx: 1, val: 75}, {idx: 2, val: 35}],
[{idx: 0, val: 50}, {idx: 1, val: 17}, {idx: 2, val: 95}],
[{idx: 0, val: 10}, {idx: 1, val: 24}, {idx: 2, val: 80}]
// ...
];
Lets say I would like to sort the first row ascending by val, so it becomes:
[{idx: 2, val: 35}, {idx: 1, val: 75}, {idx: 0, val: 90}]
Is there an easy way to sort the remaining arrays, so that their order matches the idx-order of the sorted first row?
myArrays = [
[{idx: 2, val: 35}, {idx: 1, val: 75}, {idx: 0, val: 90}]
, [{idx: 2, val: 95}, {idx: 1, val: 17}, {idx: 0, val: 50}]
, [{idx: 2, val: 80}, {idx: 1, val: 24}, {idx: 0, val: 10}]
// ...
];
Maybe this is even possible without the idx property?
You could use sorting with map and apply the mapping for all items.
This proposal saves the indices, order the array and applies the order to all other arrays as well.
// the array to be sorted
var list = [[{ idx: 0, val: 90 }, { idx: 1, val: 75 }, { idx: 2, val: 35 }], [{ idx: 0, val: 50 }, { idx: 1, val: 17 }, { idx: 2, val: 95 }], [{ idx: 0, val: 10 }, { idx: 1, val: 24 }, { idx: 2, val: 80 }]];
// temporary array holds objects with position and sort-value
var mapped = list[0].map(function (el, i) {
return { index: i, value: el.val };
})
// sorting the mapped array containing the reduced values
mapped.sort(function (a, b) {
return a.value - b.value;
});
// rearrange all items in list
list.forEach(function (a, i, aa) {
aa[i] = mapped.map(function (el) {
return a[el.index];
});
});
console.log(list);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could do something like this.
var order = myRows[0].map(function(e) { return e.idx })
myRows.forEach(function(row) {
row.sort(function(a,b) {
return order.indexOf(a.idx) - order.indexOf(b.idx);
});
});
This is very simple code just to demonstate the idea.
It will probably be slow for very large arrays.
You can do the following which does,
Sort the first row of array1, and store their idxes in a temporary array2
Assign the remaining array with a temp property according to the first idx3
Sort the remaining array based on their temp property4 (Which is based on the first array)
Remove the temp property5
E.g.
var filteredRows = [];
var myRows = [
[{idx: 0, val: 90}, {idx: 1, val: 75}, {idx: 2, val: 35}],
[{idx: 0, val: 50}, {idx: 1, val: 17}, {idx: 2, val: 95}],
[{idx: 0, val: 10}, {idx: 1, val: 24}, {idx: 2, val: 80}]
];
/* 1. Sort the first row */
myRows[0].sort(function(a, b) {
return a.val - b.val;
});
filteredRows.push(myRows[0]);
/* 2. Get indexes */
var idxs = [];
for (var obj of myRows[0]) {
idxs.push(obj.idx);
}
/* Handle the remaining array */
myRows.slice(1).map(function (val) {
/* 3. Assign temp value */
val.map(function (obj, i) {
obj.temp = idxs[i];
});
/* 4. Sort them */
val.sort(function (a, b) {
return a.temp - b.temp;
});
/* 5. Remove temp value */
val.map(function (obj, i) {
delete obj.temp;
});
});
console.log(JSON.stringify(myRows));
When you drop the idx property, you can just use an array:
// Function copied from here: http://stackoverflow.com/a/36164530/5710637
var transpose = m => m[0].map((x,i) => m.map(x => x[i]))
var sortByRow = 0
var myRows = [
[90, 75, 35],
[50, 17, 95],
[10, 24, 80]
]
var myCols = transpose(myRows)
myCols.sort((x, y) => x[sortByRow] - y[sortByRow])
myRows = transpose(myCols)
console.log(myRows)
Use a hash table to create a sorting criteria based on the first row - see demo below:
var myRows=[[{idx:0,val:90},{idx:1,val:75},{idx:2,val:35}],[{idx:0,val:50},{idx:1,val:17},{idx:2,val:95}],[{idx:0,val:10},{idx:1,val:24},{idx:2,val:80}]];
// sort the first row (as desired)
myRows[0].sort((a,b) => a.val - b.val);
myRows.forEach(function(c,i){
if(i === 0){
// create order criteria based on first row
c.forEach(function(e, k){
this[e.idx] = k;
});
} else {
c.sort(function(a,b) {
return this[a.idx] - this[b.idx];
});
}
}, Object.create(null));
console.log(myRows);
.as-console-wrapper{top:0;max-height:100%!important;}
Related
im trying to add counting number for duplicate in JS.
and i am completely stack in this case below.
i need to compare objects with two value (x, y) and if there are same values of (x, y) add count 1 on new objects.
is there any way to convert data to newData such as below?
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const newData = [
{x: 1, y:1 ,count:2}
{x: 2, y:2 ,count:1}
]
use .reduce() function
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const output = data.reduce((acc, curr) => {
curr.count = 1;
const exists = acc.find(o => o.x === curr.x && o.y === curr.y);
exists ? exists.count++ : acc.push(({ x, y, count } = curr));
return acc;
}, []);
console.log(output);
.as-console-wrapper { max-height: 100% !important; top: 0; }
One way of doing so, is to create a map with the x and y values, and increment the count accordingly, then convert the map into an array:
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const makeXYMap = (data) => data.reduce((acc, cur) => {
const { x, y } = cur;
const entry = acc[`${x}_${y}`];
if (entry) {
acc[`${x}_${y}`] = {...entry, count: entry.count + 1};
} else {
acc[`${x}_${y}`] = { x, y, count: 1 };
}
return acc;
}, {});
const makeArray = (XYMap) => Object.values(XYMap);
console.log(makeArray(makeXYMap(data)));
Note that complexity wise, this solution is a O(N).
https://jsfiddle.net/9o35neg7/
const data = [
{ id: 1, x: 1, y: 1 },
{ id: 2, x: 2, y: 2 },
{ id: 3, x: 1, y: 1 },
// .. so on ..
];
const countedData = data.reduce((acc, { x, y }, index, array) => {
acc[`x${x}y${y}`] = {
x,
y,
count: (acc[`x${x}y${y}`] ? acc[`x${x}y${y}`].count : 0) + 1
};
return index === (array.length - 1) ? Object.values(acc) : acc;
}, {});
console.log(countedData);
Use forEach and build an object with key (made of x, y) and values (aggregate count). Get the Object.values to get the results as array.
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const counts = (arr, res = {}) => {
arr.forEach(({x , y}) =>
res[`${x}-${y}`] = { x, y, count: (res[`${x}-${y}`]?.count ?? 0) + 1 })
return Object.values(res);
}
console.log(counts(data))
So simplified code.
var a = [
{ name: "first", num: 1 },
{ name: "first", num: 2 },
{ name: "first", num: 3 },
{ name: "first", num: 4 },
{ name: "first", num: 5 },
{ name: "first", num: 6 },
{ name: "first", num: 7 },
{ name: "first", num: 8 },
{ name: "first", num: 9 }
];
var b = a.filter(function(el) {
return el.num % 2 == 0;
});
console.log("a1", a); // [1, 20, 3, 40, 5, 60, 7, 80, 9]
console.log("b1", b); // [20, 40, 60, 80]
for (let i = 0; i < b.length; i++) {
b[i].num = b[i].num * 10;
}
console.log("a2", a); // [1, 20, 3, 40, 5, 60, 7, 80, 9]
console.log("b2", b); // [20, 40, 60, 80]
My new understanding is the array element contains a reference to an object, not the object. What are some ways to get those objects duplicated?
Filter, then build new objects from the filtered array and put the new things in a new array?
Use some method I'm not currently familiar with?
Redesign the code to stop using objects in an array?
Also, what's up with console.log() showing the variables have changed when placed before the for loop?
If you wish to duplicate the objects inside the array, you should use the map function.
var b = a.filter(val => val.num %2 === 0).map(val => Object.assign({}, val, { num: val.num * 10}));
The map function will return a new array with the value returned from the function. In this example, we are creating a new object Object.assign({}) and duplicating the existing object while changing the num field.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
If you want to clone objects you will need a clone function, I use this function
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: obj && typeof obj === 'object'
? Object.getOwnPropertyNames(obj).reduce((o, prop) => {
o[prop] = clone(obj[prop]);
return o;
}, {})
: obj;
You can then clone the array with
let c = clone(b);
Which will be a new array where each object is a new clone.
var a = [{name: 'first', num:1}, {name:'first', num: 2}, {name:'first', num: 3},
{name:'first', num: 4}, {name:'first', num: 5}, {name:'first', num: 6}, {name:'first', num: 7},
{name:'first', num: 8}, {name:'first', num: 9}];
var b = a.filter(function(el){return el.num%2==0 });
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: obj && typeof obj === 'object'
? Object.getOwnPropertyNames(obj).reduce((o, prop) => {
o[prop] = clone(obj[prop]);
return o;
}, {})
: obj;
let c = clone(b);
console.log(b[0] === c[0]);
Yes, elements of Array a are all pointers. so you need to use Object.assign (as many says)
and other solution with array reduce usage (see Adrian Brand comment)
var a = [ { name: 'first', num: 1 }
, { name: 'first', num: 2 }
, { name: 'first', num: 3 }
, { name: 'first', num: 4 }
, { name: 'first', num: 5 }
, { name: 'first', num: 6 }
, { name: 'first', num: 7 }
, { name: 'first', num: 8 }
, { name: 'first', num: 9 }
]
var b = a.filter(el=>!(el.num%2)).map(el=>Object.assign({},el))
// other solution with reduce
var c = a.reduce((acc,cur)=>{
if (!(cur.num%2) )acc.push(Object.assign({},cur))
return acc
}, [])
ConsoleArrayNamNum('var a -1-',a) // [1,2,3,4,5,6,7,8,9]
ConsoleArrayNamNum('var b -1-',b) // [2, 4, 6, 8]
ConsoleArrayNamNum('var c -1-',c) // [2, 4, 6, 8]
for(let elm of b)
{ elm.num *= 10 }
ConsoleArrayNamNum('var a -2-',a) // [1,2,3,4,5,6,7,8,9]
ConsoleArrayNamNum('var b -2-',b) // [20, 40, 60, 80]
function ConsoleArrayNamNum(title,arr) {
console.log(title)
for(let elm of arr)
{ console.log(`{ name: '${elm.name}', num: ${elm.num} }`) }
}
.as-console-wrapper { min-height: 100% !important; }
If you want a new array with the final values you can use reduce to do it all in one go, reduce starts with an accumulator of an empty array and each iteration if it meets the condition it adds a clone with the spread operator overriding the num time 10.
var a = [{name: 'first', num:1}, {name:'first', num: 2}, {name:'first', num: 3},
{name:'first', num: 4}, {name:'first', num: 5}, {name:'first', num: 6}, {name:'first', num: 7},
{name:'first', num: 8}, {name:'first', num: 9}];
const evensTimes10 = array => array.reduce((results, item) => {
if (item.num % 2 === 0) {
results.push({ ...item, num: item.num * 10 });
}
return results;
}, []);
var b = evensTimes10(a);
console.log('a1',a); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log('b1',b); // [20, 40, 60, 80]
A simple solution using some ES6 syntax:
var a = [{name: 'first', num:1}, {name:'first', num: 2}, {name:'first', num: 3},
{name:'first', num: 4}, {name:'first', num: 5}, {name:'first', num: 6}, {name:'first', num: 7},
{name:'first', num: 8}, {name:'first', num: 9}];
const b = a
.filter(el => {
if (el.num % 2 === 0) {
return {
...el
}
}
})
.map(newEl => newEl.num * 10);
console.log('a', a); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log('b', b);
.filter() iterates the "a" array and returns only elements with
"num" property that reaches the condition. This is a cloned array.
return { ...el } returns a cloned object thanks to spread
operator.
.map() creates a new array and returns each "el.num" value *
10
Here some info about .map() .filter() and spread operator:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
I found this very interesting site that lists all Javascript functions with their descriptions and shows if is mutable or not, this helps a lot:
https://doesitmutate.xyz/
Is there any method to merge 2 arrays of objects like this
var a = [{id: 1, val: 1},{id: 2, val: 2},{id: 3, val: 3},{id: 4, val: 4},{id: 5, val: 5}];
var b = [{id: 21, val: 21},{id: 22, val: 22},{id: 23, val: 23}]
//final result should be
c = [
{id:1, val: 1},
{id:21, val: 21},
{id:2, val: 2},
{id:22, val: 22},
{id:3, val: 3},
{id:23, val: 23},
{id:4, val: 4},
{id:5, val: 5}
]
offcourse I can create it by myself, but just want to check whether lodash provide it or not
You could first zip the arrays, flatten the result and then use compact to remove the missing array elements (zip adds them as undefined):
var c = _.compact(_.flatten(_.zip(a,b)))
Or using chaining:
var c = _(a)
.zip(b)
.flatten()
.compact()
.value()
var a = [{id: 1, val: 1},{id: 2, val: 2},{id: 3, val: 3},{id: 4, val: 4},{id: 5, val: 5}];
var b = [{id: 21, val: 21},{id: 22, val: 22},{id: 23, val: 23}]
var c = _(a)
.zip(b)
.flatten()
.compact()
.value()
document.getElementById('results').textContent = JSON.stringify(c);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<pre id="results"></pre>
var a = [{id: 1, val: 1},{id: 2, val: 2},{id: 3, val: 3},{id: 4, val: 4},{id: 5, val: 5}];
var b = [{id: 21, val: 21},{id: 22, val: 22},{id: 23, val: 23}];
// loop through the biggest array and reduce the result (no need for the value we just need the accumulator and the index)
var result = _.reduce(a.length < b.length? b: a, function(res, _, i) {
if(i < a.length) res.push(a[i]); // if there is still elements in a, push the current one
if(i < b.length) res.push(b[i]); // if there is still elements in b, push the current one
return res;
}, []);
console.log(result);
<script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script>
In plain Javascript, you could use a function which iterates to the minimum length of both, assembles the values and concat the rest at the end.
function insert(a, b) {
var c = [],
i = 0,
l = Math.min(a.length, b.length);
while (i < l) {
c.push(a[i], b[i]);
i++;
}
return c.concat(a.slice(i), b.slice(i));
}
var a = [{ id: 1, val: 1 }, { id: 2, val: 2 }, { id: 3, val: 3 }, { id: 4, val: 4 }, { id: 5, val: 5 }],
b = [{ id: 21, val: 21 }, { id: 22, val: 22 }, { id: 23, val: 23 }];
console.log(insert(a, b));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Ecmascript5 solution using Math.max()(to find the larger array size) and Array.prototype.push() functions:
var a = [{id: 1, val: 1},{id: 2, val: 2},{id: 3, val: 3},{id: 4, val: 4},{id: 5, val: 5}],
b = [{id: 21, val: 21},{id: 22, val: 22},{id: 23, val: 23}],
maxLen = Math.max(a.length, b.length), aLen = a.length, bLen = b.length,
maxList = aLen > bLen ? a : b;
result = [];
for (var i = 0; i < maxLen; i++) {
(i < aLen && i < bLen) ? result.push(a[i], b[i]) : result.push(maxList[i]);
}
console.log(result);
Is there a way to compare differences between arrays based on changes on their elements positions?
I have an original array of objects which undergoes a change on one of it's element's values, this change is mapped into a new array:
origElements = [{id: 1, value: 50},
{id: 2, value: 60},
{id: 3, value: 70}]
changedElements = [{id: 1, value: 50},
{id: 3, value: 60},
{id: 2, value: 120}]
var diff = _.difference(_.pluck(origElements, "id"), _.pluck(changedElements, "id"));
var result = _.filter(origElements, function(obj) { return diff.indexOf(obj.id) >= 0; });
In this case it is clear why 'result' would return nothing. As there's no difference of values between: [1, 2, 3] and [1, 3, 2]. What I'm trying to achieve here is a 'strict difference' which would look at index as well, thus returning some reference to the new order of the objects.
How about doing it this way:
var origElements = [{
id: 1,
value: 50
}, {
id: 2,
value: 60
}, {
id: 3,
value: 70
}];
var changedElements = [{
id: 1,
value: 50
}, {
id: 3,
value: 60
}, {
id: 2,
value: 120
}];
var origElementsIds = _.pluck(origElements, "id");
var changedElementsIds = _.pluck(changedElements, "id");
console.log("Are array element positions same ?",
origElementsIds.join() === changedElementsIds.join());
I have a stupid problem that at first seems to be simple to solve, but turns out to be tricky.
I have an array of objects, each with two properties: id and value:
[
{id: 2, value: 10},
{id: 4, value: 3},
{id: 2, value: 2},
{id: 1, value: 15}
]
I want to write an algorithm that sums up the values of ones with similar id.
My end result should be a new array with only the merged objects:
[
{id: 2, value: 12},
{id: 4, value: 3},
{id: 1, value: 15}
]
I've tried the following, but it doesn't work:
var arr = [];
arr.push({id: 2, visit:10});
arr.push({id: 4, visit:3});
arr.push({id: 2, visit:2});
arr.push({id: 1, visit:15});
// Deep copy
var copy = jQuery.extend(true, [], arr);
var masterArr = [];
for (var i = 0; i < arr.length; i++) {
var objArr = [];
objArr.push(arr[i]);
for (var j = copy.length-1; j > -1; j--) {
if (arr[i].id === copy[j].id) {
var q = copy.splice(j,1);
}
}
masterArr.push(objArr);
}
My plan was to first gather all similar objects in separate arrays (objArr), sum them up and put them in an end array (masterArr). I use jquerys extend to make a deep copy (not a reference) and reverse iteration and splice to remove objects thats already been found as "duplicates".
This doesn't work! And it doesn't seem to be a very efficient mehtod to solve my problem.
How could I do this? Performance isn't top priority but rather "nice to have"!
Thanks!
You can do it like this:
// Assuming:
a = [{id: 2, value: 10}, {id: 4, value: 3}, {id: 2, value: 2}, {id: 1, value: 15}]
var b = {}, // Temporary variable;
c = []; // This will contain the result;
// Build a id:value object ( {1: 15, 2: 12, 4: 3} )
a.map(function(current){b[current.id] = (b[current.id] || 0) + current.value});
for(var key in b){ // Form that into the desired output format.
c.push({id: parseInt(key, 10), value: b[key]});
}
console.log(c);
/* [{id: 1, value: 15},
{id: 2, value: 12},
{id: 4, value: 3}] */
I'm using parseInt(key, 10), since the keys are strings, you'll probably want them converted to integers again.
// First group the data based on id and sum the values
var temp = data.reduce(function(result, current) {
result[current.id] = (result[current.id] || 0) + current.value;
return result;
}, {});
// then recreate the objects with proper id and value properties
var result = [];
for (var key in temp) {
result.push({
id: parseInt(key, 10),
value: temp[key]
});
}
console.log(result);
Output
[ { id: 1, value: 15 },
{ id: 2, value: 12 },
{ id: 4, value: 3 } ]
The quickest approach loops over the array only once using Array.prototype.filter():
var tmp = {},
result = arr.filter(function (el) {
if (tmp.hasOwnProperty(el.id)) {
tmp[el.id].visit += el.visit;
return false;
}
else {
tmp[el.id] = el;
return true;
}
});
It also reuses the objects, though this renders the original array to contain inaccurate values. If this is a problem, you can modify the example to copy each object property to a new object.