JS Sort - Determine if Anything Changed? - javascript

I'm using array.sort to sort an array of objects.
Is there someway to determine if the order of those objects changed other than doing a comparison myself? Some in-built JS function? I don't need to know the # of changes, just that there was at least 1 change.
Update:
I'm sorting custom objects, e.g.:
[
{ 'value' : 5, 'name' : 'foo'},
{ 'value' : 3, 'name' : 'bar'},
{ 'value' : 10, 'name' : 'js'}
]
Update 2:
I'm considering adding an additional custom property 'order' as I generate the objects and then checking to see if 'order' is or isn't sequential after the .sort. Is there a faster/better method?

There is nothing built in that does what you are asking. If it is a simple array you can just use join/toString() and compare the two final strings. If the array contains objects, probably would use JSON.stringify and compare.
var myArray1 = [1,2,3,4,5],
myArray2 = [2,1,3,4,5],
myArray1_sorted = myArray1.slice().sort(),
myArray2_sorted = myArray2.slice().sort();
//Check to see if the strings are the same or different
console.log( myArray1_sorted.toString() === myArray1.toString() ); //true - sort should not change anything
console.log( myArray2_sorted.toString() === myArray2.toString() ); //false - sort should have changed order

If you don't want to mess with two copies of the array, check the array before starting the sort.
You can loop through the array and return true if every item is in its sorted position,
(compares 'less' than the next item) or false as soon as one item is detected in the wrong position.
Since you are using objects, the comparison code depends on the way you are sorting the objects.

From Array.sort docs:
array.sort([compareFunction])
So it is obvious, there is no built-in solution/method for detecting whether at least one element has changed its position during the sorting. So you need to write your own method:
function isAnyElementChangedItsPosition( unsortedArray, sortedArray ){
for( var i = 0; i < unsortedArray.length; ++i){
if( unsortedArray[i] !== sortedArray[i] ){
return true;
}
}
return false;
}

You have to compare the arrays somehow. You can easily clone an array and make the check:
var arr = [5, 4, 1, 6]
var arrBefore = arr.slice(0);
arr.sort()
for(var i =0; i< arr.length; i++) {
if (arrBefore[i] !== arr[i]) {
// a change was made
}
}

In short: no built-in function, you've gotta check for equality.
Here's one way to check for equality:
function arraysEqual(a, b) {
// Are the lengths the same?
var i = a.length;
if (i !== b.length)
return false;
// Are all the values the same?
while (i --) {
if (a[i] !== b[i])
return false;
}
}
Note that this doesn't check if they're arrays; arraysEqual("abc", ["a", "b", "c"]) is true. You can add this check if you want.

There's not a built-in function to do what you want.
How about this:
function isArraySorted(array) {
if (!array.length) {
return true;
}
var lastElement = array[0];
for (var i = 1; i < array.length; i++) {
if (array[i] <= lastElement) {
return false;
}
lastElement = array[i];
}
return true;
}
alert(isArraySorted([1,2,3]) + " " + isArraySorted([1,2,3,2.5]));
var array = [1,2,3];
if (!isArraySorted(array)) {
// No need to do the sort if the array is already sorted.
array.sort();
}

Not duplicating data, becase strings and parallel arrays do.
function chkOrder(a) {
for(var i =1; i< a.length; i++)
if (a[i-1] > a[i]) return false;
return true;
}
you might need to work on th ">" sign if you reverse the order
this will also return false (not ordered) on first occourence
if we can control the contained objects then we can control changes in the objects and fire a parent sort
function O(parent,data) {//initialize with parent and value array/object
this.parent=parent;
this.data=data;
//shortcurt to sort parent
this.sort=function()
{console.log("sortingparent");this.parent.sort(this.parent.sortfunc);}
this.setData=function(data) {
this.data=data;
this.sort();
}
//if changes can be groupped then is more efficient to signal parent dirty and sort latter
this.setKey=function(key,value) {//change value in the data
if (key==parent.sortkey&&value!=this.data[key]) {
this.data[key]=value;
this.sort();
} else this.data[key]=value;
}
this.parent.push(this);
this.sort();
return this;
}
//-------
//using a simple array, this could also be and object and have specific func's
var arr=[];
//example, sort by name, ascending
arr.sortkey="name";
//closure to build a sort predicate bound to the used key
function setkey(key) {return function(a,b) {return a.data[key]>b.data[key];}}
arr.sortfunc=setkey(arr.sortkey);
var b=new O(arr,{name:"B",value:0});
var c=new O(arr,{name:"C",value:2});
var a=new O(arr,{name:"A",value:1});
var d=new O(arr,{name:"D",value:3});
console.log("changing value");
a.setKey("value",100);//when not sorting by value its the same as a.data.value=100
console.log("changing name");
a.setKey("name","X");//this will fire parent sort
for(n=0;n<arr.length;n++) console.log(arr[n].data.name,"=",arr[n].data.value);

var data = [
{ 'value' : 5, 'name' : 'foo'},
{ 'value' : 3, 'name' : 'bar'},
{ 'value' : 10, 'name' : 'js'}
];
var changed = sortAndReport(data, byValue);
// sortAndReport :: [a], (a, a -> Number) -> Boolean
// Sorts an array according to the provided Array.prototype.sort
// compare function and returns a Boolean indicating whether
// sorting changed the order of the array.
function sortAndReport(a, fn) {
var changed = false;
a.sort(function (a, b) {
var sortResult = fn(a, b);
changed = sortResult > 0;
return sortResult;
});
return changed;
}
// byValue :: { value :: Number }, { value :: Number } -> Number
function byValue(a, b) {
return a.value - b.value;
}

Related

I need to write code that will add non-repeating elements to a new array from an old one

I need to write code that will add non-repeating elements to a new array from an old one.
I made 2 functions similar to .includes and .push., .includes checks if the new array has the same element as the old one, if not, it returns true.
Then I turn to the .push function and add this element to the new array. The problem is that in the arrIncludesTest function loop, i is reset every time it is called and the function returns true all the time. Ready-made methods do not suit me, I have to write them myself.
function unique(arr) {
let result = [];
for (let elem of arr) {
if (arrIncludesTest(result, elem)) {
arrPush1(result, elem);
}
}
return result;
}
function arrIncludesTest(result, elem) {
for (let i = 0; i <= result.length; i++) {
if (result[i] !== elem) {
return true;
}
}
return false;
}
function arrPush1(result, elem){
result[result.length] = elem
}
console.log(unique([1,2,2,3,4,4,4,44]))
Your for loop should be using < instead of <=, otherwise i will equal the length of your array which is an invalid index. Your if-statement within arrIncludesTest should only return true when the current element equals the search elem, so you need to change it to use == (or ===), not !==. By using !== you'll almost immediately stop your loop as you'll return false because the first element of result doesn't equal your elem. Then you can push your element into your array if the current element isn't in your array yet !arrIncludesTest(result, elem).
See example below:
function unique(arr) {
let result = [];
for (let elem of arr) {
if (!arrIncludesTest(result, elem)) {
arrPush1(result, elem);
}
}
return result;
}
function arrIncludesTest(result, elem) {
for (let i = 0; i < result.length; i++) {
if (result[i] === elem) {
return true;
}
}
return false;
}
function arrPush1(result, elem) {
result[result.length] = elem;
}
console.log(unique([1, 2, 2, 3, 4, 4, 4, 44]));
Your restrictions about what you can and can't use is a little vague, but I doubt that you can use a Set. As an alternative, another option could be to create an object. Object can only store unique keys, so adding the same key twice doesn't double it up. Within the object, you can add your elements as keys to the object, and then loop over the keys to get unique values:
function unique(arr) {
const obj = {};
for(const elem of arr)
obj[elem] = true;
const res = [];
for(const key in obj)
res[res.length] = +key; // convert the key to a number with the unary plus operator
return res;
}
console.log(unique([1, 2, 2, 3, 4, 4, 4, 44]))
Although, if you're going to do something like this, a Set is more appropriate (as above we're storing an object with meaningless values)

easiest way to see if array has array? Javascript

the only way I can think to do this is to do something like:
for current array length,
for array to check length
if current array i === array to check i
true
Essentially I have the following:
arr = [[1,2], [0,3]];
When I want to add another array to this arrays: [1,2] I need to first see if it exists, if it does do not push it on to the array, if it doesn't then push it.
Is there some really simple, clean readable way to check if an array exists in an array of arrays before pushing it on to the list of elements?
Update:
it should be pretty simple, you have array:
arr = [[1,2], [0,3]];
You try and push:
[1,2]
Nothing happens.
You try and push: [4,6]. New array: [[1,2], [0,3], [4,6]];
Since the complexity is limited, a simple solution exists:
function maybePush(to, val) {
if(!to.some(function(curr) {
return curr.length !== val.length ||
curr.some(function(v, i) { return val[i] === v; });
})) {
to.push(val);
}
}
arr = [[1,2], [0,3]];
maybePush(arr, [1,2]);
maybePush(arr, [5,6]);
console.log(arr);
You'd probably want to add some guards, check that what you expect to be an array really is an array and so on (left out for clarity)...
The idea is simple, check if any of the values of the outer array is equal to the val array, using an iterative comparison.
If you know your array arr contains only integers and arrays, a simple check to see if the array matches the flattened array will indicate if the array contains inner arrays.
var arr = [1,2,3,[4,5],6];
if (JSON.stringify(arr) === JSON.stringify([].concat.apply([], arr))) {
// Does not contain an array
}
The snippet [].concat.apply([], arr) flattens the array arr.
Using underscore you can do this:
Initial approach:
var i = _.findIndex(arr, function (e) {
return (e.join(',') === arr_elem.join(','));
});
if (i === -1) {
arr.push(arr_elem);
}
EDIT Considering performance (Also read the comments here), it would be better to check array equality using a brute loop approach:
function arraysEqual(arr1, arr2) {
if(arr1.length !== arr2.length)
return false;
for(var i = arr1.length; i--;) {
if(arr1[i] !== arr2[i])
return false;
}
return true;
}
var i = _.findIndex(arr, function (e) {
return arraysEqual(arr_elem, e);
});
if (i === -1) {
arr.push(arr_elem);
}

Javascript number of object by different property values in array

Best way to count the number of objects in a array with different object property p values.
function([{"p":"a"},{"p":"b"},{"p":"a"}]){
// some code
}
// in this case return 2
You can use Array.prototype.filter() to keep values that you want. In this case, you must create a variable temp for storing duplicate values and also within the filter function returns true if it does not exist in case. So you have a new array of unique values.
var arr = [{"p":"a"},{"p":"b"},{"p":"a"}], temp = [];
var arrUniques = arr.filter(function(obj){
return temp.indexOf(obj.p) === -1 ? !!temp.push(obj.p) : false
});
alert(arrUniques.length)
With a Map:
var props = new Map();
for (var i = 0; i < array.length; i++) {
var prop = array[i].p,
count = props.get(prop) || 0;
props.set(prop, count + 1);
}
var size = props.size;
If your properties can be safely casted to strings, you can use a common object:
var props = {};
...
var size = Object.keys(props).length;
Otherwise, Map is your answer.
function getUniquePropertyCount(a, p) {
return a.reduce(function (res, el) {
!~res.indexOf(el[p]) && res.push(el[p]);
return res;
}, []).length;
}
document.write(getUniquePropertyCount([{ "p": "a" }, { "p": "b" }, { "p": "a" }], 'p'));
I suppose this is one of those questions where if you ask four programmers, you'll get five answers.
The other answers so far show some interesting approaches. I would watch out for browser compatibility; for example Map() is only available in the newest browsers, not in IE 10 or prior.
So here's one more solution. It's a bit more code, but it's pretty easy to understand, and it works in every browser:
function countUniqueProperties( key, array ) {
var count = 0, values = {};
for( var i = 0; i < array.length; ++i ) {
var value = array[i][key];
if( ! Object.prototype.hasOwnProperty.call( values, value) ) {
++count;
values[value] = true;
}
}
return count;
}
countUniqueProperties( 'p', [ {p:'a'}, {p:'b'}, {p:'a'} ] );
The one complicated part here is the Object.prototype.hasOwnProperty.call(values,value). You could just use values.hasOwnProperty(value), but that would fail if one of your property values was the string "hasOwnProperty":
countUniqueProperties( 'p', [ {p:'a'}, {p:'hasOwnProperty'}, {p:'a'} ] );
Using the longer form avoids this issue.
lodash is nice for things like this.
Use
_.uniq([{"p":"a"},{"p":"b"},{"p":"a"}]).length
and it'll return 2.

This simple comparison of jQuery objects does not compare

Curious what I'm doing wrong here :
employee_ids = $('[data-employee_id="'+employee+'"]');
timestamp_ids = $('[data-scheduled_on="'+timestamp+'"]');
var common = $.grep(timestamp_ids, function(element) {
$.each(employee_ids, function(idx, item) {
if ( item === element ) { console.log ("omg!") };
});
});
This returns just the list of timestamp_ids and not that array compared against employee_ids looking for a single match.
You are not using .grep correctly. Each iteration of grep should return a boolean: true to add it to the result array, false to ignore it.
var listA = [1, 2, 3];
var listB = [2, 3, 4];
var union = $.grep(listA, function (element) {
return listB.indexOf(element) !== -1;
});
Note that IE does not support .indexOf on Arrays, you will have to implement the comparison some other way.
EDIT: if you are trying to find a single item of an array that matches some criteria, i would suggest just using a regular for loop:
var result;
for (var i = 0; i < yourArray.length; i++) {
if (yourArray[i].id === employee_ID) { // whatever conditions you have
result = yourArray[i];
break;
}
}
if (result) {
// do whatever
} else {
// no match
}
Whatever else is wrong with that, it looks like the error is happening at $.grep
What is the typeof of timestamp_ids? According to the jQ docs, it needs to be an array.
Will this work?
employee_ids = $('[data-employee_id="'+employee+'"]');
timestamp_ids = $('[data-scheduled_on="'+timestamp+'"]');
var common = $.grep(timestamp_ids, function(element) {
return !($.inArray(element, timestamp_ids) == -1)
});
Whoa! Thanks for everyone's help. I just realized I could do this :
$('[data-employee_id="'+employee+'"][data-scheduled_on="'+timestamp+'"]');
I also realized I'm an idiot :(

Javascript array sort and unique

I have a JavaScript array like this:
var myData=['237','124','255','124','366','255'];
I need the array elements to be unique and sorted:
myData[0]='124';
myData[1]='237';
myData[2]='255';
myData[3]='366';
Even though the members of array look like integers, they're not integers, since I have already converted each to be string:
var myData[0]=num.toString();
//...and so on.
Is there any way to do all of these tasks in JavaScript?
This is actually very simple. It is much easier to find unique values, if the values are sorted first:
function sort_unique(arr) {
if (arr.length === 0) return arr;
arr = arr.sort(function (a, b) { return a*1 - b*1; });
var ret = [arr[0]];
for (var i = 1; i < arr.length; i++) { //Start loop at 1: arr[0] can never be a duplicate
if (arr[i-1] !== arr[i]) {
ret.push(arr[i]);
}
}
return ret;
}
console.log(sort_unique(['237','124','255','124','366','255']));
//["124", "237", "255", "366"]
You can now achieve the result in just one line of code.
Using new Set to reduce the array to unique set of values.
Apply the sort method after to order the string values.
var myData=['237','124','255','124','366','255']
var uniqueAndSorted = [...new Set(myData)].sort()
UPDATED for newer methods introduced in JavaScript since time of question.
This might be adequate in circumstances where you can't define the function in advance (like in a bookmarklet):
myData.sort().filter(function(el,i,a){return i===a.indexOf(el)})
Here's my (more modern) approach using Array.protoype.reduce():
[2, 1, 2, 3].reduce((a, x) => a.includes(x) ? a : [...a, x], []).sort()
// returns [1, 2, 3]
Edit: More performant version as pointed out in the comments:
arr.sort().filter((x, i, a) => !i || x != a[i-1])
function sort_unique(arr) {
return arr.sort().filter(function(el,i,a) {
return (i==a.indexOf(el));
});
}
How about:
array.sort().filter(function(elem, index, arr) {
return index == arr.length - 1 || arr[index + 1] != elem
})
This is similar to #loostro answer but instead of using indexOf which will reiterate the array for each element to verify that is the first found, it just checks that the next element is different than the current.
Try using an external library like underscore
var f = _.compose(_.uniq, function(array) {
return _.sortBy(array, _.identity);
});
var sortedUnique = f(array);
This relies on _.compose, _.uniq, _.sortBy, _.identity
See live example
What is it doing?
We want a function that takes an array and then returns a sorted array with the non-unique entries removed. This function needs to do two things, sorting and making the array unique.
This is a good job for composition, so we compose the unique & sort function together. _.uniq can just be applied on the array with one argument so it's just passed to _.compose
the _.sortBy function needs a sorting conditional functional. it expects a function that returns a value and the array will be sorted on that value. Since the value that we are ordering it by is the value in the array we can just pass the _.identity function.
We now have a composition of a function that (takes an array and returns a unique array) and a function that (takes an array and returns a sorted array, sorted by their values).
We simply apply the composition on the array and we have our uniquely sorted array.
This function doesn't fail for more than two duplicates values:
function unique(arr) {
var a = [];
var l = arr.length;
for(var i=0; i<l; i++) {
for(var j=i+1; j<l; j++) {
// If a[i] is found later in the array
if (arr[i] === arr[j])
j = ++i;
}
a.push(arr[i]);
}
return a;
};
Here is a simple one liner with O(N), no complicated loops necessary.
> Object.keys(['a', 'b', 'a'].reduce((l, r) => l[r] = l, {})).sort()
[ 'a', 'b' ]
Explanation
Original data set, assume its coming in from an external function
const data = ['a', 'b', 'a']
We want to group all the values onto an object as keys as the method of deduplication. So we use reduce with an object as the default value:
[].reduce(fn, {})
The next step is to create a reduce function which will put the values in the array onto the object. The end result is an object with a unique set of keys.
const reduced = data.reduce((l, r) => l[r] = l, {})
We set l[r] = l because in javascript the value of the assignment expression is returned when an assignment statement is used as an expression. l is the accumulator object and r is the key value. You can also use Object.assign(l, { [r]: (l[r] || 0) + 1 }) or something similar to get the count of each value if that was important to you.
Next we want to get the keys of that object
const keys = Object.keys(reduced)
Then simply use the built-in sort
console.log(keys.sort())
Which is the set of unique values of the original array, sorted
['a', 'b']
The solution in a more elegant way.
var myData=['237','124','255','124','366','255'];
console.log(Array.from(new Set(myData)).sort((a,b) => a - b));
I know the question is very old, but maybe someone will come in handy
A way to use a custom sort function
//func has to return 0 in the case in which they are equal
sort_unique = function(arr,func) {
func = func || function (a, b) {
return a*1 - b*1;
};
arr = arr.sort(func);
var ret = [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (func(arr[i-1],arr[i]) != 0)
ret.push(arr[i]);
}
}
return ret;
}
Example: desc order for an array of objects
MyArray = sort_unique(MyArray , function(a,b){
return b.iterator_internal*1 - a.iterator_internal*1;
});
No redundant "return" array, no ECMA5 built-ins (I'm pretty sure!) and simple to read.
function removeDuplicates(target_array) {
target_array.sort();
var i = 0;
while(i < target_array.length) {
if(target_array[i] === target_array[i+1]) {
target_array.splice(i+1,1);
}
else {
i += 1;
}
}
return target_array;
}
I guess I'll post this answer for some variety. This technique for purging duplicates is something I picked up on for a project in Flash I'm currently working on about a month or so ago.
What you do is make an object and fill it with both a key and a value utilizing each array item. Since duplicate keys are discarded, duplicates are removed.
var nums = [1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 9, 9, 10];
var newNums = purgeArray(nums);
function purgeArray(ar)
{
var obj = {};
var temp = [];
for(var i=0;i<ar.length;i++)
{
obj[ar[i]] = ar[i];
}
for (var item in obj)
{
temp.push(obj[item]);
}
return temp;
}
There's already 5 other answers, so I don't see a need to post a sorting function.
// Another way, that does not rearrange the original Array
// and spends a little less time handling duplicates.
function uniqueSort(arr, sortby){
var A1= arr.slice();
A1= typeof sortby== 'function'? A1.sort(sortby): A1.sort();
var last= A1.shift(), next, A2= [last];
while(A1.length){
next= A1.shift();
while(next=== last) next= A1.shift();
if(next!=undefined){
A2[A2.length]= next;
last= next;
}
}
return A2;
}
var myData= ['237','124','255','124','366','255','100','1000'];
uniqueSort(myData,function(a,b){return a-b})
// the ordinary sort() returns the same array as the number sort here,
// but some strings of digits do not sort so nicely numerical.
function sort() only is only good if your number has same digit, example:
var myData = ["3","11","1","2"]
will return;
var myData = ["1","11","2","3"]
and here improvement for function from mrmonkington
myData.sort().sort(function(a,b){return a - b;}).filter(function(el,i,a){if(i==a.indexOf(el) & el.length>0)return 1;return 0;})
the above function will also delete empty array and you can checkout the demo below
http://jsbin.com/ahojip/2/edit
O[N^2] solutions are bad, especially when the data is already sorted, there is no need to do two nested loops for removing duplicates. One loop and comparing to the previous element will work great.
A simple solution with O[] of sort() would suffice. My solution is:
function sortUnique(arr, compareFunction) {
let sorted = arr.sort(compareFunction);
let result = sorted.filter(compareFunction
? function(val, i, a) { return (i == 0 || compareFunction(a[i-1], val) != 0); }
: function(val, i, a) { return (i == 0 || a[i-1] !== val); }
);
return result;
}
BTW, can do something like this to have Array.sortUnique() method:
Array.prototype.sortUnique = function(compareFunction) {return sortUnique(this, compareFunction); }
Furthermore, sort() could be modified to remove second element if compare() function returns 0 (equal elements), though that code can become messy (need to revise loop boundaries in the flight). Besides, I stay away from making my own sort() functions in interpreted languages, since it will most certainly degrade the performance. So this addition is for the ECMA 2019+ consideration.
The fastest and simpleness way to do this task.
const N = Math.pow(8, 8)
let data = Array.from({length: N}, () => Math.floor(Math.random() * N))
let newData = {}
let len = data.length
// the magic
while (len--) {
newData[data[len]] = true
}
var array = [2,5,4,2,5,9,4,2,6,9,0,5,4,7,8];
var unique_array = [...new Set(array)]; // [ 2, 5, 4, 9, 6, 0, 7, 8 ]
var uniqueWithSorted = unique_array.sort();
console.log(uniqueWithSorted);
output = [ 0, 2, 4, 5, 6, 7, 8, 9 ]
Here, we used only Set for removing duplicity from the array and then used sort for sorting array in ascending order.
I'm afraid you can't combine these functions, ie. you gotta do something like this:-
myData.unique().sort();
Alternatively you can implement a kind of sortedset (as available in other languages) - which carries both the notion of sorting and removing duplicates, as you require.
Hope this helps.
References:-
Array.sort
Array.unique

Categories

Resources