My Findings:
object.forEach loop is messing up order of element (ascending and descending) but for loop is working fine. Does anyone else experienced it too ? or any explanation about it ? I think JavaScript does not guarantee property order of objects.
What I am trying to achieve ?
I have an array of type string which is sorted in descending order.
words: string[] = [];
words = this.getSomeStringArray();
word.sort();
word.reverse();
I have then passed this array to a function named countWordFrequency() as below.
private CountWordFrequency(words: string[]) : Map<string, number> {
let mapObject: Map<string, number> = new Map<string, number>();
for(var i=0; i < words.length; i++) {
!mapObject.has(words[i]) ? mapObject.set(words[i], 1) :
mapObject.set(words[i], mapObject.get(words[i]) + 1);
}
console.log(mapObject);
return mapObject;
}
I have used for loop here because forEach was messing again with the order of words array so with for loop i avoided it and performed insertion so that keys (string) are in descending order and number here presents how many times that word occurred in words array. After this I am calling this function which will first get values from the Map object in valueArray and then it sort in descending order and keep first 5 eliminating rest of them. Then in a forEeach on mapObject I am checking if the map element has value equals to that array keep it else delete it. to this it is fine but again in this operation that descending order of insertion is messed up. Here is the function.
private identifyTopFive(mapObject: Map<string, number>) : Map<string, number> {
let valueArray: number[] = [];
//Get all values from the map in array.
valueArray = Array.from(mapObject.values());
//Sort them in descending order.
valueArray.sort( (a,b)=> { return b-a });
//Delete duplicate values from array.
valueArray = valueArray.filter((element, index, self)=> {
return index == self.indexOf(element);
});
//Get only first 5 values in array and discard rest.
valueArray = valueArray.slice(0,5);
//Delete elements from the mapObject whose values who are not Top 5.
mapObject.forEach((key,value,mapObject) => {
if(!valueArray.includes(key))
mapObject.delete(value);
});
console.log(valueArray);
console.log(mapObject);
return null;
}
This there any technique to keep this descending order or any other data structure that I can use to keep the descending order of string values along with their occurrence.
Solved
The problem was with the data structures I have been using and not the forEach loop. I converted my map object to an array of objects and sorted it based upon the value in descending order, this worked for me.
Related
I have a lot of data that I store in arrays. All array items have a string unique ID (like 123e4567-e89b-12d3-a456-426614174000). Before pushing an item, I call findIndex to make sure no duplicates are inserted with the same ID. I frequently access array items by index and by ID (find(i => i.id === UUID))
Is there a more efficient data structure for this? Supporting these features:
ordered items (should support push, unshift)
get item by ID in O(1) time (faster find)
get item index by ID in O(1) time (faster findIndex)
get items by index (arr[index])
no duplication by ID (#2 will make this easy to support)
easy iteration (for..of / forEach)
sorting items
I looked into a regular object and a Map but they don't support accessing items by index or sorting.
Using an object or Map indexed by ID will work fine. Accessing an object key will be O(1), as is Map.has.
In order to get the item index in O(1) as well without iterating over the object or Map again, create another data structure mapping IDs to their index.
const itemsById = new Map();
const itemsByIndex = new Map();
const addItem = (item) => {
if (itemsById.has(item.id)) return; // duplicate already exists
itemsById.set(item.id, item);
itemsByIndex.set(itemsByIndex.size, item);
};
Maps can be iterated over with for..of. Objects can be iterated over with .entries().
To "sort", put the values into an array, sort the array, then change itemsByIndex as needed.
Edit: you noted in a comment you don't want to sort by UUID. Well be default everything will be sorted by insertion order, and if you wanted to sort by another value you would push that value to the sort array instead of "data.id".
I am upvoting the above answer, because you will have to use a couple different data structures to accomplish what you want. Here is a way to implement this in one Javascript object:
const myDictionary = function() {
let sort = [];
const indexes = new Map();
const dictionary = new Map();
this.addItem = function(data) {
indexes.set(data.id, sort.push(data.id) - 1);
dictionary.set(data.id, data);
}
this.getItemByID = function(key) {
return dictionary.get(key);
}
this.getItemByIndex = function(i) {
return dictionary.get(sort[i]);
}
this.getItemIndex = function(id) {
return indexes.get(id);
}
this.sortItems = function(sortingAlgorithm) {
if (sortingAlgorithm) {
sort = sortingAlgorithm(sort);
} else {
function quickSortBasic(array) {
if(array.length < 2) {
return array;
}
var pivot = array[0];
var lesserArray = [];
var greaterArray = [];
for (var i = 1; i < array.length; i++) {
if ( array[i] > pivot ) {
greaterArray.push(array[i]);
} else {
lesserArray.push(array[i]);
}
}
return quickSortBasic(lesserArray).concat(pivot, quickSortBasic(greaterArray));
}
sort = quickSortBasic(sort);
}
sort.forEach(function (el, i) {
indexes.set(el, i);
});
}
}
const dictionary1 = new myDictionary();
dictionary1.addItem({id: "1", data:"data 1"});
dictionary1.addItem({id: "4", data:"data 4"});
dictionary1.addItem({id: "2", data:"data 2"});
console.log(dictionary1.getItemIndex("4"));
dictionary1.sortItems();
console.log(dictionary1.getItemIndex("4"));
console.log(dictionary1.getItemByID("2"));
console.log(dictionary1.getItemByIndex(0));
Note that sorting uses a quick sort algorithm by default but still has to iterate over the "indexes" map and update all the data, so a bit inefficient. If you can find a way to sort AND update the indexes at the same time, that would be better, but I cannot think of a way right at the moment.
I want to go through an array of strings, and depending on what the string is, make an array of objects.
For example, if the array is:
[a,a,a,b,b,c,d]
I want to map through the array and make an object with key and value pairs that add up the strings consecutively:
[{a:1},{a:2},{a:3},{b:1},{b:2},{c:1},{d:1}]
How do I do this?
I've tried mapping through, but I can't get how to add on to the previous object's value (a:1 -> a:2)
While mapping, you need to store a separate count of how many times each item has appeared, and increment the appropriate key each iteration. You might use a Map for this:
const input = ['a','a','a','b','b','c','d'];
const map = new Map();
console.log(
input.map(char => {
const count = (map.get(char) || 0) + 1;
map.set(char, count);
return { [char]: count };
})
)
I want to sort an array by a split part of an array.
example_array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"]
I want to so it sorts it like this:
console.log(code here) // prints ["Zebra:Add","Jam:Back","Pay:Cold","And:Vets"]
Note: I want "Zebra:Add","Pay:Cold", etc to stay together. I just want it be sorted by the text after the ":".
From your comment on the question:
I can't even think of a solution
Break the problem into smaller pieces. You want to sort an array by a part of the strings in the array, so you need to figure out / look into
How to sort an array (you've done that, you've found the sort method)
How to isolate the part of the string you want to sort on
How to correctly compare strings for Array#sort
How to do #2 and #3 within the context of doing #1
Re #2, there are various ways to do that. You could find the : via String#indexOf and then use substring to get all characters after it. You could split the string on :, then use the second half (if you know there won't be more than one : in the string). Or you could use a regular expression to isolate everything after the first :.
For instance, someString.match(/:.*$/)[0] isolates all characters starting with the first :. (Including the : is harmless, but you could use .substring(1) if you don't want to include it.)
Re #3: Array#sort expects its callback to return a negative number if the first argument should come before the second, 0 if their order doesn't matter, or a positive number if the second should come before the first. String#localeCompare compares strings according to the current locale and returns exactly that information, so we want to use that.
Re #4: Array#sort accepts a callback function, so you could do all the string splitting and comparison in that callback. But since the callback will be called repeatedly, frequently with either the first or second argument being one that's already been checked before, for larger arrays doing it then may be inefficient. It may make more sense to do all the string splitting / isolation in advance, then do the sort, then get your desired result.
So:
The not-particularly-efficient way (which is fine for data sets like your small array) is to isolate the part you want to sort on within the sort callback:
var array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"];
array.sort(function(left, right) {
return left.match(/:.*$/)[0].localeCompare(right.match(/:.*$/)[0]);
});
var array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"];
array.sort(function(left, right) {
return left.match(/:.*$/)[0].localeCompare(right.match(/:.*$/)[0]);
});
console.log(array);
With ES2015+ syntax:
const array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"];
array.sort((left, right) =>
left.match(/:.*$/)[0].localeCompare(right.match(/:.*$/)[0])
);
const array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"];
array.sort((left, right) =>
left.match(/:.*$/)[0].localeCompare(right.match(/:.*$/)[0])
);
console.log(array);
If it's a massive array where doing those splits on every compare is problematic, you could map first, then sort, then unmap:
var array = /*...really big array...*/;
array =
array.map(function(entry) { return {full: entry, key: entry.match(/:.*$/)[0]};})
.sort(function(left, right) { return left.key.localeCompare(right.key); })
.map(function(entry) { return entry.full; });
var array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"];
array =
array.map(function(entry) { return {full: entry, key: entry.match(/:.*$/)[0]};})
.sort(function(left, right) { return left.key.localeCompare(right.key); })
.map(function(entry) { return entry.full; });
console.log(array);
With ES2015+ syntax:
let array = /*...really big array...*/;
array =
array.map(entry => ({full: entry, key: entry.match(/:.*$/)[0] }))
.sort((left, right) => left.key.localeCompare(right.key))
.map(entry => entry.full);
let array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"];
array =
array.map(entry => ({full: entry, key: entry.match(/:.*$/)[0] }))
.sort((left, right) => left.key.localeCompare(right.key))
.map(entry => entry.full);
console.log(array);
I like the simplicity of the previous answer, in comparison My approach is probably too wordy! But here goes...
1.) take the original array and build a new sorting array from it, JSON array with each object having a text1 and text2 value... we'll sort on the text 2 value
2.) run a sort based on the text2 value
3.) empty the original array
4.) loop over the sorting array and re-populate the original array
heres a fiddle example I threw together
// STARTING ARRAY. WE WANT TO SORT BY THE TEXT AFTER THE COLON
example_array = ["Zebra:Add", "Pay:Cold", "And:Vets", "Jam:Back"];
// AN EMPTY ARRAY TO BUILD A JSON ARRAY FROM, THE SORT FROM THE DESIRED TEXT STRING
sorting_array = [];
// LOOP THROUGH THE ORIGINAL ARRAY AND PUSH A NEW OBJECT TO THE SORTING ARRAY
// EACH OBJECT CONTAINS A TEXT1 VALUE AND A TEXT2 VALUE
$.each(example_array, function(i, val){
sorting_array.push({"text1": val.split(':')[0], "text2": val.split(':')[1]})
})
// SORT THE SORTING ARRAY BY THE TEXT2 VALUE
sorting_array.sort(function(a, b){
if (a.text2 < b.text2) return -1;
if (b.text2 < a.text2) return 1;
return 0;
});
// EMPTY OUR ORIGINAL ARRAY
example_array = [];
// FOR DEMO PURPOSES LETS DISPLAY EACH IN THE DOM IN A UL ,
// AND ALSO RE-POPULATE THE ORIGINAL ARRAY WITHT HE NEW ORDER
$.each(sorting_array, function(i, val){
example_array.push(val.text1+':'+val.text2)
})
// TO SHOW THE NEW ORDER, LETS LOOP BACK OVER THE EXAMPLE_ARRAY
$.each(example_array, function(i, val){
$('ul').append('<li>' + val+ '</li>');
})
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;
}
});
I'm trying to sort some xml into different arrays and I'm a bit stuck on how to go about this.
Here is the xml: http://pastebin.ca/1754892
I'm using this xpath expression to get every "series" element that contains at least one "item" child node.
var ceerez = theXML.evaluate( '//series[item]' ,theXML, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
then I'm iterating over the result and putting the elements in a regular array:
var newArr=[];
for (var m = 0; m < ceerez.snapshotLength; m++){
newArr.push(ceerez.snapshotItem(m));
}
so what I would like to do next is to map and sort the elements in the newArr array into new arrays for each different sort.
I want to have one array that is numerically sorted by the "pub" attribute (as numbers) for each series element.
One array that is sorted alphabetically for each "title" child element's text.
One array that is sorted by date for each "item" child element.
I know how to do this with the sort() method, but I cant figure out how to do it from within the map() method so that I can make it into a new array.
var a2z1 = newArr.map(function(item){
item
.sort(function(a,b) {
return a.firstElementChild.textContent.toLowerCase().replace(/[^a-z,0-9]/gm, '') < b.firstElementChild.textContent.toLowerCase().replace(/[^a-z,0-9]/gm, '') ? -1 : 1;
});
);
(a.firstElementChild) would be the "title" element.
Make two other copies of the array (or three, if you don't want to use the original) and, using the sort method, sort each array (in place) by the criteria you specified.
Something like:
var copy1 = newArr.slice(0);
var copy2 = newArr.slice(0);
var copy3 = newArr.slice(0);
copy1.sort(function (a, b) {
// sort logic
});
copy2.sort(function (a, b) {
// sort logic
});
copy3.sort(function (a, b) {
// sort logic
});