JavaScript stable sort issue - javascript

I've looked around for some help on this topic but was unable to find some help or guidance.
My problem is I am attempting to perform a sort on a series of values separated by an equals sign.
"Foo=Bar , Shenanigans=Fun, A=B ...etc"
My current sort works, but only if no value is the same. If I have some values like:
"Foo=Bar, A=Bar, Potato=Bar"
When the sort is complete they will all be "A=Bar"
My current sort looks like this, would someone be able to point me in the right direction?
$('#sortByValue').click(function() {
var textValueArray = document.getElementById('nameValuePairList');
textArray = new Array();
valueArray = new Array();
oldValues = new Array();
for (i = 0; i < textValueArray.length; i++) {
valueArray[i] = textValueArray.options[i].value;
textArray[i] = textValueArray.options[i].text;
oldValues[i] = textValueArray.options[i].value;
}
valueArray.sort(function(a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
for (i = 0; i < textValueArray.length; i++) {
textValueArray.options[i].value = valueArray[i];
for (j = 0; j < textValueArray.length; j++) {
if (valueArray[i] == oldValues[j]) {
textValueArray.options[i].text = textArray[j];
j = textValueArray.length;
}
}
}
});
I know that my problem lies here: valueArray[i] == oldValues[j]
as when the data comes in valueArray = {Bar, Foo, Bar} while textArray = {Foo=Bar, A=Foo, Test=Bar}
However, I am unsure how to best resolve it.

Sort textArray directly, don't use valueArray since it will contain duplicates:
textArray.sort(function(a,b){
var aa = a.split('=')
var bb = b.split('=')
var a_key = aa[0].toLowerCase(), a_val = aa[1].toLowerCase();
var b_key = bb[0].toLowerCase(), b_val = bb[1].toLowerCase();
if (a_val == b_val) return a_key.localeCompare(b_key);
return a_val.localeCompare(b_val);
})

I would do something like this:
document.getElementById('sortByName').onclick = sortByName;
function sortByName(){
var myList = document.getElementById('list');
var values = [];
for (var i=0;i<myList.options.length;i++) {
values[i] = myList.options[i].text;
}
values.sort(function (a, b){
if(a !== "" && b !== ""){
return a.split('=')[0].localeCompare(b.split('=')[0]);
} else {
return 0;
}
});
clearList(myList);
fillList(myList, values);
}
function clearList(list) {
while (list.options.length > 0) {
list.options[0] = null;
}
}
function fillList(myList, values){
for (var i=0;i<values.length;i++) {
var option = document.createElement("option");
option.text = values[i];
myList.options[i] = option;
}
}
Take a look at this demo

The reasoning behind doing this at all will have you wondering why, in the future. I think you want something like this:
function inArray(v, a){
for(var i=0,l=a.length; i<l; i++){
if(a[i] === v){
return true;
}
}
return false;
}
function sortWeirdString(str){
var pairs = str.split(/\s?,\s?/), n = [], v = [], c = [], ci, idx = [], cl, nv = [], ra = [];
for(var i=0,l=pairs.length; i<l; i++){
var pair = pairs[i].split(/\s?=\s?/);
n.push(pair[0]); v.push(pair[1]);
}
c = n.concat().sort(); cl = c.length
for(var i=0; i<cl; i++){
var cv = c[i];
if(n.indexOf){
ci = n.indexOf(cv);
if(inArray(ci, idx)){
ci = n.indexOf(cv, ci+1);
}
idx.push(ci);
}
else{
for(var x=0; x<cl; x++){
if(n[x] === cv){
if(inArray(x, idx)){
continue;
}
idx.push(x);
}
}
}
}
for(var i=0,l=idx.length; i<l; i++){
ra.push(c[i]+'='+v[idx[i]]);
}
return ra.join(', ');
}
$('#sortByValue').click(function(){
console.log(sortWeirdString($('#nameValuePairList').val()));
}

Update 2019
The spec has changed and #Array.prototype.sort is now a stable sort.
The elements of this array are sorted. The sort must be stable (that
is, elements that compare equal must remain in their original order)
This is already implemented in V8

Related

How do I build an object counting occurrences in an Array in JavaScript?

I want to count how often a number in an Array occurs. For example, in Python I can use Collections.Counter to create a dictionary of how frequently an item occurs in a list.
This is as far as I've gotten in JavaScript:
var array = [1,4,4,5,5,7];
var obj = {};
for (var i=0; i < array.length; i++) {
/* obj[array[i]] = +=1 */ <= pseudo code
}
How can I create this frequency counter object?
Close but you can't increment undefined so you need to set initial value if it doesn't exist
var array = [1,4,4,5,5,7];
var obj = {};
for (var i=0; i < array.length; i++) {
obj[array[i]] = (obj[array[i]] || 0) +1 ;
}
You were almost there. See below code:
var array = [1,4,4,5,5,7];
var obj = {};
for (var i=0; i < array.length; i++) {
obj[array[i]] = (obj[array[i]] || 0 ) +1;
}
console.log(obj);
Create an object and check if that specific key exist.If exist then increase it's value by 1
var array = [1, 4, 4, 5, 5, 7];
var obj = {};
for (var i = 0; i < array.length; i++) {
if (obj.hasOwnProperty(array[i])) {
obj[array[i]] += 1;
} else {
obj[array[i]] = 1;
}
}
console.log(obj)
You can use the ? : ternary operator to set initial value as 1 and then increment it on subsequent matches.
var array = [1,4,4,5,5,7];
var obj = {};
for (var i=0; i < array.length; i++) {
obj[array[i]] = obj[array[i]]?obj[array[i]]+1:1;
}
console.log(obj);
If the array is always going to be same, and you are going to check frequency of multiple items in the same array without it it being modified, #JohanP's answer is good.
But if you are only going to check frequency of only one item, or the array can change, creating the object is nothing but extra overhead.
In that case, you can do something like this:
const getItemFrequency = function(array, item) {
return array.filter(i => i === item).length;
}
var array = [1,4,4,5,5,7];
console.log(getItemFrequency(array, 4));
Concise logic written as proper function:
function countArrayItemFrequecy(array) {
const length = array.length;
const map = {};
for ( let i = 0; i < length; i++ ) {
let currentItem = array[i];
if (typeof map[currentItem] !== 'undefined' ) {
map[currentItem]++
} else {
map[currentItem] = 1
}
}
return map;
}
You need to make sure to assign default value to your frequency object for the first occurrence of the item. As a shortcut you can use ternary operator
var array = [1,4,4,5,5,7];
var obj = {};
for (var i=0; i < array.length; i++) {
obj[array[i]] = obj[array[i]] ? obj[array[i]]++ : 1;
}
which is the same as:
var array = [1,4,4,5,5,7];
var obj = {};
for (var i=0; i < array.length; i++) {
if (obj[array[i]]) {
obj[array[i]]++;
} else {
obj[array[i]] = 1;
}
}
You can use Object.assign: below clones map and then increments/adds the counter. These are pure (no side effects/param reassignment), single-purpose functions.
addToMap does the same thing as { ...map, map[e]: [e]: (map[e] || 0) + 1 }, but that requires babel.
const addToMap = (map, e) => Object.assign({}, map, { [e]: (map[e] || 0) + 1 });
const buildMap = a => a.reduce(addToMap, {});
Using Array.reduce:
arr.reduce(function (acc, item) {
acc[item] = (acc[item] || 0) + 1;
return acc;
}, {});
Example:
var arr = [1,1,2,4,1,4];
var counts = arr.reduce(function (acc, item) {
acc[item] = (acc[item] || 0) + 1;
return acc;
}, {});
console.log(counts);

How can I return the longest string in an array—even if the array contains items that are not strings?

Let's say I'm writing a function like so:
function longestString (someArray) {
// code
}
If someArray = ['word','longer phrase',['a','b','c'],1234567891011121314151617], I would want the function to only return the longest string in the array and ignore the integers and other arrays that may also lie within it. I tried this:
function longestString (someArray) {
return someArray.sort(function (a, b) { return b.length - a.length; })[0];
}
It didn't work, and I am now stuck. :/
Filter only string element and do it
function longestString (someArray) {
return someArray
.filter(function(a){ return typeof(a)=='string' })
.sort(function (a, b) { return b.length - a.length; })[0];
}
Try this:
function longestString(someArray) {
var result = "";
for (var i = 0; i < someArray.length; i++) {
if ((typeof someArray[i] === "string") && (someArray[i].length > result.length)) {
result = someArray[i];
}
}
return result;
}
try to do :
function longestString (someArray) {
$longest_string = '';
foreach ($someArray as $value)
{
$current_length = strlen($longest_string);
if(strlen($value) > $current_length ) $longest_string = $value;
}
return $longest_string;
}
Try this.
var a = ['word', 'longer phrase', ['a', 'b', 'c'], 1234567891011121314151617];
returnLongestString(a);
function returnLongestString(arr) {
var longestString = '';
for (var i = 0; i < arr.length; i++) {
if (typeof arr[i] == "string" && arr[i].length > longestString.length) {
longestString = arr[i];
}
};
return longestString;
}
When I saw this question, it had 0 answers, when I finished writing and testing my function, there were five. But this is my approach:
The key here is to first filter only the strings. Then, list all the lengths on an array. Then using that array you can get a match on the bigger one.
function longestString (someArray) {
//we only need strings, so first we will filter all the data
var stringsOnly = [];
for (var i = 0; i < someArray.length; i++) {
if(typeof(someArray[i]) === 'string'){
stringsOnly.push(someArray[i]);
}
};
//Now with an array of just strings, we can get their indivial lenghts
var stringLengths = [];
for (var i = 0; i < stringsOnly.length; i++) {
var currentString = stringsOnly[i];
stringLengths.push(currentString.length);
};
//Get the max length
var maxLength = Math.max.apply(Math,stringLengths);
//get a string wich length equals to maxLength
for (var i = 0; i < stringsOnly.length; i++) {
var theString = stringsOnly[i];
if(theString.length === maxLength){return theString};
};
}
This function will return the largest string. If more than one string have the same length, It will return the first one. However, if you want to get various strings, you could make some little modificationson the function above:
function multipleLongestString (someArray) {
//we only need strings, so first we will filter all the data
var stringsOnly = [];
for (var i = 0; i < someArray.length; i++) {
if(typeof(someArray[i]) === 'string'){
stringsOnly.push(someArray[i]);
}
};
//Now with an array of just strings, we can get their indivial lenghts
var stringLengths = [];
for (var i = 0; i < stringsOnly.length; i++) {
var currentString = stringsOnly[i];
stringLengths.push(currentString.length);
};
//Get the max length
var maxLength = Math.max.apply(Math,stringLengths);
//modification here
longests = [];
//get a string wich length equals to maxLength
for (var i = 0; i < stringsOnly.length; i++) {
var theString = stringsOnly[i];
if(theString.length === maxLength){longests.push(theString)};
};
return longests;
}
That is an extra that may help you later. But if you just want the largest one, use the first function. I hope that my answer is relevant.
:)
Heres my approach at it. Really all you want to know is if the phrase is longer than the previous and if its a string.
jsFiddle here.
var someArray = ['word','longer phrase',['a','b','c'],1234567891011121314151617]
var longestString = function(arr) {
var longest = "";
for (var i = 0; i < arr.length; i++) {
var value = arr[i];
if (typeof value === "string") {
longest = arr[i];
}
}
alert(longest);
}
longestString(someArray);

Find out if array contains arithmetic progression in javascript

I have an array:
var myarray = [1,2,3,4,7,9,12,13,14]
I need to group values like so:
var array_1 = 1,2,3,4
var array_2 = 7
var array_3 = 8
var array_4 = 12,13,14
I need to find a sequences with an arithmetic progression and seperate from other values.
Any ideas?
Check out this solution
function explode(myarray)
{
var multi = [];
var i = j = 0;
for ( key in myarray )
{
if((myarray[key-1]) != (myarray[key]-1))
{
i++;
j=0;
}
if(j==0)
multi[i] = [];
multi[i][j] = myarray[key];
j++;
}
return multi;
}
It returns a multidimentionnal array that you can use in your example like this
var myarray = [1,2,3,4,7,9,12,13,14];
var multi_array = explode(myarray);
var array_1 = multi_array[0];
var array_2 = multi_array[1];
var array_3 = multi_array[2];
var array_4 = multi_array[3];
New update :
You can also remove the j index and use .push to add new elements to your array
function explode(myarray)
{
var multi = [];
var i = 0;
for ( key in myarray )
{
if((myarray[key-1]) != (myarray[key]-1))
i++;
if(!multi[i])
multi[i] = [];
multi[i].push(myarray[key]);
}
return multi;
}
The following seems to work, but displays a slightly different output than the one you expect.
In your example, I think 7 and 9 should be grouped (any sequence of two items is an arithmetic
progression after all). Or if they are not grouped, then 12 should not be grouped with 13 and
14 either, since 12-9 != 13-12
function split(arr) {
if (arr.length < 2) {
return;
}
var delta = undefined;
var start = 0;
for (var idx = 1; idx < arr.length; idx++) {
if (delta === undefined) {
delta = arr[idx] - arr[idx - 1];
}
if (arr[idx] - arr[idx - 1] != delta) {
alert("subarray " + arr.slice(start, idx));
start = idx;
delta = undefined;
}
}
alert("subarray from" + arr.slice(start, arr.length));
}
split([1,2,3,4,7,9,12,13,14]);
arrays = Array();
var c = 0;
array[c][] = myarray[0]);
for (var i = 1; i<myarray.length; i++) {
if (myarray[i-1] +1 != myarray[i])
c++;
array[c][] = push(myarray[i]);
}
not sure the array syntax (might mix up languages here) is correct or whether I understand your problem fully.

How do I merge a list of results into a compacted object with totals

I'm looping through a set of inputs. I need to tally up the grouped totals.
var compoundedArray = new Array();
holder.find(".dataset input").each(function(index) {
var val = $(this).val();
var dataType = $(this).data("type");
var localObj = {};
localObj[dataType] = val;
compoundedArray.push(localObj);
});
I have an object like this
[
{
"growth":30
},
{
"growth": 40
},
{
"other": 20
}
]
how do I loop through the object to produce something like
[
{
"growth": 70
},
{
"other": 20
}
]
if I looped over the initial array object
for (var i = 0; i < compoundedArray.length; i++) {
console.log(compoundedArray[i]);
}
how would I go about checking to ensure I don't have duplicates - and that I can tally up the results?
I think your selection of data structure is a bit too complicated. Try something like.
var compoundedObject = {};
holder.find(".dataset input").each(function(index) {
var val = $(this).val();
var dataType = $(this).data("type");
//Assuming all values are integers and can be summed:
if( compoundedObject.hasOwnProperty(dataType) )
{
compoundedObject[dataType] += val;
}
else
{
compoundedObject[dataType] = val;
}
});
You will end up with an object, not an array though.
var add=function (a,b){ a=a||0; b=b||0; return a+b};
var input=[ {growth:30},{growth:40},{other:20} ],output=[],temp={};
$.each(input,function(i,o){
var n;
for(i in o)
{n=i;break}
temp[n]=add(temp[n],o[n]);
});
$.each(temp,function(i,o){
var k={};
k[i]=o;
output.push(k)
});
find output at output variable.
Do not post much specific question, It might not help others.
This works. And it's pure javascript.
var totals = {};
for (var i = 0; i < compoundedArray.length; i++) {
var item = compoundedArray[i];
for (var key in item) {
totals[key] = (totals[key] || 0) + item[key]
}
};
You can loop trough an Object with a for loop.
If you want to delete an item simply set it to null.
Example:
for(var i in compoundedArray){
for(var j in compoundedArray){
if(i == j){
compoundedArray[i] += compoundedArray[j];
compoundedArray[j] = null;
}
}
}
You can do the following:
var totals = [], tmp = {};
for (var i = 0; i < compoundedArray.length; i++) {
var obj = compoundedArray[i];
for (var j in obj) {
tmp[j] = tmp[j] || 0;
tmp[j] += obj[j];
}
}
for(var k in tmp) {
var obj = {};
obj[k] = tmp[k];
totals.push(obj);
}
See this working demo

Using Javascript find array values in another array and in order

Here is one to get your brain going! I've not had any luck with it.
[1,2,1,1,2,1,1,1,2,2]
[1,2,1,1,2,1]
I would like to use the second array to find the values in the first, but they must be in the same order.
Once for I would like it to return the next key up from the last key in the second array.
So in this example it would use the first six digits in the first array and then return 6 as the key after the final one in the second array.
var a2 = [1,2,1,1,2,1,1,1,2,2]
var a1 = [1,2,1,1,0,1]
function find(arr1, arr2) {
var len = 1
var result = 0;
var s2 = arr2.toString();
for (len=1;len <= a1.length; len++)
{
var aa1 = arr1.slice(0, len)
var s1 = aa1.toString();
if(s2.indexOf(s1)>=0){
result = aa1.length;
}
else {
break;
}
}
return result;
}
alert(find(a1, a2));
var find = function(haystack, needle) {
var doesMatch = function(offset) {
for (var i = 0; i < needle.length; i++) {
if (haystack[i+offset] !== needle[i]) {
return false;
}
}
return true;
};
for (var j=0; j < haystack.length - needle.length; j++) {
if (doesMatch(j)) {
return j;
}
}
return -1;
};
This is quick, this is dirty, and this is correct only if your data doesn't include any comma.
var needle = [1,2,1,1,2,1];
var haystack = [1,2,1,1,2,1,1,1,2,2];
if ( needle.length <= 0 ) return 0;
var fromStr = ','+haystack.toString()+','
var findStr = ','+needle.toString()+','
// Find ',1,2,1,1,2,1,' in ',1,2,1,1,2,1,1,1,2,2,'
var pos = fromStr.indexOf(findStr);
// Count the end position requested
return pos >= 0 ? fromStr.slice(0,pos+1).match(/,/g).length + needle.length - 1 : -1;
Note: The comma at head and tail is to make sure [22,12] doesn't match [2,1].

Categories

Resources