I have a string "ABCDEFGHIJKLMN" that I need to shuffle in a specific manner. To do that, I write the characters sequentially in columns bottom -> top and then left -> right (4 chars per column for example) until all characters are done. If the last column is not complete, then the empty spaces need to be on the bottom (this is very important). Like so:
D H L N
C G K M
B F J
A E I
The shuffle is accomplished by producing a new string reading the block of letters as we read text, in rows left -> right:
"DHLNCGKMBFJAEI"
The cases where the columns are not complete (word.size % column_height !=0) complicate things considerably.
I came up with a few solutions, but I'm not sure if there is a simpler (ie, shorter OR easier to read) and more elegant way of coding this problem. My solutions either have an ugly, separate block of code to handle the final incomplete column or seem way too complicated.
My question is, could it be done better?
If you don't want any spoilers and decide to try and figure it out for yourself, stop reading now. If you want to work from what I fiddled so far, then a working piece of code is
var result = "";
var str = "ABCDEFGHIJKLMN";
var nr_rows = 4;
var current_row = 4;
var columns = Math.floor(str.length / nr_rows);
var modulus_table = str.length % nr_rows;
var modulus_position = -1;
for (var i = 0; i < nr_rows; i++) {
for (var j = 0; j < columns; j++) {
result += str[current_row + j * nr_rows - 1];
}
if (modulus_table > 0) {
result += str[str.length + modulus_position];
modulus_table--;
modulus_position--;
}
current_row--;
}
console.log(result);
Moving on to arrays, the next example would loop through each character, placing it correctly in a matrix-like array, but it doesn't work. The array needs to be created another way. For another example of this issue, see How to create empty 2d array in javascript?. This would also need an ugly hack to fix the last characters on the last incomplete column aligning to the bottom instead of the top.
var result = [[],[]];
var str = "ABCDEFGHIJKLMN";
var nr_rows = 4;
var row = nr_rows - 1;
var column = 0;
for (var i = 0; i < str.length; i++) {
result[row][column] = str[i];
row--;
if (row < 0) {
row = nr_rows;
column++;
}
}
console.log(result);
This last method goes full matrix array, but it quickly becomes complicated, since it needs to loop through the array in 3 different directions. First, create a dummy array with the characters in the wrong place, but where the 'undefined' positions correspond to those that should be left empty. That is acomplished by populating the array 'rotated 90º' from the reading orientation.
Without this first step, the empty positions would be stacked at the bottom instead of the top.
A second pass is required to re-write the caracters in the correct places, skipping any holes in the matrix using the 'undefined' value. This check is made for every position and there is no separate block of code to handle an incomplete last line.
A third pass then reads every character in order to form the final shuffled string. All this seems way too complicated and confusing.
// matrix populated top->bottom and left->right
// with the characters in the wrong place
// but the undefined postions in the correct place of the empty positions
var matrix = [];
var str = "ABCDEFGHIJKLMN";
var rows = 4;
var columns = Math.ceil(str.length / rows);
var k = 0;
for (var i = 0; i < rows; i++) {
matrix[i] = [];
for (var j = columns - 1; j >= 0; j--) {
matrix[i][j] = str[k];
k++;
}
}
// populate the matrix with the chars in the correct place and the 'undefined' positions left empty
var k = 0;
for (var i = 0; i < rows; i++) {
for (var j = 0; j < columns; j++) {
if (matrix[i][j] != undefined) {
matrix[i][j] = str[k];
k++;
}
}
}
// read matrix in correct direction and send to string, skipping empty positions
var result = "";
for (var j = columns - 1; j >= 0; j--) {
for (var i = 0; i < rows; i++) {
if (matrix[i][j] != undefined) {
result += matrix[i][j];
}
}
}
console.log(result);
What if you just split/reverse the array into column groups, and convert to rows?
const result = str.match(/.{1,4}/g) // split string into groups of 4
.map(i => i.split('').reverse()) // reverse each group (bottom to top, and solves the last col issue)
.reduce((res, col) => { // reduce the groups into rows
col.forEach((c, i) => res[i] += c) // concat each string char to the right row
return res
}, ['','','','']) // initialise empty strings per row
.join('') // join the rows up
Fiddle here
If you wish to return a string, I don't see why any intermediate result should use an array when it doesn't have to. The following could use one less array, but it's convenient to use split and an array to control the while loop rather than mutate the string.
The idea is to fill the strings from the bottom up until the column is full, then keep adding from the bottom of each column until it runs out of characters to assign. The row to start filling from is based on how many characters are left and how many rows there are.
Rather than building strings, it could build arrays but then generating a string requires multiple joins.
It can also produce results where there are insufficient slots for all the characters, so a result using 9 characters from 10 or more using a 3x3 "matrix" (see last example).
function verticalShuffle(s, rows, cols) {
var result = [''];
s = s.split('');
while (s.length && result[0].length < cols) {
for (var i = (rows < s.length? rows : s.length) -1 ; i>=0; i--) {
if (!result[i]) result[i] = '';
result[i] += s.splice(0,1)[0] || '';
}
}
return result.join('');
}
var s = 'ABCDEFGHIJKLMN';
console.log(verticalShuffle(s, 4, 4)); // DHLNCGKMBFJAEI
console.log(verticalShuffle(s, 6, 3)); // FLNEKMDJCIBHAG
// Only use 9 characters
console.log(verticalShuffle(s, 3, 3)); // CFIBEHADG
This uses plain ed3 functionality that will run in any browser. I don't see the point of restricting it to ECMAScript 2015 or later hosts.
If interpret Question correctly, you can use for loop, String.prototype.slice() to to populate arrays with characters of string. Use Array.prototype.pop() within recursive function to get last element of array until each array .length is 0.
To create array
[
["D","H","L","N"],
["C","G","K","M"],
["B","F","J"],
["A","E","I"]
]
from vertically inverted string you can use for loop, String.prototype.slice() to set array of arrays containing elements having .length 4, or 3 once .length of parent array is 2, having been set with two arrays containing four elements
var str = "ABCDEFGHIJKLMN";
function fnVerticalInvert(str, arr, res) {
if (!arr && !res) {
arr = []; res = "";
}
if (str) {
for (var i = 0; i < str.length; i += 4) {
arr.push([].slice.call(str.slice(i, i + 4)));
}
}
for (var i = 0; i < arr.length; i++) {
if (arr[i].length) {
res += arr[i].pop()
}
}
if (arr.some(function(curr) {return curr.length}))
return fnVerticalInvert(null, arr, res);
for (var i = 0, l = 4, j = 0, n = l - 1, k; i < l; i++, j += l) {
if (i === l / 2) k = j;
arr[i] = [].slice.call(res.slice(!k ? j : k, !k ? j + l : k + n));
if (k) k += n;
}
return {str: res, arr:arr};
};
var res = fnVerticalInvert(str);
console.log(res);
Related
How to create the np.eye function in JavaScript? Or what would be the numpy.eye equivalent in JavaScript?
I would like a function that creates the "Identity matrix" in 2d dimensions, and you can change the number of rows, columns and the index of the diagonal.
https://numpy.org/devdocs/reference/generated/numpy.eye.html
This doesn't take care of M,N,k
#Andy
function eye(n){
var t=[];
for(var i=0;i<n;i++){
var p=[]
for(var j=0;j<n;j++){
p.push(j==i?1:0)
}
t.push(p)
}}
This doesn't take care of M,N,k
You are almost there, just have to put the extra parameters there, like the outer loop (rows) runs to N, the inner loop (columns) runs to M, and the comparison would be j-i===k:
function eye(N,M,k) {
var t = [];
for (var i = 0; i < N; i++) {
var p = []
for (var j = 0; j < M; j++) {
p.push(j - i === k ? 1 : 0)
}
t.push(p)
}
return t;
}
let NMk=prompt("N,M,k").split(",").map(x=>parseInt(x));
console.log(eye(...NMk).map(x=>x.join()));
Try entering something like 3,3,0 ("classic") or 2,3,1 (a "fancy" one) when asked.
(And don't worry about the snippet printing strings, that's just the join() , to keep output small, and without much coding).
I have a problem where, given an array of integers, I need to find sets of three numbers that add up to equal zero. The below solution works but isn't as optimal as I'd like and I am looking for ways to optimize it to avoid unnecessary processing.
What I am doing below is I am iterating through the all combinations of numbers while eliminating iterating through the same indices in each nested loop and I am checking if the three numbers in the inner most loop add up to zero. If yes, I am converting the array to a string and if the string isn't already in the results array I am adding it. Right before returning I am then converting the strings back to an array.
I appreciate any suggestions on how to further optimize this or if I missed out on some opportunity to implement better. I am not looking for a total refactor, just some adjustments that will improve performance.
var threeSum = function(nums) {
const sorted = nums.sort();
if(sorted.length && (sorted[0] > 0 || sorted[sorted.length-1] < 0)) {
return [];
}
let result = [];
for(let i=0; i < sorted.length; i++) {
for(let z=i+1; z < sorted.length; z++) {
for(let q=z+1; q < sorted.length; q++) {
if(sorted[i]+sorted[z]+sorted[q] === 0) {
const temp = [sorted[i], sorted[z], sorted[q]].join(',');
if(!result.includes(temp)) {
result.push(temp);
}
}
}
}
}
return result.map(str => str.split(','));
};
Sample Input: [-1,0,1,2,-1,-4]
Expected Output: [[-1,-1,2],[-1,0,1]]
One obvious optimisation is to precalculate the sum of the two first numbers just before the third nested loop. Then compare in the third loop if that number equals the opposite of the third iterated number.
Second optimisation is to take advantage of the fact that your items are sorted and use a binary search for the actual negative of the sum of the two first terms in the rest of the array instead of the third loop. This second optimisation brings complexity from O(N3) down to O(N2LogN)
Which leads to the third optimisation, for which you can store in a map the sum as key and as value, an array of the different pairs which sum to the sum so that each time you want to operate the binary search again, first you check if the sum already exists in that map and if it does you can simply output the combination of each pair found at that sum’s index in the map coupled with the negative sum.
The OP's solution runs in O(N³) time with no additional storage.
The classic "use a hash table" solution to find the missing element can bring that down to O(N²) time with O(N) storage.
The solution involves building a number map using an object. (You could use a Map object as well, but then you can't be as expressive with ++ and -- operators). Then just an ordinary loop and inner loop to evaluate all the pairs. For each pair, find if the negative sum of those pairs is in the map.
function threeSum(nums) {
var nummap = {}; // map a value to the number of ocurrances in nums
var solutions = new Set(); // map of solutions as strings
// map each value in nums into the number map
nums.forEach((val) => {
var k = nummap[val] ? nummap[val] : 0; // k is the number of times val appears in nummap
nummap[val] = k+1; // increment by 1 and update
});
// for each pair of numbers, see if we can find a solution the number map
for (let i = 0; i < nums.length; i++) {
var ival = nums[i];
nummap[ival]--;
for (let j = i+1; j < nums.length; j++) {
var jval = nums[j];
nummap[jval]--;
var target = -(ival + jval); // this could compute "-0", but it works itself out since 0==-0 and toString will strip the negative off
// if target is in the number map, we have a solution
if (nummap[target]) {
// sort this three sum solution and insert into map of available solutions
// we do this to filter out duplicate solutions
var tmp = [];
tmp[0] = ival;
tmp[1] = jval;
tmp[2] = target;
tmp.sort();
solutions.add(tmp.toString());
}
nummap[jval]++; // restore original instance count in nummap
}
nummap[ival]--;
}
for (s of solutions.keys()) {
console.log(s);
}
}
threeSum([9,8,7,-15, -9,0]);
var threeSum = function(unsortedNums) {
const nums = unsortedNums.sort();
if(nums.length && (nums[0] > 0 || nums[nums.length-1] < 0)) {
return [];
}
const result = new Map();
for(let i=0; i < nums.length; i++) {
for(let z=i+1; z < nums.length; z++) {
for(let q=z+1; q < nums.length; q++) {
if(nums[i]+nums[z]+nums[q] === 0) {
const toAdd = [nums[i], nums[z], nums[q]];
const toAddStr = toAdd.join(',');
if(!result.has(toAddStr)) {
result.set(toAddStr, toAdd);
}
}
}
}
}
return Array.from(result.values());
};
I'm writing a simple snakes game using JavaScript and HTML5 canvas.
I have a Multidimensional array that hold snake block like this:
snake=[[1,1],[1,2]];
and set it on arrayMap using (snake.indexOf([i],[j])!=-1) then draw arrayMap on canvas.
for (var i = 0; i < blocksHeightCount; i++) {
for (var j = 0; j < blocksWidthCount; j++) {
if ((snake.indexOf(i,j)!=-1)||
(walls.indexOf(i,j)!=-1)||
(foods.indexOf(i,j)!=-1)) {
arrayMap[i][j]=1;
} else {
arrayMap[i][j]=0;
}
}
}
for (var i = 0; i < blocksHeightCount; i++) {
for (var j = 0; j < blocksWidthCount; j++) {
Block = arrayMap[i][j];
if (Block!=0){
ctx.fillStyle = (Block != 9) ? colors[Block]
: "#bdc3c7";
ctx.fillRect(j * cubeWidth, i * cubeHeight
, cubeWidth-.4,cubeHeight-.4);
}
}
}
the problem is indexOf isn't working when I set array on it!
It works fine when I set indexOf("i,j") but i need it to be array.
please help, thx
First solution : using Array.map
Each element of your arrays snake, walls and foods is an array with 2 elements. So to check if an [x,y] exists in one of the arrays you need a simple way to
compare two arrays [x1, y1] and [x2, y2]. Comparing arrays directly using the operator == will compare their references and not values (Thanks #Elena for the remarque). A way to compare values
would be to affect a hash to each array and compare hashes. By hash I mean a number which is unique for each array of type [x, y]. That could be x * blocksWidthCount + y
in your case and the code will be :
function getHash(x, y){
return x * blocksWidthCount + y;
}
var blockHashes = snake.concat(walls).concat(foods).map(function(cell) {
return getHash(cell[0], cell[1]);
}); // hashes of all blocks in one array
for (var i = 0; i < blocksHeightCount; i++) {
for (var j = 0; j < blocksWidthCount; j++) {
if (blockHashes.indexOf(getHash(i, j)) != -1) {
arrayMap[i][j]=1;
} else {
arrayMap[i][j]=0;
}
}
}
Second Solution Changing the way we see things
Instead of looping over all cells and verifying if every single cell is a block which gives a complexity of O(N * M) (N number of cells and M number of blocks).
We can do better simply by supposing that there is no block and then loop over blocks and mark them as blocks which is in O(N + M) !
function markBlock(cell){
arrayMap[cell[0]][cell[1]] = 1;
}
for (var i = 0; i < blocksHeightCount; i++)
for (var j = 0; j < blocksWidthCount; j++)
arrayMap[i][j] = 0;
snake.forEach(markBlock);
walls.forEach(markBlock);
foods.forEach(markBlock);
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.
I have a comma separated string, out of which I need to create a new string which contains a random order of the items in the original string, while making sure there are no recurrences.
For example:
Running 1,2,3,1,3 will give 2,3,1 and another time 3,1,2, and so on.
I have a code which picks a random item in the original string, and then iterates over the new string to see if it does not exist already. If it does not exist - the item is inserted.
However, I have a feeling this can be improved (in C# I would have used a hashtable, instead of iterating every time on the new array). One improvement can be removing the item we inserted from the original array, in order to prevent cases where the random number will give us the same result, for example.
I'd be happy if you could suggest improvements to the code below.
originalArray = originalList.split(',');
for (var j = 0; j < originalArray.length; j++) {
var iPlaceInOriginalArray = Math.round(Math.random() * (originalArray.length - 1));
var bAlreadyExists = false;
for (var i = 0; i < newArray.length; i++) {
if (newArray[i].toString() == originalArray[iPlaceInOriginalArray].toString()) {
bAlreadyExists = true;
break;
}
}
if (!bAlreadyExists)
newArray.push(originalArray[iPlaceInOriginalArray]);
}
Thanks!
You can still use a 'hash' in javascript to remove duplicates. Only in JS they're called objects:
function removeDuplicates(arr) {
var hash = {};
for (var i=0,l=arr.length;i<l;i++) {
hash[arr[i]] = 1;
}
// now extract hash keys... ahem...
// I mean object members:
arr = [];
for (var n in hash) {
arr.push(n);
}
return arr;
}
Oh, and the select random from an array thing. If it's ok to destroy the original array (which in your case it is) then use splice:
function randInt (n) {return Math.floor(Math.random()*n)}
function shuffle (arr) {
var out = [];
while (arr.length) {
out.push(
arr.splice(
randInt(arr.length),1 ));
}
return out;
}
// So:
newArray = shuffle(
removeDuplicates(
string.split(',') ));
// If you sort the first array, it is quicker to skip duplicates, and you can splice each unique item into its random position as you build the new array.
var s= 'Function,String,Object,String,Array,Date,Error,String,String,'+
'Math,Number,RegExp,Group,Collection,Timelog,Color,String';
var A1= s.split(',').sort(), A2= [], tem;
while(A1.length){
tem= A1.shift();
while(A1[0]== tem) tem= A1.shift();
if(tem) A2.splice(Math.floor(Math.random()*A2.length), 0, tem);
}
alert(A2.join(', '))
With your solution, you are not guaranteed not to pick same number several times, thus leaving some others of them never being picked. If the number of elements is not big (up to 100), deleting items from the source array will give the best result.
Edit
originalArray = originalList.split(',');
for (var j = 0; j < originalArray.length; j++) {
var iPlaceInOriginalArray = Math.round(Math.random() * (originalArray.length - 1 - j));
var bAlreadyExists = false;
for (var i = 0; i < newArray.length; i++) {
if (newArray[i].toString() == originalArray[iPlaceInOriginalArray].toString()) {
bAlreadyExists = true;
break;
}
}
var tmp = originalArray[originalArray.length - 1 - j];
originalArray[originalArray.Length - 1 - j] = originalArray[iPlaceInOriginalArray];
originalArray[iPlaceInOriginalArray] = tmp;
if (!bAlreadyExists)
newArray.push(originalArray[iPlaceInOriginalArray]);
}