Find all permutations of smaller string s in string b (JavaScript) - javascript

I've been trying to find a O(n) solution to the following problem: Find the number of anagrams (permutations) of string s in string b, where s.length will always be smaller than b.length
I read that the optimal solution involves keeping track of the frequencies of the characters in the smaller string and doing the same for the sliding window as it moves across the larger string, but I'm not sure how that implementation actually works. Right now my solution doesn't work (see comments) but even if it did, it would take O(s + sn) time.
EDIT: Sample input: ('aba', 'abaab'). Output: 3, because 'aba' exists in b starting at index 0, and 'baa' at 1, and 'aab' at 2.
function anagramsInStr(s,b) {
//O(s)
let freq = s.split("").reduce((map, el) => {
map[el] = (map[el] + 1) || 1;
return map;
}, {});
let i = 0, j = s.length;
// O(n)
for (let char in b.split("")) {
// O(s)
if (b.length - char + 1 > s.length) {
let window = b.slice(i,j);
let windowFreq = window.split("").reduce((map, el) => {
map[el] = (map[el] + 1) || 1;
return map;
}, {});
// Somewhere about here compare the frequencies of chars found in the window to the frequencies hash defined in the outer scope.
i++;
j++;
}
}
}

Read through the comments and let me know if you have any questions:
function countAnagramOccurrences(s, b) {
var matchCount = 0;
var sCounts = {}; // counts for the letters in s
var bCounts = {}; // counts for the letters in b
// construct sCounts
for (var i = 0; i < s.length; i++) {
sCounts[s[i]] = (sCounts[s[i]] || 0) + 1;
}
// all letters that occur in sCounts
var letters = Object.keys(sCounts);
// for each letter in b
for (var i = 0; i < b.length; i++) {
// maintain a sliding window
// if we already have s.length items in the counts, remove the oldest one
if (i >= s.length) {
bCounts[b[i-s.length]] -= 1;
}
// increment the count for the letter we're currently looking at
bCounts[b[i]] = (bCounts[b[i]] || 0) + 1;
// test for a match (b counts == s counts)
var match = true;
for (var j = 0; j < letters.length; j++) {
if (sCounts[letters[j]] !== bCounts[letters[j]]) {
match = false;
break;
}
}
if (match) {
matchCount += 1;
}
}
return matchCount;
}
console.log(countAnagramOccurrences('aba', 'abaab')); // 3
EDIT
A note about the runtime: this is sort of O(nk + m), where n is the length of s, m is the length of b, and k is the number of unique characters in b. Since m is always less than n, we can reduce to O(nk), and since k is bounded by a fixed constant (the size of the alphabet), we can further reduce to O(n).

Related

How can I know if the characters of a string repeat themselves the same amount of times when they have a wildcard built-in?

examples:
testing("xyzy**") should be true.
testing("xyzy*") should be false.
Reasoning:
In the first case, it is true because one * can behave as a x and the other a z, so all characteres would of the same amount of y.
In the second case there wouldn't be the same amount of characters repeating itself, cause there is only one *, so it is false.
Here is what I have up until now:
const testing = string => {
var array = []; //array with each character without repeating
var array_R = []; //array with the value the value that each character repeats itself
var array_C = 0; //counter for the size of "array"
//getting the character without repeating
for (var i = 0; i < string.length; i++) {
if (!array.includes(string.charAt(i))) {
array[array_C] = string.charAt(i);
array_R[array_C] = 0;
array_C++;
}
}
//how many each character repeats itself
for (var i = 0; i < array.length; i++) {
for (var j = 0; j < string.length; j++) {
if(array[i] == string.charAt(j)){
array_R[i] = array_R[i] + 1;
}
}
}
}
I really don't know how I can proceed from here.
First count up the number of occurrences of each non-* character into an object or Map. Find the maximum value in that collection, then find the sum of all differences between the maximum value and each value. If the number of asterisks is the same as the sum of differences, the repeat conditions are fulfilled.
You'll also have to consider the case where there are more asterisks left over after all holes are filled in to make the values equal - figure out how many are left over, and see if that evenly divides the number of separate characters.
const testing = (str) => {
const grouped = {};
for (const char of str) {
if (char !== '*') grouped[char] = (grouped[char] || 0) + 1;
}
const values = Object.values(grouped);
const numOfSeparateChars = values.length;
const max = Math.max(...values);
const sum = values.reduce((a, b) => a + b, 0);
const sumOfDifferencesFromMax = max * numOfSeparateChars - sum;
const numberAsterisks = (str.match(/\*/g) || []).length;
const remainingAsterisks = sumOfDifferencesFromMax - numberAsterisks;
// Now all characters have been filled in to make the values even
// eg: 'abcc**' -> 'abccab'
// But there may be more asterisks
// eg: 'abcc*****' -> 'abccaabbc'
return remainingAsterisks % numOfSeparateChars === 0;
};
console.log(testing("xyzy**"));
console.log(testing("xyzy*"));

Character with longest consecutive repetition

i think i have wirtten the correct code for the problem only one thing and it that i return the first longest sequence how can i alter that to return the last maximum sequence?
an example from codewars editor :
for input '00000000000000111111111111111112222222222222223333333333333344444444444445555555555555666666666666777777777777888888888888888999999999999999999aaaaaaaaabbbbbbbbbbbbbbbbcccccccccccccccccccdddddddddddddddddddeeeeeeeeeeeeeeefffffffffffffggggggggggggggghhhhhhhhhhhhhiiiiiiiiiijjjjjjjjjjjjjjkkkkkkkkkkkkllllllllllmmmmmmmmmmnnnnnnnnnnnnnnoooooooooooopppppppppppppppppqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrssssssssssttttttttttttuuuuuuvvvvvvvvvvvvvvvvvwwwwwwwwwwwwwwwwxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyzzzzzzzzzzzzzz'
Expected: ['c', 19], instead got: ['0', 19]
here is my code:
function longestRepetition(s) {
var count = 0;
var temp = s.charAt(0);
var arr = [];
for (var i = 0; i < s.length; i++) {
if (temp === s.charAt(i)) {
count++
temp = s.charAt(i)
}
else {
temp = s.charAt(i);
arr.push(count)
count = 1;
}
if(i==s.length-1)
arr.push(count);
}
if(arr.length>0)
{
var Max=arr[0]
for(var i=0;i<arr.length;i++)
{
if(Max<=arr[i])
Max=arr[i];
}
}
else var Max=0;
var mindex=arr.indexOf(Max);
return [s.charAt(mindex),Max]
}
I think this would be easier with a regular expression. Match any character, then backreference that character as many times as you can.
Then, you'll have an array of all the sequential sequences, eg ['000', 'aaaaa']. Map each string to its length and pass into Math.max, and you'll know how long the longest sequence is.
Lastly, filter the sequences by those which have that much length, and return the last item in the filtered array:
function longestRepetition(s) {
const repeatedChars = s.match(/(.)\1*/g);
const longestLength = Math.max(...repeatedChars.map(str => str.length));
const longestChars = repeatedChars.filter(str => str.length === longestLength);
return [longestChars.pop(), longestLength];
}
console.log(longestRepetition('00000000000000111111111111111112222222222222223333333333333344444444444445555555555555666666666666777777777777888888888888888999999999999999999aaaaaaaaabbbbbbbbbbbbbbbbcccccccccccccccccccdddddddddddddddddddeeeeeeeeeeeeeeefffffffffffffggggggggggggggghhhhhhhhhhhhhiiiiiiiiiijjjjjjjjjjjjjjkkkkkkkkkkkkllllllllllmmmmmmmmmmnnnnnnnnnnnnnnoooooooooooopppppppppppppppppqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrssssssssssttttttttttttuuuuuuvvvvvvvvvvvvvvvvvwwwwwwwwwwwwwwwwxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyzzzzzzzzzzzzzz'));
The issue in your code is that minindex is an index in your arr, but that index has nothing to do with s. So s.charAt(minindex) makes no sense. You should maintain for which character you had found the count. For instance you could push in arr both the count and the corresponding character (as a subarray with two values). Then the rest of your code would only need little modification to make it work.
Applying this idea to your code without changing anything else, we get this:
function longestRepetition(s) {
var count = 0;
var temp = s.charAt(0);
var arr = [];
for (var i = 0; i < s.length; i++) {
if (temp === s.charAt(i)) {
count++
temp = s.charAt(i) // Not necessary: was already equal
}
else {
arr.push([temp, count]); // <--- pair, BEFORE changing temp
temp = s.charAt(i);
count = 1;
}
if(i==s.length-1)
arr.push([temp, count]); // <---
}
if(arr.length>0)
{
var Max=arr[0]; // <-- Max is now a pair of char & count
for(var i=0;i<arr.length;i++)
{
if(Max[1]<arr[i][1]) // Comparison changed to just less-than
Max=arr[i];
}
}
else Max=[null, 0]; // Must be a pair here also
return Max; // Just return the pair
}
console.log(longestRepetition('00000000000000111111111111111112222222222222223333333333333344444444444445555555555555666666666666777777777777888888888888888999999999999999999aaaaaaaaabbbbbbbbbbbbbbbbcccccccccccccccccccdddddddddddddddddddeeeeeeeeeeeeeeefffffffffffffggggggggggggggghhhhhhhhhhhhhiiiiiiiiiijjjjjjjjjjjjjjkkkkkkkkkkkkllllllllllmmmmmmmmmmnnnnnnnnnnnnnnoooooooooooopppppppppppppppppqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrssssssssssttttttttttttuuuuuuvvvvvvvvvvvvvvvvvwwwwwwwwwwwwwwwwxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyzzzzzzzzzzzzzz'));
But you can do the same with less code:
function longestRepetition(s) {
let result = [null, 0]; // pair of character and count
for (var i = 0; i < s.length; null) {
let start = i++;
while (i < s.length && s[i] === s[start]) i++; // Find end of series
if (i - start > result[1]) result = [s[start], i - start];
}
return result;
}
console.log(longestRepetition('00000000000000111111111111111112222222222222223333333333333344444444444445555555555555666666666666777777777777888888888888888999999999999999999aaaaaaaaabbbbbbbbbbbbbbbbcccccccccccccccccccdddddddddddddddddddeeeeeeeeeeeeeeefffffffffffffggggggggggggggghhhhhhhhhhhhhiiiiiiiiiijjjjjjjjjjjjjjkkkkkkkkkkkkllllllllllmmmmmmmmmmnnnnnnnnnnnnnnoooooooooooopppppppppppppppppqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrssssssssssttttttttttttuuuuuuvvvvvvvvvvvvvvvvvwwwwwwwwwwwwwwwwxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyzzzzzzzzzzzzzz'));
The solution below answers the question with O(n) runtime:
function longestRepetition(s) {
let count = s.length > 0 ? 1 : 0
let char = s.length > 0 ? s[0] : ''
for (let string_i = 0; string_i < s.length - 1; string_i += 1) {
// keep track of current_char
let current_char = s[string_i]
let next_char = s[string_i + 1]
// while the next char is same as current_char
let tracker = 1
while (current_char === next_char) {
// add one to tracker
tracker += 1
string_i += 1
next_char = s[string_i + 1]
}
// if tracker greater than count
if (tracker > count) {
// returned char = current char
// count =tracker
count = tracker;
char = current_char;
}
}
return [char, count]
}
console.log(longestRepetition("bbbaaabaaaa"))//, ["a",4]

How to calculate what is probability of getting same result 8 times in a row, when flipping coin 1000 times?

I've tried to use this code:
function calc (n, c) {
let a = 0
const omega = Math.pow(2, n)
let search1 = ''
let search2 = ''
for (let i = 0; i < c; i++) {
search1 += '0'
}
for (let i = 0; i < c; i++) {
search2 += '1'
}
for (let i = 0; i < omega; i++) {
if (i.toString(2).includes(search1) || i.toString(2).includes(search2)) {
a++
}
}
const prob = a * 100 / omega
console.log({ a: a, omega: omega, prob: prob.toFixed(2) })
}
calc(1000, 8)
Which works, but is slow when it comes to big numbers. How can I optimize my code to make it faster? Or maybe there exists a Mathematical solution, that doesn't require to code at all? I just want to know the solution for this problem.
First a Monte Carlo simulation answer:
You can find a confidence interval for this simulation by doing some statistical inference on the Bernoulli distribution which I won't do here.
function doesItHappen(l,r){
var lastValue = null;
var lastN = 0;
for(var i = 0; i < l; i++){
var currentValue = Math.random() > 0.5 ? 1 : 0;
if(lastValue === currentValue) {
lastN++;
} else {
lastValue = currentValue;
lastN = 1;
}
if(lastN === r) return true;
}
return false;
}
function rep(n,l,r){
var t = 0;
for(var i = 0; i < n; i++) {
if(doesItHappen(l,r)) t++;
}
return t/n;
}
console.log(rep(100000,1000,8))
Finally the actual Mathematical answer
I couldn't find a solution to this question online so I came up with my own method to calculate this in o(n) time and space complexity, you can even get it down to o(1) space complexity by discarding valueStore objects older than the length of consecutive sequence you want. The key thing is to recognise you have to computer all the combinations prior to the current length, similar to a Fibonacci sequence.
function calculateProbability(l,r) {
var valueStore = [
{ // Initialize it
totalNumberOfCombinations: 2,
numberOfCombinationsWithSequence: 0
}
];
function getValues(index) {
// combinations with the sequence in it
// There are two ways a consecutive sequence of r length can occur, it either occured in the previous combination and we flipped a new heads or tails(doesn't matter)
// Or this flip resulted in a new consecutive sequence of r length occuring (let's say theres k combinations of this)
// Heres the genius, k must end in a sequence of heads or tails so theres 2 possible endings, the beginnings of k cannot contain the sequence of r consecutive flips
// If this previous combination ends in a head then the new sequence is all tails and vice versa
// So k = the combinations of flips without the consective flips before the current sequence
// k = the totalNumberOfCombinations 8 flips ago - numberOfCombinationsWithSequence 8 flips ago
if (index === r - 1) {
// All heads or all tails
var numberOfCombinationsWithSequence = 2;
} else if(index < r) {
var numberOfCombinationsWithSequence = 0;
} else {
var numberOfCombinationsWithSequence = valueStore[index - 1].numberOfCombinationsWithSequence * 2 + (valueStore[index - r].totalNumberOfCombinations - valueStore[index - r].numberOfCombinationsWithSequence)
}
return {
// total possible combinations
// this is just the previous number of combinations but times 2 since we flip again
totalNumberOfCombinations: valueStore[index - 1].totalNumberOfCombinations * 2,
numberOfCombinationsWithSequence: numberOfCombinationsWithSequence
}
}
for(var i = 1; i < l; i++) {
var values = getValues(i);
valueStore.push(values);
}
return valueStore[valueStore.length - 1].numberOfCombinationsWithSequence / valueStore[valueStore.length - 1].totalNumberOfCombinations;
}
console.log(calculateProbability(1000,8));
The 100% accurate answer is 0.9817098435878764 or 98.17%
how about a simulation?
function simulate(throws, streak, runs) {
let win = "".padStart(streak, "1")
let win2 = "".padStart(streak, "0")
let hits = 0
for (let n = 0; n < runs; n++) {
let res = "";
for (let i = 0; i < throws; i++) {
let val = Math.round(Math.random())
res += val
}
if (res.includes(win) || res.includes(win2)) {
hits++
}
}
console.log({
hits,
runs,
prob: ((hits / runs) * 100).toFixed(2)
})
}
simulate(1000, 8, 10000)

Measuring the identicality of strings (in Javascript)

In principle this question can be answered language-independent, but specifically I am looking for a Javascript implementation.
Are there any libraries that allow me to measure the "identicality" of two strings? More generally, are there any algorithms that do this, that I could implement (in Javascript)?
Take, as an example, the following string
Abnormal Elasticity of Single-Crystal Magnesiosiderite across the Spin
Transition in Earth’s Lower Mantle
And also consider the following, slightly adjusted string. Note the boldface parts that are different
bnormal Elasticity of Single Crystal Magnesio-Siderite across the Spin-Transition in Earths Lower Mantle.
Javascript's native equality operators won't tell you a lot about the relation between these strings. In this particular case, you could match the strings using regex, but in general that only works when you know which differences to expect. If the input strings are random, the generality of this approach breaks down quickly.
Approach... I can imagine writing an algorithm that splits up the input string in an arbitrary amount N of substrings, and then matching the target string with all those substrings, and using the amount of matches as a measurement of identicality. But this feels like an unattractive approach, and I wouldn't even want to think about how big O will depend on N.
It would seem to me that there are a lot of free parameters in such an algorithm. For example, whether case-sensitivity of characters should contribute equally/more/less to the measurement than order-preservation of characters, seems like an arbitrary choice to make by the designer, i.e.:
identicality("Abxy", "bAxy") versus identicality("Abxy", "aBxy")
Defining the requirements more specifically...
The first example is the scenario in which I could use it. I'm loading a bunch of strings (titles of academic papers), and I check whether I have them in my database. However, the source might contain typos, differences in conventions, errors, whatever, which makes matching hard. There probably is a more easy way to match titles in this specific scenario: as you can sort of expect what might go wrong, this allows you to write down some regex beast.
You can implement Hirschberg's algorithm and distinguish delete/insert operations (or alter Levenshtein).
For Hirschbers("Abxy", "bAxy") the results are:
It was 2 edit operations:
keep: 3
insert: 1
delete: 1
and for Hirschbers("Abxy", "aBxy") the results are:
It was 2 edit operations:
keep: 2
replace: 2
You can check the javascript implementation on this page.
'Optimal' String-Alignment Distance
function optimalStringAlignmentDistance(s, t) {
// Determine the "optimal" string-alignment distance between s and t
if (!s || !t) {
return 99;
}
var m = s.length;
var n = t.length;
/* For all i and j, d[i][j] holds the string-alignment distance
* between the first i characters of s and the first j characters of t.
* Note that the array has (m+1)x(n+1) values.
*/
var d = new Array();
for (var i = 0; i <= m; i++) {
d[i] = new Array();
d[i][0] = i;
}
for (var j = 0; j <= n; j++) {
d[0][j] = j;
}
// Determine substring distances
var cost = 0;
for (var j = 1; j <= n; j++) {
for (var i = 1; i <= m; i++) {
cost = (s.charAt(i-1) == t.charAt(j-1)) ? 0 : 1; // Subtract one to start at strings' index zero instead of index one
d[i][j] = Math.min(d[i][j-1] + 1, // insertion
Math.min(d[i-1][j] + 1, // deletion
d[i-1][j-1] + cost)); // substitution
if(i > 1 && j > 1 && s.charAt(i-1) == t.charAt(j-2) && s.charAt(i-2) == t.charAt(j-1)) {
d[i][j] = Math.min(d[i][j], d[i-2][j-2] + cost); // transposition
}
}
}
// Return the strings' distance
return d[m][n];
}
alert(optimalStringAlignmentDistance("Abxy", "bAxy"))
alert(optimalStringAlignmentDistance("Abxy", "aBxy"))
Damerau-Levenshtein Distance
function damerauLevenshteinDistance(s, t) {
// Determine the Damerau-Levenshtein distance between s and t
if (!s || !t) {
return 99;
}
var m = s.length;
var n = t.length;
var charDictionary = new Object();
/* For all i and j, d[i][j] holds the Damerau-Levenshtein distance
* between the first i characters of s and the first j characters of t.
* Note that the array has (m+1)x(n+1) values.
*/
var d = new Array();
for (var i = 0; i <= m; i++) {
d[i] = new Array();
d[i][0] = i;
}
for (var j = 0; j <= n; j++) {
d[0][j] = j;
}
// Populate a dictionary with the alphabet of the two strings
for (var i = 0; i < m; i++) {
charDictionary[s.charAt(i)] = 0;
}
for (var j = 0; j < n; j++) {
charDictionary[t.charAt(j)] = 0;
}
// Determine substring distances
for (var i = 1; i <= m; i++) {
var db = 0;
for (var j = 1; j <= n; j++) {
var i1 = charDictionary[t.charAt(j-1)];
var j1 = db;
var cost = 0;
if (s.charAt(i-1) == t.charAt(j-1)) { // Subtract one to start at strings' index zero instead of index one
db = j;
} else {
cost = 1;
}
d[i][j] = Math.min(d[i][j-1] + 1, // insertion
Math.min(d[i-1][j] + 1, // deletion
d[i-1][j-1] + cost)); // substitution
if(i1 > 0 && j1 > 0) {
d[i][j] = Math.min(d[i][j], d[i1-1][j1-1] + (i-i1-1) + (j-j1-1) + 1); //transposition
}
}
charDictionary[s.charAt(i-1)] = i;
}
// Return the strings' distance
return d[m][n];
}
alert(damerauLevenshteinDistance("Abxy", "aBxy"))
alert(damerauLevenshteinDistance("Abxy", "bAxy"))
Optimal String Alignment has better performance
Optimal String Alignment Distance 0.20-0.30ms
Damerau-Levenshtein Distance 0.40-0.50ms

Variable amount of nested for loops

Edit: I'm sorry, but I forgot to mention that I'll need the values of the counter variables. So making one loop isn't a solution I'm afraid.
I'm not sure if this is possible at all, but I would like to do the following.
To a function, an array of numbers is passed. Each number is the upper limit of a for loop, for example, if the array is [2, 3, 5], the following code should be executed:
for(var a = 0; a < 2; a++) {
for(var b = 0; b < 3; b++) {
for(var c = 0; c < 5; c++) {
doSomething([a, b, c]);
}
}
}
So the amount of nested for loops is equal to the length of the array. Would there be any way to make this work? I was thinking of creating a piece of code which adds each for loop to a string, and then evaluates it through eval. I've read however that eval should not be one's first choice as it can have dangerous results too.
What technique might be appropriate here?
Recursion can solve this problem neatly:
function callManyTimes(maxIndices, func) {
doCallManyTimes(maxIndices, func, [], 0);
}
function doCallManyTimes(maxIndices, func, args, index) {
if (maxIndices.length == 0) {
func(args);
} else {
var rest = maxIndices.slice(1);
for (args[index] = 0; args[index] < maxIndices[0]; ++args[index]) {
doCallManyTimes(rest, func, args, index + 1);
}
}
}
Call it like this:
callManyTimes([2,3,5], doSomething);
Recursion is overkill here. You can use generators:
function* allPossibleCombinations(lengths) {
const n = lengths.length;
let indices = [];
for (let i = n; --i >= 0;) {
if (lengths[i] === 0) { return; }
if (lengths[i] !== (lengths[i] & 0x7fffffff)) { throw new Error(); }
indices[i] = 0;
}
while (true) {
yield indices;
// Increment indices.
++indices[n - 1];
for (let j = n; --j >= 0 && indices[j] === lengths[j];) {
if (j === 0) { return; }
indices[j] = 0;
++indices[j - 1];
}
}
}
for ([a, b, c] of allPossibleCombinations([3, 2, 2])) {
console.log(`${a}, ${b}, ${c}`);
}
The intuition here is that we keep a list of indices that are always less than the corresponding length.
The second loop handles carry. As when incrementing a decimal number 199, we go to (1, 9, 10), and then carry to get (1, 10, 0) and finally (2, 0, 0). If we don't have enough digits to carry into, we're done.
Set up an array of counters with the same length as the limit array. Use a single loop, and increment the last item in each iteration. When it reaches it's limit you restart it and increment the next item.
function loop(limits) {
var cnt = new Array(limits.length);
for (var i = 0; i < cnt.length; i++) cnt[i] = 0;
var pos;
do {
doSomething(cnt);
pos = cnt.length - 1;
cnt[pos]++;
while (pos >= 0 && cnt[pos] >= limits[pos]) {
cnt[pos] = 0;
pos--;
if (pos >= 0) cnt[pos]++;
}
} while (pos >= 0);
}
One solution that works without getting complicated programatically would be to take the integers and multiply them all. Since you're only nesting the ifs, and only the innermost one has functionality, this should work:
var product = 0;
for(var i = 0; i < array.length; i++){
product *= array[i];
}
for(var i = 0; i < product; i++){
doSomething();
}
Alternatively:
for(var i = 0; i < array.length; i++){
for(var j = 0; j < array[i]; j++){
doSomething();
}
}
Instead of thinking in terms of nested for loops, think about recursive function invocations. To do your iteration, you'd make the following decision (pseudo code):
if the list of counters is empty
then "doSomething()"
else
for (counter = 0 to first counter limit in the list)
recurse with the tail of the list
That might look something like this:
function forEachCounter(counters, fn) {
function impl(counters, curCount) {
if (counters.length === 0)
fn(curCount);
else {
var limit = counters[0];
curCount.push(0);
for (var i = 0; i < limit; ++i) {
curCount[curCount.length - 1] = i;
impl(counters.slice(1), curCount);
}
curCount.length--;
}
}
impl(counters, []);
}
You'd call the function with an argument that's your list of count limits, and an argument that's your function to execute for each effective count array (the "doSomething" part). The main function above does all the real work in an inner function. In that inner function, the first argument is the counter limit list, which will be "whittled down" as the function is called recursively. The second argument is used to hold the current set of counter values, so that "doSomething" can know that it's on an iteration corresponding to a particular list of actual counts.
Calling the function would look like this:
forEachCounter([4, 2, 5], function(c) { /* something */ });
This is my attempt at simplifying the non-recursive solution by Mike Samuel. I also add the ability to set a range (not just maximum) for every integer argument.
function everyPermutation(args, fn) {
var indices = args.map(a => a.min);
for (var j = args.length; j >= 0;) {
fn.apply(null, indices);
// go through indices from right to left setting them to 0
for (j = args.length; j--;) {
// until we find the last index not at max which we increment
if (indices[j] < args[j].max) {
++indices[j];
break;
}
indices[j] = args[j].min;
}
}
}
everyPermutation([
{min:4, max:6},
{min:2, max:3},
{min:0, max:1}
], function(a, b, c) {
console.log(a + ',' + b + ',' + c);
});
There's no difference between doing three loops of 2, 3, 5, and one loop of 30 (2*3*5).
function doLots (howMany, what) {
var amount = 0;
// Aggregate amount
for (var i=0; i<howMany.length;i++) {
amount *= howMany[i];
};
// Execute that many times.
while(i--) {
what();
};
}
Use:
doLots([2,3,5], doSomething);
You can use the greedy algorithm to enumerate all elements of the cartesian product 0:2 x 0:3 x 0:5. This algorithm is performed by my function greedy_backward below. I am not an expert in Javascript and maybe this function could be improved.
function greedy_backward(sizes, n) {
for (var G = [1], i = 0; i<sizes.length; i++) G[i+1] = G[i] * sizes[i];
if (n>=_.last(G)) throw new Error("n must be <" + _.last(G));
for (i = 0; i<sizes.length; i++) if (sizes[i]!=parseInt(sizes[i]) || sizes[i]<1){ throw new Error("sizes must be a vector of integers be >1"); };
for (var epsilon=[], i=0; i < sizes.length; i++) epsilon[i]=0;
while(n > 0){
var k = _.findIndex(G, function(x){ return n < x; }) - 1;
var e = (n/G[k])>>0;
epsilon[k] = e;
n = n-e*G[k];
}
return epsilon;
}
It enumerates the elements of the Cartesian product in the anti-lexicographic order (you will see the full enumeration in the doSomething example):
~ var sizes = [2, 3, 5];
~ greedy_backward(sizes,0);
0,0,0
~ greedy_backward(sizes,1);
1,0,0
~ greedy_backward(sizes,2);
0,1,0
~ greedy_backward(sizes,3);
1,1,0
~ greedy_backward(sizes,4);
0,2,0
~ greedy_backward(sizes,5);
1,2,0
This is a generalization of the binary representation (the case when sizes=[2,2,2,...]).
Example:
~ function doSomething(v){
for (var message = v[0], i = 1; i<v.length; i++) message = message + '-' + v[i].toString();
console.log(message);
}
~ doSomething(["a","b","c"])
a-b-c
~ for (var max = [1], i = 0; i<sizes.length; i++) max = max * sizes[i];
30
~ for(i=0; i<max; i++){
doSomething(greedy_backward(sizes,i));
}
0-0-0
1-0-0
0-1-0
1-1-0
0-2-0
1-2-0
0-0-1
1-0-1
0-1-1
1-1-1
0-2-1
1-2-1
0-0-2
1-0-2
0-1-2
1-1-2
0-2-2
1-2-2
0-0-3
1-0-3
0-1-3
1-1-3
0-2-3
1-2-3
0-0-4
1-0-4
0-1-4
1-1-4
0-2-4
1-2-4
If needed, the reverse operation is simple:
function greedy_forward(sizes, epsilon) {
if (sizes.length!=epsilon.length) throw new Error("sizes and epsilon must have the same length");
for (i = 0; i<sizes.length; i++) if (epsilon[i] <0 || epsilon[i] >= sizes[i]){ throw new Error("condition `0 <= epsilon[i] < sizes[i]` not fulfilled for all i"); };
for (var G = [1], i = 0; i<sizes.length-1; i++) G[i+1] = G[i] * sizes[i];
for (var n = 0, i = 0; i<sizes.length; i++) n += G[i] * epsilon[i];
return n;
}
Example :
~ epsilon = greedy_backward(sizes, 29)
1,2,4
~ greedy_forward(sizes, epsilon)
29
One could also use a generator for that:
function loop(...times) {
function* looper(times, prev = []) {
if(!times.length) {
yield prev;
return;
}
const [max, ...rest] = times;
for(let current = 0; current < max; current++) {
yield* looper(rest, [...prev, current]);
}
}
return looper(times);
}
That can then be used as:
for(const [j, k, l, m] of loop(1, 2, 3, 4)) {
//...
}

Categories