Get the object with the lowest value attribute from array - javascript

I know I can get the object with the lowest value attribute by executing
const obj = Math.min.apply(Math, myArr.map(o => { return o.val; }));
but I have to return the object, not the value of the object. How can I return the object from this?
My current way is
const lowest = (arr.sort((a, b) => a.val < b.val))[0];
but maybe there is a more optimized way.
Working example:
function Obj(val) {
this.val = val;
}
$(document).ready(() => {
const data = [new Obj(2), new Obj(7), new Obj(9), new Obj(1), new Obj(3)];
const lowestNumber = Math.min.apply(Math, data.map(o => {
return o.val;
}));
console.log(lowestNumber);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Not working:
function Obj(val) {
this.val = val;
}
$(document).ready(() => {
const data = [new Obj(2), new Obj(7), new Obj(9), new Obj(1), new Obj(3)];
const obj = Math.min.apply(Math, data.map(o => {
return o;
}));
console.log(obj);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This returns NaN

Since you want the object, and not the value, you cannot use Math.min.
For linear algo, use reduce instead:
const lowest = arr.reduce( (acc, a) => a.val < acc.val ? a : acc, arr[0]);

This might work:
const lowest = data.reduce((a, b) => a.val < b.val ? a : b);
The reduce function is used to "reduce" an array to a single value, based on comparisons between consecutive values. An extended explanation:
const lowest = data.reduce(
function(a, b) {
if (a.val < b.val) {
// keep a as next value
return a
} else {
// keep b as next value
return b
}
})

If you are not well experienced with some special Math/Array functions, such easy task can be just implemented directly by you.
Sorting is not good way, if you have big arrays and you need reasonable performance.
const arr = [
{ val: 3 },
{ val: 1 }
]
let minObj = arr[0];
arr.forEach(obj => {
if (obj.val < minObj.val) {
minObj = obj;
}
});
console.log(minObj);

This will also do it.. I believe this method has good performance too
var arr = [
{"ID": "test1", "val": 1},
{"ID": "test2", "val": 2},
{"ID": "test3", "val": 3},
{"ID": "test4", "val": 4}
]
arr.reduce(function(p, c) {
return p.val < c.val ? p : c;
});

Related

How to Pivot Array of Objects with GroupBy and Sum in Javascipt

I have arr array of objects, I need to pivot it with product,calorie and apply (grouping & sum) on remaining parameters.
And then require data in single object.
I tried below code, it works fine but I divided code in 3 parts.
Could I have better code than this or it is ok.
var arr = [{
"product": "Jam",
"calorie": 2000,
"A": 300,
"B": 500,
"type": "Daily"
},
{
"product": "Sugar",
"calorie": 1000,
"A": 100,
"B": 200,
"type": "Daily"
}
]
var a1 = {}
var a2 = {}
//Step-1 Pivot
for (let i = 0; i < arr.length; i++) {
a1[arr[i]['product']] = arr[i]['calorie'];
}
//Step-2 Group and sum
a2 = groupAndSum(arr, ['type'], ['A', 'B'])[0];
//Step-3 merging.
console.log({ ...a1,
...a2
})
//General grouping and summing function that accepts an
//#Array:Array of objects
//#groupKeys: An array of keys to group by,
//#sumKeys - An array of keys to sum.
function groupAndSum(arr, groupKeys, sumKeys) {
return Object.values(
arr.reduce((acc, curr) => {
const group = groupKeys.map(k => curr[k]).join('-');
acc[group] = acc[group] || Object.fromEntries(groupKeys.map(k => [k, curr[k]]).concat(sumKeys.map(k => [k, 0])));
sumKeys.forEach(k => acc[group][k] += curr[k]);
return acc;
}, {})
);
}
Here a single function which takes 3 params:
const func = (arr, pivot_vals, sum_vals) => {
return arr.reduce((a, v) => {
pivot_vals.forEach((pivot) => {
a[v[pivot[0]]] = v[pivot[1]];
});
sum_vals.forEach((key) => {
if (!a[key]) a[key] = 0;
a[key] += v[key];
});
return a;
},{});
};
arr
containing the data
sum_vals
array with all props you want do be summed
pivot_vals
nested array with the props which should be linked
I wans't sure what to do with the type, since it is a string it can`t be summed. Did you want to count the amount of types ?
let arr = [
{
product: "Jam",
calorie: 2000,
A: 300,
B: 500,
type: "Daily",
},
{
product: "Sugar",
calorie: 1000,
A: 100,
B: 200,
type: "Daily",
},
];
let sum_vals = ["A","B"]
let pivot_vals = [["product", "calorie"]];
const func = (arr, pivot_vals, sum_vals) => {
return arr.reduce((a, v) => {
pivot_vals.forEach((pivot) => {
a[v[pivot[0]]] = v[pivot[1]];
});
sum_vals.forEach((key) => {
if (!a[key]) a[key] = 0;
a[key] += v[key];
});
return a;
},{});
};
console.log(func(arr, pivot_vals, sum_vals));

Sort array with timestamps and merge similar dates

I am trying to take this array :
[
{
"date": a timestamp: June 20, 2020 at 7:32:42 PM UTC
"value": 3
..
}
..
]
and accomplish 3 things effeciently
Convert the timestamp to normal date and replace with the timestamp
Merge dates of the same day. ( so i add their values and set under a single day.
Sort the array when recent are first.
I have started with something like this :
array.sort(function(a,b){ return new Date(b.date) - new Date(a.date);
var salesDates = sales.map(function(element){element.date = new Date(element.date); return element }); });
Which will only sort, but i need to replace timestamp/date, sort and merge same dates elegantly and effeciently
Is it possible with only sort function ?
Here. First i group it with .reduce(). Then i sort it .sort(). After that i change the timestamp to a date format.
let arr = [
{
"date": 1594023899426,
"value": 3
},
{
"date": 1592423499234,
"value": 2
},
{
"date": 1594023899426,
"value": 1
}
];
let result = arr
.reduce((a, v) => {
let index = a.findIndex(el => el.date === v.date);
if (index !== -1) {
a[index].value += v.value;
return a;
}
a.push({
date: v.date,
value: v.value
});
return a;
}, [])
.sort((a, b) => b.date - a.date)
.map(el => {
el.date = new Date(el.date);
return el;
});
console.log(result);
Here's another approach to it:
let sortingReducer = (accumulator, obj) => {
// This is the merging logic
let existingObj = accumulator.find(
(compareObj) => {
return obj.date?.getDate() === compareObj.date?.getDate()
}
);
if (existingObj) {
existingObj.value += obj.value;
return accumulator;
}
// This is the sorting logic
const nextIndex = accumulator.findIndex(
(compareObj) => obj.date?.getTime() < compareObj.date?.getTime()
);
const index = nextIndex > -1 ? nextIndex : accumulator.length;
accumulator.splice(index, 0, obj);
return accumulator;
};
const input = [
{
date: new Date(),
value: 2,
},
{
date: new Date(new Date().setDate(1)),
value: 4,
},
{
date: new Date(new Date().setDate(1)),
value: 1,
},
{
date: new Date(new Date().setDate(2)),
value: 7,
},
];
const output = input.reduce(sortingReducer, []);
I've take some help from this answer: https://stackoverflow.com/a/50246275/1912288
I like the approach in the above answer, this is just a different approach to it.

JavaScript array to dictionary conversation based on count

I have a array like below
data = [A,B,B,B,C,C,A,B]
How can I convert into dictionary like below format.
data = [
{
name: "A",
y: 2
},
{
name: "B",
y: 4
},
{
name: "C",
y: 2
}
]
Have to convert elements as names and count of the elements as value to y.
I've a library which accepts only in that format.
Not able to do, stuck in the middle
Any suggestions are welcome.
data = ['A','B','B','B','C','C','A','B'];
var res = Array.from(new Set(data)).map(a =>
({name:a, y: data.filter(f => f === a).length}));
console.log(res);
use array.reduce:
data = ['A','B','B','B','C','C','A','B'];
var res = data.reduce((m, o) => {
var found = m.find(e => e.name === o);
found ? found.y++ : m.push({name: o, y: 1});
return m;
}, []);
console.log(res);
function convert(data){
var myMap = {}
data.forEach(el => myMap[el] = myMap[el] != undefined ? myMap[el] + 1 : 1);
return Object.keys(myMap).map(k => {return {name: k, y: myMap[k]}})
}
console.log(convert(['A','B','B','B','C','C','A','B']))

Javascript how to join two arrays having same property value?

How do I join arrays with the same property value? I cannot map it because it has different indexes.
var array1 = [
{'label':"label1",'position':0},
{'label':"label3",'position':2},
{'label':"label2",'position':1},
];
var array2 = [
{'label':"label1",'value':"TEXT"},
{'label':"label2",'value':"SELECT"}
];
expected output:
var array3 = [
{'label':"label1",'value':"TEXT",'position':0},
{'label':"label2",'value':"SELECT", 'position':1}
];
This is what I did, I cannot make it work,
var arr3 = arr1.map(function(v, i) {
return {
"label": v.label,
"position": v.position,
"value": arr2[?].value
}
});
I think you can use array#reduce to do something like this perhaps:
var array1 = [
{'label':"label1",'position':0},
{'label':"label3",'position':2},
{'label':"label2",'position':1},
];
var array2 = [
{'label':"label1",'value':"TEXT"},
{'label':"label2",'value':"SELECT"}
];
var array3 = array2.reduce((arr, e) => {
arr.push(Object.assign({}, e, array1.find(a => a.label == e.label)))
return arr;
}, [])
console.log(array3);
You could take a Map and check the existence for a new object.
var array1 = [{ label: "label1", position: 0 }, { label: "label3", position: 2 }, { label: "label2", position: 1 }],
array2 = [{ label: "label1", value: "TEXT" }, { label: "label2", value: "SELECT" }],
map = array1.reduce((m, o) => m.set(o.label, o), new Map),
array3 = array2.reduce((r, o) => {
if (map.has(o.label)) {
r.push(Object.assign({}, o, map.get(o.label)));
}
return r;
}, []);
console.log(array3);
.as-console-wrapper { max-height: 100% !important; top: 0; }
As per the effort, we take an assumption that array1 will be having all the labels that are in array2.
Based on that first, create a map for array2and with key being labels. Post that, filter out array1 items which have labels existing in the map and then finally merging the objects of the filtered array and its corresponding values in map extracted from array2.
var array1 = [{'label':"label1",'position':0},{'label':"label3",'position':2},{'label':"label2",'position':1}];
var array2 = [{'label':"label1",'value':"TEXT"},{'label':"label2",'value':"SELECT"}];
let map = array2.reduce((a,{label, ...rest}) => Object.assign(a,{[label]:rest}),{});
let result = array1.filter(({label}) => map[label]).map(o => ({...o, ...map[o.label]}));
console.log(result);
Also, in the above snippet, you can improve the performance further by using Array.reduce against filter and map functions to retrieve the result.
var array1 = [{'label':"label1",'position':0},{'label':"label3",'position':2},{'label':"label2",'position':1}];
var array2 = [{'label':"label1",'value':"TEXT"},{'label':"label2",'value':"SELECT"}];
let map = array2.reduce((a,{label, ...rest}) => Object.assign(a,{[label]:rest}),{});
let result = array1.reduce((a,o) => {
if(map[o.label]) a.push({...o, ...map[o.label]});
return a;
}, []);
console.log(result);
If you don't know in advance which array(s) will have their labels be a subset of the other (if any), here's a method that allows for either array1 or array2 to have labels that the other array lacks. Use reduce over array1, finding the matching label in array2 if it exists:
var array1 = [
{'label':"label1",'position':0},
{'label':"label3",'position':2},
{'label':"label2",'position':1},
];
var array2 = [
{'label':"label1",'value':"TEXT"},
{'label':"label2",'value':"SELECT"}
];
const output = array1.reduce((a, { label, position }) => {
const foundValueObj = array2.find(({ label: findLabel }) => findLabel === label);
if (!foundValueObj) return a;
const { value } = foundValueObj;
a.push({ label, value, position });
return a;
}, []);
console.log(output);
See Array.prototype.map() and Map for more info.
// Input.
const A = [{'label':"label1",'position':0},{'label':"label3",'position':2},{'label':"label2",'position':1}]
const B = [{'label':"label1",'value':"TEXT"},{'label':"label2",'value':"SELECT"}]
// Merge Union.
const mergeUnion = (A, B) => {
const mapA = new Map(A.map(x => [x.label, x]))
return B.map(y => ({...mapA.get(y.label), ...y}))
}
// Output + Proof.
const output = mergeUnion(A, B)
console.log(output)
This works.
Approach: Concatenate the objects with same label, using Object.assign()
var array1 = [{'label':"label1",'position':0},{'label':"label3",'position':2},{'label':"label2",'position':1}];
var array2 = [{'label':"label1",'value':"TEXT"},{'label':"label2",'value':"SELECT"}];
var result = [];
array2.forEach(function(value, index){
result.push(Object.assign({},array1.find(function(v,i){return v.label==value.label}),value));
});
console.log(result)
Im not good with javascript,but you could also do this
var array1 = [
{'label':"label1",'position':0},
{'label':"label3",'position':2},
{'label':"label2",'position':1},
];
var array2 = [
{'label':"label1",'value':"TEXT"},
{'label':"label2",'value':"SELECT"}
];
var array3=[];
for(var i=0;i<array1.length;i++)
{
for(var x=0;x<array2.length;x++)
{
console.log(array1[i]['label'] == array2[x]['label']);
if(array1[i]['label'] == array2[x]['label']){
array3.push({label:array1[i]['label'],value:array2[x]['value'],position:array1[i]['position']});
}
}
}
console.log(array3);

lodash convert array of objects to single array of keys and multiple array of values

I need to transmit some data, that has too many key-value pairs.
As the keys are similar, I dont want to transmit them with each object.
Consider I have the following data:
[
{
x:11,
y:12
},{
x:21,
y:22
},{
x:31,
y:32
},{
x:41,
y:42
}
];
And I need the final output as
[ [x,y],[[11,12],[21,22],[31,32],[41,42]] ] OR
[ [x,y],[11,12],[21,22],[31,32],[41,42] ]
On the other end, I should be able to convert back to its original form.
It would be great if it can handle an additional key in some of the objects
I think I have seen lodash or underscore function for something close/similar to this, but I'm not able to find it right now.
NOTE: I don't know what the keys will be
Lodash v4.17.1
modify original
var modifiedOriginal = _.chain(original)
.map(_.keys)
.flatten()
.uniq()
.thru(function(header){
return _.concat(
[header],
_.map(original, function(item) {
return _.chain(item)
.defaults(_.zipObject(
header,
_.times(_.size(header), _.constant(undefined))
))
.pick(header)
.values()
.value()
})
);
})
.value();
modified back to original (keys order is not
guarantee)
var backToOriginal = _.map(_.tail(modified), function(item) {
return _.chain(_.head(modified))
.zipObject(item)
.transform(function(result, val, key) {
if (!_.isUndefined(val)) {
result[key] = val;
}
})
.value();
});
JSFiddle code https://jsfiddle.net/wa8kaL5g/1/
Using Array#reduce
var arr = [{
x: 11,
y: 12
}, {
x: 21,
y: 22
}, {
x: 31,
y: 32
}, {
x: 41,
y: 42
}];
var keys = Object.keys(arr[0]);
var op = arr.reduce(function(a, b) {
var arr = keys.reduce(function(x, y) {
return x.concat([b[y]]);
}, [])
return a.concat([arr]);
}, [keys]); //If all the objects are having identical keys!
console.log(JSON.stringify(op));
A little more verbose way of doing it:
[Edit: added the function to convert it back]
function convert(arr) {
var retArr = [ [/* keys (retArr[0]) */], [/* values (retArr[1]) */] ]
arr.forEach(function(obj){
// create new array for new sets of values
retArr[1].push([])
// put all of the keys in the correct array
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
// does the key exist in the array yet?
if (retArr[0].indexOf(key) === -1) {
retArr[0].push(key)
}
// get last index of retArr[1] and push on the values
retArr[1][retArr[1].length - 1].push(obj[key])
}
}
})
return retArr
}
function reConvert(arr) {
var retArr = []
var keys = arr[0]
arr[1].forEach(function(itemArr){
var obj = {}
itemArr.forEach(function(item, i){
obj[keys[i]] = item
})
retArr.push(obj)
})
return retArr
}
var objArr = [
{
x:11,
y:12
},{
x:21,
y:22
},{
x:31,
y:32
},{
x:41,
y:42
}
]
var arrFromObj = convert(objArr)
var objFromArr = reConvert(arrFromObj)
console.log(arrFromObj)
console.log(objFromArr)
A solution using Underscore.
First work out what the keys are:
var keys = _.chain(data)
.map(_.keys)
.flatten()
.uniq()
.value();
Then map across the data to pick out the value for each key:
var result = [
keys,
_.map(data, item => _.map(keys, key => item[key]))
];
and back again:
var thereAndBackAgain = _.map(result[1], item => _.omit(_.object(result[0], item), _.isUndefined));
Lodash's version of object is zipObject and omit using a predicate is omitBy:
var thereAndBackAgain = _.map(result[1], item => _.omitBy(_.zipObject(result[0], item), _.isUndefined));
var data = [
{
x:11,
y:12,
aa: 9
},{
x:21,
y:22
},{
x:31,
y:32,
z: 0
},{
x:41,
y:42
}
];
var keys = _.chain(data)
.map(_.keys)
.flatten()
.uniq()
.value();
var result = [
keys,
_.map(data, item => _.map(keys, key => item[key]))
];
var thereAndBackAgain = _.map(result[1], item => _.omit(_.object(result[0], item), _.isUndefined));
console.log(result)
console.log(thereAndBackAgain)
<script src="
https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
In ES6 you can do it by reducing it with Object.values(), and Object.keys(). You can restore it using a combination of Array.prototype.map() and Array.prototype.reduce():
const convertStructure = (data) => data.reduce((s, item) => {
s[1].push(Object.values(item));
return s;
}, [Object.keys(data[0]), []]); // all objects should be the same, so we can take the keys from the 1st object
const restoreStructure = ([keys, data]) => data.map((item) => item.reduce((o, v, i) => {
o[keys[i]] = v;
return o;
}, {}));
const data = [{
x: 11,
y: 12
}, {
x: 21,
y: 22
}, {
x: 31,
y: 32
}, {
x: 41,
y: 42
}];
const convertedStructure = convertStructure(data);
console.log('convertedStructure:\n', convertedStructure);
const restoredStructure = restoreStructure(convertedStructure);
console.log('restoredStructure:\n', restoredStructure);

Categories