Related
I'm looking for an efficient solution to sort an array depending of how many times an element appears
For example :
let values = ["10", "4", "4", "4", "7", "7"]
I think the best output would be something like [{number, frequency}, ...], which, in the example would look like this :
[{4, 3}, {7, 2}, {10, 1}]
I've seen a lot of ways to do it, but every solution just sort the array depending of the frequency, without any access on how many times the element appears.
At the moment I only have this code that I got from another StackOverflow topic (can't remember which one sorry)
var map = values.reduce(function(p, c) {
p[c] = (p[c] || 0) + 1;
return p;
}, {});
var newTypesArray = Object.keys(map).sort(function(a, b) {
return map[a] < map[b];
});
console.log(newTypesArray);
It's doing a great job at sorting depending on the frequency, but I can't access how many times an element is repeated. And I have no idea how to do it...
Any thoughts?
const arr = [1, 1, 1, 2, 2, 3];
// use reduce for that.
const result = arr.reduce((result, item) => {
const count = result[item];
if (count === undefined) {
result[item] = 1;
} else {
result[item] += 1;
}
return result;
}, {});
// You'll get the result similar to this: {[item]: [count]}
// And then you can transform it into entries array:
const entries = Object.entries(result);
// entries are [[item, count], ...];
// And then sort
const sorted = entries.sort((entryA, entryB) => entryA[1] - entryB[1]);
// You'll have ascending sorted array by count.
console.log(sorted);
Something like this might do the trick:
const data = ["10", "7", "5", "4", "4", "7", "7", "4", "5", "4"];
const result = data.reduce((r, c) => (r[c] = (r[c] || 0) + 1, r), {});
var keys=Object.keys(result).sort((a,b)=>result[b]-result[a]); // sorted keys
console.log(keys.map(k=>k+':'+result[k])); // output, ordered by keys
It is up to you how you want to return the results. result contains the count and keys the actual values.
You can use Object.entries() over the object you got from reduce now sorting the array of key-value pairs and finally mapping to the array of {number, frequency}:
let values = ["10", "4", "4", "4", "7", "7"]
var map = Object.entries(values.reduce(function(p, c) {
p[c] = (p[c] || 0) + 1;
return p;
}, {}))
.sort((a, b) => a[1] < b[1])
.map((a) => ({[a[0]]:a[1]}));
console.log(map);
I think the best output would be something like [{number, frequency}, ...]
Actually these are not valid JavaScript objects inside your output array, if you want to show them as pairs you can use an array of arrays instead of this, the result will be like this:
[[number, frequency], ...]
In your code you were almost there, but you just need to map the result of your first reduce call to get the desired pair:
let values = ["10", "4", "4", "4", "7", "7"];
var map = values.reduce(function(p, c) {
p[c] = (p[c] || 0) + 1;
return p;
}, {});
var newTypesArray = Object.keys(map).map(function(k) {
return [+k, map[k]];
}).sort((a,b) => b[1] - a[1]);
console.log(newTypesArray);
I am merging two sorted arrays in JavaScript. When I call function with two arrays having numbers it works fine, but when I call that function with strings then it does not work. Why?
function mergeSortedArrays(array1, array2) {
const mergedArray = [];
let array1Item = array1[0];
let array2Item = array2[0];
let i = 1;
let j = 1;
if (array1.length === 0) {
return array2;
}
if (array2.length === 0) {
return array1;
}
while (array1Item || array2Item) {
if (array2Item === undefined || array1Item < array2Item) {
mergedArray.push(array1Item);
array1Item = array1[i];
i++;
} else {
mergedArray.push(array2Item);
array2Item = array2[j];
j++;
}
}
console.log(mergedArray);
}
//working?
mergeSortedArrays([0, 3, 4, 12, 222], [3, 4, 6, 30]);
// not working why?
mergeSortedArrays(["0", "3", "4", "12", "222"], ["3", "4", "6", "30"]);
As said in the comments, strings in JS are compared lexically , so, "222" is smaller than "3".
A solution that I see that you can use, is this one:
After checking the arrays for nullity, then concat then into mergedArray, then use the JS function sort(), with the basic return of value1 - value2, that way it will sort the strings in the order you want, and also will work for numbers.
(Further read: Why is one string greater than the other when comparing strings in JavaScript?)
function mergeSortedArrays(array1, array2) {
let mergedArray = [];
if (array1.length === 0) {
return array2;
}
if (array2.length === 0) {
return array1;
}
mergedArray = array1.concat(array2)
mergedArray.sort(function(a, b) {
return a - b
})
console.log(mergedArray);
return mergedArray;
}
mergeSortedArrays([0, 3, 4, 12, 222], [3, 4, 6, 30]);
mergeSortedArrays(["0", "3", "4", "12", "222"], ["3", "4", "6", "30"]);
BUT, be knowing that this solution will only work as expected if the strings are representations of numbers ("1", "2",...), if it is something like "aa", "abc", "b" it probably won't work well and another solution may be needed. (something like this: https://stackoverflow.com/a/51169/8732818)
Sort for string works differently than numbers. Its based on the ASCII table values. For example "99" > "100000" return should return true
This question already has answers here:
JavaScript: Sort an array of objects by a numeric property in each object [duplicate]
(4 answers)
Closed 5 years ago.
This question deals with my algorithm and why it isn't working. More specifically, I would like to know how it can be improved to do what I want it to do. That is why it is different from the suggested duplicate question.
I am trying to create a function that sorts an array of objects based on a property value (int) that they all share in common, "indexFound". As you may suspect, I am trying to place elements with a lower indexFound at the beginning of the array.
function organizeTokens(list) {
for (i = 0; i < list.length - 1; i++) {
if (list[i].indexFound < list[i + 1].indexFound) {
// do nothing
} else if (list[i].indexFound > list[i + 1].indexFound) {
var tempVal = list[i];
list[i] = list[i + 1];
list[i + 1] = tempVal;
} else {
// should not happen unless we are comparing the same token
}
}
};
As it stands, this code is not making any difference when I feed it an array of objects. The elements are still not in the order that they should be. Am I approaching this in the right way? Am I missing something obvious?
EDIT: -------------------------------------------------------------------
Example input: organizeTokens([{value: "if", indexFound: 7}, {value: "a", indexFound: 0}])
Expected output: [{value: "a", indexFound: 0}, {value: "if", indexFound: 7}]
Actual output: [{value: "if", indexFound: 7}, {value: "a", indexFound: 0}]
You can use Array.prototype.sort() and define a compare function:
function compareIndexFound(a, b) {
if (a.indexFound < b.indexFound) { return -1; }
if (a.indexFound > b.indexFound) { return 1; }
return 0;
}
list.sort(compareIndexFound);
Simpler/Concise version of above compare function:
function compareIndexFound(a, b) {
return a.indexFound - b.indexFound;
}
Using ES6:
list.sort((a, b) => a.indexFound - b.indexFound);
You can define your own sortBy function:
function sortBy(arr, prop) {
return arr.sort((a, b) => a[prop] - b[prop]);
}
sortBy(list, 'indexFound');
You can use JavaScript's built-in sort:
list.sort(function (l, r) {
return l.indexFound - r.indexFound;
});
If you're using a utility like lodash or underscore, they have a sort function
that's even simpler:
var sorted = _.sortBy(list, 'indexFound');
Example:
var list = [
{ indexFound: 9 },
{ indexFound: 3 },
{ indexFound: 17 },
{ indexFound: 1 }
];
var sorted = _.sortBy(list, 'indexFound');
console.log(sorted);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Use the JS sort method with a custom callback function. Like this:
list.sort(function (a, b) {
return a.indexFound - b.indexFound;
});
This will sort as ascending (lowest to highest).
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
Suppose I have the array as follows
var array=[3,4,5,5,5,6,8,3,12,1,1,1];
Then the result should be
array=[5,1,3,4,6,8,12];
Required implementation in JavaScript or nodejs
I used a combination of lodash and plain JavaScript array methods in this jsbin example:
var arr = [3,4,5,5,5,6,8,3,12,1,1,1];
var sorted = _.sortBy(_.toPairs(arr.reduce(function(agg, curr) {
agg[curr] = agg[curr] ? agg[curr] + 1 : 1;
return agg;
}, {})), function(pair) {
return -pair[1];
}).map(function(pair) {
return pair[0];
});
console.log(sorted); // => ["1", "5", "3", "4", "6", "8", "12"]
However, the ordering of "5" and "1" is different that the ordering of the 3,4,6,8,12, because the sort order wasn't specified for numbers that have identical counts.
What the above does is created a map of number=>count (e.g. { "1": 3, "5": 3 }), and then pairs them as tuples (since objects can't be sorted deterministically in JavaScript: [["1", 3], ["5", 3]]). Then, we simply sort the collection of tuples based on the count, and map over the collection of tuples to return just the number (e.g. ["1", "5", /* etc. */ ]).
var array = [3, 4, 5, 5, 5, 6, 8, 3, 12, 1, 1, 1];
var obj = {};
array.forEach(e => obj[e] = obj[e] + 1 || 1);
var sorted = Object.keys(obj)
.map(e => ({ n: e, times: obj[e] }))
.sort((a, b) => b.times - a.times)
.map(e => e.n);
document.write(sorted);
Here's one way to do it:
JSBIN: https://jsbin.com/josuwir/1/edit?js,console
var array=[3,4,5,5,5,6,8,3,12,1,1,1];
var c = array.reduce(function(a, b) {
a[b] = ++a[b] || 1;
return a;
}, {});
var keys = Object.keys(c);
var nn = keys.sort(function(a, b) {
if (c[a] < c[b]) {
return 1;
} else {
return -1;
}
}).map(function(a) {return Number(a)});
function sortArray(array) {
var reducedArray = array.filter(function(item, pos) { //A copy without duplicates
return array.indexOf(item) == pos;
})
var elementFreq = {} //Object that contains element frequencies
for (var i=0; i<reducedArray.length; i++) {
var count = 0;
for (var j=0; j<array.length; j++) {
if (array[j] == reducedArray[i]) {
count++;
}
}
elementFreq[array[i]] = count;
}
function compare(a,b) { //compares the frequency of two elements
return elementFreq[b]-elementFreq[a]
}
reducedArray.sort(compare) //sorts reducedArray based using compare function
return reducedArray
}
For example, if I have these arrays:
var name = ["Bob","Tom","Larry"];
var age = ["10", "20", "30"];
And I use name.sort() the order of the "name" array becomes:
var name = ["Bob","Larry","Tom"];
But, how can I sort the "name" array and have the "age" array keep the same order? Like this:
var name = ["Bob","Larry","Tom"];
var age = ["10", "30", "20"];
You can sort the existing arrays, or reorganize the data.
Method 1:
To use the existing arrays, you can combine, sort, and separate them:
(Assuming equal length arrays)
var names = ["Bob","Tom","Larry"];
var ages = ["10", "20", "30"];
//1) combine the arrays:
var list = [];
for (var j = 0; j < names.length; j++)
list.push({'name': names[j], 'age': ages[j]});
//2) sort:
list.sort(function(a, b) {
return ((a.name < b.name) ? -1 : ((a.name == b.name) ? 0 : 1));
//Sort could be modified to, for example, sort on the age
// if the name is the same. See Bonus section below
});
//3) separate them back out:
for (var k = 0; k < list.length; k++) {
names[k] = list[k].name;
ages[k] = list[k].age;
}
This has the advantage of not relying on string parsing techniques, and could be used on any number of arrays that need to be sorted together.
Method 2: Or you can reorganize the data a bit, and just sort a collection of objects:
var list = [
{name: "Bob", age: 10},
{name: "Tom", age: 20},
{name: "Larry", age: 30}
];
list.sort(function(a, b) {
return ((a.name < b.name) ? -1 : ((a.name == b.name) ? 0 : 1));
});
for (var i = 0; i<list.length; i++) {
alert(list[i].name + ", " + list[i].age);
}
For the comparisons,-1 means lower index, 0 means equal, and 1 means higher index. And it is worth noting that sort() actually changes the underlying array.
Also worth noting, method 2 is more efficient as you do not have to loop through the entire list twice in addition to the sort.
http://jsfiddle.net/ghBn7/38/
Bonus Here is a generic sort method that takes one or more property names.
function sort_by_property(list, property_name_list) {
list.sort((a, b) => {
for (var p = 0; p < property_name_list.length; p++) {
prop = property_name_list[p];
if (a[prop] < b[prop]) {
return -1;
} else if (a[prop] !== a[prop]) {
return 1;
}
}
return 0;
});
}
Usage:
var list = [
{name: "Bob", age: 10},
{name: "Tom", age: 20},
{name: "Larry", age: 30},
{name: "Larry", age: 25}
];
sort_by_property(list, ["name", "age"]);
for (var i = 0; i<list.length; i++) {
console.log(list[i].name + ", " + list[i].age);
}
Output:
Bob, 10
Larry, 25
Larry, 30
Tom, 20
You could get the indices of name array using Array.from(name.keys()) or [...name.keys()]. Sort the indices based on their value. Then use map to get the value for the corresponding indices in any number of related arrays
const indices = Array.from(name.keys())
indices.sort( (a,b) => name[a].localeCompare(name[b]) )
const sortedName = indices.map(i => name[i]),
const sortedAge = indices.map(i => age[i])
Here's a snippet:
const name = ["Bob","Tom","Larry"],
age = ["10", "20", "30"],
indices = Array.from(name.keys())
.sort( (a,b) => name[a].localeCompare(name[b]) ),
sortedName = indices.map(i => name[i]),
sortedAge = indices.map(i => age[i])
console.log(indices)
console.log(sortedName)
console.log(sortedAge)
This solution (my work) sorts multiple arrays, without transforming the data to an intermediary structure, and works on large arrays efficiently. It allows passing arrays as a list, or object, and supports a custom compareFunction.
Usage:
let people = ["john", "benny", "sally", "george"];
let peopleIds = [10, 20, 30, 40];
sortArrays([people, peopleIds]);
[["benny", "george", "john", "sally"], [20, 40, 10, 30]] // output
sortArrays({people, peopleIds});
{"people": ["benny", "george", "john", "sally"], "peopleIds": [20, 40, 10, 30]} // output
Algorithm:
Create a list of indexes of the main array (sortableArray)
Sort the indexes with a custom compareFunction that compares the values, looked up with the index
For each input array, map each index, in order, to its value
Implementation:
/**
* Sorts all arrays together with the first. Pass either a list of arrays, or a map. Any key is accepted.
* Array|Object arrays [sortableArray, ...otherArrays]; {sortableArray: [], secondaryArray: [], ...}
* Function comparator(?,?) -> int optional compareFunction, compatible with Array.sort(compareFunction)
*/
function sortArrays(arrays, comparator = (a, b) => (a < b) ? -1 : (a > b) ? 1 : 0) {
let arrayKeys = Object.keys(arrays);
let sortableArray = Object.values(arrays)[0];
let indexes = Object.keys(sortableArray);
let sortedIndexes = indexes.sort((a, b) => comparator(sortableArray[a], sortableArray[b]));
let sortByIndexes = (array, sortedIndexes) => sortedIndexes.map(sortedIndex => array[sortedIndex]);
if (Array.isArray(arrays)) {
return arrayKeys.map(arrayIndex => sortByIndexes(arrays[arrayIndex], sortedIndexes));
} else {
let sortedArrays = {};
arrayKeys.forEach((arrayKey) => {
sortedArrays[arrayKey] = sortByIndexes(arrays[arrayKey], sortedIndexes);
});
return sortedArrays;
}
}
See also https://gist.github.com/boukeversteegh/3219ffb912ac6ef7282b1f5ce7a379ad
If performance matters, there is sort-ids package for that purpose:
var sortIds = require('sort-ids')
var reorder = require('array-rearrange')
var name = ["Bob","Larry","Tom"];
var age = [30, 20, 10];
var ids = sortIds(age)
reorder(age, ids)
reorder(name, ids)
That is ~5 times faster than the comparator function.
It is very similar to jwatts1980's answer (Update 2).
Consider reading Sorting with map.
name.map(function (v, i) {
return {
value1 : v,
value2 : age[i]
};
}).sort(function (a, b) {
return ((a.value1 < b.value1) ? -1 : ((a.value1 == b.value1) ? 0 : 1));
}).forEach(function (v, i) {
name[i] = v.value1;
age[i] = v.value2;
});
You are trying to sort 2 independet arrays by only calling sort() on one of them.
One way of achieving this would be writing your own sorting methd which would take care of this, meaning when it swaps 2 elements in-place in the "original" array, it should swap 2 elements in-place in the "attribute" array.
Here is a pseudocode on how you might try it.
function mySort(originals, attributes) {
// Start of your sorting code here
swap(originals, i, j);
swap(attributes, i, j);
// Rest of your sorting code here
}
inspired from #jwatts1980's answer, and #Alexander's answer here I merged both answer's into a quick and dirty solution;
The main array is the one to be sorted, the rest just follows its indexes
NOTE: Not very efficient for very very large arrays
/* #sort argument is the array that has the values to sort
#followers argument is an array of arrays which are all same length of 'sort'
all will be sorted accordingly
example:
sortMutipleArrays(
[0, 6, 7, 8, 3, 4, 9],
[ ["zr", "sx", "sv", "et", "th", "fr", "nn"],
["zero", "six", "seven", "eight", "three", "four", "nine"]
]
);
// Will return
{
sorted: [0, 3, 4, 6, 7, 8, 9],
followed: [
["zr", th, "fr", "sx", "sv", "et", "nn"],
["zero", "three", "four", "six", "seven", "eight", "nine"]
]
}
*/
You probably want to change the method signature/return structure, but that should be easy though. I did it this way because I needed it
var sortMultipleArrays = function (sort, followers) {
var index = this.getSortedIndex(sort)
, followed = [];
followers.unshift(sort);
followers.forEach(function(arr){
var _arr = [];
for(var i = 0; i < arr.length; i++)
_arr[i] = arr[index[i]];
followed.push(_arr);
});
var result = {sorted: followed[0]};
followed.shift();
result.followed = followed;
return result;
};
var getSortedIndex = function (arr) {
var index = [];
for (var i = 0; i < arr.length; i++) {
index.push(i);
}
index = index.sort((function(arr){
/* this will sort ints in descending order, change it based on your needs */
return function (a, b) {return ((arr[a] > arr[b]) ? -1 : ((arr[a] < arr[b]) ? 1 : 0));
};
})(arr));
return index;
};
I was looking for something more generic and functional than the current answers.
Here's what I came up with: an es6 implementation (with no mutations!) that lets you sort as many arrays as you want given a "source" array
/**
* Given multiple arrays of the same length, sort one (the "source" array), and
* sort all other arrays to reorder the same way the source array does.
*
* Usage:
*
* sortMultipleArrays( objectWithArrays, sortFunctionToApplyToSource )
*
* sortMultipleArrays(
* {
* source: [...],
* other1: [...],
* other2: [...]
* },
* (a, b) => { return a - b })
* )
*
* Returns:
* {
* source: [..sorted source array]
* other1: [...other1 sorted in same order as source],
* other2: [...other2 sorted in same order as source]
* }
*/
export function sortMultipleArrays( namedArrays, sortFn ) {
const { source } = namedArrays;
if( !source ) {
throw new Error('You must pass in an object containing a key named "source" pointing to an array');
}
const arrayNames = Object.keys( namedArrays );
// First build an array combining all arrays into one, eg
// [{ source: 'source1', other: 'other1' }, { source: 'source2', other: 'other2' } ...]
return source.map(( value, index ) =>
arrayNames.reduce((memo, name) => ({
...memo,
[ name ]: namedArrays[ name ][ index ]
}), {})
)
// Then have user defined sort function sort the single array, but only
// pass in the source value
.sort(( a, b ) => sortFn( a.source, b.source ))
// Then turn the source array back into an object with the values being the
// sorted arrays, eg
// { source: [ 'source1', 'source2' ], other: [ 'other1', 'other2' ] ... }
.reduce(( memo, group ) =>
arrayNames.reduce((ongoingMemo, arrayName) => ({
...ongoingMemo,
[ arrayName ]: [
...( ongoingMemo[ arrayName ] || [] ),
group[ arrayName ]
]
}), memo), {});
}
You could append the original index of each member to the value, sort the array, then remove the index and use it to re-order the other array. It will only work where the contents are strings or can be converted to and from strings successfuly.
Another solution is keep a copy of the original array, then after sorting, find where each member is now and adjust the other array appropriately.
I was having the same issue and came up with this incredibly simple solution. First combine the associated ellements into strings in a seperate array then use parseInt in your sort comparison function like this:
<html>
<body>
<div id="outPut"></div>
<script>
var theNums = [13,12,14];
var theStrs = ["a","b","c"];
var theCombine = [];
for (var x in theNums)
{
theCombine[x] = theNums[x] + "," + theStrs;
}
var theSorted = theAr.sort(function(a,b)
{
var c = parseInt(a,10);
var d = parseInt(b,10);
return c-d;
});
document.getElementById("outPut").innerHTML = theS;
</script>
</body>
</html>
How about:
var names = ["Bob","Tom","Larry"];
var ages = ["10", "20", "30"];
var n = names.slice(0).sort()
var a = [];
for (x in n)
{
i = names.indexOf(n[x]);
a.push(ages[i]);
names[i] = null;
}
names = n
ages = a
Simplest explantion is the best, merge the arrays, and then extract after sorting:
create an array
name_age=["bob#10","Tom#20","Larry#30"];
sort the array as before, then extract the name and the age, you can use # to reconise where
name ends and age begins. Maybe not a method for the purist, but I have the same issue and this my approach.