What's wrong with this combination-generating recursion? - javascript

I have the following code:
const findMult_3 = (num) => {
const powerset = (set) => {
const combinations = []
const combine = (prefix, chars) => {
for (let i = 0; i < chars.length; i++) {
combinations.push(prefix + chars[i])
combine(prefix + chars[i], chars.slice(i + 1))
}
}
combine('', set)
return combinations
}
const allCombinations = powerset(num.toString().split(''))
console.log(allCombinations)
}
findMult_3(362)
I would expect this to work, however, with the input of 362, the function console logs:
[ '3', '36', '362', '32', '6', '62', '2' ]
It's missing variants like 63, 23, 26 etc. It seems the slice call is to blame?

Still not 100% sure what the issue was with the slice call, but I fixed it by sidestepping the issue and avoiding mutating my arrays:
const findMult_3 = (num) => {
const powerset = (set) => {
const combinations = []
const combine = (prefix, chars) => {
for (let i = 0; i < chars.length; i++) {
combinations.push(prefix + chars[i])
combine(prefix + chars[i], chars.filter((x, ind) => ind !== i))
}
}
combine('', set)
return combinations
}
const allCombinations = powerset(num.toString().split(''))
console.log(allCombinations)
}
findMult_3(362)
Note the use of filter instead of splice, maintaining immutability.

I found the problem, duplicate the array (to c), and then remove the element you are combining with:
const findMult_3 = (num) => {
const powerset = (set) => {
const combinations = []
const combine = (prefix, chars) => {
console.log(chars);
for (let i = 0; i < chars.length; i++) {
combinations.push(prefix + chars[i])
var c = chars.slice();
c.splice(i,1);
combine(prefix + chars[i], c);
}
}
combine('', set)
return combinations
}
const allCombinations = powerset(num.toString().split(''));
console.log(allCombinations);

Related

replace nested for loop with array helpers - finding priority element

I am looking to replace nested for loop implementation with array helpers for optimization,
The function returns string from testArray, which contains the highest priority as per priorityArray.
const arr1 = ["A_COUNTY","B_STATE","C_COUNTRY", "D_CONTINENT","E_WORLD"];
const arr2 = ["X_COUNTY","Y_STATE","Z_PLANET"];
const priorityArray = ["CONTINENT","COUNTRY","STATE","COUNTY"];
function findPriorityElement(testArray:string[])
{
for (let i = 0; i <priorityArray.length; i++)
for (let j = 0; j <testArray.length; j++) {
if (testArray[j].includes(priorityArray[i]))
return testArray[j];
}
return "";
}
console.log(findPriorityElement(arr1)); // Result: "D_CONTINENT"
console.log(findPriorityElement(arr2)); // Result: "Y_STATE"
Any leads is appreciated.
You could use find() instead of the inner for loop
const arr1 = ["A_COUNTY", "B_STATE", "C_COUNTRY", "D_CONTINENT", "E_WORLD"];
const arr2 = ["X_COUNTY", "Y_STATE", "Z_PLANET"];
const priorityArray = ["CONTINENT", "COUNTRY", "STATE", "COUNTY"];
function findPriorityElement(testArray) {
for (p of priorityArray) {
const match = testArray.find(e => e.includes(p));
if (match) {
return match;
}
}
}
console.log(findPriorityElement(arr1)); // Result: "D_CONTINENT"
console.log(findPriorityElement(arr2)); // Result: "Y_STATE"
const arr1 = ["A_COUNTY", "B_STATE", "C_COUNTRY", "D_CONTINENT", "E_WORLD"];
const arr2 = ["X_COUNTY", "Y_STATE", "Z_PLANET"];
const priorityArray = ["CONTINENT", "COUNTRY", "STATE", "COUNTY"];
const findPriorityElement = testArray => {
for (const priorityElement of priorityArray) {
const elem = testArray.find(testElement => testElement.includes(priorityElement));
if (elem) {
return elem;
}
}
};
console.log(findPriorityElement(arr1)); // Result: "D_CONTINENT"
console.log(findPriorityElement(arr2)); // Result: "Y_STATE"

Inconsistency, when returning index of duplicate values

I'm trying to create an algorithm to find duplicate values in a list and return their respective indexes, but the script only returns the correct value, when I have 2 equal elements:
array = [1,2,0,5,0]
result -> (2) [2,4]
Like the example below:
array = [0,0,2,7,0];
result -> (6) [0, 1, 0, 1, 0, 4]
The expected result would be [0,1,4]
Current code:
const numbers = [1,2,0,5,0];
const checkATie = avgList => {
let averages, tie, n_loop, currentAverage;
averages = [... avgList];
tie = [];
n_loop = 0;
for(let n = 0; n <= averages.length; n++) {
currentAverage = parseInt(averages.shift());
n_loop++
for(let avg of averages) {
if(avg === currentAverage) {
tie.push(numbers.indexOf(avg),numbers.indexOf(avg,n_loop))
};
};
};
return tie;
}
console.log(checkATie(numbers));
if possible I would like to know some way to make this code more concise and simple
Use a Set
return [...new Set(tie)]
const numbers1 = [1,2,0,5,0];
const numbers2 = [0,0,2,7,0];
const checkATie = avgList => {
let averages, tie, n_loop, currentAverage;
averages = [... avgList];
tie = [];
n_loop = 0;
for(let n = 0; n <= averages.length; n++) {
currentAverage = parseInt(averages.shift());
n_loop++
for(let avg of averages) {
if(avg === currentAverage) {
tie.push(avgList.indexOf(avg),avgList.indexOf(avg,n_loop))
};
};
};
return [...new Set(tie)]
}
console.log(checkATie(numbers1));
console.log(checkATie(numbers2));
I hope this help you.you can use foreach function to check each item of array
var array = [0,0,2,7,0];
var result = [] ;
array.forEach((item , index)=>{
if(array.findIndex((el , i )=> item === el && index !== i ) > -1 ){
result.push(index)
}
})
console.log(result);
//duplicate entries as an object
checkDuplicateEntries = (array) => {
const duplicates = {};
for (let i = 0; i < array.length; i++) {
if (duplicates.hasOwnProperty(array[i])) {
duplicates[array[i]].push(i);
} else if (array.lastIndexOf(array[i]) !== i) {
duplicates[array[i]] = [i];
}
}
console.log(duplicates);
}
checkDuplicateEntries([1,2,0,5,0]);
// hope this will help
Create a lookup object with value and their indexes and then filter all the values which occurred more than once and then merge all indexes and generate a new array.
const array = [1, 2, 0, 5, 0, 1, 0, 2],
result = Object.values(array.reduce((r, v, i) => {
r[v] = r[v] || [];
r[v].push(i);
return r;
}, {}))
.filter((indexes) => indexes.length > 1)
.flatMap(x => x);
console.log(result);

Why isn't this function to return all permutations of a given array working?

I'm trying to write a function that would return all permutations of a given array of numbers, as in the examples below:
a = [1, 1, 2]
permutator(a) = [
[1,1,2],
[1,2,1],
[2,1,1]
]
b = [2, 3, 4]
permutator(b) = [
[2,3,4],
[2,4,3],
[3,2,4],
[3,4,2],
[4,2,3],
[4,3,2]
]
order of the results isn't accurate
Can you explain why the code below doesn't work? I tried debugging and I think the variables temp and result aren't being kept in memory as arrays but I'm not sure if that is the case nor how to solve it.
permutator = (array) => {
const result = [];
array.forEach((num, i) => {
array.forEach((num2, j) => {
if (i !== j) {
let temp = array;
temp[i] = temp[j];
temp[j] = num;
console.log(`temp: ${temp}`);
console.log(`result: ${result}`);
if (!result.includes(temp)) result.push(temp);
console.log(`result: ${result}`);
}
});
});
return result;
}
as you can see I tried console.logging everything to no avail...
This works for three digits, but I wouldn't call it elegant:
let indexAtEachPosition = function (ind, arr) {
let arrayGroup = [];
let arrLength = arr.length;
let movedArray = [...arr];
for (let indx = 0; indx < arrLength; indx++) {
let firstItem = movedArray[0];
let otherItems = movedArray.slice(1);
otherItems[arrLength - 1] = firstItem
movedArray = [...otherItems];
arrayGroup.push(movedArray.join(" "));
}
return arrayGroup;
}
let permutator = function permutator(values) {
let returnValue = new Set();
let digitCount = values.length;
returnValue.add(values.join(" "));
values.forEach(digit => {
indexAtEachPosition(digit, values).forEach(variation => {
returnValue.add(variation);
});
});
[...values.reverse()].forEach(digit => {
indexAtEachPosition(digit, values).forEach(variation => {
returnValue.add(variation);
});
});
return [...returnValue].map(eachArr => {
return eachArr.split(" ");
});
};
// console.log( permutator([0,0,0]) );
// console.log( permutator([1,0,0]) );
// console.log( permutator([1,2,3]) );
console.log( permutator([1,1,2]) );
console.log( permutator([2,3,4]) );
I'm not sure what the goal of this is, but it intrigued me like some kind of code test so I tried to solve it.
This answer is the most elegant solution I found.
const permutator = (inputArr) => {
let result = [];
const permute = (arr, m = []) => {
if (arr.length === 0) {
result.push(m)
} else {
for (let i = 0; i < arr.length; i++) {
let curr = arr.slice();
let next = curr.splice(i, 1);
permute(curr.slice(), m.concat(next))
}
}
}
permute(inputArr)
return result;
}
console.log(permutator([1,2,3]));

Mixing Arrays using JavaScript

What's the best way to mix multiple arrays like the way in the image below,
PS:
I don't know what will be the length of each array
Arrays will contain +10000 elements
There will be more than 3 arrays
I made a solution for it but I'm looking for any better solution
Here's my Own solution, I was looking for any better idea
import { compact, flattenDeep } from "lodash/array";
export const doTheMagic = master => {
const longest = master.reduce((p, c, i, a) => (a[p].length > c.length ? p : i), 0);
const mixed = master[longest].map((i, k) => {
return master.map((o, a) => {
if (master[a][k]) return master[a][k];
});
});
const flaten = flattenDeep(mixed);
return compact(flaten);// to remove falsey values
};
const one = [1,2,3];
const two = ['a','b','c','d','e'];
const three = ['k','l','m','n'];
const mix = doTheMagic([one,two,three]);
console.log('mix: ', mix);
You could use lodash for your solution.
const { flow, zip, flatten, filter} = _
const doTheMagic = flow(
zip,
flatten,
filter
)
const one = [1, 2, 3]
const two = ['😕', '🤯', '🙈', '🙊', '🙉', '😃']
const three = ['foo', 'bar', 'wow', 'okay']
const result = doTheMagic(one, two, three)
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>
Works with different length of arrays and makes use of functional programming for elegant code.
Here's a codepen to run: https://codepen.io/matteodem/pen/mddQrwe
Here's my Own solution, I was looking for any better idea
import { compact, flattenDeep } from "lodash/array";
export const doTheMagic = master => {
const longest = master.reduce((p, c, i, a) => (a[p].length > c.length ? p : i), 0);
const mixed = master[longest].map((i, k) => {
return master.map((o, a) => {
if (master[a][k]) return master[a][k];
});
});
const flaten = flattenDeep(mixed);
return compact(flaten);// to remove falsey values
};
const one = [1,2,3];
const two = ['a','b','c','d','e'];
const three = ['k','l','m','n'];
const mix = doTheMagic([one,two,three]);
console.log('mix: ', mix);
let a1 = [1, 2, 3, 4, 5];
let a2 = ["🏯", "🏜", "🏭", "🎢", "🌠", "🏗"];
let a3 = ['one', 'two', 'three', 'four', 'five'];
const doTheMagic = arrayOfArrays => {
let maxLength = 0;
let result = [];
for (arr in arrayOfArrays) {
maxLength = Math.max(maxLength, arrayOfArrays[arr].length);
}
for (let i = 0; i < maxLength; i++) {
for (let j = 0; j < arrayOfArrays.length; j++) {
if (arrayOfArrays[j][i]) {
result.push(arrayOfArrays[j][i]);
}
}
}
return result;
}
console.log(doTheMagic([a1, a2, a3]));
This works with an unknown number of arrays, each one of unknown length :
const arrays = [
[1, 2, 3, 4],
["a", "b", "c", "d", "e"],
["#", "#", "?"]
];
let output = [];
while (arrays.some(a => a.length)) { // While any of the arrays still has an element in it, keep going
for (let a of arrays) {
if (!a.length) continue;
output.push(a.shift()); // remove the first element of the array and add it to output
}
}
console.log(output)
This is my approach to achieve that, one for loop can make it. This will work if you don't know the number of arrays and array length as well.
function doTheMagic(arr){
let ans = [];
let maxLength = -1;
arr.forEach((tempArr)=>{
if(tempArr.length > maxLength){
maxLength = tempArr.length;
}
})
let l1=0,l2=0,l3=0;
for(let i=0;i<maxLength;i++){
arr.forEach((tempArr)=>{
if(tempArr[i]){
ans.push(tempArr[i]);
}
})
}
return ans;
}
let a1 = [1,2,3,4,5];
let a2 = ["🏯","🏜","🏭","🎢","🌠","🏗"];
let a3 = ['1','2','3','4','5'];
console.log(doTheMagic([a1,a2,a3]));
Not sure if this is the better, but how you can write code that handles any number of arrays passed in.
const weave = (...args) => // convert arguments to an array
args.reduce((res, arr, offset) => { // loop over the arrays
arr.forEach((v, i) => res[offset + i * args.length] = v) // loop over array and add items to their indexes
return res
}, []).filter(x => x !== undefined) // remove the unused indexes
const one = [1, 2, 3]
const two = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
const three = ['w', 'x', 'y', 'z']
const result = weave(one, two, three)
console.log(result)
const result2 = weave(one, two)
console.log(result2)
const result3 = weave(one, two, three, ['*', '&'])
console.log(result3)

Find elements that commonly appear next to each other in an array

I am trying to find values that commonly appear next to each other in an array.
E.G. given the array:
["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"]
it should return something similar to:
[[["dog","cat"],4],[["cat","pig"],2],[["dog","cat","pig"],2]]
Here is some better data: https://pastebin.com/UG4iswrZ
Help would be greatly appreciated. Here is my current failed attempt at doing something similar:
function findAssociations(words){
var temp = [],tempStore = [],store = [],found = false;
//loop through the words counting occurrances of words together with a window of 5
for(var i = 0;i<words.length-1;i++){
if(i % 5 == 0){
//on every fith element, loop through store attempting to add combinations of words stored in tempStore
for(var j = 0;j<5;j++){
temp = []
//create the current combination
for(var k = 0;k<j;k++){
temp.push(tempStore[k]);
}
//find if element is already stored, if it is, increment the occurrence counter
for(var k = 0;k<store.length;k++){
if(store[k][0]===temp){
found = true;
store[k][1] = store[k][1]+1;
}
}
//if it isn't add it
if(found == false){
store.push([temp,1]);
}
found == false;
}
tempStore = [];
} else {
//add word to tempStore if it i isnt a multiple of 5
tempStore.push(words[i]);
}
}
}
This script is doesn't remove combinations that appear once,it doesn't sort the output by occurrences, nor does it work. It is just an outline of how a possible solution might work (as suggested by benvc).
Here is a generic solution working with multiple group sizes.
You specify a range of group sizes, for example [2,4] for groups of 2 to 4 elements and a minimum number of occurrences.
The function then generates all groups of neighbours of the given sizes, sorts each group and counts the duplicates. The sorting step can be removed is the order in the groups matters.
The duplicates are counted by creating a dictionary whose keys are the group elements sorted and jointed with a special marker. The values in the dictionary are the counts.
It then returns the groups sorted by occurences and then by group size.
const data = ["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
function findSimilarNeighbors(groupSizeRange, minOccurences, data) {
const getNeighbors = (size, arr) => arr.reduce((acc, x) => {
acc.push([]);
for (let i = 0; i < size; ++ i) {
const idx = acc.length - i - 1;
(acc[idx] || []).push(x);
}
return acc;
}, []).filter(x => x.length === size);
const groups = [];
for (let groupSize = groupSizeRange[0]; groupSize <= groupSizeRange[1]; ++groupSize) {
groups.push(...getNeighbors(groupSize, data));
}
const groupName = group => group.sort().join('###'); // use a separator that won't occur in the strings
const groupsInfo = groups.reduce((acc, group) => {
const name = groupName(group);
acc[name] = acc[name] || {};
acc[name] = { group, count: (acc[name].count || 0) + 1 };
return acc;
}, {});
return Object.values(groupsInfo)
.filter(group => group.count >= minOccurences)
.sort((a, b) => {
const countDiff = b.count - a.count;
return countDiff ? countDiff : b.group.length - a.group.length;
})
.map(({ group, count }) => [group, count]);
};
console.log(findSimilarNeighbors([2, 4], 2, data));
console.log(findSimilarNeighbors([4, 4], 2, data));
Here is what I came up with. It only finds pairs, but you could modify it to find sets of 3, 4, etc, based on what you % by
const animals = ['dog','cat','goat','dog','cat','elephant','dog','cat','pig','seal','dog','cat','pig','monkey'];
let pairs = ',';
animals.forEach((animal, i) => {
let separator = ',';
if (i % 2 === 0) {
separator = ';'
}
pairs += animal + separator;
});
const evenPairs = pairs.split(',');
const oddPairs = pairs.split(';');
const allPairs = evenPairs.concat(oddPairs).map(pair => pair.replace(/[;,]/, ' '));
let result = {}
allPairs.forEach(pair => {
if (pair.length) {
if (result[pair] === undefined) {
result[pair] = 1;
} else {
result[pair]++;
}
}
});
results in:
dog: 1
cat elephant: 1
cat goat: 1
cat pig: 2
dog cat: 4
elephant dog: 1
goat dog: 1
monkey : 1
pig monkey: 1
pig seal: 1
seal dog: 1
https://stackblitz.com/edit/typescript-wvuvnr
You need to be clear what you mean by close and how close. Just looking at first neighbours you could try:
const findAssociations = words => {
const associations = {}
for (let i = 0; i < words.length - 1; i++) {
const word = words[i]
const wordRight = words[i+1]
const wordOne = word < wordRight ? word : wordRight;
const wordTwo = word < wordRight ? wordRight : word;
const keys = Object.keys(associations)
const key = `${wordOne}:${wordTwo}`
if (keys.indexOf(key) >= 0) {
associations[key]++
} else {
associations[key] = 1
}
}
const keys = Object.keys(associations)
const values = Object.values(associations)
const zipped = keys.map((key, index) => [key, values[index]])
zipped.sort((a, b) => a[1] < b[1] ? 1 : -1);
return zipped;
}
https://stackblitz.com/edit/js-3ppdit
You can use this function inside another function and add every time an element to ["dog", "cat"]
const arr = ["dog", "cat", "goat", "dog", "cat", "dog", "cat", "elephant", "dog", "cat", "pig", "seal", "dog", "cat", "pig", "monkey"]
const findArrayInArray = (arr1, arr2) => {
let count = 0,
arrString1 = arr1.join(""),
arrString2 = arr2.join("");
while (arrString2.indexOf(arrString1) > -1) {
count += 1;
arrString2 = arrString2.replace(arrString1, '');
}
return count;
}
console.log(`["dog", "cat"] exist ${findArrayInArray(["dog", "cat"], arr)} times`)
Assuming each item in the list is a delimiter of a set, and each set counts once for each item (i.e. ["dog", "cat", "goat"] counts as ["dog", "cat"] and ["dog", "cat", "goat"], and assuming you don't want any single occurrences, then here's one way:
const full_list = ["dog","cat","goat","dog","cat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
// create list of unique items
const distinct = (value, index, self) => {
return self.indexOf(value) ===index;
}
const unique_items = full_list.filter(distinct);
// get all patterns
var pre_report = {};
for (var i in unique_items) {
item = unique_items[i];
var pattern = [item];
var appending = false;
for (var j = full_list.indexOf(item) + 1; j < full_list.length; ++j) {
const related_item = full_list[j];
if (item == related_item) {
pattern = [item]
continue;
}
pattern.push(related_item);
if (pattern in pre_report) {
++pre_report[pattern];
} else {
pre_report[pattern] = 1;
}
}
}
// filter out only single occurring patterns
var report = {};
for (key in pre_report) {
if (pre_report[key] > 1) {
report[key] = pre_report[key];
}
}
console.log(report);
produces:
{ 'dog,cat': 5, 'dog,cat,pig': 2, 'cat,pig': 2 }

Categories