I am trying to write a JavaScript function that returns all combinations of the elements of an array of unknown length. The argument to be passed to the function should be an array of single digit strings e.g. [ "5", "7", "9" ].
An example to illustrate the desired functionality:
If you pass in an array of [ "5", "7", "9" ], it should return an array with all the possible 3-digit combinations of those 3 numbers i.e. [ "579", "759", "957", "795",…].
If you passed in an array of [ "2", "5", "4", "6" ], you would get the 4-digit combinations of those 4 numbers, i.e. [ "2546", "2654", "2465",…].
If you passed in an array of length 5, you would get the 5-digit combinations of those 5 numbers, and so on.
If the inputted array has the same digit multiple times, that number should appear multiple times in the resulting array e.g. an input of [ "5", "5", "6" ] produces an output of [ "556", "655", "565",…].
I have looked around and it seems that recursion might be the way to go, but I am struggling to get it working. I have attempted the below solution which currently works for 3-digit numbers but I can’t figure out how to make a function which works with an array of unknown length.
function getDoubleDigitCombinations(input) {
let result = [];
const first = input[0];
const last = input[1];
result.push(first + last);
result.push(last + first);
return result;
}
function getTripleDigitCombinations(input) {
let result = [];
let main; // This is the number in question.
let other1; // These are the other numbers.
let other2;
for (i = 0; i < input.length; i++) {
let arr = input.slice(); // Make a copy of the input array.
main = input[i];
arr.splice(i, 1); // Remove the main element from the array …
other1 = arr[0]; // … so that you are left with the other two numbers.
other2 = arr[1];
console.log(`Main is ${main}`);
console.log(`other1 is ${other1} and other2 is ${other2}`);
const arr2 = getDoubleDigitCombinations([other1, other2]); // Get the combinations of the others.
result.push(main + arr2[0]); // Concatenate main with both of the others combinations.
result.push(main + arr2[1]);
}
return result;
}
let result2 = getTripleDigitCombinations([ "6", "7", "8" ]);
console.log("result2 is ...");
for (i = 0; i < result2.length; i++) {
console.log(result2[i]);
}
Heap's algorithm is still, I believe, the gold standard for efficiency. Victor's answer covers that well.
But since any algorithm has to be at least O(n!), we're never going to get stunning performance in generating permutations. We might want to look also at simplicity.
Another implementation is decidedly simpler:
const excluding = (i) => (xs) =>
[... xs .slice (0, i), ... xs .slice (i + 1)]
const permutations = (xs) =>
xs .length == 0
? [[]]
: xs .flatMap ((x, i) => permutations (excluding (i) (xs)) .map (p => x + p))
console .log (permutations (['5', '6', '7']))
Here we use a small helper function, excluding, which returns a copy of an array removing the value at a given index. Then permutations loops over the values in the array, taking each in turn to be the first value of a set of permutations, and finding the remainder of those permutations by recurring on the array found by excluding the current index.
This has the nice feature that the permutations are returned in an obvious order. If the original array is sorted, say ['a', 'b', 'c'], then the results are returned in alphabetic order: ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']. This is not usually essential, but it can be useful.
Note that this solution is more commonly written with this line:
: xs .flatMap ((x, i) => permutations (excluding (i) (xs)) .map (p => [x, ... p]))
which returns an array of arrays ([['5', '6', '7'], ['5', '7', '6'], ...]) instead of an array of strings (['567', '576', ...]).
There is another version which I've seen recently that is even simpler. It's not original, but I don't recall where I saw it -- probably here on StackOverflow. This is my own implementation of that idea, but I think it's pretty close to the original:
const rotations = ([l, ...ls], rs = []) =>
l == undefined ? [] : [[l, ...ls, ...rs], ... rotations (ls, [...rs, l])]
const permutations = ([l, ...ls]) =>
l == undefined ? [[]] : [...permutations (ls) .flatMap (p => rotations ([l, ...p])) ]
console .log (permutations (['5', '6', '7']) .map (p => p .join ('')))
.as-console-wrapper {max-height: 100% !important; top: 0}
Here we use a function rotations which takes an array and returns all the wrapped-around rotations of that array. For example:
rotations(['x', 'y', 'z'])
//=> [['x', 'y', 'z'], ['y', 'z', 'x'], ['z', 'x', 'y']]
We use that to write permutations by taking out the first element, recursively finding the permutations of the remaining elements, and for each, sticking the first element back on and returning all the rotations.
This is short and quite clever. I would probably stick to either Heap's algorithm for speed or to the one above for the nicely ordered output. But this one is still worth considering for the simplicity.
While this algorithm could be fixed up to return strings, it would be more intrusive than it was in the previous version, involving changes to both rotations and permutations, and I would prefer to stick to generating the strings on the output of it as done above with .map (p => p .join ('')). If you wanted to do it, though, it might look like this:
const rotations = ([l, ...ls], rs = '') =>
l == undefined ? [] : [l + ls.join('') + rs, ... rotations (ls, rs + l)]
const permutations = ([l, ...ls]) =>
l == undefined ? [[]] : [...permutations (ls) .flatMap (p => rotations (l + p)) ]
permutations (['5', '7', '7']) //=> ['577', '775', '757', '577', '775', '757']
A fun problem! I wanted to implement using generators. This allows you to work with the permutations one-by-one as they are generated, rather than having to compute all permutations before the entire answer is provided -
const input =
["🔴","🟢","🔵","🟡"]
for (const p of permutations(input))
console.log(p.join(""))
🔴🟢🔵🟡
🟢🔴🔵🟡
🟢🔵🔴🟡
🟢🔵🟡🔴
🔴🔵🟢🟡
🔵🔴🟢🟡
🔵🟢🔴🟡
🔵🟢🟡🔴
🔴🔵🟡🟢
🔵🔴🟡🟢
🔵🟡🔴🟢
🔵🟡🟢🔴
🔴🟢🟡🔵
🟢🔴🟡🔵
🟢🟡🔴🔵
🟢🟡🔵🔴
🔴🟡🟢🔵
🟡🔴🟢🔵
🟡🟢🔴🔵
🟡🟢🔵🔴
🔴🟡🔵🟢
🟡🔴🔵🟢
🟡🔵🔴🟢
🟡🔵🟢🔴
This allows us to do cool things like, finding specific patterns -
// find all permutations where red is left of green
for (const p of permutations(input))
if (p.indexOf("🔴") < p.indexOf("🟢"))
console.log(p.join(""))
🟡🔵🔴🟢
🔵🟡🔴🟢
🔵🔴🟢🟡
🔵🔴🟡🟢
🟡🔴🟢🔵
🟡🔴🔵🟢
🔴🟢🟡🔵
🔴🟡🟢🔵
🔴🟡🔵🟢
🔴🟢🔵🟡
🔴🔵🟢🟡
🔴🔵🟡🟢
// find all permutations where blue and yellow are adjacent
for (const p of permutations(input))
if (Math.abs(p.indexOf("🔵") - p.indexOf("🟡")) == 1)
console.log(p.join(""))
🟢🟡🔵🔴
🟡🔵🟢🔴
🟡🔵🔴🟢
🟢🔵🟡🔴
🔵🟡🟢🔴
🔵🟡🔴🟢
🟢🔴🟡🔵
🔴🟢🟡🔵
🔴🟡🔵🟢
🟢🔴🔵🟡
🔴🟢🔵🟡
🔴🔵🟡🟢
And if we wanted to find the only the first permutation where such a condition is true, we can use return or break to stop the generator and no more permutations will be computed.
We just have to implement permutations -
function* permutations (t)
{ if (t.length < 2)
yield t
else
for (const p of permutations(t.slice(1)))
for (const r of rotations(p, t[0]))
yield r
}
Which depends on rotations -
function* rotations (t, v)
{ if (t.length == 0)
yield [v]
else
yield *chain
( [[v, ...t]]
, map(rotations(t.slice(1), v), r => [t[0], ...r])
)
}
Which depends on two generic functions for working with iterables, map and chain -
function* map (t, f)
{ for (const e of t)
yield f(e)
}
function* chain (...ts)
{ for (const t of ts)
for (const e of t)
yield e
}
Expand the snippet to verify the results in your own browser
function* permutations (t)
{ if (t.length < 2)
yield t
else
for (const p of permutations(t.slice(1)))
for (const r of rotations(p, t[0]))
yield r
}
function* rotations (t, v)
{ if (t.length == 0)
yield [v]
else
yield *chain
( [[v, ...t]]
, map(rotations(t.slice(1), v), r => [t[0], ...r])
)
}
function* map (t, f)
{ for (const e of t)
yield f(e)
}
function* chain (...ts)
{ for (const t of ts)
for (const e of t)
yield e
}
const input =
["🔴","🟢","🔵","🟡"]
console.log("\nred is left of green")
for (const p of permutations(input))
if (p.indexOf("🔴") < p.indexOf("🟢"))
console.log(p.join(""))
console.log("\nblue and yellow are adjacent")
for (const p of permutations(input))
if (Math.abs(p.indexOf("🔵") - p.indexOf("🟡")) == 1)
console.log(p.join(""))
I hope you enjoyed this post as much as I enjoyed writing it :D
To compute combinations using a similar technique, see this related Q&A.
Heap's algorithm can be used to generate all permutations (without repetitions) of array elements, you can then use those permutations with array.join("") to convert them to strings. This works for arrays of any size and any type:
Here is a quick javascript implementation:
// some global variable to store the results
var result = []
// currentSize should be invoked with the array size
function permutation(arr, currentSize) {
if (currentSize == 1) { // recursion base-case (end)
result.push(arr.join(""));
return;
}
for (let i = 0; i < currentSize; i++){
permutation(arr, currentSize - 1);
if (currentSize % 2 == 1) {
let temp = arr[0];
arr[0] = arr[currentSize - 1];
arr[currentSize - 1] = temp;
} else {
let temp = arr[i];
arr[i] = arr[currentSize - 1];
arr[currentSize - 1] = temp;
}
}
}
let array = ["1","2","3","4"];
permutation(array, array.length);
console.log(result);
// use result here
Keep in mind that this is very computationally expensive, with complexity of O(n!).
The following might serve your need after adjusting argument to accept array instead of a number.
function combinator(nbr) {
if (typeof nbr !== 'number' || nbr>999999999) return 'NaN'
const combinedArr = []
const _nbr = `${nbr}`.split('')
combinatorRec(_nbr, [])
function combinatorRec(_nbr, prev){
if (_nbr.length === 0) {
combinedArr.push(parseInt(prev))
return
}
_nbr.forEach((char,i)=>{
const _nbrI = [..._nbr]
_nbrI.splice(i,1)
combinatorRec(_nbrI, prev+char )
})
}
const uniqueArray = combinedArr.filter((item, pos) => (combinedArr.indexOf(item) === pos))
return uniqueArray
}
Related
In the code below, I am trying to check how many times a letter in a string appears. Problem with the code below is that it prints each letter more than once. It needs to collect all the same letters and show the number of times it occurs in the string and display it once.
const string = 'mississippi'
const letters = [...string]
let currentLetter = ''
let letterOccurance = []
for(let i = 0; i < letters.length; i++){
let letterFrequency = letters.filter((letter)=>{
return letter === letters[i]
})
letterOccurance.push([`${letters[i]}`,letterFrequency.length])
}
console.log(letterOccurance)
That's too much code just to get the number of times a letter appears in a string. Try the following code:
const string = 'mississippi';
let frequency = {};
for (let letter of string) {
if (frequency[letter]) {
frequency[letter]++;
} else {
frequency[letter] = 1;
}
}
console.log(frequency);
You're always pushing the letter to the array, whether it already exists there or not:
letterOccurance.push([`${letters[i]}`,letterFrequency.length])
You could check if it exists first:
if (!letterOccurance.find(l => l[0] === letters[i])) {
letterOccurance.push([`${letters[i]}`,letterFrequency.length])
}
Or even skip it entirely if you've already seen it, since the first time you find any letter you already know its complete count:
for(let i = 0; i < letters.length; i++){
if (letterOccurance.find(l => l[0] === letters[i])) {
continue;
}
// the rest of the loop...
}
There's honestly a variety of ways you could step back and re-approach the problem. But for the question about why letters are repeating, that's simply because each iteration of the loop unconditionally appends the letter to the resulting array.
How about writing a more generic item-counting function and then layering countLetters as a simple partial application of the identity function?
const countBy = (fn) => ([...xs]) =>
xs .reduce ((a, x) => {const k = fn (x); a [k] = (a[k] || 0) + 1; return a}, {})
const countLetters = countBy (x => x)
console .log (countLetters ('missisippi'))
countBy is fairly generic. You pass it a function to convert your values to strings, and pass your array of items to the function it returns. Strings are array-like enough that this just works for our simple countLetters. But we could use it for other counts as well, such as:
countBy (x => x .grade) ([{id: 1, grade: 'A'}, {id: 2, grade: 'B'}, {id: 3, grade: 'A'}])
//=> {"A": 2, "B": 1}
Here's a solution using a Set to get the individual letters and String.split() to count.
const countChars = str => Object.fromEntries(
[...new Set(str)]
.map(c => [c, str.split(c).length-1])
)
console.log(countChars('mississippi'));
Using reduce to build the object
const countChars = str => [...str].reduce(
(a, c) => (a[c] ? a[c]++ : a[c]=1, a),
{}
)
console.log(countChars('mississippi'));
var result =
For example: "Today, is the greatest day ever!" should return greatest because it has 2 e's (and 2 t's) and it comes before ever which also has 2 e's. If there are no words with repeating letters return -1. Words will be separated by spaces.
Input:"Hello Apple Pie"
Output should be:"Hello"
Do not understand what is wrong with my code, also if you think there is a simpler and shorter way to solve I would love to hear it. Thanks in advance for the help.
function LetterCountI(str) {
let unique=[... new Set(str)]
if(unique==str){
return -1}
let arr= str.split(" ")
let array=arr.map(item=>{
let temparr=item.split("")
return temparr.reduce((acc,curr)=>{
acc[curr]=acc[curr]? acc[curr]+1:1
if(acc[curr]>acc.max){
acc.max=acc[curr]}
return acc},{max:1, word:item})})
let amount=1
let largest=""
for(let item of arr){
if( item.max>amount){
amount=item.max
largest=item.word
}
}
return largest
}
Start with the simpler problem of getting the frequency of each letter in a word...
// given "sassy", will return { s:3, a:1, y:1 }
function letterFrequency(word) {
return word.split('').reduce((acc, letter) => {
if (!acc[letter]) acc[letter] = 0;
acc[letter]++;
return acc;
}, {});
}
A simple problem to get the max frequency...
// given { s:3, a:1, y:1 }, will return 3
function maxLetterFrequency(word) {
return Math.max(...Object.values(letterFrequency(word)));
}
Another simple problem to sort a sentence by frequency...
// given "She sells seashells by the seashore"
// returns ["seashells", "sells", "seashore", "She", "by", "the"]
function wordsOrderedByMaxLetterFrequency(sentence) {
let words = sentence.split(' ');
words.sort((a, b) => {
return maxLetterFrequency(b) - maxLetterFrequency(a);
});
return words;
}
The first word in the resulting array is the answer. You can retest max frequencies in that word to determine of the answer should be -1.
Demo...
// given "sassy", will return { s:3, a:1, y:1 }
function letterFrequency(word) {
return word.split('').reduce((acc, letter) => {
if (!acc[letter]) acc[letter] = 0;
acc[letter]++;
return acc;
}, {});
}
// given { s:3, a:1, y:1 }, will return 3
function maxLetterFrequency(word) {
return Math.max(...Object.values(letterFrequency(word)));
}
// given "She sells seashells by the seashore"
// returns ["seashells", "sells", "seashore", "She", "by", "the"]
function wordsOrderedByMaxLetterFrequency(sentence) {
let words = sentence.split(' ');
words.sort((a, b) => {
return maxLetterFrequency(b) - maxLetterFrequency(a);
});
return words;
}
const sentence = "She sells seashells by the seashore";
const sorted = wordsOrderedByMaxLetterFrequency(sentence);
console.log(sorted);
console.log('best word:', sorted[0]);
console.log('max freq in best word:', maxLetterFrequency(sorted[0]));
My initial approach would involve separating out a maxBy function that accepts a function extracting a comparison number from an input value and returns a function from a list of values to the largest one of those.
Then we could write letterCount to count the occurrences of various letters in a string, and wrap that up with a function, maxLetterCount that calculates the maximum letter count for a string, using Math .max on the values from letterCount, and write our main function to split your initial string into words and call maxBy using maxLetterCount against the list of words. It could look like this:
// DO NOT USE -- Doesn't meet all requirements!
const maxBy = (fn) => (xs) => xs .reduce (
({m, r}, x) => {const v = fn(x); return v > m ? {m: v, r: x} : {m, r}},
{m: -Infinity}
) .r
const letterCount = ([...cs]) =>
cs .reduce ((a, c) => {a [c] = (a [c] || 0) + 1; return a}, {})
const maxLetterCount = (cs) =>
Math .max (... Object .values (letterCount (cs)))
const letterCountI = (s) =>
maxBy (maxLetterCount) (s .split (/\s+/))
console .log (letterCountI ("Today, is the greatest day ever!"))
console .log (letterCountI ("Hello Apple Pie"))
But there is a problem with this approach. It doesn't take into account your requirement that if there are no duplicated letters, we must return -1. Right now, letterCountI ("Today is the day") will return "Today".
One fix for this problem might involve pairing the words with their max letter counts, filtering these pairs to only those which have multiply-occurring letters, then use maxBy again on the pairs, and finally pull the word out of the winning pair. To handle the -1 case we could insert a dummy pair into the filtered list with -1 for the word and -Infinity for the letter count. If the list is otherwise empty, then we will choose this one.
And doing this might lead us to add some defaulting behavior to maximumBy, which right now is limited to numbers, but ideally should work with anything we can compare using <. If we defaulted our lower bound of -Infinity, but allowing override, and paired that with a default value for an empty list, then we could probably do the above in a fairly simple way.1
But that feels a bit overcomplicated. Perhaps a simpler approach would be to simply perform the code as above, then test if the resulting word has any repeated letter. While we could track this through the function, I think the simplest version would be to call letterCount again on it. So this is how I would probably choose to write this function:
const maxBy = (fn) => (xs) => xs .reduce (
({m, r}, x) => {const v = fn(x); return v > m ? {m: v, r: x} : {m, r}},
{m: -Infinity}
) .r
const letterCount = ([...cs]) =>
cs .reduce ((a, c) => {a [c] = (a [c] || 0) + 1; return a}, {})
const maxLetterCount = (cs) =>
Math .max (... Object .values (letterCount (cs)))
const letterCountI = (s, target = maxBy (maxLetterCount) (s .split (/\s+/))) =>
maxLetterCount (target) > 1 ? target : -1
console .log (letterCountI ("Today, is the greatest day ever!"))
console .log (letterCountI ("Hello Apple Pie"))
console .log (letterCountI ("Today, is the day!"))
1 That might look like this (untested) version:
const maximumBy = (fn, {dfltVal = '', lowBound = -Infinity} = {}) => (xs) => xs .reduce (
({m, r}, x) => {const v = fn(x); return v > m ? {m: v, r: x} : {m, r}},
{m: lowBound, r: dfltVal}
) .r
Staying with the OP's reduce based approach one would provide a single function which incorporates two specific nested reduce tasks responsible for 1) aggregating the word specific statistics of a sentence and 2) the letter specific statistics for each word.
This approach also ensures the result of '' (suggested) or -1 (the OP'S requirement) in case there are only words of unique (non repeating) letters/characters.
function getFirstOccurringWordOfMaximumSameCharacterCount(value) {
function aggregateCharCountData(wordStats, char, idx, arr) {
const { charStats } = wordStats;
// aggregate character specific array.
(charStats[char] ??= []).push(char);
if (idx >= (arr.length - 1)) {
// console.log({ charStats });
// aggregate maximum character count data
// at the end of the last character reduce step.
const maxCharList = Object
.values(charStats)
.sort((aCharList, bCharList) =>
bCharList.length - aCharList.length
)[0];
wordStats.maxCharCount = maxCharList.length;
wordStats.maxCountChar = maxCharList[0];
}
return wordStats;
}
function aggregateWordSpecificCharCountData(collector, word, idx, arr) {
const { statistics } = collector;
statistics.push(
word
.split('')
.reduce(aggregateCharCountData, { word, charStats: {} })
);
if (idx >= (arr.length - 1)) {
// find the first occurring word of maximum same character count
// at the end of the last word reduce step.
const wordStats = statistics
.sort((aWordStats, bWordStats) =>
bWordStats.maxCharCount - aWordStats.maxCharCount
)[0];
// console.log({ statistics });
collector.result = (wordStats.maxCharCount >= 2)
? wordStats.word
: ''; // : -1;
}
return collector;
}
const {
// statistics,
result,
} = String(value)
.split(/\s+/)
.reduce(aggregateWordSpecificCharCountData, {
statistics: [],
result: '',
});
// console.log({ statistics });
return result;
}
console.log([
'Today is the day.', // ''
'Today, is the greatest day ever!', // 'greatest'
'Hello Apple Pie', // 'Hello'
'She sells seashells by the seashore' // 'seashells'
].map(getFirstOccurringWordOfMaximumSameCharacterCount));
.as-console-wrapper { min-height: 100%!important; top: 0; }
How can i combine the arrays?
[
['1','1','1','2','1'],
['3','3','4','4','4'],
['6','6','7','8','7'],
]
with those arrays there are many combinations, some of the are these:
1,3,6
1,3,6 -> this not, because its duplicate
1,4,7
2,4,8
1,4,7 -> this not, because its duplicate
how can we replace the wrong combination? one solution will be:
1,3,6
1,3,7 -> fixed
1,4,6
1,4,8
2,4,7 -> fixed
rules:
in the first column, 1 should appear 4 times
in the first column, 2 should appear 1 times
in the second column, 3 should appear 2 times
in the second column, 4 should appear 3 times
in the third column, 6 should appear 2 times
in the third column, 7 should appear 2 times
in the third column, 8 should appear 1 times
my attempt is: i would like to generate many combinations, but its not safe the number of appears
function available(array) {
let currentIndex = array.length,
randomIndex;
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]
];
}
return array;
}
const arrays = [
['1', '1', '1', '2', '1'],
['3', '3', '4', '4', '4'],
['6', '6', '7', '8', '7'],
]
for (const current of arrays) {
console.log(available(current))
}
Ignore that the individual strings contain digits. This doesn't matter. At that level, all that matters is that they're strings. (We wouldn't care about that either, just that they're some kind of comparable symbol, but we can take advantage of the fact that they're stings in validation.)
Integers, in general, don't have to have any particular representation. Just because, say, 3 is shaped like that doesn't say anything about the concept of 3. It could be represented in any way so long as it's unique relative to others of its type. For example, we could represent it as [1,1,1].
For each row, we need to iterate through the permutations of that row. Heap's Algorithm allows us to do that in a predictable way without breaking the rules for how many of each kind of item goes in each row.
If we think of that iteration as a kind of counting (the first permutation is 1, the next is 2, etc.), we can count through the permutations of each row as though they were digits in a strange kind of integer. In this way, we can iterate over all combinations of the permutations of the rows.
All that's left is validating each result against duplicate columns. We do this by joining along columns, and adding the results to a set. So long as the set ends up with the same length as the rows, we know there were no duplicates.
solve() only returns the first solution encountered, if any. However, it would be pretty simple to adjust it to return all solutions.
We use .every as a kind of .forEach with the additional ability to end the loop early and to signal whether or not that happened.
function* is known as a generator function. They return a generator object, a thing that wraps the function and lets you return values (via yield) and then later come back and resume the function from that point.
// Heap's Algorithm modified to skip swaps of the same value
// Allows us to iterate over permutations of a row
function* iterHeapsAlgo(arr) {
const A = Array.from(arr); // shallow copy
const len = A.length;
const c = new Array(len).fill(0);
let i = 0;
yield A;
while(i < len) {
if(c[i] < i) {
let j = i&1 ? c[i] : 0;
if(A[i] != A[j]) {
[A[j], A[i]] = [A[i], A[j]];
yield A;
}
c[i]++;
i = 0;
} else {
c[i] = 0;
i++;
}
}
};
// Iterate over all combinations of all rows
// Typical counting algorithm with shortcut to take advantage of exposed state
function* iterCount(data) {
const state = data.map(v => iterHeapsAlgo(v));
const current = state.map(v => v.next().value);
yield current;
while(true) {
const isEnd = state.every((v,i) => {
let n = v.next();
if(n.done) {
state[i] = iterHeapsAlgo(data[i]);
current[i] = state[i].next().value;
return true;
}
});
if(isEnd) return;
yield current;
}
}
const validate = data => {
const set = new Set();
return data[0].every((_,i) => {
set.add(data.reduce((s,v) => `${s}\uffff${v[i]}`, ''));
return set.size-1 == i;
});
};
const solve = start => {
for(const current of iterCount(start)) {
if(validate(current)) {
return current.map(v => Array.from(v)); // depth 2 copy
}
}
return null;
};
console.log(solve([
['1','1','1','2','1'],
['3','3','4','4','4'],
['6','6','7','8','7'],
]) || 'No solution found');
.as-console-wrapper { top: 0; max-height: 100% !important; }
EDIT: Adjustments based on comments. Thanks #Mulan and #ScottSauyet
Maybe I'm missing something, but it seems like you're looking for the product of the arrays with only unique combinations of elements -
const unique = a =>
Array.from(new Set(a))
const product = t =>
t.length == 0
? [[]]
: product(t.slice(1)).flatMap(p => unique(t[0]).map(v => [v, ...p]))
const myinput = [
['1','1','1','2','1'],
['3','3','4','4','4'],
['6','6','7','8','7'],
]
for (const combination of product(myinput))
console.log(String(combination))
1,3,6
2,3,6
1,4,6
2,4,6
1,3,7
2,3,7
1,4,7
2,4,7
1,3,8
2,3,8
1,4,8
2,4,8
By inverting the loops, we can generate the unique products in lexicographical order -
const bind = (f, x) => f(x)
const unique = a =>
Array.from(new Set(a))
const product = t =>
t.length == 0
? [[]]
: bind
( r => unique(t[0]).flatMap(v => r.map(p => [v, ...p]))
, product(t.slice(1))
)
const myinput = [
['1','1','1','2','1'],
['3','3','4','4','4'],
['6','6','7','8','7'],
]
for (const combination of product(myinput))
console.log(String(combination))
1,3,6
1,3,7
1,3,8
1,4,6
1,4,7
1,4,8
2,3,6
2,3,7
2,3,8
2,4,6
2,4,7
2,4,8
Finally we can generate the products lazily by using a generator -
const unique = a =>
Array.from(new Set(a))
function* product(t) {
if (t.length == 0)
yield []
else
for (const p of product(t.slice(1)))
for (const v of unique(t[0]))
yield [v, ...p]
}
const myinput = [
['1','1','1','2','1'],
['3','3','4','4','4'],
['6','6','7','8','7'],
]
for (const combination of product(myinput))
console.log(String(combination))
1,3,6
2,3,6
1,4,6
2,4,6
1,3,7
2,3,7
1,4,7
2,4,7
1,3,8
2,3,8
1,4,8
2,4,8
If you are looking for permutations, see this related Q&A.
Let me preface with I AM A NEWBIE so take it easy on me please lol.
I need help writing a function that takes an array of words and returns an object with the letter and the length of the longest substring of that letter. Each word in the array consists of lowercase letters from a to z and has at least 1 character. You need to concatenate the words within the array to obtain a single word with the longest possible substring composed of one particular letter.
Example:
The input array is ["ccdd", "bbbb", "bbab"] the function should return an object of 6 and “b”. One of the best concatenations is words[1] + words[0] + words[2] = "ccddbbbbbbab"
Input:
longestSingleCharSubstring(["ccdd", "bbbb", "bbab"]);
Output:
{ "letter": "b", "length": 6 }
My issues with this problem are being able to permutate the elements of the array and then loop through the results to find the substring with the most repeating character. I keep getting "abcbundefined" when I return the result from my sample code. Any help would be great, thanks!
function longestSingleCharSubstring(arr) {
let result = [];
let cur_count = 1;
if (arr.length === 0) return "";
if (arr.length === 1) return arr;
for (let i = 0; i < arr.length; i++) {
let currentChar = arr[i];
let remainingChar = arr.slice(0,i) + arr.slice(i + 1);
for (let j = 0; j < remainingChar.length; j++) {
result.push(currentChar + longestSubstring(remainingChar[j]);
}
let len = result.length;
let count = 0;
let finalRes = result[0];
for (let m=0; m<len; m++)
{
for (let k = 0; k < result[m].length; k++)
{
for (let l=k+1; l < result[m].length; l++)
if (k != l)
break;
cur_count++;
}
if (cur_count > count) {
count = cur_count;
finalRes = result[m];
}
}
return finalRes;
}
}
console.log(longestSingleCharSubstring(['abcb','bbbc','aaaa']));
Here's an implementation that uses String.join to concatenate the original array into a long string. Then immediately calls String.split to split the long string into a char array. Next, we set up some variables for the loop and then loop through the char array. In the loop, we reset the current object with a new char and count of 0 when necessary. After incrementing the current count we update the longest result if current.count is longer than longest.count.
const longestSingleCharSubstring = (arr) => {
const charArray = arr.join('').split('')
let i = 0
let longest = {
char: '',
count: 0
}
let current = {
char: '',
count: 0
}
while (i < charArray.length) {
char = charArray[i]
if (current.char != char) {
current = {
char,
count: 0
};
}
current.count += 1;
if (current.count > longest.count) {
longest = { ...current }
}
i += 1;
}
return longest
}
console.log(longestSingleCharSubstring(["ccdd", "bbbb", "bbab"]))
An entirely reduce based approach which processes a splitted list of single characters while tracking the longest available single character sequence might look like the following one ...
function longestSingleCharSequence(value) {
const {
char,
length,
} = String(value) // e.g. 'ccddbbbbbbab'
.split('') // ... ["c","c","d","d","b","b","b","b","b","b","a","b"]
.reduce((collector, char, idx, arr) => {
const { registry } = collector;
const listOfCharSequences = registry[char] || (registry[char] = []);
// if current character and the one before are not equal...
if (char !== arr[idx - 1]) {
// ...start a new char sequence.
listOfCharSequences.push(char);
} else {
// ...continue concatenation of the most recent sequence.
const recentIdx = listOfCharSequences.length - 1;
const sequence = listOfCharSequences[recentIdx] + char;
listOfCharSequences[recentIdx] = sequence;
// ...`char`/`length` statistics is in need of an update.
if (sequence.length >= collector.length) {
collector.length = sequence.length;
collector.char = char;
}
}
return collector;
}, { registry: {}, char: null, length: -1 });
return { char, length };
}
console.log(
"longestSingleCharSequence(['ccdd', 'bbbb', 'bbab'].join('')) ...",
longestSingleCharSequence(['ccdd', 'bbbb', 'bbab'].join(''))
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
A regex based solution which matches letter sequences with any kind of letter from any language based on Unicode Property Escapes ...
limited to latin word characters ... /(\w)\1{1,}/g ... [a-zA-Z0-9_]
idea from above as unicode variant ... /(\p{L})\1{1,}/gu
function longestSingleLetterSequence(value) {
// // [https://regex101.com/r/QFJKr5/1/]
// const regXSingleCharSequence = (/(\w)\1{1,}/g);
// [https://regex101.com/r/QFJKr5/2/]
const regXSingleLetterSequence = (/(\p{L})\1{1,}/gu);
const [ sequence ] = String(value)
.match(regXSingleLetterSequence)
.sort((a, b) => b.length - a.length);
return {
letter: sequence[0],
length: sequence.length,
};
}
console.log(
"longestSingleLetterSequence(['ccdd', 'bbbb', 'bbab'].join('')) ...",
longestSingleLetterSequence(['ccdd', 'bbbb', 'bbab'].join(''))
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
This answer assumes that my suggestion in the comments was correct, namely, that you want something that checks all permutations of the input array for a run of letters. That is, if we're given ['aa', 'ba', 'ac'], then we want to check 'aabaac', 'aaacba', 'baaaac', 'baacaa', 'acaaba', and 'acbaaa' to find that 'baaaac' contains a run of four a's. If this is not the case, then feel free to ignore this one.
Breaking it down
To do this, I would like to break it down in parts. I would like to write a function that takes the input strings, finds all permutations of these strings, concatenates the strings in each permutation into a single string, finds the longest single-letter streak in each one, and then selects the result with the longest streak.
I would like a main function, then, that looks like this:
const longestSingleLetterSequence = (arr) =>
maximumBy (x => x .streak) (permutations (arr) .map (xs => xs .join ('')) .map (longestStreak))
which would be used like this:
longestSingleLetterSequence (['aa', 'ba', 'ac'])
//=> {"char": "a", "streak": 4, "word": "baaaac"}
But this means we need to write three helper functions, longestStreak, permutations, and maximumBy. Note that the last two might be genuinely reusable functions.
So let's look at creating these functions. We can write permutations this way:
const permutations = (xs) =>
xs .length == 0
? [[]]
: xs .flatMap ((x, i) => permutations (excluding (i) (xs)) .map (p => x + p))
by using a simple helper excluding, which takes an index and an array and returns a copy of that array excluding the value at that index.
permutations firsts checks if the input array is empty. If it is, we simply return an array containing just the empty array. Otherwise, for each element, we remove it from the list, recursively permute the remaining elements, and prepend that initial one to each result.
Our helper is simple. It could be inlined into permutations, but I think both are cleaner if they're separated this way. It might look like this:
const excluding = (i) => (xs) =>
[... xs .slice (0, i), ... xs .slice (i + 1)]
Then we can write maximumBy, which takes a function and returns a function which takes an array and returns that element for which the function returns the highest value. It does this through a simple reduce call:
const maximumBy = (fn) => (xs) =>
xs .reduce (({val, max}, x, i, _, xVal = fn (x)) =>
(i == 0 || xVal > max) ? {max: xVal, val: x} : {val, max},
{}
) .val
It's worth noting that this is not specific to numbers. It works with any values that can be compared with <, so numbers, strings, dates, or objects with valueOf methods.
These function so far are generic utility functions we can easily imagine reusing across projects. The next one, longestStreak, is more specific to this project:
const longestStreak = ([... chars]) => {
let {streakChar, streak} = chars .reduce (
({currChar, count, streakChar, streak}, char) => ({
currChar: char,
count: char == currChar ? count + 1 : 1,
streakChar: (char == currChar ? count + 1 : 1) > streak ? currChar : streakChar,
streak: Math .max (char == currChar ? count + 1 : 1, streak),
}),
{streak: -1}
)
return {char: streakChar, streak, word: chars .join ('')}
}
Like many maxima problems, we can do this with .reduce. Here we restructure the input string into an array of characters with a parameter of [... cs]. Then as we fold our array, we keep returning an object with the structure {currChar, count, streakChar, streak}, which hold the currentChar we're tracking, the count of them seen so far, as well as the the character for the longest streak seen so far and that count. We start with an initial value of {streak: -1}.
When we're through the string, we return the streak character and its length (as well as the word that contains it; unnecessary here, but seemingly useful for the larger problem.)
Here is what it looks like altogether:
// Utility functions
const excluding = (i) => (xs) =>
[... xs .slice (0, i), ... xs .slice (i + 1)]
const permutations = (xs) =>
xs .length == 0
? [[]]
: xs .flatMap ((x, i) => permutations (excluding (i) (xs)) .map (p => [x, ... p]))
const maximumBy = (fn) => (xs) =>
xs .reduce (({val, max}, x, i, _, xVal = fn (x)) =>
(i == 0 || xVal > max) ? {max: xVal, val: x} : {val, max},
{}
) .val
// Helper function
const longestStreak = ([... chars]) => {
let {streakChar, streak} = chars .reduce (
({currChar, count, streakChar, streak}, char) => ({
currChar: char,
count: char == currChar ? count + 1 : 1,
streakChar: (char == currChar ? count + 1 : 1) > streak ? currChar : streakChar,
streak: Math .max (char == currChar ? count + 1 : 1, streak),
}),
{streak: -1}
)
return {char: streakChar, streak, word: chars .join ('')}
}
// Main function
const longestSingleLetterSequence = (arr) =>
maximumBy (x => x .streak) (permutations (arr) .map (xs => xs .join ('')) .map (longestStreak))
// Demos
console .log (longestSingleLetterSequence (['aa', 'ba', 'ac']))
console .log (longestSingleLetterSequence (["ccdd", "bbbb", "bbab"]))
.as-console-wrapper {max-height: 100% !important; top: 0}
Using additional helpers for clean-up
There is one thing I really don't like about the main function. You need to read very carefully to understand the order of operations:
const longestSingleLetterSequence = (arr) =>
maximumBy (x => x .streak) (permutations (arr) .map (xs => xs .join ('')) .map (longestStreak))
//`-------- step 4 --------' `---- step 1 ---' `--------- step 2 -------' `------ step 3 ------'
I'm one of the primary authors of Ramda, and it provides some very useful tools to help manage such complexity. But those tools are easy to write ourselves. So with a few additional helpers, I would actually write the main function this way:
const longestSingleLetterSequence = pipe (
permutations,
map (join ('')),
map (longestStreak),
maximumBy (prop ('streak'))
)
And here the steps just run sequentially line after line. I won't go into detail about these helper functions, but if you want to see it in action, you can expand this snippet:
// Utility functions
const pipe = (...fns) => (...args) =>
fns .slice (1) .reduce ((a, fn) => fn (a), fns[0] (...args))
const map = (fn) => (xs) => xs .map (x => fn (x))
const prop = (p) => (o) => o [p]
const join = (sep) => (xs) => xs .join (sep)
const excluding = (i) => (xs) => [... xs .slice (0, i), ... xs .slice (i + 1)]
const permutations = (xs) =>
xs .length == 0
? [[]]
: xs .flatMap ((x, i) => permutations (excluding (i) (xs)) .map (p => [x, ... p]))
const maximumBy = (fn) => (xs) =>
xs .reduce (({val, max}, x, i, _, xVal = fn (x)) =>
(i == 0 || xVal > max) ? {max: xVal, val: x} : {val, max},
{}
) .val
// Helper function
const longestStreak = ([... chars]) => {
let {streakChar, streak} = chars .reduce (
({currChar, count, streakChar, streak}, char) => ({
currChar: char,
count: char == currChar ? count + 1 : 1,
streakChar: (char == currChar ? count + 1 : 1) > streak ? currChar : streakChar,
streak: Math .max (char == currChar ? count + 1 : 1, streak),
}),
{streak: -1}
)
return {char: streakChar, streak, word: chars .join ('')}
}
// Main function
const longestSingleLetterSequence = pipe (
permutations,
map (join ('')),
map (longestStreak),
maximumBy (prop ('streak'))
)
// Demo
console .log (longestSingleLetterSequence (['aa', 'ba', 'ac']))
console .log (longestSingleLetterSequence (["ccdd", "bbbb", "bbab"]))
.as-console-wrapper {max-height: 100% !important; top: 0}
Alternatively, you can also use regex here, It is clean and easy to read and maintain.
/([a-z])\1+/g
const longestSingleCharSubstring = (arr) => {
const result = { char: "", count: 0 };
return arr
.join("")
.match(/([a-z])\1+/g)
.reduce((acc, curr) => {
if (curr.length > acc.count) {
acc.char = curr[0];
acc.count = curr.length;
}
return acc;
}, result);
};
console.log(longestSingleCharSubstring(["ccdd", "bbbb", "bbab"]));
Please note: the linked question, "How can I create every combination possible for the contents of two arrays?" does not solve this particular question. The persons that labeled that did not fully understand this specific permutation and request.
If you have two arrays (arr1, arr2) with n elements in each array (i.e., each array will be the same length), then the question is: What's the best method to get/determine all the possible matches where elements do not match with other elements in the same array and where order does not matter?
For example, let's say I have:
arr1 = ["A","B","C"];
arr2 = ["Z","Y","X"];
I would like to get back an array of arrays where each element of one array is paired with an element of another array. So the result would be a unique set of arrays:
matches = [
[["A","Z"],["B","Y"],["C","X"]],
[["A","Z"],["B","X"],["C","Y"]],
[["A","Y"],["B","X"],["C","Z"]],
[["A","Y"],["B","Z"],["C","X"]],
[["A","X"],["B","Z"],["C","Y"]],
[["A","X"],["B","Y"],["C","Z"]],
]
Please note, these two arrays would be the same:
[["A","Z"],["B","Y"],["C","X"]]
[["B","Y"],["C","X"],["A","Z"]]
I am trying to do this with vanilla JavaScript but am completely open to using Lodash as well. For an added bonus, since this can get out of control, speed and performance are important. But right now, I am just trying to get something that would yield a proper result set. To limit this, this function would probably not be used with more than two arrays of 50 elements each.
Here is my latest attempt (using lodash):
function getMatches(arr1, arr2){
var matches = [];
for (var arr1i = 0, arr1l = arr1.length; arr1i < arr1l; arr1i++) {
for (var arr2i = 0, arr2l = arr2.length; arr2i < arr2l; arr2i++) {
matches.push(_(arr1).zip(arr2).value());
arr2.push(arr2.shift());
}
}
return matches;
}
[[A, 1], [B, 2]]
is the same as
[[B, 2], [A, 1]]
in your case, which means that the solution depends on what you pair to the first elements of your array. You can pair n different elements as second elements to the first one, then n - 1 different elements as second elements to the second one and so on, so you have n! possibilities, which is the number of possible permutations.
So, if you change the order of the array elements but they are the same pair, they are equivalent, so you could view the first elements as a fixed ordered set of items and the second elements as the items to permutate.
Having arr1 = [a1, ..., an] and arr2 = [b1, ..., bn] we can avoid changing the order of a1. So, you permutate the inner elements and treat the outer elements' order as invariant, like:
const permutations = function*(elements) {
if (elements.length === 1) {
yield elements;
} else {
let [first, ...rest] = elements;
for (let perm of permutations(rest)) {
for (let i = 0; i < elements.length; i++) {
let start = perm.slice(0, i);
let rest = perm.slice(i);
yield [...start, first, ...rest];
}
}
}
}
var other = ['A', 'B', 'C'];
var myPermutations = permutations(['X', 'Y', 'Z']);
var done = false;
while (!done) {
var next = myPermutations.next();
if (!(done = next.done)) {
var output = [];
for (var i = 0; i < next.value.length; i++) output.push([other[i], next.value[i]]);
console.log(output);
}
}
You're just looking for permutations. The first elements of your tuples are always the same, the second ones are permuted so that you get all distinct sets of combinations.
const arr1 = ["A","B","C"];
const arr2 = ["Z","Y","X"];
const result = permutate(arr2).map(permutation =>
permutation.map((el, i) => [arr1[i], el])
);
This implementation uses Typescript and Lodash.
const permutations = <T>(arr: T[]): T[][] => {
if (arr.length <= 2)
return arr.length === 2 ? [arr, [arr[1], arr[0]]] : [arr];
return reduce(
arr,
(acc, val, i) =>
concat(
acc,
map(
permutations([...slice(arr, 0, i), ...slice(arr, i + 1, arr.length)]),
vals => [val, ...vals]
)
),
[] as T[][]
);
};