I want to group together adjacent (vertically and horizontally) characters in an array, in this case all the adjacent "*" belong to one group, see below. And then I want to be able to count how many groups of "*" there are, in this case the answer is 3.
var x = ["...***....",
"..*****...",
"...***....",
"........*.",
".......***",
"..*.....*.",
".***......"];
The code:
function compareRows(){
var totalGroups = 0;
for (i = 0; i < x.length; i++) {
var array = x[i];
for (j = 0; j < array.length; j++) {
var row = array[j];
for (k = 0; k < row.length; k++) {
var char1 = row[k];
var nextRow = j+1;
var char2 = row[nextRow];
if(char1== "*"){
if(char1 != char2) {
totalGroups+=1;
}
} else {
//console.log("Keep searching..");
}
}
}
} console.log(totalGroups);
}
compareRows();
So basically for each row I'm searching for the character "*" and when it's found, if the character at the same index on the row below isn't a "*", then one group is found. However, at the moment totalGroups is 20, the total amout of "*" found in the whole array. I feel a bit stuck and don't know how to proceed.
I believe you'll need to walk the grid at any point you find an asterisk and keep track of cells you have already visited. You can do so with a visited array to avoid walking the same cell twice and a recursive walk function. Here's what I was able to come up with:
const grid = [
"...***....",
"..*****...",
"...***....",
"........*.",
".......***",
"..*.....*.",
".***......"
];
function getGroups(grid) {
// split the grid into a 2d array of objects (cells)
const cellGrid = grid.map((s, y) => s.split('').map((value, x) => ({value, x, y})));
const height = cellGrid.length;
const width = cellGrid[0].length;
const visited = []; // keep track of visited cells
const groups = [];
let currentGroup = [];
// walk each cell left-to-right top-to-bottom
for(let y = 0; y < height; y++) {
for(let x = 0; x < width; x++) {
walkFromCell(x, y, true);
}
}
return groups;
function walkFromCell(x, y, groupStart) {
const cell = getCell(x, y);
// ignore visited and non-group cells
if(!cell || cell.value !== '*' || visited.includes(cell)) return;
currentGroup.push(cell);
visited.push(cell);
walkFromCell(x + 1, y, false);
walkFromCell(x - 1, y, false);
walkFromCell(x, y + 1, false);
// groupStart is only true for the first cell in a group
if(groupStart) {
groups.push(currentGroup);
currentGroup = [];
}
}
function getCell(x, y) {
return cellGrid[y] ? cellGrid[y][x] : null;
}
}
const groups = getGroups(grid);
const groupCount = groups.length;
console.log(`Count = ${groupCount}`);
console.log('Groups =', groups);
Related
I have several JSON files with fake users.
They look like this:
{"name":"Beau Evans","personalityType":"yellow","skills":"JavaScript"}.
They are all pushed into an array called 'people'.
Now what I wish to do is to sort them all into subgroups into const groups = []; array.
The requirement for the groups is that they all need to share the same personalityType. (blue, yellow, red, or green) and they all need to differ in skills.
So one with HTML, JavaScript, or CSS.
And this of course resulted in some groups being of just 1 user. So I wanted to extract those back into a remainingPeople array, and then add the remainingPeople EVENLY to groups that meet the requirements so maybe 1 yellowCSS, 1 yellowJavaScript, 1 yellowHTML. It didn't matter if it turned out to be maybe 2 yellowHTML.
This is the code that I've written:
Which resulted into 2 problems.
The while loop on the bottom never stopped looping and crashed the site depending on how many JSON files with fake users I had!
And problem 2: Some users got erased out of existence so I ended up with fewer people in the groups' array than I had in the people array.
Why is this?
let homogenousGroups = [];
for (let i = 0; i < Math.floor(people.length / 3); i++) {
homogenousGroups.push([{
group: i
}]);
}
let groupsCreator = () => {
// this is to add people that has the same personality and different skills to the same group
let addPersonSameColor = (group) => {
people.sort(() => Math.random() - 0.5);
for (let j = 0; j < people.length; j++) {
let person = people[j];
let hasPersonality = group.some(groupPerson => groupPerson.personalityType === person.personalityType);
let hasSkills = group.some(groupPerson => groupPerson.skills === person.skills);
if (group.length === 0) {
group.push(person);
people.splice(j, 1);
j--;
} else if (hasPersonality && !hasSkills) {
group.push(person);
people.splice(j, 1);
j--;
}
}
};
// this runs the function above for each person in
for (let x = 0; x < homogenousGroups.length; x++) {
addPersonSameColor(homogenousGroups[x]);
}
// this will remove any object that does not have the name property
for (let x = 0; x < homogenousGroups.length; x++) {
for (let y = 0; y < homogenousGroups[x].length; y++) {
if (!homogenousGroups[x][y].name) {
homogenousGroups[x].splice(y, 1);
y--;
}
}
}
// this is to push any group that has less than 3 people back to the people array
for (let x = 0; x < homogenousGroups.length; x++) {
if (homogenousGroups[x].length <= 2) {
let removed = homogenousGroups[x].splice(0, homogenousGroups[x].length);
people2.push(...removed);
}
for (let x = 0; x < people.length; x++) {
if (people[x].group) {
people.splice(x, 1);
x--;
}
}
}
// this is to remove any empty arrays in my group array
for (let x = 0; x < homogenousGroups.length; x++) {
if (homogenousGroups[x].length === 0) {
homogenousGroups.splice(x, 1);
x--;
}
}
while (people.length > 1) {
for (let i = 0; i < homogenousGroups.length; i++) {
for (let j = 0; j < people.length; j++) {
if (people[j].personalityType === homogenousGroups[i][0].personalityType) {
homogenousGroups[i].push(people[j]);
people.splice(j, 1);
j--;
break;
}
}
}
}
};
For some reason, the manipulated doubleArray below is not shown in the console. Any variables that I declare after the for loop won't show to the console on both cases. Consider that in the first algorithm, there is only one for loop with x being incremented everytime. Whereas, in the second algorithm, it's a nested for loop. Can someone help me fix my error in both algorithms?
First Algorithm:
var isDuplicate = function() {
var helloWorld = [1,2,3,4,3];
var doubleValue = [];
var x = 0;
for (i = 0; i < helloWorld.length; i++) {
x = x + 1;
if (helloWorld[i] === helloWorld[x] && i !== x) {
doubleValue.push(helloWorld[i])
console.log(helloWorld[i]);
} else {
continue;
}
}
console.log(doubleValue);
};
The second Algorithm:
var isDuplicate = function() {
var helloWorld = [1,2,3,4,3];
var doubleValue = [];
for (i = 0; i < helloWorld.length; i++) {
for (x = 1; x < helloWorld.length; i++) {
if (helloWorld[i] === helloWorld[x] && i !== x) {
doubleValue.push(helloWorld[x]);
}
}
}
console.log(doubleValue);
};
In first algorithm, you are only checking if the number at current index is equal to the number at the next index, meaning you are only comparing numbers at consecutive indexes. First algorithm will work only if you have duplicate numbers on consecutive indexes.
In second algorithm, you are incrementing i in both loops, increment x in nested loop, change x = 1 to x = i + 1 and your error will be fixed.
Here's the fixed second code snippet
var isDuplicate = function() {
var helloWorld = [1,2,3,4,3, 1, 2];
var doubleValue = [];
for (let i = 0; i < helloWorld.length; i++) {
for (let x = i + 1; x < helloWorld.length; x++) {
if (helloWorld[i] === helloWorld[x] && i !== x) {
doubleValue.push(helloWorld[x]);
}
}
}
console.log(doubleValue);
};
isDuplicate();
Heres's another way to find the duplicates in an array, using an object. Loop over the array, if current number is present as key in the object, push the current number in the doubleValue array otherwise add the current number as key-value pair in the object.
const isDuplicate = function() {
const helloWorld = [1,2,3,4,3, 1, 2];
const doubleValue = [];
const obj = {};
helloWorld.forEach(n => obj[n] ? doubleValue.push(n): obj[n] = n);
console.log(doubleValue);
};
isDuplicate();
Not entirely sure what you are trying to do. If you are only looking for a method to remove duplicates you can do the following:
const hello_world = [1, 2, 2, 3, 4, 5, 5];
const duplicates_removed = Array.from(new Set(hello_world));
A set is a data object that only allows you to store unique values so, when converting an array to a set it will automatically remove all duplicate values. In the example above we are creating a set from hello_world and converting it back to an array.
If you are looking for a function that can identify all the duplicates in an array you can try the following:
const hello_world = [1, 2, 2, 3, 4, 5, 5];
const duplicates_found = hello_world.filter((item, index) => hello_world.indexOf(item) != index);
The main problem by finding duplicates is to have nested loop to compare each element of the array with any other element exept the element at the same position.
By using the second algorithm, you can iterate from the known position to reduce the iteration count.
var isDuplicate = function(array) {
var doubleValue = [];
outer: for (var i = 0; i < array.length - 1; i++) { // add label,
// declare variable i
// no need to check last element
for (var j = i + 1; j < array.length; j++) { // start from i + 1,
// increment j
if (array[i] === array[j]) { // compare values, not indices
doubleValue.push(array[i]);
continue outer; // prevent looping
}
}
}
return doubleValue;
};
console.log(isDuplicate([1, 2, 3, 4, 3])); // [3]
You could take an object for storing seen values and use a single loop for getting duplicate values.
const
getDuplicates = array => {
const
seen = {}
duplicates = [];
for (let value of array) {
if (seen[value]) duplicates.push(value);
else seen[value] = true;
}
return duplicates;
};
console.log(getDuplicates([1, 2, 3, 4, 3])); // [3]
Your first algorithm doesn't work because it only looks for duplicates next to each other. You can fix it by first sorting the array, then finding the duplicates. You can also remove the x and replace it by ++i in the loop.
var isDuplicate = function() {
var helloWorld = [1,2,3,4,3,6];
var doubleValue = [];
helloWorld = helloWorld.sort((a, b) => { return a - b });
for (i = 0; i < helloWorld.length; i++) {
if (helloWorld[i] === helloWorld[++i]) {
doubleValue.push(helloWorld[i])
console.log(helloWorld[i]);
} else {
continue;
}
}
console.log(doubleValue);
};
isDuplicate();
For the second algorithm loop, you probably meant x++ instead of i++ in the second loop. This would fix the problem.
var isDuplicate = function() {
var helloWorld = [1,2,3,4,3,4];
var doubleValue = [];
for (i = 0; i < helloWorld.length; i++) {
for (x = i + 1; x < helloWorld.length; x++) {
if (helloWorld[i] === helloWorld[x]) {
doubleValue.push(helloWorld[x]);
}
}
}
console.log(doubleValue);
};
isDuplicate()
The first algorithm can't be fixed, it can only detect consecutive duplicates,
in the second algorithm you increment i in both loops.
To avoid the duplicates beeing listed too often, you should start the second loop with i + 1
i will go straight to the point.. So i have created a multidimentional array and i have it like this:
var arr = [];
for (var i = 0; i < 10; i++) {
var sArr = [];
for (var a = 0; a < 10; a++) {
cell = Object.create(cellObj)
sArr.push(cell);
}
arr.push(sArr);
}
And about the cellObj, i created it like this:
var cellObj = {
state: 0,
img: ""
};
Now that i have everything setup, i generate two images in the multidimentional array like this:
//the array imgs contain two images
for (var l = 0; l < imgs.length; l++) {
var x = Math.floor(Math.random() * arr.length);
var y = Math.floor(Math.random() * arr[x].length);
//here is a test to make sure that the two images are in two separate cells
if (arr[x][y].state === 0) {
arr[x][y].state = 3;
arr[x][y].img = imgs[l];
}
else
{
l--;
}
}
the two images can be on the same row or column with an empty cell between them but i dont want them to be side by side horizontally or vertically.
I hope a nice day to everyone, thank's.
The easiest way to achieve what you're looking for is to eliminate those options up front. In the method below, I'm creating a flat array that contains all positions in your array (I've renamed arr to arrayOfImages for clarity) and naming it coordLookup. Once you place an image, the positions next to it vertically and horizontally are removed from coordLookup in the filter statement. I've increased the number of test images to show that it's maintaining that spacing - just be aware that if you try to place more images than the array has room to accomodate, it will start to fail, so be sure to add error handling for that case.
(To see this in action, open your console and run the code snippet, it should present the completed array as a table.)
var cellObj = {
state: 0,
img: ""
};
var arrayOfImages = [];
for (var i = 0; i < 10; i++) {
var sArr = [];
for (var a = 0; a < 10; a++) {
cell = Object.create(cellObj)
sArr.push(cell);
}
arrayOfImages.push(sArr);
}
var coordLookup = [], i = 10, j = 10 //create coordinate lookup
while(--i > -1){
j = 10
while(--j > -1){
coordLookup.push([i,j])
}
}
var imgs = ['image1.jpg', 'image2.jpg', 'image1.jpg', 'image2.jpg', 'image1.jpg', 'image2.jpg', 'image1.jpg', 'image2.jpg', 'image1.jpg', 'image2.jpg', 'image1.jpg', 'image2.jpg']
for (var l = 0; l < imgs.length; l++) {
let pairIndex = Math.floor(Math.random() * coordLookup.length),
pair = coordLookup[pairIndex],
x = pair[0], y = pair[1]
//insert image
arrayOfImages[x][y].state = 3;
arrayOfImages[x][y].img = imgs[l]
//disable horizontal and vertical neighbor cells
const prevX = x - 1, nextX = x + 1, prevY = y - 1, nextY = y + 1
coordLookup = coordLookup.filter(coordpair => {
const _x = coordpair[0], _y = coordpair[1],
isMatch = (_y == y && _x >= prevX && _x <= nextX) || (_x == x && _y >= prevY && _y <= nextY)
return !isMatch
})
}
//display results
console.table(arrayOfImages.map(row => row.map(cell => JSON.stringify(cell))))
I am trying to count the number of permutations that do not contain consecutive letters. My code passes tests like 'aabb' (answer:8) and 'aab' (answer:2), but does not pass cases like 'abcdefa'(my answer: 2520; correct answer: 3600). Here's my code:
function permAlone(str) {
var totalPerm = 1;
var result = [];
//assign the first letter
for (var i = 0; i < str.length; i++) {
var firstNum = str[i];
var perm = firstNum;
//create an array from the remaining letters in the string
for (var k = 0; k < str.length; k++) {
if (k !== i) {
perm += str[k];
}
}
//Permutations: get the last letter and change its position by -1;
//Keep changing that letters's position by -1 until its index is 1;
//Then, take the last letter again and do the same thing;
//Keep doing the same thing until the total num of permutations of the number of items in the string -1 is reached (factorial of the number of items in the string -1 because we already established what the very first letter must be).
var permArr = perm.split("");
var j = permArr.length - 1;
var patternsLeft = totalNumPatterns(perm.length - 1);
while (patternsLeft > 0) {
var to = j - 1;
var subRes = permArr.move(j, to);
console.log(subRes);
if (noDoubleLettersPresent(subRes)) {
result.push([subRes]);
}
j -= 1;
if (j == 1) {
j = perm.length - 1;
}
patternsLeft--;
}
}
return result.length;
}
Array.prototype.move = function(from, to) {
this.splice(to, 0, (this.splice(from, 1))[0]);
return this.join("");
};
function totalNumPatterns(numOfRotatingItems) {
var iter = 1;
for (var q = numOfRotatingItems; q > 1; q--) {
iter *= q;
}
return iter;
}
function noDoubleLettersPresent(str) {
if (str.match(/(.)\1/g)) {
return false;
} else {
return true;
}
}
permAlone('abcdefa');
I think the problem was your permutation algorithm; where did you get that from? I tried it with a different one (after Filip Nguyen, adapted from his answer to this question) and it returns 3600 as expected.
function permAlone(str) {
var result = 0;
var fact = [1];
for (var i = 1; i <= str.length; i++) {
fact[i] = i * fact[i - 1];
}
for (var i = 0; i < fact[str.length]; i++) {
var perm = "";
var temp = str;
var code = i;
for (var pos = str.length; pos > 0; pos--) {
var sel = code / fact[pos - 1];
perm += temp.charAt(sel);
code = code % fact[pos - 1];
temp = temp.substring(0, sel) + temp.substring(sel + 1);
}
console.log(perm);
if (! perm.match(/(.)\1/g)) result++;
}
return result;
}
alert(permAlone('abcdefa'));
UPDATE: In response to a related question, I wrote an algorithm which doesn't just brute force all the permutations and then skips the ones with adjacent doubles, but uses a logical way to only generate the correct permutations. It's explained here: Permutations excluding repeated characters and expanded to include any number of repeats per character here: Generate all permutations of a list without adjacent equal elements
I agree with m69, the bug seems to be in how you are generating permutations. I got 3600 for 'abcdefa' by implementing a different algorithm for generating permutations. My solution is below. Since it uses recursion to generate the permutations the solution is not fast, however you may find the code easier to follow, if speed is not important.
The reason for having a separate function to generate the array index values in the permutations was to verify that the permutation code was working properly. Since there are duplicate values in the input strings it's harder to debug issues in the permutation algorithm.
// Simple helper function to compute all permutations of string indices
function permute_indices_helper(input) {
var result = [];
if (input.length == 0) {
return [[]];
}
for(var i = 0; i < input.length; i++) {
var head = input.splice(i, 1)[0];
var tails = permute_indices_helper(input);
for (var j = 0; j < tails.length; j++) {
tails[j].splice(0, 0, head);
result.push(tails[j]);
}
input.splice(i, 0, head); // check
}
return result;
};
// Given an array length, generate all permutations of possible indices
// for array of that length.
// Example: permute_indices(2) generates:
// [[0,1,2], [0,2,1], [1,0,2], ... , [2, 0, 1]]
function permute_indices(array_length) {
var result = [];
for (var i = 0; i < array_length; i++) {
result.push(i);
}
return permute_indices_helper(result);
}
// Rearrange letters of input string according to indices.
// Example: "car", [2, 1, 0]
// returns: "rac"
function rearrange_string(str, indices) {
var result = "";
for (var i = 0; i < indices.length; i++) {
var string_index = indices[i];
result += str[string_index];
}
return result;
}
function permAlone(str) {
var result = 0;
var permutation_indices = permute_indices(str.length);
for (var i = 0; i < permutation_indices.length; i++) {
var permuted_string = rearrange_string(str, permutation_indices[i]);
if (! permuted_string.match(/(.)\1/g)) result++;
}
return result;
}
You can see a working example on JSFiddle.
My goal is to make a randomly generated 2D Array in Javascript, that has an X amount of the same one character value while the rest of the values are equal to another character.
In this example, there are 10 rows and 10 columns for the 2D Array. 20 out of the possible 100 values of the Array should be equal to 'Y' (for yes) and the 80 others should be 'N' (for no). I want the 'Y's to be randomly placed all over the Array, and I absolute need exactly 20 of them to be 'Y's and the rest 'N's.
I had a less efficient way before, and I thought to try this approach, where after I define the Array, I make the first X amount of values a 'Y' and then the rest all 'N's. Then I shuffle the array, (using the shuffle from the underscore library) so that the 'Y's are all spread out randomly everywhere.
Is this an efficient way of getting what I need done? Are there any better solutions? I tried making a JSFiddle with my example, but the site appears to be down at the moment.
(I was unable to test my code yet to see if the shuffle worked correctly on my 2D array)
var rows = 10;
var cols = 10;
var elements = 20;
//Define Empty Array
var test = new Array(rows);
for (var k = 0; k < rows; k++)
{
test[k] = Array(cols);
}
var i = 1;
for (var x = 0; x < rows; x++)
{
for (var y = 0; y < cols; y++)
{
if (i <= elements)
{
test[x][y] = "Y";
}
else
{
test[x][y] = "N";
}
}
}
//Shuffle all those values so they're no longer in order
var shuffledTest = _.shuffle(test);
//Print in rows
for (var x = 0; x < rows; x++)
{
console.log(shuffledTest[x]);
}
A very simple solution is to first create an array, fill it with a number of "N"s, insert the "Y"s at random indexes, and then finally splitting it into the 2-dimensional array that you want:
var tmpArr = [], // Temporary 1-dimensional array to hold all values
arr = [], // The final 2-dimensional array
rows = 10,
cols = 10,
elements = 20; // Number of "Y"s
// 1. Fill temporary array with "N"s
for (var i = 0; i < rows * cols - elements; i += 1) {
tmpArr.push("N");
}
// 2. Insert "Y"s at random indexes in the temporary array
for (var i = 0; i < elements; i += 1) {
var index = Math.round(Math.random() * (tmpArr.length + 1));
tmpArr.splice(index, 0, "Y");
}
// 3. Split temporary array into 10 seperate arrays
// and insert them into the final array
for (var i = 0; i < rows; i += 1) {
var row = tmpArr.slice(i * cols, (i + 1) * cols);
arr.push(row);
}
JSBin to illustrate: http://jsbin.com/luyacora/1/edit
You can try this solution, it uses underscores range to create a pair of arrays to use as iterators, though their values don't matter.
Play around with the randomizer function to get an even distribution of 'y's
JSBIN: http://jsbin.com/yaletape/1/
var rows = _.range(0, 10, 0);
var columns = _.range(0, 10, 0);
function randomizer(mult){
return Math.floor((Math.random()*mult)+1);
}
var y_count = 0;
var matrix = _.map(rows, function(){
return _.map(columns, function(v, i){
var value;
var y_allowed = randomizer(3);
var current_y_count = 0;
if(y_count < 20 && current_y_count < y_allowed){
var rand = randomizer(5);
if(rand > 4){
value = 'y';
current_y_count++;
y_count++;
}
}
if(!value){
value = 'n';
}
return value;
});
});
//The above could be simplified to
var matrix = _.range(0,10,0).map(function(){
return _.range(0,10,0).map(function(){
//put the logic code from above here
});
});
Maybe shuflle a 2D array is not the best way. As #Zeb mentioned, here is some code that fill random positions with the 'Y' value. After that, the other positions are filled with 'N'.
http://plnkr.co/edit/avyKfgsgOSdAkRa1WOsk
var arr = [];
var cols = 10;
var rows = 10;
var positions = rows*cols; // 100
var YQty = 10; // only 10 'Y' are needed
// 'Y' values.
for(i = 0; i < YQty; i++)
{
do
{
x = parseInt(Math.random() * cols);
y = parseInt(Math.random() * rows);
filled = false;
if (typeof(arr[x]) == "undefined")
{
arr[x] = [];
}
if (typeof(arr[x][y]) == "undefined")
{
arr[x][y] = 'Y';
filled = true;
}
}
while (!filled);
}
// 'N' values.
for (x = 0; x < cols; x++)
{
if (typeof(arr[x]) == "undefined")
{
arr[x] = [];
}
for (y = 0; y < rows; y++)
{
if (arr[x][y] != 'Y')
{
arr[x][y] = 'N';
}
}
}
Shuffling the multidimensional array is not the best approach. Seeing as any sort is worse than linear time complexity. The easiest solution would be to create your multidimensional array and then set each index value to the char you want the 'rest' of the values to be. Then for 1 -> the number of other char value choose a random index and set that to the char.
Note: If the randomly picked spot has already been changed you need to choose a new one to make sure you have the right amount at the end.