I'm quite new to JavaScript and I'm trying to implement my code in pseudocode in JavaScript, however I'm not getting the result that I'm supposed to. I want the function to permute the elements of the array p places to the left. In pseudocode I'm using a queue data structure, but I thought I can as well us an array. As the result of my function, I get an array with [2, 2, 2, 2]. Can you please help me out?
My code in pseudocode:
Function PERMUTEVECTOR(row, p)
If p=0 then
Return row
End if
New Queue q
For 0<= i <4 do
ENQUEUE[row[i], q]
End for
For 1<= i <= p do
ENQUEUE[HEAD[q],q]
DEQUEUE[q]
End for
For 0<=i<4 do
Row[i] <- HEAD[q]
DEQUEUE[q]
End for
Return row
End function
My code in JavaScript:
function permute_vector(row, p)
{
if (p=0)
{return row}
let q = new Array()
for (i=0; i<4; i++)
{
q.push(row[i])
}
for (i=0; i<p; i++)
{
q.push(q[0])
q.pop()
}
for (i=0; i<4; i++)
{
row[i] = q[0]
q.pop()
}
return row
}
px = permute_vector([2,4,1,3], 1)
console.log("px is:", px)
}
I did the same in Python and it works fine:
def permute_vector(row, p):
if p==0:
return row
q = []
for i in range(4):
q.append(row[i])
for i in range(p):
q.append(q[0])
q.pop(0)
for i in range(4):
row[i] = q[0]
q.pop(0)
return row
What am I doing wrong with my JavaScript code?
Many thanks!
In javascript, Array.pop() removes the last element. You need Array.shift() for removing the first one.
function permute_vector(row, p)
{
if (p===0) // in js, checks are with == (loose) or === (strict).
// = is for assignment and you were assigning p to 0 here.
{return row}
let q = new Array()
for (i=0; i<4; i++)
{
q.push(row[i])
}
for (i=0; i<p; i++)
{
q.push(q[0])
q.shift() // shift instead of pop
}
for (i=0; i<4; i++)
{
row[i] = q[0]
q.shift() // shift instead of pop
}
return row
}
px = permute_vector([2,4,1,3], 1)
console.log("px is:", px)
}
Two mistakes:
Comparisons need double or triple equal signs. In fact, you should prefer triple equal signs as you can read in this question/answer.
In python pop() accepts an index to remove a specific item. You're making use of that to remove the first item. In JavaScript Array.pop() merely is capable of removing the last item. Use Array.shift() to remove the first item.
Working code:
function permute_vector(row, p) {
if (p === 0) {
return row;
}
let q = new Array();
for (i = 0; i < 4; i++) {
q.push(row[i]);
}
for (i = 0; i < p; i++) {
q.push(q[0]);
q.shift();
}
for (i = 0; i < 4; i++) {
row[i] = q[0];
q.shift();
}
return row;
}
px = permute_vector([2, 4, 1, 3], 1);
console.log("px is:", px);
Now when you've got it, you can just ditch the whole thing and replace it with
vec[n:] + vec[:n] (Python)
[...vec.slice(n), ...vec.slice(0, n)] (JS)
Basic textbook algorithms are rarely useful in scripting languages, because they implement stuff like this out of the box.
Related
I have written this js code for palindrome, I know there are better and more efficient palindrome methods online but I want to know why I am unable to get my palindrome function to work properly?
CODE:
var pal = function(str) {
var len = str.length;
for (var i = 0; i < len; i++) {
var comp1 = str.substring(i, i + 1);
for (var j = len; j > 0; j--) {
var comp2 = str.substring(j - 1, j);
}
if (comp1 != comp2) {
console.log("not palindrome")
break;
} else {
console.log('palindrome')
}
}
}
pal('maddog');
OUTPUT :
palindrome
not palindrome
There are lot of better algorithms to check Palindrome. Let use the similar algorithm that you are using.
We basically use two pointers - left and right, and move to middle at the same time. In the original question, left pointer and right pointer doesn't move at the same time.
Pointers should move like this -
a b c b a
^ ^
a b c b a
^ ^
a b c b a
^
var isPalindrome = function (str) {
for (var i = 0, j = str.length-1; i < j; i++ , j--) {
if (str[i] != str[j]) {
return false;
}
}
return true;
}
console.log('maddog : ' + isPalindrome('maddog'));
console.log('abcba : ' + isPalindrome('abcba'));
console.log('deed : ' + isPalindrome('deed'));
console.log('a : ' + isPalindrome('a'));
Try the following code. It works by dividing the string length by 2, and then iterating up, checking mirroring characters against each other:
var pal = function(str){
var len = str.length;
for(var i = 0; i < Math.floor(len/2); i++){
if(str[i] != str[(len-1)-i]){
return false;
}
}
return true;
}
console.log(pal("bunny"));
console.log(pal("amoreroma"));
The inner loop is totally unnecessary. It does the same thing every time -- it loops through the whole string, starting from the end, repeatedly setting comp2 to the character; when it's done, comp2 always contains the first character. So your function just tests whether every character in the string is the same as the first character.
To test if something is a palindrome, you need to compare each character with the corresponding character from the other end of the string. You don't need two loops for this. You also only need to loop through the first half of the string, not the whole string.
Finally, you should only echo Palindrome at the end of the loop. Inside the loop you only know that one character matches, not all of them.
var pal = function(str) {
var len = str.length;
var half = Math.floor(len / 2);
var isPal = true;
for (var i = 0; i < half; i++) {
var comp1 = str[i];
var comp2 = str[len - i - 1];
if (comp1 != comp2) {
console.log("not palindrome")
isPal = false;
break;
}
}
if (isPal) {
console.log('palindrome')
}
}
pal('maddog');
pal('maddam');
You don't really need the nested loops, you can just loop backwards through the string to invert the string and then compare it to the original string. I updated the Snippet to work.
Before, your code was not inverting the string but rather just iterating through the characters and assigning them to the comp1 and comp1 variables. You need to concatenate the strings in order to build the new string backwards comp = comp + str.substring(j-1, j);
var pal = function(str) {
var len = str.length;
var comp = '';
for (var j = len; j > 0; j--) {
comp = comp + str.substring(j - 1, j);
}
if (str !== comp) {
console.log("not palindrome")
return;
}
console.log('palindrome')
}
pal('arepera');
I had ten rows which each rows contain 4 column, now I want to get the value which I had import using localStorage. I find a way to put all these value independently but the code is all the repeat one. These will cause to redundancy of code. I wonder if there are a way to shorten the code using loop?
Here is my code
var res = {};
$(function(){
$('#subbtn').click(function() {
console.log($('#tab').find('tr'))
$('tr').each(function(){
var tmp = [];
var cl ;
$(this).find('select').each(function(){
cl = $(this).attr('class');
//console.log(cl);
tmp.push($(this).val());
})
res[cl] = tmp
})
console.log(res);
localStorage.setItem("testingvalue",JSON.stringify(res));
document.getElementById("results__display").innerHTML = (localStorage.getItem("testingvalue"));
})
})
$( document ).ready(function(){
var res = {};
try {
console.log('existed');
res = JSON.parse(localStorage.getItem("testingvalue"));
//alert(res.r1[2]);
document.getElementsByClassName("r1")[0].selectedIndex=res.r1[0];
document.getElementsByClassName("r1")[1].selectedIndex=res.r1[1];
document.getElementsByClassName("r1")[2].selectedIndex=res.r1[2];
document.getElementsByClassName("r1")[3].selectedIndex=res.r1[3];
document.getElementsByClassName("r2")[0].selectedIndex=res.r2[0];
document.getElementsByClassName("r2")[1].selectedIndex=res.r2[1];
document.getElementsByClassName("r2")[2].selectedIndex=res.r2[2];
document.getElementsByClassName("r2")[3].selectedIndex=res.r2[3];
document.getElementsByClassName("r3")[0].selectedIndex=res.r3[0];
document.getElementsByClassName("r3")[1].selectedIndex=res.r3[1];
document.getElementsByClassName("r3")[2].selectedIndex=res.r3[2];
document.getElementsByClassName("r3")[3].selectedIndex=res.r3[3];
document.getElementsByClassName("r4")[0].selectedIndex=res.r4[0];
document.getElementsByClassName("r4")[1].selectedIndex=res.r4[1];
document.getElementsByClassName("r4")[2].selectedIndex=res.r4[2];
document.getElementsByClassName("r4")[3].selectedIndex=res.r4[3];
document.getElementsByClassName("r5")[0].selectedIndex=res.r5[0];
document.getElementsByClassName("r5")[1].selectedIndex=res.r5[1];
document.getElementsByClassName("r5")[2].selectedIndex=res.r5[2];
document.getElementsByClassName("r5")[3].selectedIndex=res.r5[3];
document.getElementsByClassName("r6")[0].selectedIndex=res.r6[0];
document.getElementsByClassName("r6")[1].selectedIndex=res.r6[1];
document.getElementsByClassName("r6")[2].selectedIndex=res.r6[2];
document.getElementsByClassName("r6")[3].selectedIndex=res.r6[3];
document.getElementsByClassName("r7")[0].selectedIndex=res.r7[0];
document.getElementsByClassName("r7")[1].selectedIndex=res.r7[1];
document.getElementsByClassName("r7")[2].selectedIndex=res.r7[2];
document.getElementsByClassName("r7")[3].selectedIndex=res.r7[3];
document.getElementsByClassName("r8")[0].selectedIndex=res.r8[0];
document.getElementsByClassName("r8")[1].selectedIndex=res.r8[1];
document.getElementsByClassName("r8")[2].selectedIndex=res.r8[2];
document.getElementsByClassName("r8")[3].selectedIndex=res.r8[3];
document.getElementsByClassName("r9")[0].selectedIndex=res.r9[0];
document.getElementsByClassName("r9")[1].selectedIndex=res.r9[1];
document.getElementsByClassName("r9")[2].selectedIndex=res.r9[2];
document.getElementsByClassName("r9")[3].selectedIndex=res.r9[3];
document.getElementsByClassName("r10")[0].selectedIndex=res.r10[0];
document.getElementsByClassName("r10")[1].selectedIndex=res.r10[1];
document.getElementsByClassName("r10")[2].selectedIndex=res.r10[2];
document.getElementsByClassName("r10")[3].selectedIndex=res.r10[3];
}
catch (error){
console.log(error.message);
}
});
Looking at this repeated line:
document.getElementsByClassName("r1")[0].selectedIndex=res.r1[0];
...a simple first pass improvement would be to just use a nested for loop with variables instead of "r1" and 0:
for (var r = 1; r <= 10; r++) {
for (var i = 0; i < 4; i++) {
document.getElementsByClassName("r" + r)[i].selectedIndex = res["r" + r][i];
}
}
Notice, though, that this means the .getElementsByClassName("r" + r) call happens four time for each value of r, which is not very efficient - it would be better to move that into the outer loop:
var els;
for (var r = 1; r <= 10; r++) {
els = document.getElementsByClassName("r" + r);
for (var i = 0; i < 4; i++) {
els[i].selectedIndex = res["r" + r][i];
}
}
In the second version the inner loop could say i < els.length rather than i < 4, although note that either way you need to be sure you match the number of HTML elements to the number of items in your res object.
You've seem to have the jQuery library loaded. Using jQuery makes this much easier.
Here is an example:
var res = JSON.parse(localStorage.getItem("testingvalue"));
$("tr select").each(function(){
$(this).val(res[$(this).attr("class")][$(this).index()]);
});
Of course, this will only work if the select elements have only one class name and the res object contains values for all the select elements that are inside tr elements. Based on the jQuery code in your question that seems to be the case.
And this is a safer approach
Object.keys(res).forEach(function(key){
res[key].forEach(function(val, index){
$("tr select." + key).eq(index).val(val);
});
});
Code below will work regardless the size of your data in storage:
res = JSON.parse(localStorage.getItem("testingvalue"));
// Let's start with checking 'res' type.
// - if it's an Array, get the the length from .length
// - if it's Object, get the the length from Object.keys().length
var resLength = Array.isArray(res) ? res.length : typeof res === 'object' ? Object.keys(res).length : 0;
// loop throw the rows.
for (var i = 0; i < resLength; i++) {
// Do the same as above: get type of the row and calculate it length for the loop.
var rowLength = Array.isArray(res[i]) ? res.length : typeof res[i] === 'object' ? Object.keys(res[i]).length : 0;
// loop throw the columns on the row.
for (var j = 0; j < rowLength; j++) {
document.getElementsByClassName('r'+i)[j].selectedIndex=res['r'+i][j];
}
}
I'm trying to find an index of a number in a 2d array, but console gives out
Uncaught TypeError: block[((a * 10) + c)].indexOf is not a function
I think it has something to do with the way of accessing the array element, but can't seem to find the problem.
Here's the code.
var block = [];
var temp;
var del;
for(var a = 0;a < 9;a++){
for(var b = 0;b < 9;b++){
temp = parseInt(prompt("enter element number " + b + " of row number " + a));
console.log(temp);
if(temp>0){
block[a*10+b] = temp;
}else{
block[a*10+b] = [1,2,3,4,5,6,7,8,9];
}
// console.log(block[a*10+b]);
}
}
for(var a = 0;a < 9;a++){
for(var b = 0;b < 9;b++){
if(typeof(block[a][b]) == "number"){
for(var c = 0;c < 9;c++){
if(c != b){
del = block[a*10+c].indexOf(b);
block[a*10+c].splice(del,1);
}
}
}
}
}
You have a mix of data types assigned to the block array. When the user enters a value that is not numeric, you assign indeed a nested array to one of the block elements, but not so when the user enters a valid number.
From what I think you are doing (a Sudoko game?) this might be intended: the numbers are known values in the grid, the nested arrays represent a list of values that are still possible at that particular cell.
But then in the second part of your code, you should check in which of the two cases you are, as you only want to remove array elements if the value you are looking at is indeed an array. This test you can do with Array.isArray().
There are also some other issues in the second part of your script:
The expression block[a][b] is not consistent with how you have filled that array: it should be block[a*10+b] to be consistent.
the b in .indexOf(b) is wrong: you are not looking for that value, but for block[a*10+b].
the splice() is always executed, even if the indexOf returned -1. This leads to an undesired effect, because if the first argument to splice() is negative, the index really is counted from the end of the array, and still an element is removed from the array. This should not happen: you should only execute the splice if the indexOf result is non-negative.
Below I have put a working version, but in order to avoid the almost endless prompts, I have provided this snippet with a textarea where you can input the complete 9x9 grid in one go, and then press a button to start the execution of your code:
document.querySelector('button').onclick = function () {
var block = [];
var temp;
var del;
var text = document.querySelector('textarea').value.replace(/\s+/g, '');
for(var a = 0;a < 9;a++){
for(var b = 0;b < 9;b++){
temp = parseInt(text[a*9+b]); // <-- get char from text area
if(temp>0){
block[a*10+b] = temp;
}else{
block[a*10+b] = [1,2,3,4,5,6,7,8,9];
}
}
}
for(var a = 0;a < 9;a++){
for(var b = 0;b < 9;b++){
var num = block[a*10+b]; // <-- get content, fix the index issue
if(typeof num == "number"){
for(var c = 0;c < 9;c++){
if(c != b && Array.isArray(block[a*10+c])){ //<-- add array-test
del = block[a*10+c].indexOf(num); // <-- not b, but num
if (del > -1) // <-- only splice when found
block[a*10+c].splice(del,1);
}
}
}
}
}
document.querySelector('pre').textContent = 'block='+ JSON.stringify(block);
};
<textarea rows=9>
53..7....
6..195...
.98....6.
8...6...3
4..8.3..1
7...2...6
.6....28.
...419..5
....8..79
</textarea>
<button>Process</button>
<pre></pre>
Note that there are elements in block which remain null. I suppose you intended this: as you multiply a with 10, and only store 9 values per "row", there is always one index that remains untouched.
I haven't looked over your second for loop, but you can try applying similar logic there as in the snippet I've provided. The issue is that you need to create a temporary array inside the outer for loop over values of a (but NOT inside the inner, nested for loop over values of b). Inside the for loop for values of b, then, you need to push something into that temporary array (which I called temp). Then, outside of the b for loop, but before the next iteration of a, push that temporary array temp to the block array. In this way, you will generate a 2D array.
var block = [];
var del;
for(var a = 0; a < 9; a++) {
let temp = [];
for(var b = 0; b < 9; b++) {
let num = parseInt(prompt(`Enter element ${b} of row ${a}:`));
if (num > 0) {
temp.push(num);
} else {
// block[a*10+b] = [1,2,3,4,5,6,7,8,9];
temp.push(b);
}
}
block.push(temp);
}
I've been looking for real world examples of recursion. Remember, programming Wizards, I'm and artist and in Photoshop scripting (scriptus modus operandi) it's normally used to loop over all layers and sub layers.
I'm working on a (simple) recursion script to solve a four digit combination lock. You know, start with 1, then try 2, then 3 etc until the solution is found. To make things easy in the example the second digit is correct so we know that we don't have to change that. Also the initial state the numbers start zero, but we know there are no zeroes in the final solution.
The attempt must match the solution AND also add up to 10 in order to
be solved.
This may seem a bit stoppid, but I want to put in a two part condition of the solution, mainly because I can then apply what I've learned and write a brute force suduko solver. But you must crawl before you can ice skate...
var puzzle = [0,2,0,0]; // source
var solution = [1,2,3,4];
var s = superCopy(puzzle); // working array
drawPuzzle(s);
solvePuzzle(s, puzzle);
var total = checkTotal(s, solution);
var solution = checkSolution(s, solution);
function checkTotal(arr, source)
{
var c = 0;
// count the total
for (var i = 0; i < arr.length; i++)
{
c += arr[i];
}
if (c == 10)
{
alert("Total OK")
return true;
}
}
function checkSolution(arr, source)
{
// check the solution
for (var i in arr)
{
if (arr[i] != source[i]) return false
return true;
}
}
function solvePuzzle(arr, source)
{
for (var i = 0; i < arr.length; i++)
{
// check the source
var sourceCell = source[i];
//alert("checking source " + sourceCell)
//if it's a zero we can change it
if (arr[i] == 0)
{
cell = arr[i];
cell+=1;
if (cell > 4) cell = 0;
arr[i] = cell;
}
}
// check the solution
for (var i in arr)
{
// overflow time!
if (arr[i] != source[i]) solvePuzzle(arr, source)
else
{
alert("All done!")
}
}
}
function drawPuzzle(arr)
{
var p = "";
var c = 0;
for (var i = 0; i < arr.length; i++)
{
if (arr[i] == 0) p += "-"
else p += arr[i];
c+=1;
}
alert(p);
}
function superCopy(arr)
{
// returns a true copy of an array
tempArr = new Array();
for (var i = 0; i < arr.length; i++)
{
if (arr[i] == 0) tempArr[i] = 1 // changed, thanks Nostradamnit!
else tempArr[i] = arr[i]
}
return tempArr
}
The script is incomplete. This what I have so far, it falls over with an overflow error. Note solvePuzzle and checkTotal functions are not called because I realised that solvePuzzle needs to call itself and work out the solution...which is when I ran into overflow problems and got a bit confused.
I realise that this type of question runs dangerously close to a "fix my code" venture, so I'm prepared to put a bounty out for it. Thanks.
There are a couple of problems with your code. First up, your checkSolution function stops at the very first number that matches. You probably want it to check every number before returning true, so you should move return true outside the for loop. It only gets there if all numbers match up.
Another flaw is the superCopy function, which as Nostradamnit pointed out has a flawed condition.
Your next problem is in solvePuzzle, where you have this:
if (arr[i] == 0)
{
cell = arr[i];
cell+=1;
if (cell > 4) cell = 0;
arr[i] = cell;
}
The thing is, because arr[i] is 0 and you only add 1, cell will never be 4. So your if is never going to fire. This is where your infinite loop is: it only increments values that are zero with one, so after all zeros became one, it never goes further and you keep checking if "1111" is the solution.
Now the overflow: you shouldn't be calling solvePuzzle for every cell, this grows exponentially:
for (var i in arr)
{
// overflow time!
if (arr[i] != source[i]) solvePuzzle(arr, source)
else
{
alert("All done!")
}
}
Also, you never check the result again, so the looping never ends. You would probably want to change this to this:
if(checkTotal(arr) && checkSolution(arr, source))
{
alert("All done!");
return;
}
solvePuzzle(arr, source);
As a side note (not a bug causing thing): your checkTotal function isn't using the source parameter, so you can probably leave that out. Also, there is a rogue variable called cell in solvePuzzle, which isn't a big deal in this case, but it would be better to put var in front of it so it doesn't become global. Also, there is a sourceCell variable that never gets used.
At first glance, it seems that your superCopy function is flawed. You create an empty array, then compare its 0 index (which doesn't exist) to the input array. Is that where you are getting your "overflow"?
The problem seems to be that you call solvePuzzle(arr, source) multiple times in the same iteration:
for (var i in arr)
{
// overflow time!
if (arr[i] != source[i]) solvePuzzle(arr, source)
else
{
alert("All done!")
}
}
So you iterate over every item in the array, and if it is not equal to the source item, you call solvePuzzle again. But if you have several items that don't match, solvePuzzle will get called multiple times on the same array. And for each call, it gets called again multiple times. Therefore your function calls grow exponentially and result finally in a stack overflow.
What you probably intended to do, is the following:
var areArraysEqual = true;
for (var i in arr)
{
if (arr[i] != source[i]) {
areArraysEqual = false;
break;
}
}
if (areArraysEqual) {
alert("All done!");
} else {
solvePuzzle(arr, source);
}
I haven't looked over your solution yet, but i did write a recursive one for myself to solve the problem. Perhaps it can be of help as a guideline?
var solution = [1,2,3,4];
function checkSolution(arr) {
return solution.every(function(item, index){ return item === arr[index]; });
}
function increment(arr) {
for (var i=0; arr[i] === 9; i++) {
arr[i] = 0;
}
arr[i]++;
return arr;
}
function solvePuzzle(arr) {
if (isNaN(arr[arr.length-1])) {
return null;
}
if (checkSolution(arr)) {
return arr;
}
return solvePuzzle(increment(arr));
}
var solution = solvePuzzle([1,1,1,1]);
console.log(solution); // [1,2,3,4]
Maybe little bit off topic but I have created the easiest recursion example possible in my opinion.
http://jsfiddle.net/33yfS/
var str = "9785";
function rec(a, b)
{
var c=0;
if(b==-1)
return "";
if(a!=str.charAt(b))
return rec(a+1, b);
else
{
return rec(0,b-1)+""+a;
}
}
var ans = rec(0,str.length-1)
In how many ways can a group of 9 people work in 3 disjoint subgroups of 2, 3 and 4 persons? How can I generates all the possibilities via backtracking with javascript.
Example:
Gs = group([aldo,beat,carla,david,evi,flip,gary,hugo,ida],[2,2,5]);
console.log(Gs); // [[aldo,beat],[carla,david],[evi,flip,gary,hugo,ida]], ...
Note that I do not want permutations of the group members; i.e. [[aldo,beat],...] is the same solution as [[beat,aldo],...]. However, there's a difference between [[aldo,beat],[carla,david],...] and [[carla,david],[aldo,beat],...].
*No libraries please.
If you only need the number of ways a group of 9 people can be divided into 3 subgroups of 2, 3 and 4 people each then that's easy to compute mathematically using C (the function to calculate the number of combinations).
First you have 9 people out of which you need to select 2 people. Hence you do C(9, 2).
Next you have 7 people out of which you need to select 3 people. Hence you do C(7, 3).
Finally you have 4 people out of which you need to select 4 people. Hence you do C(4, 4). However C(n, n) is always 1.
Hence the number of ways to divide a group of 9 people into 3 subgroups of 2, 3 and 4 people is C(9, 2) * C(7, 3) * C(4, 4). This can be simplified to C(9, 2) * C(7, 3), which is 36 * 35 which is 1260.
We can write a function to compute this for us:
function ways(n) {
var l = arguments.length, w = 1;
for (var i = 1; i < l; i++) {
var m = arguments[i];
w *= combinations(n, m);
n -= m;
}
return w;
}
To make this function work we need to define the function combinations:
function combinations(n, k) {
return factorial(n) / factorial(n - k) / factorial(k);
}
Finally we need to define the function for factorial:
function factorial(n) {
var f = n;
while (--n) f *= n;
return f;
}
Then we compute the number of ways as follows:
alert(ways(9, 2, 3)); // 1260
You can see the demo here: http://jsfiddle.net/bHSuh/
Note that we didn't need to specify the last subgroup of 4 people because that is implied.
However I believe that you want to generate each possible way. This is the sort of thing that the amb operator is perfect for. So the first thing we'll do is write the amb operator in JavaScript:
function amb(options, callback) {
var length = options.length;
for (var i = 0; i < length; i++) {
try {
callback(options[i]); // try the next option
return; // no problem, quit
} catch (e) {
continue; // problem, next
}
}
throw new Error("amb tree exhausted"); // throw a tantrum
}
Next we'll write a function which picks a given set of items from a list of indices:
function pick(list, items) {
var length = list.length, selected = [], rest = [];
for (var i = 0; i < length; i++) {
if (items.indexOf(i) < 0) rest.push(list[i]);
else selected.push(list[i]);
}
return [selected, rest];
}
We also need a function which will generate a list of indices:
function getIndices(length) {
var indices = [];
for (var i = 0; i < length; i++)
indices.push(i);
return indices;
}
Finally we'll implement the group function recursively:
function group(options, divisions) {
var subgroup = [], groups = [], n = 0;
var indices = getIndices(options.length);
var division = divisions.shift(), remaining = divisions.length;
try {
amb(indices, select);
} catch (e) {
return groups;
}
function select(index) {
subgroup.push(index);
if (++n < division) {
try { amb(indices.slice(index + 1), select); }
catch (e) { /* we want to continue processing */ }
} else {
var subgroups = pick(options, subgroup);
if (remaining) {
var children = group(subgroups.pop(), divisions.slice());
var length = children.length;
for (var i = 0; i < length; i++)
groups.push(subgroups.concat(children[i]));
} else groups.push(subgroups);
}
n--;
subgroup.pop();
throw new Error;
}
}
Now you can use it as follows:
var groups = group([
"aldo", "beat", "carla",
"david", "evi", "flip",
"gary", "hugo", "ida"
], [2, 3]);
Notice again that you didn't need to specify the last subgroup of 4 people since it's implied.
Now let's see whether the output is as we expected it to be:
console.log(groups.length === ways(9, 2, 3)); // true
There you go. There are exactly 1260 ways that a group of 9 people can be divided into 3 subgroups of 2, 3 and 4 people each.
Now I know that my group function looks a little daunting but it's actually really simple. Try to read it and understand what's going on.
Imagine that you're the boss of 9 people. How would you divide them into 3 subgroups of 2, 3 and 4 people? That's exactly the way my group function works.
If you still can't understand the logic after a while then I'll update my answer and explain the group function in detail. Best of luck.
BTW I just realized that for this problem you don't really need amb. You may simply use forEach instead. The resulting code would be faster because of the absence of try-catch blocks:
function group(options, divisions) {
var subgroup = [], groups = [], n = 0;
var indices = getIndices(options.length);
var division = divisions.shift(), remaining = divisions.length;
indices.forEach(select);
return groups;
function select(index) {
subgroup.push(index);
if (++n < division) indices.slice(index + 1).forEach(select);
else {
var subgroups = pick(options, subgroup);
if (remaining) {
var children = group(subgroups.pop(), divisions.slice());
var length = children.length;
for (var i = 0; i < length; i++)
groups.push(subgroups.concat(children[i]));
} else groups.push(subgroups);
}
subgroup.pop();
n--;
}
}
Since we don't use amb anymore the execution time of the program has decreased tenfold. See the result for yourself: http://jsperf.com/amb-vs-foreach
Also I've finally created a demo fiddle of the above program: http://jsfiddle.net/Ug6Pb/
i am sure there are faster formulas, but i was never that great at math, and this seems to work if i understand the problem correctly:
function combo(r, ops){
function unq(r){return r.filter(function(a,b,c){return !this[a] && (this[a]=1);},{}); }
var combos={}, pairs=[];
r.forEach(function(a,b,c){
combos[a]=r.filter(function not(a){return a!=this && !combos[a]}, a);
});
Object.keys(combos).forEach(function(k){
combos[k].forEach(function(a){
pairs.push([k, a]+'');
});
});
return unq(unq(
pairs.map(function(a){
return unq(a.split(",")).sort();
})).map(function(a){
return a.length==ops && a;
}).filter(Boolean))
.sort();
}//end combo
var r="aldo,beat,carla,david,evi,flip,gary,hugo,ida".split(",");
// find groups of different lengths:
combo(r, 2) // 2 folks == 36 combos
combo( combo(r, 2), 3) // 3 folks == 84 combos
combo( combo( combo(r, 2), 3), 4) // 4 folks == 126 combos
i didn't bother to recursive-ize the function since you only need to go 4-in and a lispy invocation works, but if i had to go further, i'd want to write one additional outer wrapper to sandwich the calls...
the core implementation of the backtracking algorithm is simple (see function doBacktrack below). Usually the complexity is in the details of the specific backtracking problem
the following is my implementation of a backtracking algorithm for your problem. it is based on the backtracking algorithm description in Steven Skiena's Algorithm Design Manual (or what I remember of it).
I've not added pruning to the algorithm (because it's already taken me longer than I thought it would do :) ) but if you want to improve its performance just add a reasonable implementation for the function done() to prevent continuing with the processing of candidates that can be inferred to not be viable solutions
function backtrack() {
var people =
['aldo','beat','carla','david','evi','flip','gary','hugo','ida'];
var initial_state =
[[], [], []];
var groups =
[2, 3, 4];
var data =
{groups: groups, people: people, people_idx_for_name: {}};
people.forEach(function(e, i) {
data['people_idx_for_name'][e] = i;
});
var solutions = [];
doBacktrack(initial_state, solutions, data);
return solutions;
}
function doBacktrack(candidate, solutions, data) {
// console.log('processing: ' + candidate);
if (isSolution(candidate, data)) {
processSolution(candidate, solutions);
}
if (done(candidate, solutions, data)) {
return;
}
var new_candidates = calculateNewCandidates(candidate, data);
for (var i=0; i<new_candidates.length; i++) {
doBacktrack(new_candidates[i], solutions, data);
}
}
function calculateNewCandidates(candidate, data) {
var groups = data['groups'];
var i = 0;
while (i<groups.length && candidate[i].length == groups[i]) { i++; }
if (i < groups.length) {
//determine list of not yet selected people
var not_yet_selected = determineNotYetSelectedPeople(candidate, data, i);
var results = [];
for (var j=0; j<not_yet_selected.length; j++) {
var candidate_copy = candidate.slice(0);
for (var k=0; k<candidate_copy.length; k++) {
candidate_copy[k] = candidate_copy[k].slice(0);
}
candidate_copy[i].push(not_yet_selected[j])
results.push(candidate_copy);
}
return results;
} else {
return [];
}
}
function determineNotYetSelectedPeople(candidate, data, group) {
var people = data['people'];
var people_idx_for_name = data['people_idx_for_name'];
var selected_people = {};
var results = [];
var max = -Number.MAX_VALUE;
candidate.forEach(function(candidate_group, i) {
candidate_group.forEach(function(already_selected_person_name) {
var already_selected_person_idx = people_idx_for_name[already_selected_person_name];
if (max < already_selected_person_idx && i==group) { max = already_selected_person_idx; }
selected_people[already_selected_person_name] = true;
});
});
for (var i=0; i<people.length; i++) {
if (!selected_people[people[i]] && i > max) { results.push(people[i]); }
}
return results;
}
function isSolution(candidate, data) {
var groups = data['groups'];
for (var i=0; i<groups.length; i++) {
if (candidate[i].length != groups[i]) {return false;}
}
return true;
}
function processSolution(candidate, solutions) {
var solution = [];
candidate.forEach(function(e) {
var l = [];
solution.push(l);
e.forEach(function(f) {
l.push(f);
});
});
solutions.push(solution);
}
//use this to improve performance with prunning if possible
function done() {
return false;
}
var solutions = backtrack();
console.log(solutions);
console.log(solutions.length);