I have array like below , I want to sort it by key and then remove everything except last 2 items and delete remaining.
var status = new Array();
status.push({key: 'BOB', value: 10});
status.push({key: 'TOM', value: 3});
status.push({key: 'ROB', value: 22});
status.push({key: 'JON', value: 7});
If I again push below with duplicate key for example :
status.push({key: 'BOB', value: 20});
I need following output , how do i achieve this in javascript.
[
{
"key": "BOB",
"value": 20
},
{
"key": "TOM",
"value": 3
},
{
"key": "ROB",
"value": 22
},
{
"key": "JON",
"value": 7
}
]
Note : I need to sort this by key later.
Edit : If I have object like this , How do i sort by keys ? and get only last 2 items and delete remaining.
var status = new Object();
status['BOB'] = 10
status['TOM'] = 3
status['ROB'] = 22
status['JON'] = 7
I'd use a Map rather than an array or an object. Maps are like objects but with some important differences.
// initialize the map
var stats = new Map([['BOB',10],['TOM',3],['ROB',22],['JON',7]]);
// set a specific key's value
stats.set('BOB', 20);
// sort by key
var keys = Array.from(stats.keys());
keys.sort();
// get the last two
keys = keys.slice(-2);
// map the remaining keys to the desired structure
var result = keys.map(key => {
return {
key: key,
value: stats.get(key)
};
});
console.log(result);
Instead of using an array, use an object:
(function () {
var status = {};
status['BOB'] = 10;
status['TOM'] = 3;
status['ROB'] = 22;
status['JON'] = 7;
status['BOB'] = 20;
// convert to array
var output = Object.keys(status)
// sort by key
.sort()
// keep last two after sorting
.slice(-2)
// convert [key, value] to { key, value }
.map(function (key) {
return { key: key, value: status[key] };
});
console.log(output);
})();
.as-console-wrapper{max-height:100%!important}
In the case you decide to keep the array of objects structure, you can implement a method using Array.findIndex() and Array.splice() this way:
const pushWithCheck = (arr, obj) =>
{
let idx = arr.findIndex(({key}) => key === obj.key);
if (idx >= 0)
arr.splice(idx, 1, obj);
else
arr.push(obj);
}
var _status = [];
pushWithCheck(_status, {key: 'BOB', value: 10});
pushWithCheck(_status, {key: 'TOM', value: 3});
pushWithCheck(_status, {key: 'ROB', value: 22});
pushWithCheck(_status, {key: 'JON', value: 7});
console.log("Before duplicated key:", _status);
pushWithCheck(_status, {key: 'BOB', value: 20});
pushWithCheck(_status, {key: 'ROB', value: 99});
console.log("After duplicated key:", _status);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
Now, to sort by the key property of the objects and get last 2 elements, you can use Array.sort() and Array.slice() with a negative (-2) argument, like this:
const pushWithCheck = (arr, obj) =>
{
let idx = arr.findIndex(({key}) => key === obj.key);
if (idx >= 0)
arr.splice(idx, 1, obj);
else
arr.push(obj);
}
// Generate the _status array.
var _status = [];
pushWithCheck(_status, {key: 'BOB', value: 10});
pushWithCheck(_status, {key: 'TOM', value: 3});
pushWithCheck(_status, {key: 'ROB', value: 22});
pushWithCheck(_status, {key: 'JON', value: 7});
pushWithCheck(_status, {key: 'BOB', value: 20});
pushWithCheck(_status, {key: 'ROB', value: 99});
console.log("_status is: ", _status);
// Sort the _status array by ascending.
let sorted = _status.slice().sort((a, b) => a.key.localeCompare(b.key));
// Get last two elements of the sorted array.
let lastTwo = sorted.slice(-2);
console.log("Sorted is: ", sorted);
console.log("Last two elements are: ", lastTwo);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
if you want to do it the way others are suggesting you should use a set set always have unique elements in them
// initialize the map
var stats = new Set([['BOB',10],['TOM',3],['ROB',22],['JON',7]]);
// set a specific key's value
stats.add('BOB', 20);
// sort by key
var keys = Array.from(stats.keys());
keys.sort();
// get the last two
keys = keys.slice(-2);
// map the remaining keys to the desired structure
var result = keys
console.log(result);
I think this answers all your questions
let status1 = [];
status1.push({
key: 'BOB',
value: 10
}, {
key: 'TOM',
value: 3
}, {
key: 'ROB',
value: 22
}, {
key: 'JON',
value: 7
});
console.log('Initial array', status1);
//const newItem = {
// key: 'BOB',
// value: 20
//};
const newItem = {
key: 'BOB',
value: 99
};
for (let i = 0; i < status1.length; i++) {
if (status1[i].key === newItem.key) {
status1[i] = newItem;
}
}
Array.prototype.inArray = function(comparer) {
for(var i=0; i < this.length; i++) {
if(comparer(this[i])) return true;
}
return false;
};
Array.prototype.pushIfNotExist = function(element, comparer) {
if (!this.inArray(comparer)) {
this.push(element);
}
};
//https://stackoverflow.com/questions/1988349/array-push-if-does-not-exist
status1.pushIfNotExist(newItem, function(e) {
return e.key === newItem.key;
});
console.log('after push', status1);
function sortByKey(array, key) {
return array.sort(function (a, b) {
var x = a[key];
var y = b[key];
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
sortByKey(status1, 'key');
console.log('AFter sort', status1);
console.log('Last item', status1[status1.length - 1]);
console.log('Second Last item', status1[status1.length - 2]);
I have two objects:
a = {id: "1", value: "hello"}
b = {id: "2", value: "bye"}
That I want to convert to:
c = { 1: "hello", 2: "bye" }
Thus creating a single new object without any nested subjects.
I have tried a few ways but not quite getting what I need.
Such as:
Object.keys(a).forEach((key) => { a[key] = b[key]; });
You could use .reduce:
a = {id: "1", value: "hello"}
b = {id: "2", value: "bye"}
c = [a, b].reduce((carry, current) => { carry[current.id] = current.value; return carry }, {})
console.log(c)
Just create a new blank object and copy the values..
var a = {id: "1", value: "hello"}
var b = {id: "2", value: "bye"}
var c = {};
c[a.id] = a.value;
c[b.id] = b.value;
console.log(c);
If using new ESNext features this is even easier.
const a = {id: "1", value: "hello"}
const b = {id: "2", value: "bye"}
const c = {
[a.id]: a.value,
[b.id]: b.value
};
console.log(c);
You could do this with Object.assign and map() methods.
const a = {id: "1", value: "hello"}
const b = {id: "2", value: "bye"}
const result = Object.assign({}, ...[a, b].map(({id, value}) => ({[id]: value})));
console.log(result)
Simple way, with a loop so it does all your elements:
var list = [
{id: "1", value: "hello"},
{id: "2", value: "bye"}
// ... more elements
];
var c = {};
list.forEach(function (element) {
c[element['id']] = element['value'];
// or c[element.id] = element.value;
});
Another alternative
var a = {id: "1", value: "hello"}
var b = {id: "2", value: "bye"}
var c = {};
[a, b].forEach(function(x){
c[x.id] = x.value;
});
console.log(c);
I have two arrays of objects(arr1 and arr2). I want to select objects from arr1 where arr1.id == arr2.typeId and add to result arr2.Price
var arr1 =
[{"id":20,"name":"name1"},
{"id":24,"name":"name2"},
{"id":25,"name":"name3"},
{"id":28,"name":"name4"},
{"id":29,"name":"name5"}]
var arr2 =
[{"typeId":20,"Price":500},
{"typeId":24,"Price":1100},
{"typeId":28,"Price":1000}]
How can I get the following?
var result =
[{"item":{"id":20,"name":"name1"}, "price":"500"}},
{{"item":{"id":24,"name":"name2"}, "price":"1100"},
{{"item":{"id":28,"name":"name4"}, "price":"1000"}]
var result = arr1.filter(function(obj1){
return arr2.some(function(obj2){
return obj1.id === obj2.typeId;
});
})
You can use reduce() on arr2 and then check if object with same id exists in arr1 with find().
var arr1 =
[{"id":20,"name":"name1"},
{"id":24,"name":"name2"},
{"id":25,"name":"name3"},
{"id":28,"name":"name4"},
{"id":29,"name":"name5"}]
var arr2 =
[{"typeId":20,"Price":500},
{"typeId":24,"Price":1100},
{"typeId":28,"Price":1000}]
var result = arr2.reduce(function(r, e) {
var c = arr1.find(a => e.typeId == a.id)
if(c) r.push({item: c, price: e.Price})
return r
}, [])
console.log(result)
You could create an object without any prototypes with Object.create as hash table and push the new object only if both arrays have a common id.
var arr1 = [{ id: 20, name: "name1" }, { id: 24, name: "name2" }, { id: 25, name: "name3" }, { id: 28, name: "name4" }, { id: 29, name: "name5" }],
arr2 = [{ typeId: 20, Price: 500 }, { typeId: 24, Price: 1100 }, { typeId: 28, Price: 1000 }],
hash = Object.create(null),
result = [];
arr1.forEach(function (a) {
hash[a.id] = a;
});
arr2.forEach(function (a) {
if (hash[a.typeId]) {
result.push({ item: hash[a.typeId], price: a.Price });
}
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Another approach, using Array#forEach.
var arr1 = [{id:20,name:"name1"},{id:24,name:"name2"},{id:25,name:"name3"},{id:28,name:"name4"},{id:29,name:"name5"}],
arr2 = [{typeId:20,Price:500},{typeId:24,Price:1100},{typeId:28,Price:1e3}],
result = [];
arr2.forEach(function(v){
var elem = arr1.find(c => c.id == v.typeId); //find corresponding element from the `arr1` array
result.push({item: elem, price: v.Price}); //push an object containing `item` and `price` keys into the result array
});
console.log(result); //reveal the result
I wonder if there is a simpler method in lodash to replace an item in a JavaScript collection? (Possible duplicate but I did not understand the answer there:)
I looked at their documentation but could not find anything
My code is:
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
// Can following code be reduced to something like _.XX(arr, {id:1}, {id:1, name: "New Name"});
_.each(arr, function(a, idx){
if(a.id === 1){
arr[idx] = {id:1, name: "Person New Name"};
return false;
}
});
_.each(arr, function(a){
document.write(a.name);
});
Update:
The object I'm trying to replace with has many properties like
{id: 1, Prop1: ..., Prop2:..., and so on}
Solution:
Thanks to dfsq but I found a proper solution within lodash that seems to work fine and is pretty neat and I put it in a mixin as well since I've this requirement at many places. JSBin
var update = function(arr, key, newval) {
var match = _.find(arr, key);
if(match)
_.merge(match, newval);
else
arr.push(newval);
};
_.mixin({ '$update': update });
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
_.$update(arr, {id:1}, {id:1, name: "New Val"});
document.write(JSON.stringify(arr));
Faster Solution
As pointed out by #dfsq, following is way faster
var upsert = function (arr, key, newval) {
var match = _.find(arr, key);
if(match){
var index = _.indexOf(arr, _.find(arr, key));
arr.splice(index, 1, newval);
} else {
arr.push(newval);
}
};
In your case all you need to do is to find object in array and use Array.prototype.splice() method, read more details here:
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
// Find item index using _.findIndex (thanks #AJ Richardson for comment)
var index = _.findIndex(arr, {id: 1});
// Replace item at index using native splice
arr.splice(index, 1, {id: 100, name: 'New object.'});
// "console.log" result
document.write(JSON.stringify( arr ));
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
Seems like the simplest solution would to use ES6's .map or lodash's _.map:
var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
// lodash
var newArr = _.map(arr, function(a) {
return a.id === 1 ? {id: 1, name: "Person New Name"} : a;
});
// ES6
var newArr = arr.map(function(a) {
return a.id === 1 ? {id: 1, name: "Person New Name"} : a;
});
This has the nice effect of avoiding mutating the original array.
[ES6] This code works for me.
let result = array.map(item => item.id === updatedItem.id ? updatedItem : item)
function findAndReplace(arr, find, replace) {
let i;
for(i=0; i < arr.length && arr[i].id != find.id; i++) {}
i < arr.length ? arr[i] = replace : arr.push(replace);
}
Now let's test performance for all methods:
// TC's first approach
function first(arr, a, b) {
_.each(arr, function (x, idx) {
if (x.id === a.id) {
arr[idx] = b;
return false;
}
});
}
// solution with merge
function second(arr, a, b) {
const match = _.find(arr, a);
if (match) {
_.merge(match, b);
} else {
arr.push(b);
}
}
// most voted solution
function third(arr, a, b) {
const match = _.find(arr, a);
if (match) {
var index = _.indexOf(arr, _.find(arr, a));
arr.splice(index, 1, b);
} else {
arr.push(b);
}
}
// my approach
function fourth(arr, a, b){
let l;
for(l=0; l < arr.length && arr[l].id != a.id; l++) {}
l < arr.length ? arr[l] = b : arr.push(b);
}
function test(fn, times, el) {
const arr = [], size = 250;
for (let i = 0; i < size; i++) {
arr[i] = {id: i, name: `name_${i}`, test: "test"};
}
let start = Date.now();
_.times(times, () => {
const id = Math.round(Math.random() * size);
const a = {id};
const b = {id, name: `${id}_name`};
fn(arr, a, b);
});
el.innerHTML = Date.now() - start;
}
test(first, 1e5, document.getElementById("first"));
test(second, 1e5, document.getElementById("second"));
test(third, 1e5, document.getElementById("third"));
test(fourth, 1e5, document.getElementById("fourth"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
<div>
<ol>
<li><b id="first"></b> ms [TC's first approach]</li>
<li><b id="second"></b> ms [solution with merge]</li>
<li><b id="third"></b> ms [most voted solution]</li>
<li><b id="fourth"></b> ms [my approach]</li>
</ol>
<div>
If you're just trying to replace one property, lodash _.find and _.set should be enough:
var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
_.set(_.find(arr, {id: 1}), 'name', 'New Person');
You can also use findIndex and pick to achieve the same result:
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
var data = {id: 2, name: 'Person 2 (updated)'};
var index = _.findIndex(arr, _.pick(data, 'id'));
if( index !== -1) {
arr.splice(index, 1, data);
} else {
arr.push(data);
}
As the time passes you should embrace a more functional approach in which you should avoid data mutations and write small, single responsibility functions. With the ECMAScript 6 standard, you can enjoy functional programming paradigm in JavaScript with the provided map, filter and reduce methods. You don't need another lodash, underscore or what else to do most basic things.
Down below I have included some proposed solutions to this problem in order to show how this problem can be solved using different language features:
Using ES6 map:
const replace = predicate => replacement => element =>
predicate(element) ? replacement : element
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const predicate = element => element.id === 1
const replacement = { id: 100, name: 'New object.' }
const result = arr.map(replace (predicate) (replacement))
console.log(result)
Recursive version - equivalent of mapping:
Requires destructuring and array spread.
const replace = predicate => replacement =>
{
const traverse = ([head, ...tail]) =>
head
? [predicate(head) ? replacement : head, ...tail]
: []
return traverse
}
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const predicate = element => element.id === 1
const replacement = { id: 100, name: 'New object.' }
const result = replace (predicate) (replacement) (arr)
console.log(result)
When the final array's order is not important you can use an object as a HashMap data structure. Very handy if you already have keyed collection as an object - otherwise you have to change your representation first.
Requires object rest spread, computed property names and Object.entries.
const replace = key => ({id, ...values}) => hashMap =>
({
...hashMap, //original HashMap
[key]: undefined, //delete the replaced value
[id]: values //assign replacement
})
// HashMap <-> array conversion
const toHashMapById = array =>
array.reduce(
(acc, { id, ...values }) =>
({ ...acc, [id]: values })
, {})
const toArrayById = hashMap =>
Object.entries(hashMap)
.filter( // filter out undefined values
([_, value]) => value
)
.map(
([id, values]) => ({ id, ...values })
)
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const replaceKey = 1
const replacement = { id: 100, name: 'New object.' }
// Create a HashMap from the array, treating id properties as keys
const hashMap = toHashMapById(arr)
console.log(hashMap)
// Result of replacement - notice an undefined value for replaced key
const resultHashMap = replace (replaceKey) (replacement) (hashMap)
console.log(resultHashMap)
// Final result of conversion from the HashMap to an array
const result = toArrayById (resultHashMap)
console.log(result)
Came across this as well and did it simply that way.
const persons = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
const updatedPerson = {id: 1, name: "new Person Name"}
const updatedPersons = persons.map(person => (
person.id === updated.id
? updatedPerson
: person
))
If wanted we can generalize it
const replaceWhere = (list, predicate, replacement) => {
return list.map(item => predicate(item) ? replacement : item)
}
replaceWhere(persons, person => person.id === updatedPerson.id, updatedPerson)
var arr= [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
var index = _.findIndex(arr, {id: 1});
arr[index] = {id: 100, name: 'xyz'}
If the insertion point of the new object does not need to match the previous object's index then the simplest way to do this with lodash is by using _.reject and then pushing new values in to the array:
var arr = [
{ id: 1, name: "Person 1" },
{ id: 2, name: "Person 2" }
];
arr = _.reject(arr, { id: 1 });
arr.push({ id: 1, name: "New Val" });
// result will be: [{ id: 2, name: "Person 2" }, { id: 1, name: "New Val" }]
If you have multiple values that you want to replace in one pass, you can do the following (written in non-ES6 format):
var arr = [
{ id: 1, name: "Person 1" },
{ id: 2, name: "Person 2" },
{ id: 3, name: "Person 3" }
];
idsToReplace = [2, 3];
arr = _.reject(arr, function(o) { return idsToReplace.indexOf(o.id) > -1; });
arr.push({ id: 3, name: "New Person 3" });
arr.push({ id: 2, name: "New Person 2" });
// result will be: [{ id: 1, name: "Person 1" }, { id: 3, name: "New Person 3" }, { id: 2, name: "New Person 2" }]
Using lodash unionWith function, you can accomplish a simple upsert to an object. The documentation states that if there is a match, it will use the first array. Wrap your updated object in [ ] (array) and put it as the first array of the union function. Simply specify your matching logic and if found it will replace it and if not it will add it
Example:
let contacts = [
{type: 'email', desc: 'work', primary: true, value: 'email prim'},
{type: 'phone', desc: 'cell', primary: true, value:'phone prim'},
{type: 'phone', desc: 'cell', primary: false,value:'phone secondary'},
{type: 'email', desc: 'cell', primary: false,value:'email secondary'}
]
// Update contacts because found a match
_.unionWith([{type: 'email', desc: 'work', primary: true, value: 'email updated'}], contacts, (l, r) => l.type == r.type && l.primary == r.primary)
// Add to contacts - no match found
_.unionWith([{type: 'fax', desc: 'work', primary: true, value: 'fax added'}], contacts, (l, r) => l.type == r.type && l.primary == r.primary)
If you want to make a function and keep it "lodash-ey", you can make a wrapper function that works with callbacks. It makes the function more general use.
To write this try something like
function findAllAndReplace(array, replacement, callback){
return array.map( element => callback(element) ? replacement : element )
}
To find and replace by key, just make your callback very simple. (itemInArray) => itemInArray.keyOnItem
But if you want more advanced functionality you can incorporate it with barely any extra effort. Here are some examples.
(Simple) Find the item with id 2, replace it to have an id: 7
const items = [{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}]
findAllAndReplace( items, {id: 7}, item => item.id === 2 )
(Slightly More Complex) Find 28 year old named John, and replace him with a 28 year old named Jon
const people = [
{
name: "John",
age: 20
},
{
name: "John",
age: 28
},
{
name: "Jim",
age: 28
},
]
findAllAndReplace(
people, // all the people
{ name: "Jon", age: 28 }, // Replacement value
(person) => person.name === "jon" && person.age === 21 // callback function
)
Also, the method above will find all instances that match and replace them, but if you just want to do it for one you could do something like below.
function findOneAndReplace(array, replacement, callback){
const splitIndex = array.findIndex(callback)
// This if statement can be ommitted, but might
// be handy depending on your use case
if(splitIndex < 0){
throw new Error("Swap Element not found")
}
const leadingTerms = array.slice(0, splitIndex)
const trailingTerms = array.slice(splitIndex + 1, array.length)
return [...leadingTerms, replacement, ...trailingTerms]
)
note: It might be useful to make your function break if it doesn't find a matching element, but if you don't want that feature you can cut those lines of code out.
Not bad variant too)
var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
var id = 1; //id to find
arr[_.find(arr, {id: id})].name = 'New Person';
If you're looking for a way to immutably change the collection (as I was when I found your question), you might take a look at immutability-helper, a library forked from the original React util. In your case, you would accomplish what you mentioned via the following:
var update = require('immutability-helper')
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}]
var newArray = update(arr, { 0: { name: { $set: 'New Name' } } })
//=> [{id: 1, name: "New Name"}, {id:2, name:"Person 2"}]
You can do it without using lodash.
let arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
let newObj = {id: 1, name: "new Person"}
/*Add new prototype function on Array class*/
Array.prototype._replaceObj = function(newObj, key) {
return this.map(obj => (obj[key] === newObj[key] ? newObj : obj));
};
/*return [{id: 1, name: "new Person"}, {id: 2, name: "Person 2"}]*/
arr._replaceObj(newObj, "id")
Immutable, suitable for ReactJS:
Assume:
cosnt arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
The updated item is the second and name is changed to Special Person:
const updatedItem = {id:2, name:"Special Person"};
Hint: the lodash has useful tools but now we have some of them on Ecmascript6+, so I just use map function that is existed on both of lodash and ecmascript6+:
const newArr = arr.map(item => item.id === 2 ? updatedItem : item);