Recursively Sorting an Array based on Keys [duplicate] - javascript

This question already has answers here:
Sort an array of objects by dynamically provided list of object properties in a order by then by style
(5 answers)
Closed 3 years ago.
I have two arrays. One array is the array of items needing to be sorted. Another array is the keys (properties of that object) to sort by. I am wanting to have a function that sorts the array by each key given.
I have tried to loop over the keys array and pop each key off of the array and then sort but adding that key to the ternary I am using to sort the array has been giving me issues.
export function sortOrdersByKeys<T>(ordersArr: T[], sortByKeys: string[]): T[]
{
if (sortByKeys.length === 0) {
return ordersArr;
} else {
const lastItem = sortByKeys.pop();
return sortWithKey(ordersArr, lastItem);
}
}
function sortWithKey(arr, key) {
key = key[0];
for (let i = 0; i < key.length(); i++) {
}
return arr.sort((a, b) => (a.key > b.key) ? 1 : -1);
}

This is a recursive function to sort based in the keys array. let me know if you need an explanation.
function sortWithKey(arr, keys) {
const KEY = keys.pop();
arr = arr.sort((a, b) => (a[KEY]> b[KEY]) ? 1 : -1);
if(keys.legth <=0){
return arr;
}
return sortWithKey(arr, keys) ;
}

There are a few things going wrong here:
1) a.key will look up the "key" property of that object. You probably want a[key]
2) .length() is probably not a function
3) there is neither a recursive call nor a loop in your sortOrderByKeys
4) What is key = key[0]; supposed to do? To take only the first character of the key?
Your overall algorithm will also not work.
array.sort(a).sort(b)
... will sort the array first on a and then on b. So it actually yields the same result as array.sort(b) ...
You rather have to sort once, and then when comparing two array elements a and b, then go over the keys until you find a difference.

Related

How can I loop over an array of objects without Object.keys()?

I'm a beginner at JavaScript and I'm trying to solve this problem without Object.keys() or any regex. I have a working solution but I'm wondering if there's a better way to call on the object key within the array while still looping. If anyone has a way to do this that's basic please let me know.
Problem:
Create a function called keyCount which accepts two parameters, an array of objects, and a string. The function should return a number which is the number of times that key appears in the array of objects.
Expected Result:
countTimesOfKey([{name:"Sharon"}, {name: "Manish"},{lastName: "Terma"}], "name")) // 2
My Answer:
function countTimesOfKey(arr, str) {
let count = 0
for (let i in arr){
let test = arr[i]
let test2 = test[str]
if (test2 !== undefined){
count += 1
}
}
return count
}
You can use Array.filter to filter out the items in the array which have str as a property (by using Object.hasOwnProperty), then return the length of the resulting array:
function countTimesOfKey(arr, str) {
return arr.filter(e => e.hasOwnProperty(str)).length;
}
console.log(countTimesOfKey([{
name: "Sharon"
}, {
name: "Manish"
}, {
lastName: "Terma"
}], "name"))
Of course, using Object.keys() and #Spectric's solution are way better than the one below, I just wanted to show that we can even more 'simplify' this.
We can use for...in to
Loop over each object in the array
Loop over each key of the object on the current index
Compare the name of each key against our check variable
Increase our counter
return the result counter
const result = countTimesOfKey([{name:"Sharon"}, {name: "Manish"},{lastName: "Terma"}], "name");
console.log(result);
function countTimesOfKey(arr, name) {
let counter = 0;
for (a in arr) {
for (let k in arr[a]) {
if (k === name) {
counter++;
}
}
}
return counter;
}

how to get the last 3 elements with specific name value with JMESPATH and javascript?

I have the below json data array:
[{"name":"Button1","date":"1596959802144"},{"name":"Button2","date":"1596959804238"},{"name":"Button3","date":"1596959809334"},{"name":"Button1","date":"1597000878135"},{"name":"Button2","date":"1597000896335"},{"name":"Button3","date":"1597000901536"},{"name":"Button2","date":"1597000904437"},{"name":"Button3","date":"1597000909535"},{"name":"Button1","date":"1597000912250"},{"name":"Button2","date":"1597000939937"},{"name":"Button3","date":"1597000957940"},{"name":"Button2","date":"1597000964640"},{"name":"Button1","date":"1597001005141"},{"name":"Button2","date":"1597001010240"},{"name":"Button3","date":"1597001014845"},{"name":"Button2","date":"1597001021644"},{"name":"Button1","date":"1597001025738"},{"name":"Button2","date":"1597001049030"},{"name":"Button3","date":"1597001054139"},{"name":"Button1","date":"1597001057741"},{"name":"Button2","date":"1597001060340"},{"name":"Button3","date":"1597001062445"},{"name":"Button1","date":"1597002599045"},{"name":"Button1","date":"1597002604128"},{"name":"Button1","date":"1597002609546"},{"name":"Button1","date":"1597002613435"},{"name":"Button1","date":"1597002681736"},{"name":"Button1","date":"1597002690843"},{"name":"Button1","date":"1597002694136"},{"name":"Button1","date":"1597002696349"},{"name":"Button1","date":"1597002699243"}]
and I would like to use JMESPath javascript library to get only the last 3 entries per each distinct name value. For example:
[{"name":"Button3","date":"1597001014845"},{"name":"Button2","date":"1597001021644"},{"name":"Button2","date":"1597001049030"},{"name":"Button3","date":"1597001054139"},{"name":"Button2","date":"1597001060340"},{"name":"Button3","date":"1597001062445"},{"name":"Button1","date":"1597002694136"},{"name":"Button1","date":"1597002696349"},{"name":"Button1","date":"1597002699243"}]
So the last 3 occurrences fro each name = Button*
checking on stackOverflow and I saw that with JQ is possible to do using this function: map_values(delpaths(keys_unsorted[:-2] | map([.])))
Get last N elements for each item of a JSON object
Is there any way to do? or using other javascript module?
If you don't care about the order in your resulting array, here would be a pure JavaScript way to do this:
const getLastNForEveryName = (arr, n) => {
const lastNOfEach = arr.reduce((acc, curr) => {
if(acc[curr.name] == null) { // If the key doesnt exist yet, create it with the current item in the array
acc[curr.name] = [curr];
} else {
if(acc[curr.name].length >= n) // If the array is as big as the desired size alread, remove the first added one
acc[curr.name].shift();
acc[curr.name].push(curr); // push the current item in the array
}
return acc;
}, {})
return Object.values(lastNOfEach).flatMap(l => l); // Just get the values of the object and flatMap it, so that we dont have arrays of arrays
}
// Testing
const values = [{"name":"Button1","date":"1596959802144"},{"name":"Button2","date":"1596959804238"},{"name":"Button3","date":"1596959809334"},{"name":"Button1","date":"1597000878135"},{"name":"Button2","date":"1597000896335"},{"name":"Button3","date":"1597000901536"},{"name":"Button2","date":"1597000904437"},{"name":"Button3","date":"1597000909535"},{"name":"Button1","date":"1597000912250"},{"name":"Button2","date":"1597000939937"},{"name":"Button3","date":"1597000957940"},{"name":"Button2","date":"1597000964640"},{"name":"Button1","date":"1597001005141"},{"name":"Button2","date":"1597001010240"},{"name":"Button3","date":"1597001014845"},{"name":"Button2","date":"1597001021644"},{"name":"Button1","date":"1597001025738"},{"name":"Button2","date":"1597001049030"},{"name":"Button3","date":"1597001054139"},{"name":"Button1","date":"1597001057741"},{"name":"Button2","date":"1597001060340"},{"name":"Button3","date":"1597001062445"},{"name":"Button1","date":"1597002599045"},{"name":"Button1","date":"1597002604128"},{"name":"Button1","date":"1597002609546"},{"name":"Button1","date":"1597002613435"},{"name":"Button1","date":"1597002681736"},{"name":"Button1","date":"1597002690843"},{"name":"Button1","date":"1597002694136"},{"name":"Button1","date":"1597002696349"},{"name":"Button1","date":"1597002699243"}];
console.log(getLastNForEveryName(values, 3));

Sort a JSON array by one field during iteration using JS

I have an array as follows:
var array = {"week1":[{"id":1,"name":"x","mark":"20"},{"id":2,"name":"y","mark":"30"}],"week2":[{"id":1,"name":"x","mark":"40"},{"id":2,"name":"y","mark":"60"},{"id":3,"name":"z","mark":"10"}]}
I want to sort the array by mark field. How can I achieve this?
UPDATE
I used the following function to sort the above array object by mark.
$scope.GetSortOrder = function(prop) {
return function(a, b) {
if (a[prop] > b[prop]) {
return 1;
} else if (a[prop] < b[prop]) {
return -1;
}
return 0;
}
};
array.sort($scope.GetSortOrder("mark"));
But then I get the following error
array.sort is not a function
Desired output
var outPut =
{
"week1":[
{"id":1,"name":"x","mark":"20"},
{"id":2,"name":"y","mark":"30"}
],
"week2":[
{"id":3,"name":"z","mark":"10"},
{"id":1,"name":"x","mark":"40"},
{"id":2,"name":"y","mark":"60"}
]
}
var array is not an array (it's an object), therefore you can't use .sort() on it.
It looks like you want to sort the object's values. If so, you want to fetch the object's values using Object.values(), loop through them, and sort those instead.
var obj = {"week1":[{"id":1,"name":"x","mark":"20"},{"id":2,"name":"y","mark":"30"}],"week2":[{"id":1,"name":"x","mark":"40"},{"id":2,"name":"y","mark":"60"},{"id":3,"name":"z","mark":"10"}]}
Object.values(obj).forEach(arr => arr.sort((a,b) => a.mark-b.mark));
console.log(obj);
If you preferred a method that accepts a property name (like in your example), perhaps this curried approach would work for you.
var obj = {"week1":[{"id":1,"name":"x","mark":"20"},{"id":2,"name":"y","mark":"30"}],"week2":[{"id":1,"name":"x","mark":"40"},{"id":2,"name":"y","mark":"60"},{"id":3,"name":"z","mark":"10"}]}
const sortArrayByProperty = prop => arr => arr.sort((a,b) => a[prop].localeCompare(b[prop]));
Object.values(obj).forEach(sortArrayByProperty("mark"));
console.log(obj);

Why is the Array.sort() method in my Javascript program unstable? [duplicate]

This question already has answers here:
Fast stable sorting algorithm implementation in javascript
(16 answers)
Closed 6 years ago.
Here is my jsFiddle:
//Change this variable to change the number of players sorted
var numberOfPlayers = 15;
var teams = [];
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for(var a=0; a<numberOfPlayers; a++){
updateStandings();
teams.push(new Team(alphabet.charAt(a)));
}
console.log("Teams:");
for(var x=0; x<teams.length; x++){
console.log(teams[x].name);
}
//Functions and such
function updateStandings(){
teams.sort(function(a, b) {
if(a.score == b.score){
if(a.tiebreak == b.tiebreak){
return teams.indexOf(a)-teams.indexOf(b);
}else{
return b.tiebreak-a.tiebreak;
}
}else{
return b.score-a.score;
}
});
}
function Team(name){
this.name = name;
this.score = 0;
this.tiebreak = 0;
}
I assumed the problem was that javascript sorting was unstable, and changed my compare function, but it still does not work.
The generic approach to stable sorting in JS is as follows:
function stable_sort(array, sortfunc) {
function _sortfunc(a, b) { return sortfunc(array[a], array[b]) || a - b; }
return array.map((e, i) => i) . sort(_sortfunc) . map(i => array[i]);
}
What this actually does is to sort a list of indices. Then it maps the sorted list of indices back to the original array. The sort function is rewritten to compare the values in the array at those indices, and if they are equal then fall back to a comparison of indices themselves.
This approach avoids the problem in your code which is that it is doing indexOf look-ups into an array which is the middle of being sorted.
This question could be informative.
According to the documentation, sort method is not required to be stable: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort In some browsers it is stable, in some not.
You do need to change the compare function, but not in the way that you tried. The reason is that you compare
return teams.indexOf(a)-teams.indexOf(b);
in the current array. It means that if the order of a and b has changed on the previous steps, your sorting routine will preserve this new order, not the one that these elements had in the beginning.
There are different ways to solve it. For example, you can create a copy of the array before sorting and execute indexOf on this copy. It will preserve the order that elements had had before sorting started.
But if your know that order in advance, you can also use this knowledge. For example, if before sorting the teams was sorted by their names, you can compare names as strings instead of positions in the array, it would be much more efficient than the first option.
Because JS' sorting is typically unstable. From §22.1.3.24 of the spec:
The elements of this array are sorted. The sort is not necessarily stable (that is, elements that compare equal do not necessarily remain in their original order).
Your teams are created with identical properties except their name, so the line actually performing the sort is:
return teams.indexOf(a)-teams.indexOf(b);
Because you're calling indexOf, it searches for the item (and its index) each repetition of the sort. Sorting mutates the array (from MDN: it "sorts the elements of an array in place and returns the array").
You are searching for the item within the same array you are sorting, so the index may change on each iteration. Done correctly (relatively speaking), you could produce a never-ending sort with that.
For example:
const data = [1, 3, 2, 4];
let reps = 0;
data.sort((a, b) => {
console.log(data);
const ia = data.indexOf(a), ib = data.indexOf(b);
if (ia === ib || reps > 50) {
return 0;
} else if (ia < ib) {
return 1;
} else if (ib < ia) {
return -1;
}
});

Associative array loop order [duplicate]

This question already has answers here:
Elements order in a "for (… in …)" loop
(10 answers)
Closed 9 years ago.
I have an associative array (object) in wich I store data loaded from a 3rd party.
// 3rdPartyData is also an associative array
for(var key in 3rdPartyData) {
cookie[key] = 3rdPartyData[key];
}
My object gets stored into a cookie at the end and gets loaded form a cookie before (or created {} if no cookie exists).
Each page view the data from the 3rd party gets added or updated in my cookie array.
Question: If I wore to look the cookie, would the loop always get each key in the order they wore added to it or would the order be changed?
If so can anyone provide an idea on how to manage something like this?
The order of keys in for(var key in some_object) can be any, and certainly is not always sorted, but you can force the order to be the one you want:
for(var key in Object.keys(3rdPartyData).sort(keysSorter) {
cookie[key] = 3rdPartyData[key];
}
keysSorter = function(a, b) {
if (a === b) { // Should not actually be the case when sorting object keys
return 0;
}
// Compare a and b somehow and return 1 or -1 accordingly
return a < b ? 1 : -1;
}
And to make sure Object.keys() will work:
Object.keys = Object.keys || function(o) {
var result = [];
for(var name in o) {
if (o.hasOwnProperty(name))
result.push(name);
}
return result;
};

Categories