I'm trying to find every permutations of 2 arrays like this:
// input
lowerWords = ['one', 'two', 'three' ]
upperWords = [ 'ONE', 'TWO', 'THREE' ]
// output
keywords = {
'one two three': true,
'ONE two three': true,
'ONE TWO three': true,
'ONE TWO THREE': true,
'ONE two THREE': true,
'one TWO three': true,
'one two THREE': true,
'one TWO THREE': true,
}
It should function with more than 3 items, both arrays will always be same length. This is my code:
const keywords = {}
const lowerWords = ['one', 'two', 'three' ]
const upperWords = [ 'ONE', 'TWO', 'THREE' ]
const wordCount = lowerWords.length
let currentWord = 0
let currentWords = [...upperWords]
while (currentWord < wordCount) {
currentWords[currentWord] = lowerWords[currentWord]
let keyword = currentWords.join(' ')
keywords[keyword] = true
currentWord++
}
currentWord = 0
currentWords = [...lowerWords]
while (currentWord < wordCount) {
currentWords[currentWord] = upperWords[currentWord]
let keyword = currentWords.join(' ')
keywords[keyword] = true
currentWord++
}
result is missing some
ONE TWO THREE: true
ONE TWO three: true
ONE two three: true
one TWO THREE: true
one two THREE: true
one two three: true
You could transpose the arrays for getting an array of pairs and then get all combinations of the pairs.
const
transpose = array => array.reduce((r, a) => a.map((v, i) => [...(r[i] || []), v]), []),
combinations = array => array.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
var lowerWords = ['one', 'two', 'three'],
upperWords = ['ONE', 'TWO', 'THREE'],
pairs = transpose([lowerWords, upperWords]),
result = combinations(pairs);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Thought I'd give it a try. I used binary to get the possible combinations, as this problem needed a base 2 solution:
const low = ["one", "two", "three"];
const up = ["ONE", "TWO", "THREE"];
const words = [low, up]
const len = words[0].length
function getCombinations(noOfArrays, len) {
var temp, newCombo, combos = [];
for (var i = 0; i < (noOfArrays ** len); i++) {
temp = new Array(len).fill(0)
newCombo = i.toString(noOfArrays).split('');
newCombo.forEach((el, i) => temp[temp.length - newCombo.length + i] = +el);
combos.push(temp);
}
return combos;
}
function setCombinations(combos) {
return combos.map(combo => combo.map((el, i) => words[el][i]))
}
var combos = getCombinations(words.length, len)
combos = setCombinations(combos)
console.log(combos)
Explanation of loop:
1. temp = new Array(len).fill(0)
2. newCombo = i.toString(2).split("");
3. newCombo.forEach((el, i) => temp[temp.length - newCombo.length + i] = +el);
Creates temp array [0,0,0]
Grabs loop number (i) and converts it to binary e.g:
1 -> 1
2 -> 10
3 -> 11
4 -> 100
etc...
Then split the binary into an array 100 -> [1,0,0].
Then for each element push it in that new array. This gave a problem with pushing the 1 and 2 element arrays (10 -> [1,0]) into the back of the array. I used temp.length - newCombo.length + i to fix that.
That function then returns:
[ 0, 0, 0 ]
[ 0, 0, 1 ]
[ 0, 1, 0 ]
[ 0, 1, 1 ]
[ 1, 0, 0 ]
[ 1, 0, 1 ]
[ 1, 1, 0 ]
[ 1, 1, 1 ]
Then, I can map over each combination, and grab each array depending on the value, and get the words ('one' or 'ONE') via loop index.
Note this code works with more than one array, as long as the arrays are all the same length.
You need to get a total of 2 ^ 3 combinations. If you create a 2D matrix from the 2 arrays, the below table represents the row number from which of the item should be taken.
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
If you analyze indexes of the combinations, each of this a binary number from 0 to 2 ^ 3 with leading zeros.
So, you could
loop from 0 to 8
create binary number using toString(2)
Add leading zero using padStart
split each digit to get an array
Get each item from matrix[digit-from-binary][position-of-each-split]
join the array of items with a ' ' separator to get the key
Add the key to the output object
Working snippet:
function getAllCombinations(matrix) {
const combinations = 2 ** 3,
output = {};
for(let i = 0; i < combinations; i++) {
const key = i.toString(2)
.padStart(3, 0)
.split('')
.map((n, j) => matrix[n][j])
.join(" ")
output[key] = true;
}
return output
}
console.log(getAllCombinations([['one', 'two', 'three' ],[ 'ONE', 'TWO', 'THREE' ]]))
You can generalize this for m x n matrix. Instead of converting each to a binary number, you need to convert it to base-m and padStart to length n
function getAllCombinations(matrix) {
const rows = matrix.length,
columns = matrix[0].length,
combinations = rows ** columns;
return Array.from({ length: combinations },
(_, i) => i.toString(rows)
.padStart(columns, 0)
.split('')
.map((n, j) => matrix[n][j])
)
}
console.log(JSON.stringify(
getAllCombinations( [[1, 2, 3], [4, 5, 6], [7, 8, 9]] ) // 3 x 3 matrix
));
console.log(JSON.stringify(
getAllCombinations( [[1, 2], [3, 4], [5, 6], [7, 8]] ) // 4 x 2 matrix
));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Following Code should work for you its recursive approach:
const lowerWords = ['one', 'two', 'three']
const upperWords = ['ONE', 'TWO', 'THREE']
let result = {};
function getCombinations(index, caseType, arr) {
if (index == 3) {
arr[index] = (caseType == 'lower' ? lowerWords : upperWords)[index];
result[arr.join(' ')] = true
return
}
arr[index] = (caseType == 'lower' ? lowerWords : upperWords)[index];
getCombinations(index + 1, 'lower', arr);
getCombinations(index + 1, 'upper', arr);
}
getCombinations(0, 'lower', [])
getCombinations(0, 'upper', [])
console.log('resultresult', result)
Here's a generalised version based on my other answer that handles a variable number of input arrays with varying lengths:
const g = (arrs, i=0, comb=[]) =>
!arrs.some(arr => i < arr.length)
? [comb]
: arrs.reduce((acc, arr) =>
i >= arr.length ? acc :
acc.concat(g(arrs, i + 1, comb.slice().concat(arr[i])))
, [])
// Example output
let input = [['ONE','TWO','THREE'], ['one','two'], [1,2,3,4]]
let str = ''
for (let line of g(input))
str += JSON.stringify(line) + '\n'
console.log(str)
We can enumerate these directly. Here's one algorithm:
If we've reached the end of
the list, return the combination
the current recursion branch is
building.
Otherwise, create a new branch
that picks the next item from B,
while the current branch picks
the next item from A.
JavaScript code:
function f(A, B, i=0, comb=[]){
return i == A.length
? [comb]
: f(A, B, i + 1, comb.concat(A[i])).concat(
f(A, B, i + 1, comb.slice().concat(B[i])))
}
console.log(JSON.stringify(f(['one','two','three'], ['ONE','TWO','THREE'])))
Related
I want to sort an array by element frequency. My code works for arrays of strings, but not for arrays of numbers:
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
return d[b] - d[a]
})
return arr
}
frequencySort(['a','b','b','b','c','c'])) returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]
frequencySort([4, 6, 2, 2, 6, 4, 4, 4]) returns [ 4, 4, 4, 4, 6, 2, 2, 6 ]
The only reason your letters worked is because you didn't have the same number of any two letters, where in your numbers, you have 2 of both 2 and 6.
Here's your snippet, but with 2 a's and 2 c's. You'll see it's out of order just like the numbers.
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
return d[b] - d[a]
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c', 'a']))
You need a way to sort instances that have the same number of occurrences. I adapted your forEach loop to give the last index of each letter to your b object and then changed your sort to use that index in case the number of occurrences is the same.
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach((i,index) => d[i] = {
num: countOccurrences(arr,i),
i: index
});
arr.sort(function(a,b){
let diff = d[b].num - d[a].num;
if(diff == 0)
diff = d[b].i - d[a].i;
return diff;
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c', 'a']))
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4]));
It has nothing to do with the elements being letters or numbers. In you letters array, each letter has unique occurence count (3, 2, 1), therefore they are sorted the way you want to.
However, in your numbers array, "2" and "6" both occur 2 times each. Therefore, your sort callback function returns 0 for them, and they are treated as equal order by the sort function.
In your array of numbers you have the same amount of the number 2 as 6 and your sorting function doesn't care about the actual values it just cares about their counts. So in your example 2 and 6 both have the same priority.
You want to adjust your sorting function to compare values of elements if they have the same amount of occurrences.
You'll need to implement separate comparisons for all the data types you want to accept and decide if you want ascending/descending order.
Here is a basic example for number and string elements:
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
const r = d[b] - d[a]
if (r != 0) return r
switch (typeof d[a]) {
case 'number': return a - b
case 'string': return a.localeCompare(b)
default: return 0
}
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c'])) // returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4])) // returns [ 4, 4, 4, 4, 2, 2, 6, 6 ]
A possible approach would first collect all equal array items within an item specific group array by a reduce task ...
console.log(
"grouped ['a','b','b','b','c','c'] ...",
['a','b','b','b','c','c'].reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {})
);
console.log(
"grouped [4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'] ...",
[4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'].reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The final computation then has to transform ... via Object.values ... the temporary result (as shown above) into an array of arrays of equal items where the former gets 1stly sorted by each array's length (indicates the items frequency) and 2ndly, for arrays of equal length', by a locale compare of each array's first item. The final result is the sorted array's flatted version ...
function sortItemsByFrequency(arr) {
const groupedItems = arr.reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {});
return Object
.values(groupedItems)
.sort((a, b) =>
// - sort by frequency first indicated by an
// array's length.
// - the higher frequency count wins.
b.length - a.length ||
// in case of equal frequency counts do a
// locale compare of both array's first items.
b[0].toLocaleString().localeCompare(a[0].toLocaleString())
)
.flat();
}
console.log(
"sortItemsByFrequency(['a','b','b','b','c','c']) ...",
sortItemsByFrequency(['a','b','b','b','c','c'])
);
console.log(
"sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2']) ...",
sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
sort first based on frequency of characters in desc order,if freq is same, then sort alphabetically in asc order.
const str = 'zzzzvnttteeeqqaao';
const frequencySort = (str = '') => {
let map = {}
for (const letter of str) {
map[letter] = (map[letter] || 0) + 1;
};
let res = "";
let sorted = Object.keys(map).sort((a, b) => map[b] < map[a] ? -1 : 1);
//console.log(sorted);
for (let letter of sorted) {
for (let count = 0; count < map[letter]; count++) {
res += letter
}
}
return res;
};
console.log(frequencySort(str));
I'm searching for a nice approach for the following task: I have a game with rounds. In each round we have a number between 1-10.
This is how I'm doing it so far but I'm stuck.
gameValues.forEach(function(x) { counts[x] = (counts[x] || 0)+1; });
I want to count the double entrees in my array but I only want to watch out for the last 5 rounds.
Round 6 ( We already have 5 values ):
gameValues = [9,9,9,2,1]
result: 9:3, 2:1, 1:1,
Round 7 ( Now we have 6 values but I only want to count the first 5):
gameValues = [3,9,9,9,2,1]
result: 3:1, 9:3, 2:1,
I can't manage to only count the last 5 rounds..
Use gameValues.slice(-5) to get the last 5 values of the array, then do the same count operation :
gameValues = [3,9,9,9,2,1]
counts = {}
gameValues.slice(-5).forEach(function(x) { counts[x] = (counts[x] || 0)+1; });
console.log(counts);
// { '1': 1, '2': 1, '9': 3 }
You might want to slice the array before looping through it.
gameValues.slice(-5).forEach(function(x) { counts[x] = (counts[x] || 0)+1; });
const gameValues = [3,9,9,9,2,1]
const uniq = gameValues.splice(-5)
.map((number) => {
return {
count: 1,
number: number
}})
.reduce((a, b) => {
a[b.number] = (a[b.number] || 0) + b.count
return a
}, {})
console.log(uniq);
You can get the last 5 numbers in an array using splice and then using reduce you can get the counts of each number.
The splice() method changes the contents of an array by removing or
replacing existing elements and/or adding new elements in place. To
access part of an array without modifying it, see slice().
function countLastFive(arr) {
return arr.splice(-5).reduce((acc, curr) => {
acc[curr] = acc[curr] ? ++acc[curr] : 1;
return acc;
}, {});
}
console.log(countLastFive([9, 9, 9, 2, 1, 3, 1]));
console.log(countLastFive([9, 9, 9, 2, 1]));
console.log(countLastFive([3, 9, 9, 9, 2, 6, 4, 3, 2, 1]));
Just be sure that the slice changes the original array. So it would be better to clone the array so that It won't change the original array
function countLastFive(arr) {
return arr.splice(-5).reduce((acc, curr) => {
acc[curr] = acc[curr] ? ++acc[curr] : 1;
return acc;
}, {});
}
const arr1 = [9, 9, 1, 9, 2, 1];
const arr2 = [9, 9, 1, 9, 2, 1];
console.log("Before -> arr1 => ", arr1);
console.log(countLastFive(arr1));
console.log("After -> arr1 => ", arr1);
console.log("Before -> arr2 => ", arr2);
console.log(countLastFive([...arr2]));
console.log("After -> arr2 => ", arr2);
I am trying to split an array which has a repeating pattern of elements 1, 2, 3, and 4. I want to turn my array [1,2,3,4,5,6,7,8,9,10] into four arrays: [1,5,10], [2,6,11], [3,7,12], and [4,8,13]. I tried using multiples, but the result creates the new arrays in a wrong order. Here is my attempt:
var upload_names_and_ids = [
"Certificat de salaire", //first line is the upload's visible title
"certificat-de-salaire", //second line is the upload's id
"no-info-circle", //third line is the info-circle class
"", //fourth line is the info-circle text
"Allocations Familiales",
"alloc-familiales",
"no-info-circle",
"",
"Courrier Impot (déclaration précédente)",
"courrier-impot",
"info-circle right",
""
];
//Seperate our first array into 4
var upload_names = [];
var upload_ids = [];
var upload_info_circle_class = [];
var upload_info_circle_content = [];
for (var i=0; i<upload_names_and_ids.length; i++){
if (i%4==0) {
upload_info_circle_content.push(upload_names_and_ids[i]);
} else if (i%3==0) {
upload_info_circle_class.push(upload_names_and_ids[i]);
} else if (i%2==0) {
upload_names.push(upload_names_and_ids[i]);
} else {
upload_ids.push(upload_names_and_ids[i]);
}
}
Any help is much appreciated, thank you!
You could take a remainder with index and wanted length.
const
array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
length = 4,
result = array.reduce(
(r, v, i) => (r[i % length].push(v), r),
Array.from({ length }, _ => [])
);
console.log(result);
If you like to use predeclared array directly, you could replace this line
Array.from({ length }, _ => [])
with
[upload_names, upload_ids, upload_info_circle_class, upload_info_circle_content]
where the accumulator of Array#reduce keeps the object references.
It's not i%3==0 (which matches 0, 3, 6, …) but i%4==1 (to match 1, 5, 10, …). Same for i%2==0.
I would add a helper sliceN that takes an array and a positive integer. Then returns an array of arrays where the inner arrays are of length n.
sliceN([1,2,3,4,5,6,7,8,9], 3) //=> [[1,2,3], [4,5,6], [7,8,9]]
sliceN([1,2,3,4,5,6], 2) //=> [[1,2], [3,4], [5,6]]
Then also add a helper transpose that transposes a matrix.
transpose([[1,2,3], [4,5,6], [7,8,9]]) //=> [[1,4,7], [2,5,8], [3,6,9]]
transpose([[1,2], [3,4], [5,6]]) //=> [[1,3,5], [2,4,6]]
With these two helpers you can create the wanted result with ease.
const upload_names_and_ids = [
"Certificat de salaire", //first line is the upload's visible title
"certificat-de-salaire", //second line is the upload's id
"no-info-circle", //third line is the info-circle class
"", //fourth line is the info-circle text
"Allocations Familiales",
"alloc-familiales",
"no-info-circle",
"",
"Courrier Impot (déclaration précédente)",
"courrier-impot",
"info-circle right",
""
];
const [
upload_names,
upload_ids,
upload_info_circle_class,
upload_info_circle_content,
] = transpose(sliceN(upload_names_and_ids, 4));
console.log(upload_names);
console.log(upload_ids);
console.log(upload_info_circle_class);
console.log(upload_info_circle_content);
function sliceN(array, n) {
const slices = [];
for (let i = 0; i < array.length; i += n) {
slices.push(array.slice(i, i + n));
}
return slices;
}
function transpose(rows) {
if (rows.length == 0) return [];
const columns = rows[0].map(cell => Array.of(cell));
for (let iRow = 1; iRow < rows.length; iRow += 1) {
for (let iCol = 0; iCol < columns.length; iCol += 1) {
columns[iCol].push(rows[iRow][iCol]);
}
}
return columns;
}
If you are already use a library with helper functions chances are that one or both of these data transforming methods are present. sliceN can often be found as something with split, slice or chunk in the name. transpose is very specific and if present will probably be present under the same name.
As an example Ramda offers both these methods.
R.transpose(R.splitEvery(4, upload_names_and_ids))
I want to sort only odd numbers without moving even numbers. For example, when I write :
sortArray([5, 3, 2, 8, 1, 4])
The expected result is :
[1, 3, 2, 8, 5, 4]
I am new to JavaScript and I came across a challenge on the Internet that has me perplexed. I normally wouldn't post asking for a solution on the Internet, BUT I have tried for hours and I would like to learn this concept in JavaScript.
The challenge states :
You have an array of numbers.
Your task is to sort ascending odd numbers but even numbers must be on their places.
Zero isn't an odd number and you don't need to move it. If you have an empty array, you need to return it.
Here is my code so far, please take it easy on me I am in the beginning stages of programming.
function sortArray(array) {
let oddNums = [];
for(let i = 0; i < array.length; i++) {
if(array[i] % 2 !== 0) {
oddNums.push(array[i]);
}
}
oddNums = oddNums.sort((a,b)=> a-b);
array.concat(oddNums);
array = array.sort((a,b) => a-b);
return array;
}
You could take a helper array for the odd indices and another for the odd numbers, sort them and apply them back on the previously stored indices of the original array.
var array = [5, 3, 2, 8, 1, 4],
indices = [];
array
.filter((v, i) => v % 2 && indices.push(i))
.sort((a, b) => a - b)
.forEach((v, i) => array[indices[i]] = v);
console.log(array);
Here's a solution using mostly the built-in array methods. Get a list of just the odds, sort it, then map through the original, replacing each item with the first sorted odd if the item is odd, or itself if even:
const array = [5, 3, 2, 8, 1, 4] // to: [1, 3, 2, 8, 5, 4]
function sortOddsOnly(arr) {
const odds = arr
.filter(x => x%2)
.sort((a, b) => a - b);
return arr
.map(x => x%2 ? odds.shift() : x);
}
console.log(sortOddsOnly(array));
I have a solution like this.
Build a sorted odd number array 1st, and then fill the rest of even numbers in order:
const arr = [5, 3, 2, 8, 1, 4];
const odd = arr.filter(i => i%2 !== 0).sort();
let i = 0,
result = [];
arr.forEach(e => {
if (e%2 === 0) {
result.push(e)
} else {
result.push(odd[i]);
i++;
}
});
console.log(result);
just do:
arr.sort((a, b) => a%2 && b%2 ? a - b : 0)
If that works depends on the sort algorithm your browser uses.
A browserindependent version:
for(const [i1, v1] of arr.entries())
for(const [i2, v2] of arr.entries())
if( v1%2 && v2%2 && (i1 < i2) === (v1 > v2))
([arr[i1], arr[i2]] = [v2, v1]);
One of the possible solutions is this. What I have done is created new array odd(array with odd position in original array using Array.prototype.filter) and then sort that array using Array.prototype.sort. Then using Array.prototype.map change value of all odd element of original array with odd array.
x1=[5, 3, 2, 8, 1, 4];
function sortArray(array) {
var odd = array.filter((x,i) => (i+1) % 2 ).sort((a,b) => a > b); //sort odd position and store that in new array
return array.map((x,i) => (i+1) % 2 ? odd.shift() : x ); //if i is odd then replace it with element from
//odd array otherwise keep the element as it is
}
console.log(sortArray(x1));
Here is a possible solution using a slightly customized selection sort :
var xs = [5, 3, 2, 8, 1, 4];
console.log(sortOddsOnly(xs));
function sortOddsOnly (xs) {
var n = xs.length;
for (var i = 0; i < n - 1; i++) {
if (xs[i] % 2 === 1) {
for (var j = i + 1; j < n; j++) {
if (xs[j] % 2 === 1) {
if (xs[i] > xs[j]) {
var min = xs[j];
xs[j] = xs[i];
xs[i] = min;
}
}
}
}
}
return xs;
}
The first two if guarantee that we swap only odd numbers (x % 2 === 1 means "x is odd").
def sort_array(source_array):
b = sorted([n for n in source_array if n % 2 != 0])
c = -1
d = []
for i in source_array:
c = c+1
if i % 2 != 0 :
d.append(c)
for x in range (len(d)):
z = d[x]
source_array[z] = b[x]
return source_array
I want to convert this nested loops in recursion. How do I achieve this?
for(let i = 0; i < 5; i++) {
for(let j = 0; j < 5; j++) {
console.log(i,j);
}
}
Here another example of this recursion:
function loop(i,j,limitI,limitJ){
if(i>=limitI) return;
if(j>=limitJ) loop(i+1,0,limitI,limitJ);
else{
console.log(i,j);
loop(i,j+1,limitI,limitJ)
}
}
loop(0,0,4,4);
Generic function product calculates the Cartesian product of its inputs - You can polyfill Array.prototype.flatMap if it's not already in your environment
Array.prototype.flatMap = function (f, context)
{
return this.reduce ((acc, x) => acc.concat (f (x)), [])
}
const product = (first = [], ...rest) =>
{
const loop = (comb, first, ...rest) =>
rest.length === 0
? first.map (x => [ ...comb, x ])
: first.flatMap (x => loop ([ ...comb, x ], ...rest))
return loop ([], first, ...rest)
}
const suits =
['♤', '♡', '♧', '♢']
const ranks =
['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
for (const card of product (ranks, suits))
console.log (card)
// [ 'A', '♤' ]
// [ 'A', '♡' ]
// [ 'A', '♧' ]
// [ 'A', '♢' ]
// [ '2', '♤' ]
// ...
// [ 'Q', '♧' ]
// [ 'K', '♤' ]
// [ 'K', '♡' ]
// [ 'K', '♧' ]
// [ 'K', '♢' ]
product is a variadic function (by use of a rest parameter) which accepts 1 or more inputs
const range = (min = 0, max = 0) =>
max < min
? []
: [ min, ...range (min + 1, max) ]
const r =
range (0, 2)
for (const comb of product (r, r, r))
console.log (comb)
// [ 0, 0, 0 ]
// [ 0, 0, 1 ]
// [ 0, 0, 2 ]
// [ 0, 1, 0 ]
// ...
// [ 2, 1, 2 ]
// [ 2, 2, 0 ]
// [ 2, 2, 1 ]
// [ 2, 2, 2 ]
Using destructuring assignment, you can effectively create nested loops
for (const [ i, j ] of product (range (0, 5), range (0, 5)))
console.log ("i %d, j %d", i, j)
// i 0, j 0
// i 0, j 1
// i 0, j 2
// i 0, j 3
// i 0, j 4
// i 0, j 5
// i 1, j 0
// ...
// i 4, j 5
// i 5, j 0
// i 5, j 1
// i 5, j 2
// i 5, j 3
// i 5, j 4
// i 5, j 5
product can also be written using generators - below, we find all perfect Pythagorean triples under 20
const product = function* (first, ...rest)
{
const loop = function* (comb, first, ...rest)
{
if (rest.length === 0)
for (const x of first)
yield [ ...comb, x ]
else
for (const x of first)
yield* loop ([ ...comb, x ], ...rest)
}
yield* loop ([], first, ...rest)
}
const range = (min = 0, max = 0) =>
max < min
? []
: [ min, ...range (min + 1, max) ]
const pythagTriple = (x, y, z) =>
(x * x) + (y * y) === (z * z)
const solver = function* (max = 20)
{
const N = range (1, max)
for (const [ x, y, z ] of product (N, N, N))
if (pythagTriple (x, y, z))
yield [ x, y, z ]
}
console.log ('solutions:', Array.from (solver (20)))
// solutions:
// [ [ 3, 4, 5 ]
// , [ 4, 3, 5 ]
// , [ 5, 12, 13 ]
// , [ 6, 8, 10 ]
// , [ 8, 6, 10 ]
// , [ 8, 15, 17 ]
// , [ 9, 12, 15 ]
// , [ 12, 5, 13 ]
// , [ 12, 9, 15 ]
// , [ 12, 16, 20 ]
// , [ 15, 8, 17 ]
// , [ 16, 12, 20 ]
// ]
I think using map (and reduce), while it allows for more complex recursive structures as you demonstrate, is actually an implicit for loop, which does not really answer the question on how to convert one into a recurrence. However, if you also defined a recursive map and reduce, then it would be OK :) - גלעד ברקן
Your wish is my command :D
const Empty =
Symbol ()
const concat = (xs, ys) =>
xs.concat (ys)
const append = (xs, x) =>
concat (xs, [ x ])
const reduce = (f, acc = null, [ x = Empty, ...xs ]) =>
x === Empty
? acc
: reduce (f, f (acc, x), xs)
const mapReduce = (m, r) =>
(acc, x) => r (acc, m (x))
const map = (f, xs = []) =>
reduce (mapReduce (f, append), [], xs)
const flatMap = (f, xs = []) =>
reduce (mapReduce (f, concat), [], xs)
const product = (first = [], ...rest) =>
{
const loop = (comb, first, ...rest) =>
rest.length === 0
? map (x => append (comb, x), first)
: flatMap (x => loop (append (comb, x), ...rest), first)
return loop ([], first, ...rest)
}
const suits =
['♤', '♡', '♧', '♢']
const ranks =
['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
for (const card of product (ranks, suits))
console.log (card)
// same output as above
I don't recommend this but you can do following(as it is difficult to read, for readability and understandability your code is best).
function forLoop(i,j){
if(j===0){
if(i!==0)
forLoop(i-1,4);
console.log(i,j);
}
else{
forLoop(i,j-1);
console.log(i,j);
}
}
forLoop(4,4);
Here's my rendition:
function nested(i, j, maxI, maxJ) {
if (i == maxI) return
console.log(i, j)
if (i < maxI) {
++j < maxJ ? nested(i, j, maxI, maxJ) : nested(++i, 0, maxI, maxJ)
}
}
nested(0, 0, 5, 5)
This is an alternative.
This approach uses param initialization with comma operator (just to make the code shorter).
Additionally, an operator param (callback) to execute any logic for each iteration.
function loop(n, operator, i = 0, j = 0) { // Param initialization.
if (j === n) (j = 0, i++); // Comma operator.
if (i === n) return;
operator(i, j);
loop(n, operator, i, ++j);
}
loop(5, (i, j) => console.log(i, j));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could use an array for limit and values. The order is reversed, because of the incrementing of the lowest index first.
This works for an arbitrary count of nested loops and it allowes to use an arbitrary limit of the max values.
function iter(limit, values = limit.map(_ => 0)) {
console.log(values.join(' '));
values = values.reduce((r, v, i) => {
r[i] = (r[i] || 0) + v;
if (r[i] >= limit[i]) {
r[i] = 0;
r[i + 1] = (r[i + 1] || 0) + 1;
}
return r;
}, [1]);
if (values.length > limit.length) {
return;
}
iter(limit, values);
}
iter([2, 3]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's an outline of a "recurrence relation," where "each further term of the sequence ... is defined as a function of the preceding terms."
As you are probably aware, recursive functions usually have at least one base case, terminating the recursion, and at least one recursive call. To find a pattern, let's examine the sequence:
0,0
0,1
0,2
0,3
0,4
1,0
1,2
...
Our base case, where the call to a preceding parameter terminates, seems to be 0,0. But this is also where the console logs begin, which means we first have to call all the way back to the base case. For convenience, let's assume the function expects positive parameters:
function f(i, j){
if (i == 0 && j == 0){
console.log(i,j);
return;
}
}
We can also notice that the outer loop, the i, stays constant for each cycle of js:
function f(i, j){
if (i == 0 && j == 0){
console.log(i,j);
return;
}
if (j == 0)
// ... what happens here?
}
but here we get stuck. When j is greater than zero, we can determine that the current term came from f(i, j - 1), but if j is zero in the current term, we have no way of formulating what it was in the preceding term. We need one more parameter:
function f(i, j, jj){
if (i == 0 && j == 0){
console.log(i,j);
return;
}
if (j == 0)
f(i - 1, jj, jj);
else
f(i, j - 1, jj);
console.log(i,j);
}
f(4,4,4);
You could recurse by taking a depth and the values to iterate:
function loop(start, end, depth, exit, ...args){
for(let i = start; i < end; i++)
depth ? loop(start, end, depth - 1, exit, ...args, i) : exit(...args, i);
}
Usable as:
loop(0, 5, 1, (i, j) => console.log(i, j))
The only real usecase would be deeper loops, e.g. this one
If you want it completely without for:
const range = (start, end, cb) =>
(cb(start), start + 1 >= end || range (start + 1, end, cb));
function loop(start, end, depth, exit, ...args){
range(start, end, i =>
depth ? loop(start, end, depth - 1, exit, ...args, i) : exit(...args, i));
}
Try it
Transforming a nested for loop into its recursive counterpart is surprisingly hard. Good question!
You can transform every loop (without a stack) into a tail recursive algorithm. So this rule should hold for a nested loop too.
I think we need two distinct functions to get something equivalent to your two nested loops:
const loop = ([i, j], [k, l]) => {
const loop_ = (k_, l_) => {
if (k_ >= l_) return;
else {
console.log(i, k_);
loop_(k_ + 1, l_);
}
};
if (i >= j) return;
else {
loop_(k, l);
loop([i + 1, j], [k, l]);
}
};
loop([0, 5], [0, 5]);
You have to pass ranges for both the out and the inner loop.
As you can see both recursive calls are in tail position. I think this is the closest equivalent we can get.
suggested solution
function recurse(arg1=0, arg2=0, cb) {
if ( arg2 <= 5 ) {
let _l = arg2++;
if ( arg1 === 5 )
return ;
if ( ++_l === 6 ) {
arg2 = 0;
cb(arg1++, arg2);
recurse(arg1, arg2, cb);
} else {
cb(arg1, arg2 - 1);
recurse(arg1, arg2, cb);
}
}
}
recurse( 0 , 0 , (i,j) => console.log(i,j));