1

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
Gracie williams
  • 1,287
  • 2
  • 16
  • 39
  • 9
    Why are you using an array instead of an object whose keys are the names? That will do this automatically. – Barmar May 08 '19 at 20:43
  • 2
    Before pushing the new value, search the array to see if the key already exists. If it does, replace it. – Barmar May 08 '19 at 20:43
  • I want to sort it later by key , I guess thats not possible in object – Gracie williams May 08 '19 at 20:44
  • Possible duplicate of [Array.push() and unique items](https://stackoverflow.com/questions/36719477/array-push-and-unique-items) – Aziz.G May 08 '19 at 20:44
  • @Graciewilliams you can get and array from Object.keys or Object.items and sort when you need to. – Mark May 08 '19 at 20:45
  • @G.aziz That question leaves the old object alone, it doesn't replace it. – Barmar May 08 '19 at 20:45
  • 2
    @g.aziz that duplicate sounds promising, but the answers to it are not. – Jonas Wilms May 08 '19 at 20:45
  • You may want to look at [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). It won't solve the problem of replacing, but it's like an object, and you can get sorted keys a bit easier. – Heretic Monkey May 08 '19 at 20:49
  • I have edited my question with objects , but in stack overflow ,they tell we cannot sort - https://stackoverflow.com/questions/12788051/how-to-sort-an-associative-array-by-value – Gracie williams May 08 '19 at 20:50
  • What are you doing with the keys/values that you need to sort them? You can get all of the keys as an array and sort them, then get the values... – Heretic Monkey May 08 '19 at 20:52
  • Yea seems like two steps , I actually need to sort them and remove first two items. – Gracie williams May 08 '19 at 20:52

4 Answers4

1

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);
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
  • status map count is increasing everytime i use set , if i use var stats instead var result , will it work ? – Gracie williams May 08 '19 at 21:35
  • @Graciewilliams I'm not sure what you mean. I just updated to use the variable name `stats` instead of `status` to get around the `window.status` issue. The `result` variable simply holds the new structure (an array of objects with key and value properties). – Heretic Monkey May 08 '19 at 21:38
  • 1
    I mean if do console.log(stats) again after slice , it should have same content of result... and reuse it again , because I am getting 100's of data every second , – Gracie williams May 08 '19 at 21:40
  • 1
    This answer, like the others, answers the question posed. If you have further requirements which you did not mention in this question, please, ask a new question and make sure you mention all of the requirements up front. – Heretic Monkey May 08 '19 at 21:42
  • I think its better to use set instead of a map since A set is a collection of items which are unique i.e no element can be repeated – dota2pro May 08 '19 at 21:51
  • @dota2pro a map has unique keys, and it provides a mapping from each key to a respective value. A set does not have that capability, and your answer using `Set` is completely wrong. Try logging `stats`, and also what `keys` is before you slice, and you'll see what I mean. – Patrick Roberts May 08 '19 at 21:55
  • According to https://javascript.info/map-set-weakmap-weakset Map is a collection of keyed data items, just like an Object. But the main difference is that Map allows keys of any type. A Set is a collection of values, where each value may occur only once. Do you have any source for your info ? – dota2pro May 08 '19 at 21:58
  • 1
    @dota2pro [`Map` key equality algorithm](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Key_equality) ensures that no duplicate keys exist in a `Map`. It's the [exact same algorithm that `Set` uses](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#Value_equality) for its values. – Patrick Roberts May 08 '19 at 22:00
0

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}
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • Thank you works smooth , How do I remove everything except last 2 after sorting ? – Gracie williams May 08 '19 at 20:54
  • See @Triptych answer , he sorted it – Gracie williams May 08 '19 at 20:59
  • @Graciewilliams see [`Object.keys`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys#Description), [`for...in`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in#Deleted_added_or_modified_properties) and finally [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes). His answer is wrong. If you want guaranteed iteration order of keys, use a [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map), not an object. – Patrick Roberts May 08 '19 at 21:03
  • I used something like myObject.set('@', 2); , but i push items randomly on ajax request ,so cannot push in order , I gotta sort it some how later , then remove except last two items. – Gracie williams May 08 '19 at 21:05
  • @Graciewilliams better? – Patrick Roberts May 08 '19 at 21:10
  • What about @dota2pro answer ? he was able to do it with array – Gracie williams May 08 '19 at 21:19
  • Never said you _couldn't_ do it with an array, just that you _shouldn't_. Replacing an entry in an array based on a key in the object entry is cumbersome and inefficient. Replacing a property on an object is a simple assignment that occurs in approximately constant time. You're welcome to accept whatever answer works best for you though. – Patrick Roberts May 08 '19 at 21:21
  • I want optimal solution , I mean u sorted and u sliced like I asked , will urs work just in chrome latest ? – Gracie williams May 08 '19 at 21:22
  • @Graciewilliams if you replace `const` with `var`, my answer works all the way back to internet explorer 9. If you polyfill `Object.keys()` and `Array.prototype.map()`, my answer would then even work on browsers such as netscape. Browser compatibility will not be an issue. – Patrick Roberts May 08 '19 at 21:25
  • When i change to var , I get this - [ { "key": "8", "value": "O" }, { "key": "9", "value": "b" } ] – Gracie williams May 08 '19 at 21:27
  • I am just confused , u told u cannot sort object , so how is ur answer working perfectly ? – Gracie williams May 08 '19 at 21:28
  • @Graciewilliams The problem was that [`status`](https://developer.mozilla.org/en-US/docs/Web/API/Window/status) conflicted with a global variable declaration. Putting the code in an IIFE fixes that. I'm also not sorting an object, I'm sorting an array. Please read the comments and study the code. – Patrick Roberts May 08 '19 at 21:32
  • I want to slice status , because there are 100's of data coming every second.status count is keep increasing. – Gracie williams May 08 '19 at 21:38
  • @Graciewilliams you're conflating two different problems. If you want the last 2 values _sorted by key_, you need to keep all of the properties anyway. If you want the last 2 _recently used_ values, then that's an entirely different question than what you've asked. – Patrick Roberts May 08 '19 at 21:40
  • :( sorry , but my question has this point - delete except last 2 items – Gracie williams May 08 '19 at 21:42
  • "last 2 items" _sorted by what criteria_? You've already specified _sorted by key_, which requires you to keep all the properties in order to determine what the last 2 items of _all the data_ sorted by key is. I'm not editing my answer, and you shouldn't edit your question at this point since several people have already sufficiently answered the question you already asked. Ask a new question and make sure to be very specific about your desired output so you don't waste people's time again. – Patrick Roberts May 08 '19 at 21:45
  • Ok will post new question – Gracie williams May 08 '19 at 21:47
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]);
dota2pro
  • 7,220
  • 7
  • 44
  • 79
0

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;}
Shidersz
  • 16,846
  • 2
  • 23
  • 48