Related
I have an array of prices
I would like to group these prices into ranges if they are within 2 of each other
How do I achieve this
// present array
const array = [
'3','5','6','12','17','22'
]
// the result I want
const array_ranges = [
'3-6', '12',
'17','22'
]
Here is a less terse version
const array = ['3', '5', '6', '12', '14', '17', '22'],
rangeGap = 2,
arrRange = array.reduce((acc, num) => {
const range = acc.at(-1).split("-"), last = range.at(-1);
if ((num - last) <= rangeGap) {
if (range.length === 1) range.push(num); // new range
else range[range.length-1] = num; // rewrite last slot
acc[acc.length-1] = range.join("-"); // save the range
} else acc.push(num);
return acc;
}, [array[0]]); // initialise with first entry
console.log(arrRange)
You could define an offset of 2 and check the last pair if the delta is greater as this offset and push a new value the result set, otherwise take the value of the first part of the last stored or value and build a new pair.
const
array = ['3','5','6','12','17','22'],
offset = 2,
result = array.reduce((r, v, i, a) => {
if (!i || v - a[i - 1] > offset) r.push(v);
else r.push(`${r.pop().split('-', 1)[0]}-${v}`);
return r;
}, []);
console.log(result);
One possible generic, configurable and re-usable approach was to implement the reducer function as function statement where the initial value is an object of two properties threshold and result where the former defines a range value's tolerance or delta to its previous/next range value and the latter featuring all created/collected range values.
The reducer does process an array of just number values; therefore a map task which assures number values only has to be executed before.
function createAndCollectNumberRanges({ threshold, result }, current, idx, arr) {
threshold = Math.abs(threshold);
const previous = arr[idx - 1] ?? null;
const next = arr[idx + 1] ?? null;
if (
previous === null ||
previous < current - threshold
) {
result.push(String(current));
} else if (
(next > current + threshold || next === null) &&
previous >= current - threshold
) {
result.push(`${ result.pop() }-${ current }`);
}
return { threshold, result };
}
console.log(
['3', '5', '6', '12', '17', '22']
.map(Number)
.reduce(createAndCollectNumberRanges, {
threshold: 2,
result: [],
}).result
);
console.log(
['0', '3', '5', '6', '12', '14', '15', '17', '22', '23']
.map(Number)
.reduce(createAndCollectNumberRanges, {
threshold: 2,
result: [],
}).result
);
I would make this more generic in two ways. First, it probably improves readability to make 2 a parameter so that in other circumstances, you could choose 10 instead, or ten million.
Second, I would build this atop a more generally useful utility function that groups a sequence of values according to the result of a predicate applied pair-wise to the previous element and the most recent one. Here we would use the predicate (a, b) => b - a <= 2 (or really, given the above, (a, b) => b - a <= threshold.)
It could look like this:
const push = (x) => (xs) => ((xs .push (x)), xs)
const groupWhen = (pred) => (xs) =>
xs .reduce ((r, x, i, a) => i == 0 || !pred (a [i - 1], x)
? push ([x]) (r)
: ((push (x) (r .at (-1))), r),
[]
)
const groupCloseNumbers = (threshold) => (numbers) =>
groupWhen ((a, b) => b - a <= threshold) (numbers .sort ((a, b) => a - b))
.map (ns => ns .length == 1 ? ns [0] : `${ns .at (0)}-${ns .at (-1)}`)
console .log (groupCloseNumbers (2) (['3','5','6','12','17','22']))
//=> ["3-6", "12", "17", "22"]
push is a trivial helper, pushing the value onto an array, returning that array.
Our utility function is groupWhen; it continually adds values to the current group so long as our predicate returns true, creating a new group when it's false. Using this with our predicate, we'll get a result like [["3", "5", "6"], ["12"], ["17"], ["22"]].
Our main function, groupCloseNumbers, numerically sorts the values (if you know they're coming in already sorted, you can skip this step), calls groupWhen, and then maps over the results, to list single values as is and longer lists as ranges.
And if I was feeling that this was still not clear, I would look at several more helper functions, perhaps some or all of these:
const withinDistance = (threshold) => (a, b) => b - a <= threshold
const sortNumerically = (ns) => ns .sort ((a, b) => a - b)
const asRange = (ns) => `${ns .at (0)}-${ns .at (-1)}`
const valueOrRange = (ns) => ns .length == 1 ? ns [0] : asRange (ns)
const groupCloseNumbers = (threshold) => (numbers) =>
groupWhen (withinDistance (threshold)) (sortNumerically (numbers))
.map (valueOrRange)
But that's up to the developer and the developer's team, not me.
I want to sort a 2 dimensional array of integers. What is the simplest and most readable way to achieve this?
input:
[
[3,4,2],
[5,1,3],
[2,6,1],
]
output:
[
[1,1,2],
[2,3,3],
[4,5,6]
]
If you'd need the deeper arrays to be in order as well, I'd tackel it like so:
Flatten the arrays using flat(), so get just a regular list
input.flat()
Sort them using a custom integer sort function
.sort((a, b) => a - b)
Re-create the second dimension
array_chunks(sortedArray, 3);
(Function used taken from this answer)
const input = [
[3,4,2],
[5,1,3],
[2,6,1],
];
const array_chunks = (array, chunk_size) => Array(Math.ceil(array.length / chunk_size)).fill().map((_, index) => index * chunk_size).map(begin => array.slice(begin, begin + chunk_size));
let result = array_chunks(input.flat().sort((a, b) => a - b), 3);
console.log(result);
[
[ 1, 1, 2 ],
[ 2, 3, 3 ],
[ 4, 5, 6 ]
]
When working with nested arrays, its common to work from the inner most level and then step out. Using this method, we start with the inner arrays:
// Sorting the inner most array
var result = [];
[
[3,4,2],
[5,1,3],
[2,6,1],
].forEach(function(row) { // a for loop could work as well, this is just shorter.
result.push(row.sort(function(a, b) {
return a-b; // ascending
// return b-a; // descending
}));
});
Then, you sort the outer array. You cannot sort the outer array as a single number, so its necessary to decide on a method. Examples being: average of the array, lowest value, or highest value.
// Using the first value to sort the outer array.
[
[2,3,4],
[1,3,5],
[1,2,6],
].sort(function(a, b) {
return a[0]-b[0];
});
Alternatively
The wanted output "[[1,1,2],[2,3,3],[4,5,6]]" disregards the inner arrays so it doesn't seem practical. To do this however, we'd reconstruct all inner arrays into one, sort, and then rebuild a nested array assuming each new array is to have 3 values.
var oneDimensionalArray = [];
var finalArray = [];
[
[3,4,2],
[5,1,3],
[2,6,1],
].forEach(function(row) {
oneDimensionalArray = oneDimensionalArray.concat(row);
});
oneDimensionalArray.sort();
for (var i=0; i<oneDimensionalArray.length; i++) {
if (i%3==0) {
temp = [];
}
temp.push(oneDimensionalArray[i]);
if (i%3==2) { // 3 per line (0-2)
finalArray.push(temp);
}
}
Again, I don't see this having practical usefulness. It would be easier to leave them as a regular array or start with a different setup if all the data is to be used in a way that disregards grouping/array.
This is a general approach of nested array, which could have more nested arrays or different lengths.
It works by taking an array of indices to every value, a flat array of values and finally by assigning all values back to their place.
const
getIndices = (value, index) => Array.isArray(value)
? value.flatMap(getIndices).map(array => [index, ...array])
: [[index]],
setValue = (array, keys, value) => {
const last = keys.pop();
keys.reduce((a, i) => a[i] ??= [], array)[last] = value;
return array;
};
data = [[3, 4, 2], [5, 1, 3], [2, 6, 1]],
references = data.flatMap(getIndices),
result = data
.flat()
.sort((a, b) => a - b)
.reduce((r, v, i) => setValue(r, references[i], v), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var arr = [{a: "one", b: "two"}];
/* in real code I have actual filter condition, but the filtered result
share some common properties with value */
var res = {
arr1: arr.filter(x => x),
arr2: arr.filter(x => x)
};
res.arr1.forEach(x => x.a = "a");
console.log(arr); //should print [{a: "one", b: "two"}]
console.log(res.arr1); //should print [{a: "a", b: "two"}]
console.log(res.arr2); //should print [{a: "one", b: "two"}]
If I change the values in arr1 array of object res then why the changes are applied to the arr2 and res also? filter creates new array then the effect should not be applied.
What I am doing wrong here?
Each element in the new array keeps the same object reference so you need to clone the object. In case there is no nested value then you can use Object.assign along with Array#map method. For deeper cloning, you need to use some other library or need to implement your own custom function.
var arr = [{a: "one", b: "two"}];
/* in real code I have actual filter condition, but the filtered result
share some common properties with value */
var res = {
arr1: arr.map(x => Object.assign({}, x)),
arr2: arr.map(x => Object.assign({}, x))
};
res.arr1.forEach(x => x.a = "a");
console.log(arr); //should print [{a: "one", b: "two"}]
console.log(res.arr1); //should print [{a: "a", b: "two"}]
console.log(res.arr2); //should print [{a: "one", b: "two"}]
FYI : What is the most efficient way to deep clone an object in JavaScript?
The reason is that the array contains references to the objects, not copies of them. So while filter does returns a new array, the objects inside them still reference to the same objects.
So, when forEach is mutating the object referenced in res.arr1, it is modifying objects in all the arrays as they all point to the same reference.
Hope this helps.
Your items in the array are objects - they are reference types. Means that you have only one object and the rests are references to that same object. So changing it from one reference affects the result reading from another reference. You need to copy the items from the array. Here I used property spreading to copy the objects.
var arr = [{a: "one", b: "two"}];
var res = {
arr1: arr.map(x => ({...x})),
arr2: arr.map(x => ({...x}))
};
res.arr1.forEach(x => x.a = "a");
console.log(arr);
console.log(res.arr1);
console.log(res.arr2);
When you write :
xs = [
{ p : true },
{ p : false }
];
ys = xs.filter(
x => x.p
);
Here is how it looks like in memory :
xs { p : false }
\ /
[ 0 , 1 ]
\
{ p : true }
/
[ 0 ]
/
ys
As you said, .filter() creates a new array, that's why xs and ys are linked to different arrays. Then, since xs[0].p is true, it makes a copy of xs[0] and pushes it to the new array. What you need to realize here is that xs[0] is a link to { p : true }, it's not the object itself, and ys[0] is a copy of this link. As a consequence, xs[0] and ys[0] are linked to the same object, and if you write xs[0].p = false, you can read the update with ys[0].p.
xs[0]
\
{ p : false }
/
ys[0]
If you want to avoid this situation, you have to copy the object itself :
function copy (x) {
var y = {}; // new object
for (var k in x) y[k] = x[k];
return y;
}
ys[0] = copy(ys[0]);
Since copy() returns a new object, xs[0] and ys[0] are now linked to different objects, thus, changes to one object won't affect the other :
xs[0].p = true;
xs[0] --- { p : true }
ys[0] --- { p : false }
Regarding your code, arr[0] is a link to { a: "one", b: "two" }, and .filter() creates new arrays that contains a copy of this link pointing to the same object :
res.arr1[0]
\
\
arr[0] ----- { a: "one", b: "two" }
/
/
res.arr2[0]
Again, if you want to avoid this situation, you have to copy the object itself. However, since arr is likely to contain more than one object, you have to iterate over the array to copy every object one after the other. .map() is perfect for this job :
res.arr1 = arr.filter(f).map(copy);
res.arr2 = arr.filter(f).map(copy);
res.arr1[0] --- { a: "one", b: "two" }
arr[0] -------- { a: "one", b: "two" }
res.arr2[0] --- { a: "one", b: "two" }
Be careful though, it's a bit trickier when it comes to nested objects :
xs = [{ p: {} }]; // nested objects
ys = [copy(xs[0])];
In the above case, xs[0] and ys[0] are different, but xs[0].p and ys[0].p are the same :
xs[0]
\
{ p }
\
{}
/
{ p }
/
ys[0]
In such a case, you have to make a deep copy of the object. This function will do the trick :
function copyTree (x) {
if (x instanceof Array) {
var copy = [];
for (var i = 0; i < x.length; i++) {
copy[i] = copyTree(x[i]);
}
return copy;
} else if (x === Object(x) && typeof x !== "function") {
var copy = {};
for (var k in x) {
copy[k] = copyTree(x[k]);
}
return copy;
} else {
return x;
}
}
res.arr1 = arr.filter(f).map(copyTree);
res.arr2 = arr.filter(f).map(copyTree);
Note that the same problem arises with nested arrays, hence the array test above.
Excuse me, a quick question:
I have a 2D array of elements where each element has an int value encapsulated. Something like this:
MyComponent = {... , value: 2, ...} // each instance has a value property
MyArray = [
[Component1, Component2],
[Component3],
[Component4, Component5, Component6, Component7],
... ]
I would like to sort my 2D array rows, based on the sum of value of elements in each row.
A little detailed: I have groups of choices (combinations) for the user to select, where each single choice in any group has a certain value. I would like to show the combinations that have the highest sum or total first (so it is descending).
What is a quick and efficient way to achieve that in javascript?
var originalArray = [
[{value: 2}, {value: 3}],
[{value: 11}],
[{value: 1}, {value: 2}, {value: 3}, {value: 4}]
];
var sortedArray = originalArray.sort(function(row1, row2) {
add = function(a, b) {return a + b.value};
return row1.reduce(add, 0) - row2.reduce(add, 0);
});
console.log(sortedArray);
Let's break this down, shall we?
We start with the original array:
var originalArray = [
[{value: 2}, {value: 3}],
[{value: 11}],
[{value: 1}, {value: 2}, {value: 3}, {value: 4}]
];
Then we sort it. Let's work from the inside out. First, how do we calculate the sum of a row? We can use the reduce function:
exampleRow = [{value: 2}, {value: 3}];
add = function(a, b) {return a + b.value};
console.log(exampleRow.reduce(add, 0));
The reduce function takes in another function (add in this case) and uses it to iterate over an array and reduce it to a single item (usually a number). reduce needs a function that takes in two arguments - the running total, and the next item. Reduce roughly does the following:
array.prototype.reduce = function(fn, initial) {
runningTotal = initial;
for (var i = 0; i <= array.length; i++) {
runningTotal = fn(runningTotal, array[i]);
}
return runningTotal;
}
In words, it starts with the initial value, and then runs your function over and over, with each run using the last run's output and the next item in the array.
In our case, the fn function is add:
add = function(a, b) {return a + b.value};
add is very simple; it takes the running total (a) and adds the value of the next item (b). Over the entire array, this simplifies to 0 + array[0].value + array[1].value + ... + array[array.length-1].value - the sum of the array.
We're almost there! The last part is the actual sorting. We do this with the sort function (surprise!). The sort function also takes in a function with two arguments that it uses to iterate over the array. The function you give to sort, however, has to return a number. sort uses this function to compare the two arguments; if the output is positive, the first item is 'bigger', while if the output is negative, the second item is 'bigger'. (If the output is zero they are equal.) sort uses this to sort the array in ascending order.
In our case, our function takes in the two rows and returns the sum of the first minus the sum of the other. This means that if the first row's sum is greater ist will be later in the array and vice versa.
The following sorting function precomputes row-sums to avoid redundant summations and stores the sums in a Map for fast retrieval within the array.sort() callback:
// Sort a 2D array in-place by the sum of its rows:
function sortByRowSum(array) {
let sums = new Map(array.map(row =>
[row, row.reduce((sum, element) => sum + element, 0)]
));
return array.sort((a, b) => sums.get(a) - sums.get(b));
}
// Example:
console.log(sortByRowSum([[1, 3], [2], [0, 1, 8], [0]]));
Since your row elements aren't simple numbers as in the example above, you would need to adapt the reduce callback to the structure of your row elements, e.g.:
let sums = new Map(array.map(row =>
[row, row.reduce((sum, component)=> sum + component.value, 0)]
));
Comparing performance of 2 solutions for myself, might be useful for others, feel free to optimize more: https://jsperf.com/sort-array-by-sum-of-rows/1
I got only 30-70% improvement in Firefox, Chrome and Edge (my ES6 code does not compile in IE11), so the reduced readability is not worth it IMHO:
const simpleSorter = (arr) => [...arr].sort(
(a, b) => b.reduce((sum, i) => sum + i.value, 0) -
a.reduce((sum, i) => sum + i.value, 0)
)
const fasterSorter = (arr) => {
// note: should use stable sort for equal row scores
const rowScores = arr.map((row, index) => ({
score: row.reduce((sum, i, index) => sum + i.value, 0),
index
})).sort((a, b) => b.score - a.score)
return rowScores.map(s => arr[s.index])
}
const rand = () => Math.floor((Math.random() * 10)) + 1
const idGenerator = (() => {
let i = 0
return () => i++
})()
const componentGenerator = () => ({a: 'x', value: rand(), id: idGenerator()})
const rowGenerator = () => [...Array(rand())].map(componentGenerator)
const myArray = [...Array(10)].map(rowGenerator)
const prettyPrinter = (arr) => arr.map(i => {
const temp = i.map(j => j.value)
return temp.join(' + ') + ' = ' + temp.reduce((a, b) => a + b)
})
const myArraySimpleSorted = simpleSorter(myArray)
const myArrayFasterSorted = fasterSorter(myArray)
console.log('test',
prettyPrinter(myArraySimpleSorted).toString() ===
prettyPrinter(myArrayFasterSorted).toString())
console.log('myArray', prettyPrinter(myArray))
console.log('myArraySimpleSorted', prettyPrinter(myArraySimpleSorted))
console.log('myArrayFasterSorted', prettyPrinter(myArrayFasterSorted))
console.log('first in myArray', myArray[0][0])
console.log('first in myArraySimpleSorted', myArraySimpleSorted[0][0])
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.