Nested recursion, find all possible piece counts - javascript

Given an example input:
[
{"id":1,"currentBlack":1,"currentWhite":0,"max":1},
{"id":2,"currentBlack":0,"currentWhite":1,"max":1},
]
Output all possible states of the input where currentBlack and currentWhite can have a value anywhere in the range from their initial value up to the maximum value.
Correct output for this example:
[
[
{"id":1,"currentBlack":1,"currentWhite":0,"max":1},
{"id":2,"currentBlack":0,"currentWhite":1,"max":1},
],
[
{"id":1,"currentBlack":1,"currentWhite":1,"max":1},
{"id":2,"currentBlack":0,"currentWhite":1,"max":1},
],
[
{"id":1,"currentBlack":1,"currentWhite":1,"max":1},
{"id":2,"currentBlack":1,"currentWhite":1,"max":1},
],
[
{"id":1,"currentBlack":1,"currentWhite":0,"max":1},
{"id":2,"currentBlack":1,"currentWhite":1,"max":1},
]
]
The real input will have max anywhere between 1 and 8 and there will be far more objects within the input array. My attempt is below (heavily commented):
function allPossibleCounts(pieceCounts) {//pieceCounts is the input
var collection = []; //used to collect all possible values
recursiveCalls(pieceCounts); //runs recursive function
return collection; //returns result
function recursiveCalls(pieceCounts) {
//if pieceCounts is already in collection then return, not yet implemented so duplicates are currently possible
collection.push(pieceCounts);//inputs a potential value
console.log(JSON.stringify(pieceCounts));//this is successfully logs the correct values
console.log(JSON.stringify(collection));//collection isn't correct, all values at the top of the array are copies of each other
for (let n in pieceCounts) {//pieceCounts should be the same at the start of each loop within each scope, aka pieceCounts should be the same at the end of this loop as it is at the start
subBlackCall(pieceCounts);
function subBlackCall(pieceCounts) {
if (pieceCounts[n].currentBlack < pieceCounts[n].max) {
pieceCounts[n].currentBlack++;//increment
recursiveCalls(pieceCounts);
subBlackCall(pieceCounts);//essentially you're either adding +1 or +2 or +3 ect all the way up to max and calling recursiveCalls() off of each of those incremented values
pieceCounts[n].currentBlack--;//decrement to return pieceCounts to how it was at the start of this function
}
}
subWhiteCall(pieceCounts);
function subWhiteCall(pieceCounts) {
if (pieceCounts[n].currentWhite < pieceCounts[n].max) {
pieceCounts[n].currentWhite++;
recursiveCalls(pieceCounts);
subWhiteCall(pieceCounts);
pieceCounts[n].currentWhite--;
}
}
}
}
}
But currently my attempt outputs as this ungodly mess of copied arrays
[[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}]]
Edit: working code: https://pastebin.com/qqFTppsY

The pieceCounts[n] always reference to the one object. You should recreate the pieceCount for saving in to the collection as different object. For example, you can add
pieceCounts = JSON.parse(JSON.stringify(pieceCounts)); // just clone
at the start of recursiveCalls function.

To avoid conversion to JSON and back, I would suggest using Object.assign to perform a deeper copy in combination with map on the array:
function allPossibleCounts(pieceCounts) {
var result = [],
current = deeperCopy(pieceCounts);
function deeperCopy(arr) {
return arr.map( row => Object.assign({}, row) );
}
function recurse(depth) {
// depth: indication of which value will be incremented. Each "row" has
// 2 items (black/white), so when depth is even, it refers to black, when
// odd to white. Divide by two for getting the "row" in which the increment
// should happen.
var idx = depth >> 1, // divide by 2 for getting row index
prop = depth % 2 ? 'currentWhite' : 'currentBlack', // odd/even
row = pieceCounts[idx];
if (!row) { // at the end of the array
// Take a copy of this variation and add it to the results
result.push(deeperCopy(current));
return; // backtrack for other variations
}
for (var value = row[prop]; value <= row.max; value++) {
// Set the value of this property
current[idx][prop] = value;
// Collect all variations that can be made by varying any of
// the property values that follow after this one
recurse(depth+1);
// Repeat for all higher values this property can get.
}
}
recurse(0); // Start the process
return result;
}
// Sample input
var pieceCounts = [
{"id":1,"currentBlack":1,"currentWhite":0,"max":1},
{"id":2,"currentBlack":0,"currentWhite":1,"max":1},
];
// Get results
var result = allPossibleCounts(pieceCounts);
// Output
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The idea is to use recursion: imagine the problem can be solved for all variations that can be made for all properties, except the first one. Produce those, and then change the first property value to the next possible value. Repeat again the production of all variations, etc. The combination of all those results together will be the solution for when the first property value should also be varied.
This is an ideal situation for recursion. The recursion stops when there are no more property values remaining: in that case there is only one solution; the one with all the values set as they are. It can be added to the list of results.
The properties can be enumerated like this:
row currentBlack currentWhite
---------------------------------
0 0 1
1 2 3
2 4 5
3 6 7
...
n 2n-2 2n-1
We could call that number depth, and increase it at every step of deeper recursion. Given a depth, the property to vary is defined by:
depth is even => currentBlack
depth is odd => currentWhite
row number = depth / 2 (ignoring the remainder)

Related

Binary insertion, second half of array isn't being called recursively?

I'm trying to insert number into a sorted array of unique numbers such that the new array is also sorted. My intended operation is as follows:
divide array in half until each half is either 1 element or 2 elements
if 2 elements: if number is between the 2 elements, return [element1, number, element2], otherwise return [element1,element2]
if 1 element: if number is equal to element, return [number, element], otherwise return [element]
My understanding of how a recursive solution should work is that this breaks the array down until number can either be appended to its duplicate or sandwiched between a lower and upper bound, and then new array is assembled going back up the call stack. (and yes I know this doesn't work if number is lower than the lowest array element or higher than the highest)
My code:
function insertNum(num,sortedSet){
var returnValue;
console.log(`sortedSet:${sortedSet}`);
if (sortedSet.length==0){
return [num];
}
if(sortedSet.length>1){
console.log('length greater than 1');
output=splitInHalf(sortedSet);
if(num>output.first[output.first.length-1]&&num<output.second[0]){
console.log(`${num} belongs between`)
return output.first.concat([num]).concat(output.second);
}
else{
return insertNum(num,output.first).concat(insertNum(num,output.second));
}
}
else{
if(num==sortedSet[0]){
returnValue=[num,sortedSet[0]]
console.log(`returning append:${returnValue}`);
return returnValue;
}
else{
return sortedSet;
}
}
}
function splitInHalf(sortedSet){
halflength_floor=Math.floor(sortedSet.length/2);
halflength_ceil=Math.ceil(sortedSet.length/2);
console.log(`halflength_floor:${halflength_floor}`);
console.log(`halflength_ceil:${halflength_ceil}`);
first=sortedSet.slice(0,halflength_floor);
second=sortedSet.slice(halflength_floor);
console.log(`first:${first}`);
console.log(`second:${second}`);
var output=[];
output.first=first;
output.second=second;
return output;
}
console.log(insertNum(7,[2,3,5,6,8]));
I'm getting the output:
sortedSet:2,3,5,6,8
length greater than 1
halflength_floor:2
halflength_ceil:3
first:2,3
second:5,6,8
sortedSet:2,3
length greater than 1
halflength_floor:1
halflength_ceil:1
first:2
second:3
sortedSet:2
sortedSet:3
sortedSet:3
[ 2, 3, 3 ]
Because we never see the output of:
sortedSet:5
sortedSet:6,8
That makes me think that in the initial call of:
insertNum(num,first).concat(insertNum(num,second))
which should have the arguments
insertNum(7,[2,3]).concat(insertNum(7,[5,6,8]))
only the first insertNum() call is being made, and the other insertNum() call, wrapped in concat(), never occurs. Why not?
Please change code like below.
From
output=splitInHalf(sortedSet);
To
var output=splitInHalf(sortedSet);

JS pushing arrays to array (concat) in apps script

Note this is not a duplicate of How to extend an existing JavaScript array with another array, without creating a new array? because I'm looking to have a nested array, not to simply extend an array with another array to result into 1 array. Please read the question properly before you mark this as duplicate.
I'm looping through rows in a (Google) sheet to collect values, and would like to add each row as array to an array, which should result in an output like this (simplified example to illustrate):
array_main = [[row1_cell1,row1_cell2,row1_cell3], [row2_cell1,row2_cell2,row2_cell3], ...]
I first tried this with .push, which adds the values, but not as array:
accounts_last_row = 10
accounts_array = []
for (var i = 0; i < accounts_last_row; ++i) {
if ((accounts_range[i][1] == 'test') {
accounts_array.push([ [accounts_range[i][1]],[accounts_range[i][2]] ])
}
}
I'm aware similar questions have been asked, but most of them simply recommend using .concat to merge 2 arrays. I tried this as well but it doesn't add anything to the array:
...
if ((accounts_range[i][1] == 'test') {
accounts_array.concat( [accounts_range[i][1]],[accounts_range[i][2]] )
}
...
What am I missing? Thanks in advance.
You almost had it, inner arrays are simple ones, you had too many brackets.
Try like this:
accounts_array.push( [accounts_range[i][1],accounts_range[i][2]] );
the code above will work to add rows.
If you want to add data as a single column the you will have to change the brackets like this:
accounts_array.push( [accounts_range[i][1]],[accounts_range[i][2]] );
This type of operation can be done neatly with Array#filter and Array#push and apply:
const results = [];
const colIndexToTest = /** 0, 1, etc. */;
const requiredValue = /** something */;
SpreadsheetApp.getActive().getSheets().forEach(
function (sheet, sheetIndex) {
var matchedRows = sheet.getDataRange().getValues().filter(
function (row, rowIndex) {
// Return true if this is a row we want.
return row[colIndexToTest] === requiredValue;
});
if (matchedRows.length)
Array.prototype.push.apply(results, matchedRows);
});
// Use Stackdriver to view complex objects properly.
console.log({message: "matching rows from all sheets", results: results});
The above searches the given column of all rows on all sheets for the given value, and collects it into a 2d array. If all rows are the same number of columns, this array would be directly serializable with Range#setValues.
This code could have used map instead of forEach and the push.apply, but that would place empty or undefined elements for sheet indexes that had no matches.
I'm assuming if account-range[i][1] is 'test' copy the entire row to accounts_array. Drop the second index.
accounts_last_row = 10
accounts_array = []
for (var i = 0; i < accounts_last_row; ++i) {
if ((accounts_range[i][1] == 'test') {
accounts_array.push(accounts_range[i])
}
}

JS check if an array is embedded in another array

I have a two dimensional JS array in which some rows are useless and needs to be deleted;
In particular I need to delete the rows that are embedded in other rows (by saying that row B is embedded in row A I mean not just that A is a superset of B, but that A contains all the elements in B, in sequence and in the same order)
EX. I have:
var matrix = [
["User","Shop","Offer","Product","File"],
["User","Shop","File"],
["User","Shop","Map"],
["User","Shop","Promotion"],
["User","Shop","Offer","Product","Reservation"],
["User","Group","Accesslevel"],
["User","Group"],
["User","Reservation"],
["User","Shop"],
["User","Shop","Offer","Product","Markers"]
];
In this example the second row (["User","Shop","File"]) should NOT be deleted (all its elements are in the first row, but not consecutive);
Row 7 (["User","Group"]) should be deleted because is embedded in the 6th (["User","Group","Accesslevel"]) and also row 9 (["User","Shop"]) because is embedded in many others..
I'm looking for a possible efficient algorithm to check if an array is embedded in another one;
I will use this in nodejs.
This should do the trick.
// Is row2 "embedded" in row1?
function embedded(row1, row2) {
return row2.length < row1.length &&
row2.every(function(elt, i) { return elt === row1[i]; });
}
//filter out rows in matrix which are "embedded" in other rows
matrix.filter(function(row) {
return !matrix.some(function(row2) { return embedded(row2, row); });
});
here is a solution which I used few days ago for the same purpose but on the client side, This would also work on node server.
http://jsfiddle.net/8wLst3qr/
I have changed the program according to your needs,
What I have done here is,
some initialisation,
var matrix = [
["User","Shop","Offer","Product","File"],
["User","Shop","File"],
["User","Shop","Map"],
["User","Shop","Promotion"],
["User","Shop","Offer","Product","Reservation"],
["User","Group","Accesslevel"],
["User","Group"],
["User","Reservation"],
["User","Shop"],
["User","Shop","Offer","Product","Markers"]
];
var tempArr=matrix;
here are the steps
convert the array of arrays to an array of strings-(this is because you need to clear the redundant data only if it is in the same order), code as follows.
var json=[];
for(k=0;k<tempArr.length;k++)
{
json[k]=tempArr[k].toString();
}
and then match the index of each string in other strings in the array, if matches, check the string whose length is less and delete
it.
for(k=0;k<json.length;k++)
{
for(l=0;l<json.length;l++)
{
console.log("val l="+json[l]+"val k="+json[k]+"value="+json[l].indexOf(json[k]));
console.log("k="+k+";l="+l);
if(k!=l && (json[k].indexOf(json[l]) !=-1))
{
console.log("removing");
console.log("removing");
if(json[k].length>json[l].length)
{
json.splice(l, 1);
}
else
{
json.splice(k, 1);
}
}
}
}
hope it helps,
edit-sorry you would require to check the console.log for the output on fiddle

How to test random functionality in JavaScript/Jasmine?

I have a short function which gets an array and shuffles the values "around".
array_name.sort(function() { return 0.5 - Math.random() });
How can I test that I get a different array then I had before?
Of course I can mock the array, and then test if
array_name[2] != array_after[2];
But, as its random, it could happen that sometimes this position is equal and sometimes not.
What is a good approach to this?
So, its about testing the randomness
I have an approach, and of course my problem:
it('different option order when randomization is true', function() {
var array = // getting from json.file here
var array_after = question.shuffle(array);
expect(array[4].text.localeCompare(array_after[1].text)).toBe(0);
});
Of course, i cannot say now that this is always different. Because i run my tests several times, and sometimes they are the same, and sometimes not...
What you're after is a seeded random number generator. In your tests you can control the seed and verify that different seeds consistently generates different numbers.
See http://indiegamr.com/generate-repeatable-random-numbers-in-js/ for more information.
Related questions:
Seeding the random number generator in Javascript
Seedable Random number generator in JavaScript
If you need to compare if the before / after arrays are indeed different, do something like this:
function arraysAreEqual(before, after){
if(before.length !== after.length){
return false;
}
for(var i = 0; i < before.length; i++){
if(before[i] !== after[i]){
return false;
}
}
return true;
}
Depending on the contents of the arrays, you may need to modify the equality check in the for loop to account for nested arrays / objects.
Assuming:
var myArray = [
{text: 'foo', value: 1},
{text: 'bar', value: 2}
]
You can use this, for example:
if(before[i].text !== after[i].text ||
before[i].value !== after[i].value){
return false;
}
With this function, you should be able to use:
expect(arraysAreEqual(array, array_after)).toBe(false);
However, the test case will fail in the (improbable, but possible) case the randomizer returns the same array as the input.

javascript compare objects in array

I have a grid of pictures on a page. And, periodically, I want to randomly swap one out for one of 50 I have in an array of Objects- but only if they're not already in the grid. This last part is what my code is failing to do.
I first get all 50 items, and put them into an allmedia array:
// initialize
var allmedia = getAllMedia();
var imagesInGrid = [];
When I place the items in the grid, I add to an array of grid items:
imagesInGrid.push(allmedia [i]); // while looping to fill DOM grid
Then, every 8 seconds I run a getRandomImage() routine that randomly gets an image from the allmedia array and then tests it to see if it's not already in the DOM.
function getRandomImageNotInGrid(){
var randomNumber = Math.floor(Math.random() * allmedia.length);
if (!isInArray(allmedia[randomNumber], imagesInGrid)) {
return allmedia[randomNumber];
} else {
return getRandomImageNotInGrid();
}
}
function isInArray(item, arr) {
if(arr[0]===undefined) return false;
for (var i=arr.length;i--;) {
if (arr[i]===item) {
return true;
}
}
return false;
}
But when I step through the code the (arr[i]===item) test is failing. I can see that the two objects are exactly the same, but the === isn't seeing this as true.
Is this a ByReference / ByValue issue? What am I doing wrong?
console.log:
arr[i]===item
false
arr[i]==item
false
typeof item
"object"
typeof arr[i]
"object"
Edit::
In the output below, I don't understand why arr[0] is not the same as 'item'. I use the exact same object that I put into allmedia as I do when I place the item into the page and, accordingly update imagesInGrid.
console.dir(arr[0]);
Object
caption: Object
comments: Object
created_time: "1305132396"
filter: "Poprocket"
id: "69204668"
images: Object
likes: Object
link: "http://instagr.am/p/EH_q8/"
location: Object
tags: Array[2]
type: "image"
user: Object
__proto__: Object
console.dir(item);
Object
caption: Object
comments: Object
created_time: "1305132396"
filter: "Poprocket"
id: "69204668"
images: Object
likes: Object
link: "http://instagr.am/p/EH_q8/"
location: Object
tags: Array[2]
type: "image"
user: Object
__proto__: Object
Instead of randomly selecting one from allmedia, can you instead remove one from allmedia?
var randomNumber = Math.floor(Math.random() * allmedia.length);
imagesInGrid.push(allmedia.splice(randomNumber,1));
When you use ===, you are comparing the objects by reference. What type of objects are you using to compare? Are you sure they are the same object reference? For example, if you are using strings, you may want to use == instead of ===. If you are using DOM objects, you will want to compare the source, as Alxandr suggested.
Also, your for loop syntax appears to be wrong:
for (var i=arr.length;i--;)
Should be:
for (var i=arr.length - 1; i >= 0; i--)
...if I'm not mistaken.
You don't show any code for how the image "objects" are created or how they are added and removed from the DOM. If you are creating image elements and storing references in an array, then replacing the DOM element with the one from the array, then the comparision should work.
However, if your image object is a bundle of data that is used to create an image element for display, then they will never be equal to each other. Every javascript object is unique, it will only ever be equal to itself.
But I suspect the simplest solution is that suggested by ic3b3rg - remove the object from allMedia when you select it, that way you don't have to test if it's already in imagesInGrid because you can only select each image once. If you want the display to go on forever, then when allmedia is empty, put all the images from imagesInGrid back into it and start again.
Edit
Your problem is the for loop. When you set :
for (var i=arr.length;i--;) {
// On first iteration, i=arr.length
// so arr[i] is undefined
}
i is not decremented until after the first loop, so set i=arr.length-1. It is more common to use while with a decrementing counter:
var i = arr.length;
while (i--) {
// i is decremented after the test
}

Categories