Two element combinations of an array javascript - javascript

If i have an array of letters, for example
['A','C','D','E']
and I wanted to find all the 2 letter combinations of this array, what is the best way of doing this without using 2 for loops. For example:
for (var i=0; i<arr.length;i++) {
for (var j=i+1; j<arr.length;j++) {
console.log(arr[i] + " " + arr[j]);
}
}
The issue with this, is that if the array becomes massive (1000 elements), it usually times out. Is there another way (alternate data structure etc to do this)?

Use the .map()
Something like this
var arr = ['A','C','D','E'],
combinations = arr.map((v,i)=>arr.slice(i+1).map(v2=>v+v2));
console.log(combinations);
Although this code will also iterate twice over the elements.
(it will actually perform worse than your code since map executes a function for each item and it also creates temporary array copies with the slice, so it is here just for an alternate approach, not a more performant.)

Not only two but with any number of elements you might do as follows;
Array.prototype.combinations = function(n){
return this.reduce((p,c,i,a) => p.concat(n > 1 ? a.slice(i+1).combinations(n-1).map(e => [].concat(e,c))
: [[c]]),[]);
};
console.log(JSON.stringify([1,2,3,4,5,6].combinations(2)));
console.log(JSON.stringify([1,2,3,4,5,6].combinations(3)));
Well as per #Lucas Kot-Zaniewski's comments I have refactored my code to use .push() operations in the place of .concat() instructions and also where required a spread operation i did use Array.prototype.push.apply(context,[args]). These two changes made the code to run 2.5 ~ 3 times faster (resulting 3.5-7 msecs vs 9.5-19 msecs) when an input of 100 items array is given and a combination of two of each is requested. Yet once tried with 1000 items of 2 combinations the difference is more dramatic like 400ms vs 6000ms.
A test can be seen at https://repl.it/DyrU
Array.prototype.combinations = function(n){
return this.reduce((p,c,i,a) => (Array.prototype.push.apply(p,n > 1 ? a.slice(i+1).combinations(n-1).map(e => (e.push(c),e))
: [[c]]),p),[]);
};
console.log(JSON.stringify([1,2,3,4,5,6].combinations(2)));

I really beat this one into the ground. As expected, #Louis Durand 's answer of two nested for loops was fastest on an array containing 100 strings (around 4 ms on my machine). This shows that a nested loop is probably your best bet in this situation.
Second fastest is my recursive solution which did the same in around 7-8 ms.
Third was #Redu 's answer which clocked in around 12-15 ms for the same task. I suspect his implementation is slower because he uses slice method in his algo to update array (other answers just increment the index leaving the input array unchanged which is much faster). Also this implementation results in multiple copies of the input array being stored in memory (every time the function is called it creates a new input array from the original array from which it removes the first elelment). This could also potentially affect performance.
So to answer your question: no I don't think there is a much better approach to what you are doing other than concatenating onto string and printing answers at the very end (what Louis suggested).
var arr = [];
for (var i = 0; i< 100; i++){
arr.push(i+"");
}
/*
console.time("test0");
test0();
function test0() {
var s = "";
for (var i=0; i<arr.length-1;i++) {
for (var j=i+1; j<arr.length;j++) {
s += arr[i] + " " + arr[j]+" ; ";
}
s += "\n";
}
console.log(s);
}
console.timeEnd("test0");
*/
console.time("test1");
test1();
function test1() {
var output = [];
getCombos(0, 0, [], 2);
console.log(JSON.stringify(output));
function getCombos(index, depth, tmp, k){
if(depth < k){
for(var i = index; i<arr.length; i++){
var tmp1 = [arr[i]];
Array.prototype.push.apply(tmp1, tmp);
getCombos(i+1, depth+1,tmp1, k);
}
}else{
output.push(tmp);
}
}
}
console.timeEnd("test1");
/*
console.time("test2");
test2();
function test2(){
Array.prototype.combinations = function(n){
return this.reduce((p,c,i,a) => (Array.prototype.push.apply(p,n > 1 ? a.slice(i+1).combinations(n-1).map(e => (e.push(c),e))
: [[c]]),p),[]);
};
console.log(JSON.stringify(arr.combinations(2)));
}
console.timeEnd("test2");*/
Here is a recursive solution that doesn't solve your time complexity problem but is another way to consider. The added advantage is that you can generalize it to any k so you are not stuck finding only combinations of two letters. Also you only declare one loop (although more than one copy of it will exist in your call stack)
var arr = ["a", "b", "c", "d", "e"];
var output = "";
getCombos(0, 0, [], 2);
console.log(output);
function getCombos(index, depth, tmp, k){
if(depth < k){
for(var i = index; i<arr.length; i++){
var tmp1 = [...tmp, arr[i]];
getCombos(i+1, depth+1,tmp1, k);
}
}else{
output += tmp.toString() + ";";
}
}

I think your code has a mistake cause here, E would never be used.
It should rather be:
var s = "";
for (var i=0; i<arr.length-1;i++) {
for (var j=i+1; j<arr.length;j++) {
s += arr[i] + " " + arr[j]+" ; ";
}
s += "\n";
}
console.log(s);
Note that if you log everything in the console, it's no surprise that it times out.

Related

How to partition array of integers to even and odd?

I want to partition an array (eg [1,2,3,4,5,6,7,8]), first partition should keep even values, second odd values (example result: [2,4,6,8,1,3,5,7]).
I managed to resolve this problem twice with built-in Array.prototype methods. First solution uses map and sort, second only sort.
I would like to make a third solution which uses a sorting algorithm, but I don't know what algorithms are used to partition lists. I'm thinking about bubble sort, but I think it is used in my second solution (array.sort((el1, el2)=>(el1 % 2 - el2 % 2)))... I looked at quicksort, but I don't know where to apply a check if an integer is even or odd...
What is the best (linear scaling with array grow) algorithm to perform such task in-place with keeping order of elements?
You can do this in-place in O(n) time pretty easily. Start the even index at the front, and the odd index at the back. Then, go through the array, skipping over the first block of even numbers.
When you hit an odd number, move backwards from the end to find the first even number. Then swap the even and odd numbers.
The code looks something like this:
var i;
var odd = n-1;
for(i = 0; i < odd; i++)
{
if(arr[i] % 2 == 1)
{
// move the odd index backwards until you find the first even number.
while (odd > i && arr[odd] % 2 == 1)
{
odd--;
}
if (odd > i)
{
var temp = arr[i];
arr[i] = arr[odd];
arr[odd] = temp;
}
}
}
Pardon any syntax errors. Javascript isn't my strong suit.
Note that this won't keep the same relative order. That is, if you gave it the array [1,2,7,3,6,8], then the result would be [8,2,6,3,7,1]. The array is partitioned, but the odd numbers aren't in the same relative order as in the original array.
If you are insisting on an in-place approach instead of the trivial standard return [arr.filter(predicate), arr.filter(notPredicate)] approach, that can be easily and efficiently achieved using two indices, running from both sides of the array and swapping where necessary:
function partitionInplace(arr, predicate) {
var i=0, j=arr.length;
while (i<j) {
while (predicate(arr[i]) && ++i<j);
if (i==j) break;
while (i<--j && !predicate(arr[j]));
if (i==j) break;
[arr[i], arr[j]] = [arr[j], arr[i]];
i++;
}
return i; // the index of the first element not to fulfil the predicate
}
let evens = arr.filter(i=> i%2==0);
let odds = arr.filter(i=> i%2==1);
let result = evens.concat(odds);
I believe that's O(n). Have fun.
EDIT:
Or if you really care about efficiency:
let evens, odds = []
arr.forEach(i=> {
if(i%2==0) evens.push(i); else odds.push(i);
});
let result = evens.concat(odds);
Array.prototype.getEvenOdd= function (arr) {
var result = {even:[],odd:[]};
if(arr.length){
for(var i = 0; i < arr.length; i++){
if(arr[i] % 2 = 0)
result.odd.push(arr[i]);
else
result.even.push(arr[i]);
}
}
return result ;
};

Speed differences in JavaScript functions finding the most common element in an array

I'm studying for an interview and have been working through some practice questions. The question is:
Find the most repeated integer in an array.
Here is the function I created and the one they created. They are appropriately named.
var arr = [3, 6, 6, 1, 5, 8, 9, 6, 6]
function mine(arr) {
arr.sort()
var count = 0;
var integer = 0;
var tempCount = 1;
var tempInteger = 0;
var prevInt = null
for (var i = 0; i < arr.length; i++) {
tempInteger = arr[i]
if (i > 0) {
prevInt = arr[i - 1]
}
if (prevInt == arr[i]) {
tempCount += 1
if (tempCount > count) {
count = tempCount
integer = tempInteger
}
} else {
tempCount = 1
}
}
console.log("most repeated is: " + integer)
}
function theirs(a) {
var count = 1,
tempCount;
var popular = a[0];
var temp = 0;
for (var i = 0; i < (a.length - 1); i++) {
temp = a[i];
tempCount = 0;
for (var j = 1; j < a.length; j++) {
if (temp == a[j])
tempCount++;
}
if (tempCount > count) {
popular = temp;
count = tempCount;
}
}
console.log("most repeated is: " + popular)
}
console.time("mine")
mine(arr)
console.timeEnd("mine")
console.time("theirs")
theirs(arr)
console.timeEnd("theirs")
These are the results:
most repeated is: 6
mine: 16.929ms
most repeated is: 6
theirs: 0.760ms
What makes my function slower than their?
My test results
I get the following results when I test (JSFiddle) it for a random array with 50 000 elements:
mine: 28.18 ms
theirs: 5374.69 ms
In other words, your algorithm seems to be much faster. That is expected.
Why is your algorithm faster?
You sort the array first, and then loop through it once. Firefox uses merge sort and Chrome uses a variant of quick sort (according to this question). Both take O(n*log(n)) time on average. Then you loop through the array, taking O(n) time. In total you get O(n*log(n)) + O(n), that can be simplified to just O(n*log(n)).
Their solution, on the other hand, have a nested loop where both the outer and inner loops itterate over all the elements. That should take O(n^2). In other words, it is slower.
Why does your test results differ?
So why does your test results differ from mine? I see a number of possibilities:
You used a to small sample. If you just used the nine numbers in your code, that is definately the case. When you use short arrays in the test, overheads (like running the console.log as suggested by Gundy in comments) dominate the time it takes. This can make the result appear completely random.
neuronaut suggests that it is related to the fact that their code operates on the array that is already sorted by your code. While that is a bad way of testing, I fail to see how it would affect the result.
Browser differences of some kind.
A note on .sort()
A further note: You should not use .sort() for sorting numbers, since it sorts things alphabetically. Instead, use .sort(function(a, b){return a-b}). Read more here.
A further note on the further note: In this particular case, just using .sort() might actually be smarter. Since you do not care about the sorting, only the grouping, it doesnt matter that it sort the numbers wrong. It will still group elements with the same value together. If it is faster without the comparison function (i suspect it is), then it makes sense to sort without one.
An even faster algorithm
You solved the problem in O(n*log(n)), but you can do it in just O(n). The algorithm to do that is quite intuitive. Loop through the array, and keep track of how many times each number appears. Then pick the number that appears the most times.
Lets say there are m different numbers in the array. Looping through the array takes O(n) and finding the max takes O(m). That gives you O(n) + O(m) that simplifies to O(n) since m < n.
This is the code:
function anders(arr) {
//Instead of an array we use an object and properties.
//It works like a dictionary in other languages.
var counts = new Object();
//Count how many of each number there is.
for(var i=0; i<arr.length; i++) {
//Make sure the property is defined.
if(typeof counts[arr[i]] === 'undefined')
counts[arr[i]] = 0;
//Increase the counter.
counts[arr[i]]++;
}
var max; //The number with the largest count.
var max_count = -1; //The largest count.
//Iterate through all of the properties of the counts object
//to find the number with the largerst count.
for (var num in counts) {
if (counts.hasOwnProperty(num)) {
if(counts[num] > max_count) {
max_count = counts[num];
max = num;
}
}
}
//Return the result.
return max;
}
Running this on a random array with 50 000 elements between 0 and 49 takes just 3.99 ms on my computer. In other words, it is the fastest. The backside is that you need O(m) memory to store how many time each number appears.
It looks like this isn't a fair test. When you run your function first, it sorts the array. This means their function ends up using already sorted data but doesn't suffer the time cost of performing the sort. I tried swapping the order in which the tests were run and got nearly identical timings:
console.time("theirs")
theirs(arr)
console.timeEnd("theirs")
console.time("mine")
mine(arr)
console.timeEnd("mine")
most repeated is: 6
theirs: 0.307ms
most repeated is: 6
mine: 0.366ms
Also, if you use two separate arrays you'll see that your function and theirs run in the same amount of time, approximately.
Lastly, see Anders' answer -- it demonstrates that larger data sets reveal your function's O(n*log(n)) + O(n) performance vs their function's O(n^2) performance.
Other answers here already do a great job of explaining why theirs is faster - and also how to optimize yours. Yours is actually better with large datasets (#Anders). I managed to optimize the theirs solution; maybe there's something useful here.
I can get consistently faster results by employing some basic JS micro-optimizations. These optimizations can also be applied to your original function, but I applied them to theirs.
Preincrementing is slightly faster than postincrementing, because the value does not need to be read into memory first
Reverse-while loops are massively faster (on my machine) than anything else I've tried, because JS is translated into opcodes, and guaranteeing >= 0 is very fast. For this test, my computer scored 514,271,438 ops/sec, while the next-fastest scored 198,959,074.
Cache the result of length - for larger arrays, this would make better more noticeably faster than theirs
Code:
function better(a) {
var top = a[0],
count = 0,
i = len = a.length - 1;
while (i--) {
var j = len,
temp = 0;
while (j--) {
if (a[j] == a[i]) ++temp;
}
if (temp > count) {
count = temp;
top = a[i];
}
}
console.log("most repeated is " + top);
}
[fiddle]
It's very similar, if not the same, to theirs, but with the above micro-optimizations.
Here are the results for running each function 500 times. The array is pre-sorted before any function is run, and the sort is removed from mine().
mine: 44.076ms
theirs: 35.473ms
better: 32.016ms

Is this as fast as JS array copy can get without loop unrolling?

This is related to this question.
I have heard that the while pattern with a decrement and a greater than test is faster than any other loop pattern. Given that, is this the fastest possible array copy in js?
function arrayCopy(src,sstart,dst,dstart,length) {
length += sstart;
dstart += length;
while(--length >= sstart) {
dst[--dstart] = src[length];
}
}
Other test functions
function slowCopy(src,sstart,dst,dstart,length) {
for(var i = sstart; i < sstart+length;i+=1 ) {
dst[dstart++] = src[i];
}
}
function aCopy(src,sstart,dst,dstart,length) {
Array.prototype.splice.apply(dst,[dstart, length].concat(src.slice(sstart,sstart+length)));
}
Test Results http://jsperf.com/fastest-js-arraycopy
arrayCopy -
2,899
±5.27%
fastest
slowCopy - WINNER
2,977
±4.86%
fastest
aCopy -
2,810
±4.61%
fastest
I want to add some more of the suggested functions below to the jsPerf tests but none of them incorporate source start offset, destination start offset or length of copy. Anyway, I was somewhat surprised by these results which appear to be the opposite of what I expect
Who says you need a loop?
var myArrayCopy = JSON.parse(JSON.stringify(myArray));
This method makes a deep clone of the array. Here it is in a function:
function arrayCopy(src,sstart,dst,dstart,length) {
dst = JSON.parse(JSON.stringify(src));
}
Keep in mind the other variables (besides src and dst) are there just to maintain your original code structure in case you have pre-existing calls to this function. They won't be used and can be removed.
Slow Copy is, surprisingly, the winner. By a narrow margin:
function slowCopy(src,sstart,dst,dstart,length) {
for(var i = sstart; i < sstart+length;i+=1 ) {
dst[dstart++] = src[i];
}
}
I think this is the fastest way:
var original = [1, 2, 3];
var copy = original.slice(0);

Javascript code help - Wrong return

So I'm working on this script. When I'm done with it, it should be used for making 2-and-2 groups. But anyway; The 'input' array in the start of the script, will get 22 different inputs from my HTML file. As a standard, I gave them the values 1-22. The thing is my two blocks '1st number' and '2nd number' doesn't work very well: they don't return the right numbers. Cause I want every elev[x] to be used once! Not 2 times, not 0 times, once! And the blocks returns like some of them twice and some of them isn't even used. So how can I fix this?
function Calculate(){
var elev = [];
var inputs = document.getElementsByName("txt");
for(i=0; i<inputs.length; i++) {
elev[i] = {
"Value": inputs[i].value,
"Used": false
};
}
function shuffle(elev){
var len = elev.length;
for(i=1; i<len; i++){
j = ~~(Math.random()*(i+1));
var temp = elev[i];
arr[i] = elev[j];
arr[j] = temp;
}
}
for(var v=0; v<1; v++) {
shuffle(elev);
document.write(elev + '<br/>\n');
}}
Yes, I'm still new at programming and I just wanna learn what I can learn.
Problem solved by doing the Fisher-Yates shuffle.
The idea of shuffling an array and iterating over it is correct, however, sorting by a coin-flipping comparator (a common mis-implementation; suggested by the other answer) is not the correct way to shuffle an array; it produces very skewed results and is not even guaranteed to finish.
Unless you're willing to fetch underscore.js (arr = _.shuffle(arr)), It is recommended to use the Fisher-Yates shuffle:
function shuffle(arr){
var len = arr.length;
for(var i=1; i<len; i++){
var j = ~~(Math.random()*(i+1)); // ~~ = cast to integer
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
...
shuffle(elev);
for(var i=0; i<elev.length; i++){
//do something with elev[i]
console.log(elev[i].Value);
}
also, note that object fields should be lowercase, not uppercase (value', not 'Value'). Also, theUsed` field should not be neccessary since you now iterate in order. Also, any chance you could use an array of Values? They seem to be the only field in your objects.
Also, Don't use document.write(). It doesn't work as expected when used after the page load. If you want to append something to the document, you hate jQuery and other frameworks and you don't want to go the long way of creating and composing DOM nodes, document.body.innerHTML += is still better than document.write() (but still consider appending DocumentFragments instead').
demo: http://jsfiddle.net/honnza/2GSDd/1/
You are only getting random numbers once, and then processing them if they haven't already been used.
I would suggest shuffling the array (elev.sort(function() {return Math.random()-0.5})) and then just iterating through the result without further random numbers.

alternatives for excessive for() looping in javascript

Situation
I'm currently writing a javascript widget that displays a random quote into a html element. the quotes are stored in a javascript array as well as how many times they've been displayed into the html element. A quote to be displayed cannot be the same quote as was previously displayed. Furthermore the chance for a quote to be selected is based on it's previous occurences in the html element. ( less occurrences should result in a higher chance compared to the other quotes to be selected for display.
Current solution
I've currently made it work ( with my severely lacking javascript knowledge ) by using a lot of looping through various arrays. while this currently works ( !! ) I find this solution rather expensive for what I want to achieve.
What I'm looking for
Alternative methods of removing an array element from an array, currently looping through the entire array to find the element I want removed and copy all other elements into a new array
Alternative method of calculating and selecting a element from an array based on it's occurence
Anything else you notice I should / could do different while still enforcing the stated business rules under Situation
The Code
var quoteElement = $("div#Quotes > q"),
quotes = [[" AAAAAAAAAAAA ", 1],
[" BBBBBBBBBBBB ", 1],
[" CCCCCCCCCCCC ", 1],
[" DDDDDDDDDDDD ", 1]],
fadeTimer = 600,
displayNewQuote = function () {
var currentQuote = quoteElement.text();
var eligibleQuotes = new Array();
var exclusionFound = false;
for (var i = 0; i < quotes.length; i++) {
var iteratedQuote = quotes[i];
if (exclusionFound === false) {
if (currentQuote == iteratedQuote[0].toString())
exclusionFound = true;
else
eligibleQuotes.push(iteratedQuote);
} else
eligibleQuotes.push(iteratedQuote);
}
eligibleQuotes.sort( function (current, next) {
return current[1] - next[1];
} );
var calculatePoint = eligibleQuotes[0][1];
var occurenceRelation = new Array();
var relationSum = 0;
for (var i = 0; i < eligibleQuotes.length; i++) {
if (i == 0)
occurenceRelation[i] = 1 / ((calculatePoint / calculatePoint) + (calculatePoint / eligibleQuotes[i+1][1]));
else
occurenceRelation[i] = occurenceRelation[0] * (calculatePoint / eligibleQuotes[i][1]);
relationSum = relationSum + (occurenceRelation[i] * 100);
}
var generatedNumber = Math.floor(relationSum * Math.random());
var newQuote;
for (var i = 0; i < occurenceRelation.length; i++) {
if (occurenceRelation[i] <= generatedNumber) {
newQuote = eligibleQuotes[i][0].toString();
i = occurenceRelation.length;
}
}
for (var i = 0; i < quotes.length; i++) {
var iteratedQuote = quotes[i][0].toString();
if (iteratedQuote == newQuote) {
quotes[i][1]++;
i = quotes.length;
}
}
quoteElement.stop(true, true)
.fadeOut(fadeTimer);
setTimeout( function () {
quoteElement.html(newQuote)
.fadeIn(fadeTimer);
}, fadeTimer);
}
if (quotes.length > 1)
setInterval(displayNewQuote, 10000);
Alternatives considered
Always chose the array element with the lowest occurence.
Decided against this as this would / could possibly reveal a too obvious pattern in the animation
combine several for loops to reduce the workload
Decided against this as this would make the code to esoteric, I'd probably wouldn't understand the code anymore next week
jsFiddle reference
http://jsfiddle.net/P5rk3/
Update
Rewrote my function with the techniques mentioned, while I fear that these techniques still loop through the entire array to find it's requirements, at least my code looks cleaner : )
References used after reading the answers here:
http://www.tutorialspoint.com/javascript/array_map.htm
http://www.tutorialspoint.com/javascript/array_filter.htm
http://api.jquery.com/jQuery.each/
I suggest array functions that are mostly supported (and easily added if not):
[].splice(index, howManyToDelete); // you can alternatively add extra parameters to slot into the place of deletion
[].indexOf(elementToSearchFor);
[].filter(function(){});
Other useful functions include forEach and map.
I agree that combining all the work into one giant loop is ugly (and not always possible), and you gain little by doing it, so readability is definitely the winner. Although you shouldn't need too many loops with these array functions.
The answer that you want:
Create an integer array that stores the number of uses of every quote. Also, a global variable Tot with the total number of quotes already used (i.e., the sum of that integer array). Find also Mean, as Tot / number of quotes.
Chose a random number between 0 and Tot - 1.
For each quote, add Mean * 2 - the number of uses(*1). When you get that that value has exceeded the random number generated, select that quote.
In case that quote is the one currently displayed, either select the next or the previous quote or just repeat the process.
The real answer:
Use a random quote, at the very maximum repeat if the quote is duplicated. The data usages are going to be lost when the user reloads/leaves the page. And, no matter how cleverly have you chosen them, most users do not care.
(*1) Check for limits, i.e. that the first or last quota will be eligible with this formula.
Alternative methods of removing an array element from an array
With ES5's Array.filter() method:
Array.prototype.without = function(v) {
return this.filter(function(x) {
return v !== x;
});
};
given an array a, a.without(v) will return a copy of a without the element v in it.
less occurrences should result in a higher chance compared to the other quotes to be selected for display
You shouldn't mess with chance - as my mathematician other-half says, "chance doesn't have a memory".
What you're suggesting is akin to the idea that numbers in the lottery that haven't come up yet must be "overdue" and therefore more likely to appear. It simply isn't true.
You can write functions that explicitly define what you're trying to do with the loop.
Your first loop is a filter.
Your second loop is a map + some side effect.
I don't know about the other loops, they're weird :P
A filter is something like:
function filter(array, condition) {
var i = 0, new_array = [];
for (; i < array.length; i += 1) {
if (condition(array[i], i)) {
new_array.push(array[i]);
}
}
return new_array;
}
var numbers = [1,2,3,4,5,6,7,8,9];
var even_numbers = filter(numbers, function (number, index) {
return number % 2 === 0;
});
alert(even_numbers); // [2,4,6,8]
You can't avoid the loop, but you can add more semantics to the code by making a function that explains what you're doing.
If, for some reason, you are not comfortable with splice or filter methods, there is a nice (outdated, but still working) method by John Resig: http://ejohn.org/blog/javascript-array-remove/

Categories