Javascript natural sort array/object and maintain index association - javascript

I have an array of items as follows in Javascript:
var users = Array();
users[562] = 'testuser3';
users[16] = 'testuser6';
users[834] = 'testuser1';
users[823] = 'testuser4';
users[23] = 'testuser2';
users[917] = 'testuser5';
I need to sort that array to get the following output:
users[834] = 'testuser1';
users[23] = 'testuser2';
users[562] = 'testuser3';
users[823] = 'testuser4';
users[917] = 'testuser5';
users[16] = 'testuser6';
Notice how it is sorted by the value of the array and the value-to-index association is maintained after the array is sorted (that is critical). I have looked for a solution to this, tried making it, but have hit a wall.
By the way, I am aware that this is technically not an array since that would mean the indices are always iterating 0 through n where n+1 is the counting number proceeding n. However you define it, the requirement for the project is still the same. Also, if it makes a difference, I am NOT using jquery.

The order of the elements of an array is defined by the index. So even if you specify the values in a different order, the values will always be stored in the order of their indices and undefined indices are undefined:
> var arr = [];
> arr[2] = 2;
> arr[0] = 0;
> arr
[0, undefined, 2]
Now if you want to store the pair of index and value, you will need a different data structure, maybe an array of array like this:
var arr = [
[562, 'testuser3'],
[16, 'testuser6'],
[834, 'testuser1'],
[823, 'testuser4'],
[23, 'testuser2'],
[917, 'testuser5']
];
This can be sorted with this comparison function:
function cmp(a, b) {
return a[1].localeCompare(b[1]);
}
arr.sort(cmp);
The result is this array:
[
[834, 'testuser1'],
[23, 'testuser2'],
[562, 'testuser3'],
[823, 'testuser4'],
[917, 'testuser5'],
[16, 'testuser6']
]

If I understand the question correctly, you're using arrays in a way they are not intended to be used. In fact, the initialization style
// Don't do this!
var array = new Array();
array[0] = 'value';
array[1] = 'value';
array[2] = 'value';
teaches wrong things about the nature and purpose of arrays. An array is an ordered list of items, indexed from zero up. The right way to create an array is with an array literal:
var array = [
'value',
'value',
'value'
]
The indexes are implied based on the order the items are specified. Creating an array and setting users[562] = 'testuser3' implies that there are at least 562 other users in the list, and that you have a reason for only knowing the 563rd at this time.
In your case, the index is data, and is does not represent the order of the items in the set. What you're looking for is a map or dictionary, represented in JavaScript by a plain object:
var users = {
562: 'testuser3',
16: 'testuser6',
834: 'testuser1',
823: 'testuser4',
23: 'testuser2',
917: 'testuser5'
}
Now your set does not have an order, but does have meaningful keys. From here, you can follow galambalazs's advice to create an array of the object's keys:
var userOrder;
if (typeof Object.keys === 'function') {
userOrder = Object.keys(users);
} else {
for (var key in users) {
userOrder.push(key);
}
}
…then sort it:
userOrder.sort(function(a, b){
return users[a].localeCompare(users[b]);
});
Here's a demo

You can't order arrays like this in Javascript. Your best bet is to make a map for order.
order = new Array();
order[0] = 562;
order[1] = 16;
order[2] = 834;
order[3] = 823;
order[4] = 23;
order[5] = 917;
In this way, you can have any order you want independently of the keys in the original array.
To sort your array use a custom sorting function.
order.sort( function(a, b) {
if ( users[a] < users[b] ) return -1;
else if ( users[a] > users[b] ) return 1;
else return 0;
});
for ( var i = 0; i < order.length; i++ ) {
// users[ order[i] ]
}
[Demo]

Using the ideas from the comments, I came up with the following solution. The naturalSort function is something I found on google and I modified it to sort a multidimensional array. Basically, I made the users array a multidimensional array with the first index being the user id and the second index being the user name. So:
users[0][0] = 72;
users[0][1] = 'testuser4';
users[1][0] = 91;
users[1][1] = 'testuser2';
users[2][0] = 12;
users[2][1] = 'testuser8';
users[3][0] = 3;
users[3][1] = 'testuser1';
users[4][0] = 18;
users[4][1] = 'testuser7';
users[5][0] = 47;
users[5][1] = 'testuser3';
users[6][0] = 16;
users[6][1] = 'testuser6';
users[7][0] = 20;
users[7][1] = 'testuser5';
I then sorted the array to get the following output:
users_sorted[0][0] = 3;
users_sorted[0][1] = 'testuser1';
users_sorted[1][0] = 91;
users_sorted[1][1] = 'testuser2';
users_sorted[2][0] = 47;
users_sorted[2][1] = 'testuser3';
users_sorted[3][0] = 72;
users_sorted[3][1] = 'testuser4';
users_sorted[4][0] = 20;
users_sorted[4][1] = 'testuser5';
users_sorted[5][0] = 16;
users_sorted[5][1] = 'testuser6';
users_sorted[6][0] = 18;
users_sorted[6][1] = 'testuser7';
users_sorted[7][0] = 12;
users_sorted[7][1] = 'testuser8';
The code to do this is below:
function naturalSort(a, b) // Function to natural-case insensitive sort multidimensional arrays by second index
{
// setup temp-scope variables for comparison evauluation
var re = /(-?[0-9\.]+)/g,
x = a[1].toString().toLowerCase() || '',
y = b[1].toString().toLowerCase() || '',
nC = String.fromCharCode(0),
xN = x.replace( re, nC + '$1' + nC ).split(nC),
yN = y.replace( re, nC + '$1' + nC ).split(nC),
xD = (new Date(x)).getTime(),
yD = xD ? (new Date(y)).getTime() : null;
// natural sorting of dates
if ( yD )
if ( xD < yD ) return -1;
else if ( xD > yD ) return 1;
// natural sorting through split numeric strings and default strings
for( var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++ ) {
oFxNcL = parseFloat(xN[cLoc]) || xN[cLoc];
oFyNcL = parseFloat(yN[cLoc]) || yN[cLoc];
if (oFxNcL < oFyNcL) return -1;
else if (oFxNcL > oFyNcL) return 1;
}
return 0;
}
// Set values for index
var users = Array();
var temp = Array();
users.push(Array('72', 'testuser4'));
users.push(Array('91', 'testuser2'));
users.push(Array('12', 'testuser8'));
users.push(Array('3', 'testuser1'));
users.push(Array('18', 'testuser7'));
users.push(Array('47', 'testuser3'));
users.push(Array('16', 'testuser6'));
users.push(Array('20', 'testuser5'));
// Sort the array
var users_sorted = Array();
users_sorted = users.sort(naturalSort);

I'd use map once to make a new array of users,
then a second time to return the string you want from the new array.
var users= [];
users[562]= 'testuser3';
users[16]= 'testuser6';
users[834]= 'testuser1';
users[823]= 'testuser4';
users[23]= 'testuser2';
users[917]= 'testuser5';
var u2= [];
users.map(function(itm, i){
if(itm){
var n= parseInt(itm.substring(8), 10);
u2[n]= i;
}
});
u2.map(function(itm, i){
return 'users['+itm+']= testuser'+i;
}).join('\n');
/*returned value: (String)
users[834]= testuser1
users[23]= testuser2
users[562]= testuser3
users[823]= testuser4
users[917]= testuser5
users[16]= testuser6
*/
If you want to avoid any gaps. use a simple filter on the output-
u2.map(function(itm, i){
return 'users['+itm+']= testuser'+i;
}).filter(function(itm){return itm}).join('\n');

Sparse arrays usually spell trouble. You're better off saving key-value pairs in an array as objects (this technique is also valid JSON):
users = [{
"562": "testuser3"
},{
"16": "testuser6"
}, {
"834": "testuser1"
}, {
"823": "testuser4"
}, {
"23": "testuser2"
}, {
"917": "testuser5"
}];
As suggested, you can use a for loop to map the sorting function onto the array.

Array.prototype.sort() takes an optional custom comparison function -- so if you dump all of your users into an array in this manner [ [562, "testuser3"], [16, "testuser6"] ... etc.]
Then sort this array with the following function:
function(comparatorA, comparatorB) {
var userA = comparatorA[1], userB = comparatorB[1]
if (userA > userB) return 1;
if (userA < userB) return -1;
if (userA === userB) return 0;
}
Then rebuild your users object. (Which will loose you your sorting.) Or, keep the data in the newly sorted array of arrays, if that will work for your application.

A oneliner with array of array as a result:
For sorting by Key.
let usersMap = users.map((item, i) => [i, item]).sort((a, b) => a[0] - b[0]);
For sorting by Value. (works with primitive types)
let usersMap = users.map((item, i) => [i, item]).sort((a, b) => a[1] - b[1]);

Related

Algorithm to sort an array in JS

I have an array
var arr= [
["PROPRI","PORVEC"],
["AJATRN","PROPRI"],
["BASMON","CALVI"],
["GHICIA","FOLELI"],
["FOLELI","BASMON"],
["PORVEC","GHICIA"]
] ;
And I'm trying to sort the array by making the second element equal to the first element of the next, like below:
arr = [
["AJATRN","PROPRI"],
["PROPRI","PORVEC"],
["PORVEC","GHICIA"],
["GHICIA","FOLELI"],
["FOLELI","BASMON"],
["BASMON","CALVI"]
]
The context is : these are somes sites with coordinates, I want to identify the order passed,
For exemple, I have [A,B] [C,D] [B,C] then I know the path is A B C D
I finally have one solution
var rs =[];
rs[0]=arr[0];
var hasAdded=false;
for (var i = 1; i < arr.length; i++) {
hasAdded=false;
console.log("i",i);
for (var j = 0, len=rs.length; j < len; j++) {
console.log("j",j);
console.log("len",len);
if(arr[i][1]===rs[j][0]){
rs.splice(j,0,arr[i]);
hasAdded=true;
console.log("hasAdded",hasAdded);
}
if(arr[i][0]===rs[j][1]){
rs.splice(j+1,0,arr[i]);
hasAdded=true;
console.log("hasAdded",hasAdded);
}
}
if(hasAdded===false) {
arr.push(arr[i]);
console.log("ARR length",arr.length);
}
}
But it's not perfect, when it's a circle like [A,B] [B,C] [C,D] [D,A]
I can't get the except answer
So I really hope this is what you like to achieve so have a look at this simple js code:
var vector = [
["PROPRI,PORVEC"],
["AJATRN,PROPRI"],
["BASMON,CALVI"],
["GHICIA,FOLELI"],
["FOLELI,BASMON"],
["PORVEC,GHICIA"]
]
function sort(vector) {
var result = []
for (var i = 1; i < vector.length; i++) result.push(vector[i])
result.push(vector[0])
return (result)
}
var res = sort(vector)
console.log(res)
Note: Of course this result could be easily achieved using map but because of your question I'm quite sure this will just confuse you. So have a look at the code done with a for loop :)
You can create an object lookup based on the first value of your array. Using this lookup, you can get the first key and then start adding value to your result. Once you add a value in the array, remove the value corresponding to that key, if the key has no element in its array delete its key. Continue this process as long as you have keys in your object lookup.
var vector = [["PROPRI", "PORVEC"],["AJATRN", "PROPRI"],["BASMON", "CALVI"],["GHICIA", "FOLELI"],["FOLELI", "BASMON"],["PORVEC", "GHICIA"]],
lookup = vector.reduce((r,a) => {
r[a[0]] = r[a[0]] || [];
r[a[0]].push(a);
return r;
}, {});
var current = Object.keys(lookup).sort()[0];
var sorted = [];
while(Object.keys(lookup).length > 0) {
if(lookup[current] && lookup[current].length) {
var first = lookup[current].shift();
sorted.push(first);
current = first[1];
} else {
delete lookup[current];
current = Object.keys(lookup).sort()[0];
}
}
console.log(sorted);

How to separate array of comma separated string into various arrays?

What's the best way to convert this array of comma separated values
[ 'com--test,LFutx9mQbTTyRo4A9Re5ksjdnfsI4cKN4q2,on',
'com--fxtrimester,SEzlksdfMpW3FxkSbzL7eo5MmqkPczCl2,on',
'com--fxtrimester,LFutx9mQbTTyRoldksfns4A9Re5I4cKN4q2,on' ]
Into three arrays?
[ 'com--test', [ LFutx9mQbTTyRo4A9Re5Ilsdf4cKN4q2', ['on',
'com--fxtrimester', SEzMpW3FxkSbzL7eo5MmlkdfqkPczCl2', 'on',
'com--fxtrimester' ] LFutksdfx9mQbTTyRo4A9Re5I4cKN4q2 ] 'on']
I was trying something like:
var indexToSplit = unique.indexOf(',');
var status = unique.slice(3, indexToSplit - 1);
var use = unique.slice(2, indexToSplit - 2);
var pros = unique.slice(0, indexToSplit - 3);
console.log(pros);
But I figured that is wrong ... any help is appreciated!
You will have to loop over array and use string.split to get seperate parts.
Once you have seperate parts, you can push them to necessary array;
var d = [ 'com--test,LFutx9mQbTTyRo4A9Re5ksjdnfsI4cKN4q2,on',
'com--fxtrimester,SEzlksdfMpW3FxkSbzL7eo5MmqkPczCl2,on',
'com--fxtrimester,LFutx9mQbTTyRoldksfns4A9Re5I4cKN4q2,on'];
var result = [[],[],[]];
var len = 3;
d.forEach(function(str, i){
var _tmp = str.split(',');
for (var i = 0; i<len; i++){
result[i].push(_tmp[i])
}
})
console.log(result)
A little generic way.
Loop over data and split each string using comma(,)
Loop over split values and check if necessary array exists.
If not, initialise array, but you cannot do p[i] = [] as this will push to first value. You will have to also initialise all previous values. For this, you can use new Array(length). By default, if length is greater than 0, all indexes will be initialise to undefined.
Now push to necessary array. Position will be maintained.
var d = ['com--test,LFutx9mQbTTyRo4A9Re5ksjdnfsI4cKN4q2,on',
'com--fxtrimester,SEzlksdfMpW3FxkSbzL7eo5MmqkPczCl2,on',
'com--fxtrimester,LFutx9mQbTTyRoldksfns4A9Re5I4cKN4q2,on,test'
];
var result = d.reduce(function(p, c, index) {
var _tmp = c.split(',');
for (var i = 0; i < _tmp.length; i++) {
// Check if position not defined.
if (p[i] === undefined)
// Initialize array and add default (undefined) to all elements before current element
p[i] = new Array(index);
p[i].push(_tmp[i])
}
return p;
}, [])
console.log(result)
With map this becomes:
for positions X out of 0, 1 and 2:
convert each item in the list into an array, and choose the Xth item
var start = [ 'com--test,LFutx9mQbTTyRo4A9Re5ksjdnfsI4cKN4q2,on',
'com--fxtrimester,SEzlksdfMpW3FxkSbzL7eo5MmqkPczCl2,on',
'com--fxtrimester,LFutx9mQbTTyRoldksfns4A9Re5I4cKN4q2,on' ]
var out = [0,1,2].map(i =>
start.map(x => x.split(',')[i]) )
console.log(out)
Since your question does not ask for a more general case, i am safely assuming it for 3 array. We can use forEach function on array below code can be one amongst the possible solutions
var arr1 = [];
var arr2 = [];
var arr3 = [];
var x = ['com--test,LFutx9mQbTTyRo4A9Re5ksjdnfsI4cKN4q2,on', 'com--fxtrimester,SEzlksdfMpW3FxkSbzL7eo5MmqkPczCl2,on', 'com--fxtrimester,LFutx9mQbTTyRoldksfns4A9Re5I4cKN4q2,on']
x.forEach(function(data) {
var dataArray = data.split(',');
arr1.push(dataArray[0]);
arr2.push(dataArray[1]);
arr3.push(dataArray[2]);
});
console.log(arr1)
console.log(arr2)
console.log(arr3)

How can I flatten an array of objects to return the exact result I need?

I have an array of objects like so:
0: Object
1: Object
2: Object
3: Object
4: Object
Then when you open each object up I have some data inside like so:
id: "restore-product"
result: 0
Currently the data is output like the above but what I would love is to be able to manipulate it so i can get the returned array of objects to look like this:
[[id:"restore-product",result:0],[id:"enhance-product",result:20]]
Then when the time comes I can loop through each item and find the highest value out of the objects and get the id of that highest value.
Currently I have this function that pushes items into the array:
var amounts = [];
$.each(entries, function(i, entry){
amounts.push(entry);
});
== edit here ===
This is what i use to get the highest value:
var lowest = Number.POSITIVE_INFINITY;
var highest = Number.NEGATIVE_INFINITY;
var tmp;
for (var i=entries.length-1; i>=0; i--) {
tmp = entries[i].result;
if (tmp < lowest) lowest = tmp;
if (tmp > highest) highest = tmp;
}
console.log(highest, lowest);
Can anyone point me in the right direction?
There's a much simpler way of finding the highest value with a matching ID. Just setup two tracker variables and update them as you move through the array.
var id = null;
var localMax = Number.MIN_SAFE_INTEGER;
for (var i = 0; i < entries.length; i++) {
var obj = objects[i];
if (obj.result > localMax) {
id = obj.id;
localMax = obj.result;
}
}
The function you need is something like this:
function getIdFromMaximumResult(arr) {
var id, maxResult = Number.MIN_SAFE_INTEGER;
$.each(arr, function(i, object) {
if (object.result > maxResult) {
maxResult = object.result;
id = object.id;
}
});
return id;
}
The easiest way would be to sort the array and take either the last (ascending sort) or first (descending sort) value from the result.
Ascending sort
var sorted = yourArray.sort(function(a, b) {
return a.result < b.result ? -1 : +(a.result > b.result);
}),
highest = sorted[sorted.length - 1];
console.log(highest.id);
You could also use sorted.pop() but note that this actually removes the item from the original array too.
Descending sort
var sorted = yourArray.sort(function(a, b) {
return a.result > b.result ? -1 : +(a.result < b.result);
}),
highest = sorted[0];
console.log(highest.id);
You could also use sorted.shift() but note that this actually removes the item from the original array too.
If you need any further action, for example get all id's whose result match the highest (if any), you can now use other array methods such as filter.
var sameResult = yourArray.filter(function(data) {
return data.result === highest.result;
});
console.log(sameResult);
If you want an array containing only the id's, use map
var idOnly = sameResult.map(function(data) {
return data.id;
});
console.log(idOnly);
You can use Array.prototype.reduce()
var minMax = data.reduce(function(minMax, curr){
minMax.min = curr.result < minMax.min ? curr.result : minMax.min;
minMax.max = curr.result > minMax.max ? curr.result : minMax.max;
return minMax;
},{min:Number.POSITIVE_INFINITY, max: Number.MIN_SAFE_INTEGER} );
console.log(minMax)// {min:0, max:323}

Hashing array of strings in javascript

Just wondering if there is some other way than this.
var hashStringArray = function(array) {
array.sort();
return array.join('|');
};
I don't like sorting much and using that delimiter is not safe either if it's contained in one of the strings. In overall I need to produce same hash no matter the order of strings. It will be rather short arrays (up to 10 items), but it will be required very often so it shouldn't be too slow.
I intend to use it with ES6 Map object and I need to easily find same array collection.
Updated example of use
var theMap = new Map();
var lookup = function(arr) {
var item = null;
var hashed = hashStringArray(arr);
if (item = theMap.get( hashed )) {
return item;
}
theMap.set( hashed, itemBasedOnInput );
return itemBasedOnInput;
}
var arr1 = ['alpha','beta','gama'];
var arr2 = ['beta','alpha','gama'];
lookup(arr1) === lookup(arr2)
Performance tests
http://jsperf.com/hashing-array-of-strings/5
Two things occurred to me as the basis of a solution:
summing doesn't depend on order, which is actually a flaw in simple checksums (they don't catch changes in block order within a word), and
we can convert strings to summable numbers using their charcodes
Here's a function to do (2) :
charsum = function(s) {
var i, sum = 0;
for (i = 0; i < s.length; i++) {
sum += (s.charCodeAt(i) * (i+1));
}
return sum
}
Here's a version of (1) that computes an array hash by summing the charsum values:
array_hash = function(a) {
var i, sum = 0
for (i = 0; i < a.length; i++) {
var cs = charsum(a[i])
sum = sum + (65027 / cs)
}
return ("" + sum).slice(0,16)
}
Fiddle here: http://jsfiddle.net/WS9dC/11/
If we did a straight sum of the charsum values, then the array ["a", "d"] would have the same hash as the array ["b", "c"] - leading to undesired collisions. So based on using non-UTF strings, where charcodes go up to 255, and allowing for 255 characters in each string, then the max return value of charsum is 255 * 255 = 65025. So I picked the next prime number up, 65027, and used (65027 / cs) to compute the hash. I am not 100% convinced this removes collisions... perhaps more thought needed... but it certainly fixes the [a, d] versus [b, c] case.
Testing:
var arr1 = ['alpha','beta','gama'];
var arr2 = ['beta','alpha','gama'];
console.log(array_hash(arr1))
console.log(array_hash(arr2))
console.log(array_hash(arr1) == array_hash(arr2))
Outputs:
443.5322979371356
443.5322979371356
true
And testing a case that shows different hashes:
var arr3 = ['a', 'd'];
var arr4 = ['b', 'c'];
console.log(array_hash(arr3))
console.log(array_hash(arr4))
console.log(array_hash(arr3) == array_hash(arr4))
outputs:
1320.651443298969
1320.3792001649144
false
Edit:
Here's a revised version, which ignore duplicates from the arrays as it goes, and return the hash based on unique items only:
http://jsfiddle.net/WS9dC/7/
array_hash = function(a) {
var i, sum = 0, product = 1
for (i = 0; i < a.length; i++) {
var cs = charsum(a[i])
if (product % cs > 0) {
product = product * cs
sum = sum + (65027 / cs)
}
}
return ("" + sum).slice(0, 16)
}
testing:
var arr1 = ['alpha', 'beta', 'gama', 'delta', 'theta', 'alpha', 'gama'];
var arr2 = ["beta", "gama", "alpha", "theta", "delta", "beta"];
console.log(array_hash(arr1))
console.log(array_hash(arr2))
console.log(array_hash(arr1) === array_hash(arr2))
returns:
689.878503111701
689.878503111701
true
Edit
I've revised the answer above to account for arrays of words that have the same letters. We need these to return different hashes, which they now do:
var arr1 = ['alpha', 'beta']
var arr2 = ['alhpa', 'ateb']
The fix was to add a multiplier to the charsum func based on the char index:
sum += (s.charCodeAt(i) * (i+1));
If you calculate a numeric hash code for each string, then you can combine them with an operator where the order doesn't matter, like the ^ XOR operator, then you don't need to sort the array:
function hashStringArray(array) {
var code = 0;
for (var i = 0; i < array.length; i++) {
var n = 0;
for (var j = 0; j < array[i].length; j++) {
n = n * 251 ^ array[i].charCodeAt(j);
}
code ^= n;
}
return code
};
You can do this:
var hashStringArray = function(array) {
return array.sort().join('\u200b');
};
The \u200b character is an unicode character that also means null, but is not the same as the \0 character, which is most widely used.
'\u200b' == '\0'
> false
An idea to have very fast hash if your set of possible string is less than 32 items long : hash the string with a built-in hash function that will return power-of two as hash :
function getStringHash(aString) {
var currentPO2 = 0;
var hashSet = [];
getStringHash = function ( aString) {
var aHash = hashSet[aString];
if (aHash) return aHash;
aHash = 1 << currentPO2++;
hashSet[aString] = aHash;
return aHash;
}
return getStringHash(aString);
}
Then use this hash on your string array, ORing the hashes ( | ) :
function getStringArrayHash( aStringArray) {
var aHash = 0;
for (var i=0; i<aStringArray.length; i++) {
aHash |= getStringHash(aStringArray[i]);
}
return aHash;
}
So to test a bit :
console.log(getStringHash('alpha')); // 1
console.log(getStringHash('beta')); // 2
console.log(getStringHash('gamma')); // 4
console.log(getStringHash('alpha')); // 1 again
var arr1 = ['alpha','beta','gama'];
var arr2 = ['beta','alpha','gama'];
var arr3 = ['alpha', 'teta'];
console.log(getStringArrayHash(arr1)); // 11
console.log(getStringArrayHash(arr2)); // 11 also, like for arr1
var arr3 = ['alpha', 'teta'];
console.log(getStringArrayHash(arr3)); // 17 : a different array has != hashset
jsbin is here : http://jsbin.com/rozanufa/1/edit?js,console
RQ !!! with this method, arrays are considered as set, meaning that a repeated item won't change the hash of an array !!!
This HAS to be faster since it uses only 1) function call 2) lookup 3) integer arithmetic.
So no sort, no (long) string, no concat.
jsperf confirms that :
http://jsperf.com/hashing-array-of-strings/4
EDIT :
version with prime numbers, here : http://jsbin.com/rozanufa/3/edit?js,console
// return the unique prime associated with the string.
function getPrimeStringHash(aString) {
var hashSet = [];
var currentPrimeIndex = 0;
var primes = [ 2, 3, 5, 7, 11, 13, 17 ];
getPrimeStringHash = function ( aString) {
var aPrime = hashSet[aString];
if (aPrime) return aPrime;
if (currentPrimeIndex == primes.length) aPrime = getNextPrime();
else aPrime = primes[currentPrimeIndex];
currentPrimeIndex++
hashSet[aString] = aPrime;
return aPrime;
};
return getPrimeStringHash(aString);
// compute next prime number, store it and returns it.
function getNextPrime() {
var pr = primes[primes.length-1];
do {
pr+=2;
var divides = false;
// discard the number if it divides by one earlier prime.
for (var i=0; i<primes.length; i++) {
if ( ( pr % primes[i] ) == 0 ) {
divides = true;
break;
}
}
} while (divides == true)
primes.push(pr);
return pr;
}
}
function getStringPrimeArrayHash( aStringArray) {
var primeMul = 1;
for (var i=0; i<aStringArray.length; i++) {
primeMul *= getPrimeStringHash(aStringArray[i]);
}
return primeMul;
}
function compareByPrimeHash( aStringArray, anotherStringArray) {
var mul1 = getStringPrimeArrayHash ( aStringArray ) ;
var mul2 = getStringPrimeArrayHash ( anotherStringArray ) ;
return ( mul1 > mul2 ) ?
! ( mul1 % mul2 )
: ! ( mul2 % mul1 );
// Rq : just test for mul1 == mul2 if you are sure there's no duplicates
}
Tests :
console.log(getPrimeStringHash('alpha')); // 2
console.log(getPrimeStringHash('beta')); // 3
console.log(getPrimeStringHash('gamma')); // 5
console.log(getPrimeStringHash('alpha')); // 2 again
console.log(getPrimeStringHash('a1')); // 7
console.log(getPrimeStringHash('a2')); // 11
var arr1 = ['alpha','beta','gamma'];
var arr2 = ['beta','alpha','gamma'];
var arr3 = ['alpha', 'teta'];
var arr4 = ['alpha','beta','gamma', 'alpha']; // == arr1 + duplicate 'alpha'
console.log(getStringPrimeArrayHash(arr1)); // 30
console.log(getStringPrimeArrayHash(arr2)); // 30 also, like for arr1
var arr3 = ['alpha', 'teta'];
console.log(getStringPrimeArrayHash(arr3)); // 26 : a different array has != hashset
console.log(compareByPrimeHash(arr1, arr2) ); // true
console.log(compareByPrimeHash(arr1, arr3) ); // false
console.log(compareByPrimeHash(arr1, arr4) ); // true despite duplicate

Is there a more concise way to initialize empty multidimensional arrays?

I've been trying to find a reasonably concise way to set the dimensions of an empty multidimensional JavaScript array, but with no success so far.
First, I tried to initialize an empty 10x10x10 array using var theArray = new Array(10, 10 10), but instead, it only created a 1-dimensional array with 3 elements.
I've figured out how to initialize an empty 10x10x10 array using nested for-loops, but it's extremely tedious to write the array initializer this way. Initializing multidimensional arrays using nested for-loops can be quite tedious: is there a more concise way to set the dimensions of empty multidimensional arrays in JavaScript (with arbitrarily many dimensions)?
//Initializing an empty 10x10x10 array:
var theArray = new Array();
for(var a = 0; a < 10; a++){
theArray[a] = new Array();
for(var b = 0; b < 10; b++){
theArray[a][b] = new Array();
for(var c = 0; c < 10; c++){
theArray[a][b][c] = 10
}
}
}
console.log(JSON.stringify(theArray));
Adapted from this answer:
function createArray(length) {
var arr = new Array(length || 0),
i = length;
if (arguments.length > 1) {
var args = Array.prototype.slice.call(arguments, 1);
while(i--) arr[i] = createArray.apply(this, args);
}
return arr;
}
Simply call with an argument for the length of each dimension.
Usage examples:
var multiArray = createArray(10,10,10); Gives a 3-dimensional array of equal length.
var weirdArray = createArray(34,6,42,2); Gives a 4-dimensional array of unequal lengths.
function multiDimArrayInit(dimensions, leafValue) {
if (!dimensions.length) {
return leafValue;
}
var arr = [];
var subDimensions = dimensions.slice(1);
for (var i = 0; i < dimensions[0]; i++) {
arr.push(multiDimArrayInit(subDimensions, leafValue));
}
return arr;
}
console.log(multiDimArrayInit([2,8], "hi")); // counting the nested "hi"'s yields 16 of them
demo http://jsfiddle.net/WPrs3/
Here is my take on the problem: nArray utility function
function nArray() {
var arr = new Array();
var args = Array.prototype.slice.call(arguments, 1);
for(var i=0;i<arguments[0];i++) {
arr[i] = (arguments.length > 1 && nArray.apply(this, args)) || undefined;
}
return arr;
}
Usage example:
var arr = nArray(3, 3, 3);
Results in 3x3x3 array of undefined values.
Running code with some tests also available as a Fiddle here: http://jsfiddle.net/EqT3r/7/
The more dimension you have, the more you have interest in using one single flat array and a getter /setter function for your array.
Because for a [d1 X d2 X d3 X .. X dn] you'll be creating d2*d3*...*dn arrays instead of one, and when accessing, you'll make n indirection instead of 1.
The interface would look like :
var myNArray = new NArray(10,20,10);
var oneValue = myNArray.get(5,8,3);
myNArray.set(8,3,2, 'the value of (8,3,2)');
the implementation depends on your preference for a fixed-size
n-dimensionnal array or an array able to push/pop and the like.
A more succinct version of #chris code:
function multiDim (dims, leaf) {
dims = Array.isArray (dims) ? dims.slice () : [dims];
return Array.apply (null, Array (dims.shift ())).map (function (v, i) {
return dims.length
? multiDim (dims, typeof leaf == 'string' ? leaf.replace ('%i', i + ' %i') : leaf)
: typeof leaf == 'string' ? leaf.replace ('%i', i) : leaf;
});
}
console.log (JSON.stringify (multiDim ([2,2], "hi %i"), null, ' '));
Produces :
[
[
"hi 0 0",
"hi 0 1"
],
[
"hi 1 0",
"hi 1 1"
]
]
In this version you can pass the first argument as a number for single dimension array.
Including %i in the leaf value will provide index values in the leaf values.
Play with it at : http://jsfiddle.net/jstoolsmith/r3eMR/
Very simple function, generate an array with any number of dimensions. Specify length of each dimension and the content which for me is '' usually
function arrayGen(content,dims,dim1Len,dim2Len,dim3Len...) {
var args = arguments;
function loop(dim) {
var array = [];
for (var a = 0; a < args[dim + 1]; a++) {
if (dims > dim) {
array[a] = loop(dim + 1);
} else if (dims == dim) {
array[a] = content;
}
}
return array;
}
var thisArray = loop(1);
return thisArray;
};
I use this function very often, it saves a lot of time

Categories