Sorting a Multidimensional Array with Objects in JavaScript - javascript

I have an array with the following structure:
[[],[{"id":1,"meaning":1,"word":"b"},{"id":2,"meaning":1,"word":"a"}],[{"id":3,"meaning":2,"word":"f"},{"id":4,"meaning":2,"word":"c"}],[{"id":5,"meaning":3,"word":"d"}]]
array[0] needs to be empty, because I need that index for a special usage later on.
array[1] for example contains all objects with meaning:1, array[2] all those with meaning:2.
What I want to do now is to sort this array by the first object in the 2D-Array(so to say by the first column).
Thus the output should be like:
[[],[{"id":1,"meaning":1,"word":"b"},{"id":2,"meaning":1,"word":"a"}], [{"id":5,"meaning":3,"word":"d"}], [{"id":3,"meaning":2,"word":"f"},{"id":4,"meaning":2,"word":"c"}]]
I would appreciate all types of answers.
EDIT:
The sort criterion is an ascending alphabetic order

First off, ditch the empty array, as its much easier to add that later than to code around it in your sorting function. Assuming your top level array is called 'arr':
//first we'll sort the sub arrays themselves
var newArr = arr.map(function(subArray) {
subArray.sort(function(a, b) {
if (a.word > b.word) {
return -1;
} else {
return 1;
}
});
return subArray;
})
//and now sort the containing array
.sort(function(a, b) {
if (a[0].word > b[0].word) {
return -1;
} else {
return 1;
}
});
//now we'll add back in your empty array
newArr = [[]].concat(newArr);
if the sub arrays are already in alphebetical order than you only need the global sort.

Did you know that sort may take a function as an argument?
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

Sorting is a comparison, if I understand you correctly you just need to rearrange the objects based on the meaning property.
This should do the trick for you, of course the sorting on word is up to you.
function arrangeByMeaning (arr) {
arr.forEach(function (innerArr, index) {
innerArr.forEach(function (obj, i) {
if (parseInt(obj.meaning) !== index) {
var o = innerArr.pop(i);
arr[index].push(o);
}
});
});
// sort innerArrays on word after rearranging objects
arr.forEach(function (innerArr) {
innerArr.sort(function (a, b) {
return a.word < b.word ? -1 : a.word > b.word ? 1 : 0;
});
});
return arr;
};
This will modify the original array.
Fiddle

Here you go. Bit untidy but works: http://jsfiddle.net/8xv6s9g1/2/
var array = [[],[{"id":1,"meaning":1,"word":"b"},{"id":2,"meaning":1,"word":"a"}],[{"id":3,"meaning":2,"word":"f"},{"id":4,"meaning":2,"word":"c"}],[{"id":5,"meaning":3,"word":"d"}]];
var arrayCopy = array.slice();
for(var i = 0; i < arrayCopy.length; i++) {
arrayCopy[i].sort(function(x, y) {
if(!x || x.word < y.word) return 1;
if(x.word > y.word) return -1;
return 0;
});
}
arrayCopy.sort(function(x, y) {
if(!x.length && !y.length) return 0;
if(!x.length) return -1;
if(!y.length) return 1;
if(x[0].word < y[0].word) return -1;
if(x[0].word > y[0].word) return 1;
return 0;
});
for(var i = 0; i< arrayCopy.length; i++) {
arrayCopy[i].index = i;
}
array.sort(function(x, y) {
if(!x || x.index < y.index) return -1;
if(x.index > y.index) return 1;
return 0;
});
console.log(array);
Output is [[],[{"id":1,"meaning":1,"word":"b"},{"id":2,"meaning":1,"word":"a"}], [{"id":5,"meaning":3,"word":"d"}], [{"id":3,"meaning":2,"word":"f"},{"id":4,"meaning":2,"word":"c"}]] as required

Related

d3 js sort error in chrome [duplicate]

I have an array of objects to sort. Each object has two parameters: Strength and Name
objects = []
object[0] = {strength: 3, name: "Leo"}
object[1] = {strength: 3, name: "Mike"}
I want to sort first by Strength and then by name alphabetically. I am using the following code to sort by the first parameter. How do I sort then by the second?
function sortF(ob1,ob2) {
if (ob1.strength > ob2.strength) {return 1}
else if (ob1.strength < ob2.strength){return -1}
return 0;
};
Thanks for your help.
(I am using Array.sort() with the aforementioned sortF as the sort comparison function passed into it.)
Expand your sort function to be like this;
function sortF(ob1,ob2) {
if (ob1.strength > ob2.strength) {
return 1;
} else if (ob1.strength < ob2.strength) {
return -1;
}
// Else go to the 2nd item
if (ob1.name < ob2.name) {
return -1;
} else if (ob1.name > ob2.name) {
return 1
} else { // nothing to split them
return 0;
}
}
A < and > comparison on strings is an alphabetic comparison.
This little function is often handy when sorting by multiple keys:
cmp = function(a, b) {
if (a > b) return +1;
if (a < b) return -1;
return 0;
}
or, more concisely,
cmp = (a, b) => (a > b) - (a < b)
Which works because in javascript:
true - true // gives 0
false - false // gives 0
true - false // gives 1
false - true // gives -1
Apply it like this:
array.sort(function(a, b) {
return cmp(a.strength,b.strength) || cmp(a.name,b.name)
})
Javascript is really missing Ruby's spaceship operator, which makes such comparisons extremely elegant.
You could chain the sort order with logical OR.
objects.sort(function (a, b) {
return a.strength - b.strength || a.name.localeCompare(b.name);
});
When I was looking for an answer to this very question, the answers I found on StackOverflow weren't really what I hoped for. So I created a simple, reusable function that does exactly this. It allows you to use the standard Array.sort, but with firstBy().thenBy().thenBy() style.
https://github.com/Teun/thenBy.js
PS. This is the second time I post this. The first time was removed by a moderator saying "Please don't make promotional posts for your own work". I'm not sure what the rules are here, but I was trying to answer this question. I'm very sorry that it is my own work. Feel free to remove again, but please point me to the rule involved then.
steve's answer, but prettier.
objects.sort(function(a,b)
{
if(a.strength > b.strength) {return 1;}
if(a.strength < b.strength) {return -1;}
if(a.name > b.name ) {return 1;}
if(a.name < b.name ) {return -1;}
return 0;
}
function sortF(ob1,ob2) {
if (ob1.strength > ob2.strength) {return 1}
else if (ob1.strength < ob2.strength) {return -1}
else if (ob1.name > ob2.name) {return 1}
return -1;
};
EDIT: Sort by strength, then if strength is equal, sort by name.
The case where strength and name are equal in both objects doesn't need to be accounted for seperately, since the final return of -1 indicates a less-than-or-equal-to relationship. The outcome of the sort will be correct. It might make it run faster or slower, I don't know. If you want to be explicit, just replace
return -1;
with
else if (ob1.name < ob2.name) {return -1}
return 0;
Find 'sortFn' function below. This function sorts by unlimited number of parameters(such as in c#: SortBy(...).ThenBy(...).ThenByDesc(...)).
function sortFn() {
var sortByProps = Array.prototype.slice.call(arguments),
cmpFn = function(left, right, sortOrder) {
var sortMultiplier = sortOrder === "asc" ? 1 : -1;
if (left > right) {
return +1 * sortMultiplier;
}
if (left < right) {
return -1 * sortMultiplier;
}
return 0;
};
return function(sortLeft, sortRight) {
// get value from object by complex key
var getValueByStr = function(obj, path) {
var i, len;
//prepare keys
path = path.replace('[', '.');
path = path.replace(']', '');
path = path.split('.');
len = path.length;
for (i = 0; i < len; i++) {
if (!obj || typeof obj !== 'object') {
return obj;
}
obj = obj[path[i]];
}
return obj;
};
return sortByProps.map(function(property) {
return cmpFn(getValueByStr(sortLeft, property.prop), getValueByStr(sortRight, property.prop), property.sortOrder);
}).reduceRight(function(left, right) {
return right || left;
});
};
}
var arr = [{
name: 'marry',
LocalizedData: {
'en-US': {
Value: 10000
}
}
}, {
name: 'larry',
LocalizedData: {
'en-US': {
Value: 2
}
}
}, {
name: 'marry',
LocalizedData: {
'en-US': {
Value: 100
}
}
}, {
name: 'larry',
LocalizedData: {
'en-US': {
Value: 1
}
}
}];
document.getElementsByTagName('pre')[0].innerText = JSON.stringify(arr)
arr.sort(sortFn({
prop: "name",
sortOrder: "asc"
}, {
prop: "LocalizedData[en-US].Value",
sortOrder: "desc"
}));
document.getElementsByTagName('pre')[1].innerText = JSON.stringify(arr)
pre {
font-family: "Courier New" Courier monospace;
white-space: pre-wrap;
}
Before:
<pre></pre>
Result:
<pre></pre>
With ES6 you can do
array.sort(function(a, b) {
return SortFn(a.strength,b.strength) || SortFn(a.name,b.name)
})
private sortFn(a, b): number {
return a === b ? 0 : a < b ? -1 : 1;
}
Here is the function I use. It will do an arbitrary number.
function Sorter(){
var self = this;
this.sortDefs = [];
for (let i = 0; i < arguments.length; i++) {
// Runs 5 times, with values of step 0 through 4.
this.sortDefs.push(arguments[i]);
}
this.sort = function(a, b){
for (let i = 0; i < self.sortDefs.length; i++) {
if (a[self.sortDefs[i]] < b[self.sortDefs[i]]) {
return -1;
} else if (a[self.sortDefs[i]] > b[self.sortDefs[i]]) {
return 1
}
}
return 0;
}
}
data.sort(new Sorter('category','name').sort);
In 2018 you can use just sort() ES6 function, that do exactly, what you want.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

How to sort string numbers which consist of 3 point?

I have an array which consists of String numbers which consist of 3 point such as "1.452", "11.3.2","12".
For example if I sort this array
$scope.myArray=["11","14.2","9.2.1","5.6.3","0.9","6.7.2","2","1"];
I want to see this array like so
0.9,
1,
2,
5.6.3,
6.7.2,
9.2.1,
11,
14.2
I want to sort these values in descending order. How can I do this with AngularJS ?
Thank you in advance.
It's not pretty, but I think it works:
var myArray = ["11", "14.2", "9.2.1", "5.6.3", "0.9", "6.7.2", "2", "1"];
function compare(a, b) {
let aa = a.split(".");
let bb = b.split(".");
for (let i = 0; i < aa.length; i++) {
if (parseInt(aa[i]) > parseInt(bb[i])) {
return 1;
} else if (parseInt(aa[i]) < parseInt(bb[i])) {
return -1;
}
}
return -1;
}
myArray = myArray.sort(function(a, b) {
return compare(a, b)
})
console.log(myArray);
So all you need is to write your custom comparator for Array.prototype.sort() function. Let's look at the example below (I'll try to explain it in the comments)
const compareValues = (a, b) => {
// for each element which we compare we need to split it using '.' to get comparable integers
const aNumbers = a.split('.');
const bNumbers = b.split('.');
// then we start a look over the received arrays
for (let i = 0; i < aNumbers.length || i < bNumbers.length; i++) {
// here we take a current element. If we doesn't exist we consider it as 0
let currentA = parseInt(aNumbers[i]) || 0;
let currentB = parseInt(bNumbers[i]) || 0;
if (currentA === currentB) {
// if the numbers are equal, we go to the next pair of items
continue;
} else {
// otherwise we return the difference between them, which says to sort() function which one is bigger
return currentA - currentB;
}
}
// if nothing was returned from the above for statement all numbers are equal
return 0;
};
// here's the array, that you want to sort
const arr = ["11","14.2","9.2.1","5.6.3","0.9","6.7.2","2","1"];
// and just call the sort function with our comparator.
const sortedArr = arr.sort(compareValues);

Find the index of an array element without using built in indexOf function

I am fairly new to JS and have a project to find the index of an array element, without using indexOf built in function. I have tried to search for solutions, but all I get are examples of the aforementioned built in function, which I already know how to use. Can anyone provide a simple example for me to go on?
My mind goes towards something like this, but please note I am new to this and it is an academic exercise that I really want to understand:
var index;
var target = 10;
for(var val in collection){
if(collection[val] === target){
index = val;
}
return index;
}
This attempt is almost correct. You seem to already understand of what is required: loop over the elements of the array until you find a match, and then return the index of that match.
You've made a few mistakes, though. Let's walk through some improvements, step by step.
Your return statement is inside the loop, but outside of the if. You only want to return if you're found a match. Currently, you always return after the first iteration of the loop!
function myIndexOf(collection, target) {
var index;
for(var val in collection){
if(collection[val] === target){
index = val;
return index;
}
}
}
There is no need for a separate index variable. You can return val as soon as you determine it's the correct answer.
function myIndexOf(collection, target) {
for(var val in collection){
if(collection[val] === target){
return val;
}
}
}
You should loop using a numeric for loop, not a for-in loop. for-in loops are not guaranteed to have a set order, so you may not always get the lowest index that is a match. (Also, for-in could match on non-numeric property names, which might not be what you want.)
function myIndexOf(collection, target) {
for(var val=0; val<collection.length; val++){
if(collection[val] === target){
return val;
}
}
}
To act just like indexOf, you could return -1 in the case that you don't find a match.
function myIndexOf(collection, target) {
for(var val=0; val<collection.length; val++){
if(collection[val] === target){
return val;
}
}
return -1;
}
Note: for..in should not be used to iterate over an Array where the
index order is important.
for..in - JavaScript | MDN
var find_index = function(collection, item) {
for (var i = 0; i < collection.length; ++i) {
if (collection[i] === item) {
return i;
}
}
};
find_index([5,4,3,2,1], 5)
When looping through an array you should use a simple for loop from index 0 to Length-1 of your array. Don't use for...in because it can iterate properties of the array that aren't the actual contents of the cells and the order is not guaranteed. You want to find the first match to have the same behavior as .indexOf. So the correct implementation would look something like this:
function myIndexOf(array, target) {
for (var i=0; i < array.length; i++) {
if (array[i] === target) {
return i;
}
}
// item was not found
return -1;
}
You can use in statement to iterate an array, assumings keys are values. Here val will be 0, 1, 2... so you can return it.
Then, you can use the return inside the if : it will stop the function and return the value just right you find what you are loonking for.
The === is the strict comparaison operator, checking var type, allowing you to test this is the exact value you are looking for.
You can add a return with an other value (here -1) if the value is not found at the end of the loop.
function myIndexOf(array, search) {
for(var val in array){
if(array[val] === search){
return val;
}
}
return -1;
}
var myArray = ['a', 'b', 'c'];
console.log(myIndexOf(myArray, 'c')); //2
console.log(myIndexOf(myArray, 'f')); //-1 <- Not found
This would work:
var index = 0;
var target = 'c';
var collection = ['a', 'b', 'c', 'd'];
function findIndex(){
for(var val in collection) {
if(collection[val] === target){
return index;
}
index++;
}
}
findIndex();
You can use every as well. Iterate through the array, return true if value doesn't match value being searched so every continues, otherwise set ind and return false.
var getIndex = function(arr, match){
var ind = -1;
arr.every(function(val, index) {
if (val === match){
ind = index;
return false;
}
return true;
});
return ind;
}
getIndex([1, 2, 3], 2);

How to sort array of strings based on a contained number?

I have an array with strings like this:
"1115.49|Onroll|Paiporta|/v2/networks/onroll-paiporta"
"1767.92|Göteborg|Göteborg|/v2/networks/goeteborg"
"190.4|ARbike|Arezzo|/v2/networks/arbike"
"201.36|JesinBici|Jesi|/v2/networks/jesinbici"
"403.59|Venezia|Venezia|/v2/networks/venezia"
"395.07|Mantova|Mantova|/v2/networks/mantova"
the first value is a distance, I would like to sort the array based on that distance, how can I do?
Everything I've tried does not work, I would that 1000 come after 200 not before!
thanks!
You can do something like this:
yourArray.sort(function (a, b) {
var aNum = +a.substring(0, a.indexOf('|'));
var bNum = +b.substring(0, b.indexOf('|'));
if (aNum > bNum) return 1;
if (aNum < bNum) return -1;
return 0;
});
which will return an array in the ascending order you wanted.
If you add a sortBy function to Array.prototype you can do things like that more easily.
Array.prototype.sortBy = function(f) {
this.sort(function(a, b) {
a = f(a); b = f(b);
if(a > b) return 1;
if(a < b) return -1;
return 0;
});
}
and then you can write
array.sortBy(function(s) { return +s.split("|")[0]; });

Trying to return up a recursive combinations function without getting 'undefined'

When I call this with [1,2,3,4], it returns undefined and I'm not understanding why. The goal is for it to return true if any combination of numbers in the array add up to the maximum number in the array, and false if it's not possible.
function ArrayAdditionI(arr) {
var max = Math.max.apply(null, arr);
arr.splice(arr.indexOf(max), 1);
var sum = function(arr) { return arr.reduce(function(a,b) { return a + b; }); };
function combos(arr) {
var f = function(prefix, arr) {
for (var i = 0; i < arr.length; i++) {
var clone = prefix.slice(0);
clone.push(arr[i]);
if (sum(clone) == max) { return true; }
return f(clone, arr.slice(i+1));
}
}
return f([], arr);
}
return combos(arr);
}
f is returning undefined when it is called with an empty arr! You will need to explicitly return false if none of the tests in the loop returned from the function. And you must not return false on the first occasion of the loop, but break only when you found true and continue the loop elsewhile.
To fix this, you'd have something like
function combos(arr) {
function f(prefix, arr) {
for (var i = 0; i < arr.length; i++) {
var clone = prefix.slice(0);
clone.push(arr[i]);
if (sum(clone) == max) return true;
if (f(clone, arr.slice(i+1))) return true;
}
return false;
}
return f([], arr);
}
However, also your recursion scheme with the loop looks a bit complicated. I would rather go with the naive enumeration of the "binary tree", where the nodes of each level decide whether the current item will be included in the to-be-tested subset:
function ArrayAdditionI(arr) {
var max = Math.max.apply(null, arr);
arr.splice(arr.indexOf(max), 1);
var sum = function(arr) { return arr.reduce(function(a,b) { return a + b; }, 0); };
function f(subset, arr) {
return arr.length
? f(subset, arr.slice(1)) || f(subset.concat([arr[0]]), arr.slice(1))
: sum(subset) == max
}
return f([], arr);
}
It seems that you don't test all the possible combination.
Here you will test 1+2+3, 2+3, 3 but never 1+3.
Do you really want to have a recursive function here ?
There may be some more simple way to find that.
function ArrayAdditionI(arr) {
var max = Math.max.apply(null, arr);
arr.splice(arr.indexOf(max), 1);
var res = arr.filter(function(num, idx) {
var combination = false;
// Check all combination non previously tested
for (var i = idx; i < arr.length - 1; i++) {
if (num + arr[i+1] === max) {
combination = true;
break;
}
}
return combination;
});
return res.length > 0;
}
The problem is your f function is not ever hitting the
if (sum(clone) == max) { return true; }
line of code so it will just keep recursively calling until arr.length == 0 and it will return undefined.
Your variables torf and results are unused, maybe you forgot to do something with them?

Categories

Resources