Javascript: sort multiple arrays with two possible items - javascript

I have an array containing a bunch of arrays, where every one of these arrays has the same length and just two possible entries (+1 and -1 or +1 and 0). Now I'm searching for an efficient way to sort these arrays, so if I want to check if the list contains a given array, there is no need to compare the given array with every one in the list.
The list may look like this:
list = [
[1, 1, -1, 1],
[-1, 1, -1, 1],
[1, -1, -1, 1]
];
To be more precise, this list is created in a while loop, where the condition is that the new array created in the loop does not appear in the list yet. Therefore, it would be nice to sort the array on the fly, e.g. insert it at an appropriate place in the list.
To avoid misunderstandings, the loop now looks like this
var list = [];
var array = someArray;
while (indexOfArray(list, array) === -1) {
list.push(array);
array = calculateNewArray(array);
}
where indexOfArray is a function that returns the index of the array in list if existing, -1 otherwise, and calculateArray takes the previous array and returns a new one. The problem is that the list may get very long (thousands or tens of thousands of different arrays, which may have a length of several hundred entries), so always compare the new array with every saved array in the list becomes extremely time consuming.
What is an efficient and sound approach to tackle this problem?
Edit: to provide an example, the list above could be sorted as
sortedList = sortList(list)
// sortedList should look like this
[
[1, 1, -1, 1],
[1, -1, -1, 1]
[-1, 1, -1, 1],
]
where first comes the array containing as many +1 in a row as possible, second the one with least fewer +1 and so and, while the last array contains as many -1 as possible in a row.

You could use Sorting with map and simply add all values of the inner array.
// the array to be sorted
var list = [[1, 1, -1, 1], [-1, 1, -1, 1], [1, -1, -1, 1]];
// temporary array holds objects with position and sort-value
var mapped = list.map(function (el, i) {
return { index: i, value: el.reduce(function (a, b) { return a + b; }, 0) };
});
// sorting the mapped array containing the reduced values
mapped.sort(function (a, b) {
return b.value - a.value;
});
// container for the resulting order
var result = mapped.map(function (el) {
return list[el.index];
});
console.log(result);
Edit for in situ sorting
Basically, you have two options
use a property for it. (the property sum remains until it became deleted)
var list = [[1, 1, -1, 1], [-1, 1, -1, 1], [1, -1, -1, 1]];
list.forEach(function (el) {
el.sum = el.reduce(function (a, b) { return a + b; }, 0);
});
list.sort(function (a, b) {
return b.sum - a.sum;
});
console.log(list);
get for every sort loop the sums (does not need a property, but the perfomance is bad)
function sum(a, b) { return a + b; }
var list = [[1, 1, -1, 1], [-1, 1, -1, 1], [1, -1, -1, 1]];
list.sort(function (a, b) {
return b.reduce(sum, 0) - a.reduce(sum, 0);
});
console.log(list);

You could just map through the list and sort all the inner arrays with sort().
list = [
[1, 1, -1, 1],
[-1, 1, -1, 1],
[1, -1, -1, 1]
];
list = list.map(function(x){return x.sort(function(a,b){return a-b})});
console.log(list);
It could be even shorter with Javascript ES6:
list = list.map(x=>x.sort((a,b)=>(a-b)));

Related

Sort an object according to an array with underscore

I am trying to sort an object comparing with an array. So the loop will look for specific values on the array, until it finds one, and put those 3 elements at the beginning and the rest at the end.
I am unsure what is the best way to do this any ideas?
It is something like that:
var arr = [1, 3, 2,4,5,6, 2];
var arrSimilar = [1,2,5]
var testSortBy = _.sortBy(arr, function(arrSimilar){
// [1,2,5,3,4,6,2]
});
console.log(testSortBy); // [1,2,5,3,4,6,2]
You could use sorting with map and take the index of the value of similar array as priority sorting and then take the index of all other values as order.
Important is to delete a used value of the similar array, because it is now in use and has no meaning for further similar values. That means, same values are sorted to their original relative index.
var array = [1, 3, 2, 4, 5, 6, 2],
similar = [1, 2, 5],
result = array
.map(function (a, i) {
var priority = similar.indexOf(a);
delete similar[priority]; // delete value, but keep the index of other items
return { index: i, priority: (priority + 1) || Infinity };
})
.sort(function (a, b) {
return a.priority - b.priority || a.index - b.index;
})
.map(function (o) {
return array[o.index];
});
console.log(result); // [1, 2, 5, 3, 4, 6, 2]
You can do that in the following way
Suppose A[] is the original array and B is the priority Array
The answer would be (B intersection A) concat (A-B)
var arr = [1, 3, 2,4,5,6];
var arrSimilar = [1,2,5];
let bInterA = arrSimilar.filter((e) => arr.indexOf(e) != -1);
let aDiffb = arr.filter((e) => arrSimilar.indexOf(e) == -1);
console.log(bInterA.concat(aDiffb));

Join multiple arrays of order indexes

I have multiple arrays of indexes, representing an order:
[[0,1,2], [2,0,1], [1,2,0], [0,1,2], ...]
I need to construct a new array of indexes with a length equal to the total number of indexes in the input while ordering the values using the position indicated in each array in the input.
This would be the output of the input above:
[0, 1, 2, 5, 3, 4, 7, 8, 6, 9, 10, 11, ...]
The total length is 12, so the list of indexes will contain 0-11
The first array of the input is 0, 1, 2 so the output starts with 0, 1, 2
The second array of the input is 2, 0, 1 and the next 3 indexes in the new list are 3-5. ordering these using the second array of the input results in 5,3,4
And so on...
You could use Array#reduce, Array#forEach and the length of the actual array for the count. Then push the sum of all length until now and the value of inner array to the result set.
var array = [[0, 1, 2], [2, 0, 1], [1, 2, 0], [0, 1, 2]],
result = [];
array.reduce(function (r, a) {
a.forEach(function (b) {
result.push(r + b);
});
return r + a.length;
}, 0);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I'm confused by your example, but it sounds like you want to flatten your array by one level. You can do so like this:
var arrays = [[0,1,2], [2,0,1], [1,2,0], [0,1,2]];
var result = [].concat.apply([], arrays);
console.log(result);
If you're using a library like Underscore, there are built-in methods to do that, like the flatten method.
The input and output examples are confusing, but I think what you want it this:
var array = [...] // your array
var result = Array.prototype.concat.apply([], array);
I believe that I interpreted your question based on the expected output, and edited the question accordingly.
You'll need to loop over the sub-arrays in the input and get the next set of indexes for the output, then add those to the output using the order of the sub-array.
This seems to do the trick:
var input = [[0, 1, 2], [2, 0, 1], [1, 2, 0], [0, 1, 2]];
var output = [];
// add values to the output for each of the inputs
$.each(input, function (index, value) {
// get the next and last values to add
var startIndex = output.length;
var lastIndex = (startIndex + value.length) - 1;
// make an array of sequential values from startIndex to lastIndex
var arrayToAdd = [];
for (var i = startIndex; i <= lastIndex; i++) {
arrayToAdd.push(i);
}
// add the values from arrayToAdd in the order of the indexes in the input
$.each(value, function (innerindex, innerindexvalue) {
output.push(arrayToAdd[innerindexvalue]);
});
});
console.log(output);

Can i sort nested array using nested sort()?

This should be the input array
var a = [2,1,3,4,1,[4,6,2,4],2,4,1];
For the output i have two cases :- (index of internal array is not changing)
a = [1,1,2,3,4,[2,4,4,6],1,2,4]
and
a = [1,1,1,2,2,[2,4,4,6],3,4,4]
This is what i am trying to use :-
a.sort(function(a,b){
if(b instanceof Array){
b.sort();
}
})
Array.sort() is not built to handle partial Arrays, what you would need in your case, but we can work around this problem by pre-processing the data (wrapping it with additional information), then sorting and at the end, extracting the original values:
case 1: sorting the parts between the Arrays
[2,1,3,4,1,[4,6,2,4],2,4,1] -> [1,1,2,3,4,[2,4,4,6],1,2,4]
function sort1(arr){
//I add an artificial "property" of to the values, to "describe" the groups, and to be able to sort by
//each Array is it's own group (so they stay in order), and the values in between share the same group
var group = 0,
isArray = false;
//an intermediate Array holding all the information (in order) to either apply it to the current Array, or to return (map) it as a new Array
var intermediate = arr.map(function(v,i){
//last value was an Array, this is the first value after an Array, start a new group
if(isArray) ++group;
if(isArray = Array.isArray(v)){ //update isArray
v = sort1(v); //recursive sorting
++group; //the last group just ended here
}
//return a composition, that contains all the data I need to sort by
return {
group: group,
value: v
}
}).sort(function(a, b){
//forst sort by group, and (only) if two values share the same group, sort by the original value
return a.group - b.group || a.value - b.value
});
//apply data to current Array
intermediate.forEach(function(obj, i){ arr[i] = obj.value });
return arr;
//return new Array
//return intermediate.map(function(obj){ return obj.value });
}
case 2: treating an Array like it's first value
[2,1,3,4,1,[4,6,2,4],2,4,1] -> [1,1,1,2,2,[2,4,4,6],3,4,4]
function sort2(arr){
//an utility to fetch the first non-array value recursively
function _value(v){
while(Array.isArray(v)) v = v[0];
return v;
}
var intermediate = arr.map(function(v, i){
if(Array.isArray(v)) v = sort2(v);
return {
index: i,
value: v,
sortingValue: _value(v)
}
}).sort(function(a, b){
return a.sortingValue - b.sortingValue || a.index - b.index;
});
//apply data to current Array
intermediate.forEach(function(obj, i){ arr[i] = obj.value });
return arr;
//return new Array
//return intermediate.map(function(obj){ return obj.value });
}
This is the perfect solution, use nested function invoke to sort array.
Firstly , store all the array position and sub array.
Secondly, extract numbers into new array,
Finally insert sorted array into same position as before.
var a = [2,1,3,4,1,[4,6,[4,5,[7,3,2,1,6],1,2],2,4],2,4,1];
function nestedSort(arr){
var items = [];
var numArr = [];
for ( key in arr){
if (arr[key] instanceof Array)
{
items.push({index:key,array:arr[key]});
}else{
numArr.push(arr[key]);
}
}
numArr.sort();
for (key in items){
numArr.splice(items[key].index,0,nestedSort(items[key].array));
}
return numArr;
}
console.log(nestedSort(a));
[
1,
1,
1,
2,
2,
[
2,
4,
[
1,
2,
[
1,
2,
3,
6,
7
],
4,
5
],
4,
6
],
3,
4,
4
]
Hope this can solve your problem. :)
You can loop over array and remove all sub arrays and save their index and then sort the new array and again push sorted sub arrays on specific indexes.
Sample
var arr = [2, 1, 3, 4, 1, [4, 6, 2, 4], 2, 4, 1];
var arr1 = [2, 1, 3, 4, 1, [4, 6, 2, 4], 2, 6, 4, [4, 5, 3], 1, 2, 1, 3]
var a = [2,1,3,4,1,[4,6,[4,5,[7,3,2,1,6],1,2],2,4],2,4,1];
function mySort(arr) {
var _list = [];
arr.forEach(function(item, index) {
if (Array.isArray(item)) {
_list.push({
index: index,
value: arr.splice(index, 1).pop()
});
}
});
arr.sort();
_list.forEach(function(item) {
arr.splice(item.index, 0, mySort(item.value))
})
return arr;
}
console.log(mySort(arr.slice()))
console.log(mySort(arr1.slice()))
console.log(mySort(a.slice()))
Edit 1
Inspired from joey-etamity's answer, have made it generic for nested structure.
No, you don't put the sort call in the comparison function. You would recurse through your arrays, bottom to top, and sort them one after the other. In your case you might not even need recursion if it's only one array in another:
a.forEach(function(element) {
if (Array.isArray(element))
element.sort(function compare(a, b) { return a-b; });
})
(I've chosen a simple numerical compare here).
Then you'd sort the outer array:
a.sort(function compare(a, b) {
if (Array.isArray(a)) a = a[0];
if (Array.isArray(b)) b = b[0];
return a - b;
})
(here compare takes the first element of the array to compare by that against the other numbers).
I suggest to splice the array if there is an element an array. Then sort the array and reassemble the array.
This proposal iterates from the back and keeps the array intact while splicing.
function sort(array) {
var i = array.length,
inside = [];
while (i--) {
if (Array.isArray(array[i])) {
inside.unshift({ pos: i, value: sort(array.splice(i, 1)[0]) });
}
}
array.sort(function (a, b) { return a - b; });
inside.forEach(function (a) {
array.splice(a.pos, 0, a.value);
});
return array;
}
var a = [2, 1, 3, 4, 1, [4, 6, 2, 4], 2, 4, 1];
console.log(sort(a));
I think this would be better to use Array.prototype.sort this way:
// var arr = [2, 1, 3, 4, 1, [4, 6, 2, 4], 2, 4, 1];
var arr = [2, 1, 3, 4, 1, [4, 6, 2, 4], 2, 6, 4, [4, 5, 3], 1, 2, 1, 3];
var chunks = chunkate(arr)
console.log(JSON.stringify(chunks));
chunks.forEach(ch => ch.sort(_sort));
var result = chunks.reduce((p, c) => p.concat(c));
console.log(JSON.stringify(result));
function _sort(a, b) {
var isAa = Array.isArray(a),
isAb = Array.isArray(b);
isAb && b.sort(_sort);
return (isAa || isAb) ? 0 : a - b;
}
function chunkate(arr) {
return arr.reduce((a, c) => {
Array.isArray(c) ? a.push(chunkate(c), []) : a[a.length - 1].push(c)
return a;
}, [[]]);
}
How it works?
If items to compare are are array then they shouldn't be replaced so by sending false sort function recognize that there is no need to replace. Otherwise the simple compare is the answer.
Edit
As discussed in comments, it's better to separate values to chunks and then sort each part then join parts again. If nesting depth is only one level you can use default sort (without _sort function) but be aware of array in array used for nested array. So the sort should be changed like this:
chunks.forEach(ch => Array.isArray(ch[0])? ch[0].sort(): ch.sort());

diff 2 arrays where items were simply reordered

Given two arrays arr1 and arr2 that both have the same items, but are sorted differently, how to make a list of least number of item-move operations required to make arr1 match arr2?
The function / algorithm to do this should accept my two arrays as the only arguments, and return an array like this:
[
[1,5],
[3,0],
[7,2]
]
The above array would be interpreted as "Move item at index 1 to index 5, then move item at index 3 to index 0, and finally move item at index 7 to index 2."
By an item-move operation I mean the following:
function arrayMove(array, from, to) {
return array.splice(to, 0, array.splice(from, 1)[0]);
}
When an item is moved from index a to index b, items after index a "slide down" so the item that had index a + 1 now has index a, and when the item is added back at index b, the items that had an index >= b will slide up, so that the item that had index b would now have index b + 1.
Feel free to provide your algorithm in JS or pseudocode, any help appreciated.
This strikes me as related to the edit distance problem. Perhaps you could exploit the Wagner-Fischer algorithm.
Something like this perhaps?
Javascript
// swap two elements in an array by their indexes a and b and
// return an array of the swapped coordinates.
function swap(arr, a, b) {
// assign the value at index a to temp
var temp = arr[a];
// assign the value at index b to index a
arr[a] = arr[b];
// assign the value of temp to the value at index b
arr[b] = temp;
// coordinates of move
return [a, b];
}
// return an array of moved coordinates
function minMoves(arr1, arr2) {
// take a shallow copy of arr2 so that the original is not modified
arr2 = arr2.slice();
// apply a function against an accumulator (moves) for each value of
// the array (arr1) (from left-to-right)
return arr1.reduce(function (moves, item, index) {
// if the values of each array at the index are not the same
if (item !== arr2[index]) {
// swap the current indexed element of arr2 with the value of
// the correct element as indexed in arr1. Add the moved
// coordinates to the beginning of the accumulator
moves.unshift(swap(arr2, index, arr2.lastIndexOf(item)));
}
// return the accumulater for the next iteration
return moves;
}, []);
}
var before = [1, 5, 6, 3, 2, 4, 7, 8, 9, 0],
test = before.slice(),
after = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0],
moves = minMoves(before, after);
console.log('moves: ' + JSON.stringify(moves));
moves.forEach(function(move) {
swap(test, move[0], move[1]);
});
console.log('Should be ordered nicely: ' + JSON.stringify(test));
Output
moves: [[3,5],[2,5],[1,4]]
Should be ordered nicely: [1,2,3,4,5,6,7,8,9,0]
On jsFiddle
This is what I would do, it is not based on any research of algorithms that have been proven optimal.
And here is the code using your arrayMove method instead of swap
Javascript
function arrayMove(array, from, to) {
return array.splice(to, 0, array.splice(from, 1)[0]);
}
// return an array of moved coordinates
function minMoves(arr1, arr2) {
// take a shallow copy of arr2 so that the original is not modified
arr2 = arr2.slice();
// apply a function against an accumulator (moves) for each value of
// the array (arr1) (from left-to-right)
return arr1.reduce(function (moves, item, index) {
var last;
// if the values of each array at the index are not the same
if (item !== arr2[index]) {
// swap the current indexed element of arr2 with the value of
// the correct element as indexed in arr1. Add the moved
// coordinates to the beginning of the accumulator
last = arr2.lastIndexOf(item);
arrayMove(arr2, last, index);
moves.unshift([index, last]);
}
// return the accumulater for the next iteration
return moves;
}, []);
}
var before = [1, 5, 6, 3, 2, 4, 7, 8, 9, 0],
test = before.slice(),
after = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0],
moves = minMoves(before, after);
console.log('moves: ' + JSON.stringify(moves));
moves.forEach(function(move) {
arrayMove(test, move[0], move[1]);
});
console.log('Should be ordered nicely: ' + JSON.stringify(test));
Output
moves: [[3,4],[2,5],[1,4]]
Should be ordered nicely: [1,2,3,4,5,6,7,8,9,0]
On jsFiddle
Finally a jsPerf to compare the two methods.

How to check if a multidimensional array has a specific array?

How to check if arrays has array?
var arrays = [[1, 1], [2, 2]];
var array = [1,1];
[1, 1] === [1, 1]; // false
arrays.includes(array); // false
arrays.indexOf(array); // -1
indexOf compares using strict equality (===). Your elements would have to be the exact same object.
so
var a = [1,1];
var b = [a,[1,2]];
b.indexOf(a)// 0
because a === a
but
b.indexOf([1,1])// -1
because [1,1] is a different object than a so they're not strictly equal.
MDN Docs
To do what you want to do you'll need to do something more involved. You can loop over the values and use something like whats in this question's answers to do the comparison
indexOf returns -1 if it does not find a match in the array. Your array a does not contain element b.
EDIT:
To clarify on what #JonathanLonowski said, the reason there is not a match is because you are doing a strict comparison, comparing the references, not the values.
<Array>.some method tests whether at least one array in the multi-dimensional array passes the test implemented by the provided function.
<Array>.every method tests whether all array items in the array pass the test implemented by the provided function.
These two methods combined make it possible to check if all the items of an array in the multidimensional one are worth those of that sought.
const checkArray = (arrays, array) => arrays.some(a => {
return (a.length > array.length ? a : array).every((_, i) => a[i] === array[i]);
});
const arrays = [[0, 1], [2, 2], [0, 3, 2, 1]];
[
[0, 1], // true
[2, 2], // true
[0, 3, 2, 1], // true
[1, 0, 3, 2, 1], // false
[2, 2, 1], // false
[0, 0], // false
[1, 2], // false
[0, 1, 2] // false
].forEach(array => {
console.log(checkArray(arrays, array));
});

Categories