Creating unique combination of unique pairs - javascript

I'm trying to create a tournament system that will pair players together. Each tournament will have multiple of 4 players. Each player will only ever be paired with another player once. Those pairs will only ever play against another pair once. I believe the algorithm is (n-1)*(n/4) where n is the number of players.
Here’s the code:
function generateCombinations(arr) {
let result = [];
function combination(start, current) {
if (current.length === 2) {
result.push(current);
return;
}
for (let i = start; i < arr.length; i++) {
let newCurrent = current.concat(arr[i]);
if (new Set(newCurrent).size !== newCurrent.length) {
continue;
}
combination(i + 1, newCurrent);
}
}
for (let i = 0; i < arr.length; i++) {
combination(i + 1, [arr[i]]);
}
let finalResult = [];
for (let i = 0; i < result.length; i += 2) {
let found = false;
for (let j = 0; j < finalResult.length; j++) {
if (finalResult[j][0][0] === result[i][0] && finalResult[j][0][1] === result[i][1]) {
found = true;
break;
}
}
if (!found) {
finalResult.push([result[i], result[i + 1]]);
}
}
return JSON.stringify(finalResult);
}
let elements = ['a', 'b', 'c', 'd'];
let combinations = generateCombinations(elements);
console.log(combinations);
It incorrectly outputs [[["a","b"],["a","c"]],[["a","d"],["b","c"]],[["b","d"],["c","d"]]]It should instead output [[["a","b"],["c","d"]],[["a","c"],["b","d"]],[["a","d"],["b","c"]]]
The difference is that it each each unique pair of pairs should be unique overall. eg. a+b will be paired with c+d . It’s correctly creating the unique possible pairs, but is incorrectly pairing them with each other.
This code should handle multiples of 4 players up to 16 players.
4 people = 3 rows of 2v2 8 people = 14 rows of 2v2 12 people = 33 rows of 2v2 16 people = 60 rows of 2v2

Here's a simple brute-force solution:
function makeRounds(n) {
let sets = []
let rounds = []
for (let r = 0; r < n - 1; r++) {
sets.push({})
rounds.push([])
}
for (let i = 0; i < n - 1; i++) {
for (let j = i + 1; j < n; j++) {
for (let r = 0; r < n - 1; r++) {
if (!sets[r][i] && !sets[r][j]) {
sets[r][i] = sets[r][j] = 1
rounds[r].push([i, j])
break
}
}
}
}
return rounds
}
for (r of makeRounds(8))
console.log(r.join(' '))
Basically, for each "round" maintain a set of "teams" (numbers) already in this round. Generate each possible pair [a,b] and put it in the first round whose respective set doesn't contain a or b.

Our friends, the math people, have an algorithm for that. It works by removing the last element from the list, and then going through the remaining elements, pairing the one at the index with the removed element, while also pairing elements with the same distance from the current index. For example, at index 2 with six elements, it will build the pairs (2,5), (1,3), (0,4):
Here is an implementation (buildPairIndexes()), which returns tuples of array indexes. Then pairPlayers() just gets the indexes and resolves them for the players.
const players = new Array(6).fill(null).map((_, ix) => String.fromCharCode(97 + ix))
function buildPairIndexes(n) {
if (n % 2 !== 0) {
throw new Error(`${n} is an odd number`)
}
const pairings = []
const max = n - 1
for (let i = 0; i < max; i++) {
const pairing = [[max, i]]
for (let k = 1; k < (n / 2); k++) {
pairing.push([(i + k) % max, (max + i - k) % max])
}
pairings.push(pairing)
}
return pairings
}
function pairPlayers(players) {
const pairings = buildPairIndexes(players.length)
const pairIxToPlayer = (i) => players[(i + 1) % players.length]
return pairings.map(pairing => pairing.map(pair => pair.map(pairIxToPlayer)))
}
console.log(pairPlayers(players))
Here is the output of just the indexes for six items:
[
[ [ 5, 0 ], [ 1, 4 ], [ 2, 3 ] ],
[ [ 5, 1 ], [ 2, 0 ], [ 3, 4 ] ],
[ [ 5, 2 ], [ 3, 1 ], [ 4, 0 ] ],
[ [ 5, 3 ], [ 4, 2 ], [ 0, 1 ] ],
[ [ 5, 4 ], [ 0, 3 ], [ 1, 2 ] ]
]

Related

Finding occurences of each element in an array and aranging them according to the count of occurences in ascending or descending order

Task: Given an array of N elements. Find the number of occurrences of each character and print it in the decreasing order of occurrences, if 2 or more number occurs the same number of times, print the numbers in decreasing order.
Input Size : |N| <= 100000
Sample Testcase :
INPUT
3 3 4 4 7 8
OUTPUT
4 3 8 7
I am able to convert it to a map, but what further steps should I implement to get the answer?
function asc(arr) {
let map = new Map
let c = 1;
for (let i = 0; i < arr.length; i++) {
if (map.has(arr[i])) {
map.set(arr[i], map.get(arr[i]) + 1)
}
else {
map.set(arr[i], c)
}
}
let narr = [...map];
let tmp;
for (i = 0; i < narr.length; i++) {
for (j = i + 1; j < narr.length; j++) {
if (narr[i][1] > narr[j][1]) {
tmp = narr[i];
narr[i] = narr[j];
narr[j] = tmp;
}
else if ((narr[i][1] = narr[j][1])) {
if (narr[i][0] > narr[j][0]) {
tmp = narr[i];
narr[i] = narr[j];
narr[j] = tmp;
}
}
}
}
return narr;
}
arr = [3, 8, 7, 4, 7, 3, 4];
console.log(asc(arr));
The problems in the given code:
Look at this code sample:
else if ((narr[i][1] = narr[j][1])) {
if (narr[i][0] > narr[j][0]) {
tmp = narr[i];
narr[i] = narr[j];
narr[j] = tmp;
}
}
In the first line, there is a mistake: comparison mixed up with assignment. That is the reason, why the frequency of 8 in your example becomes 2.
It should be rewritten as:
else if (narr[i][1] == narr[j][1]) {
My advice:
read about sorting of the arrays in JS
The reason for this suggestion is the fact, that the sort in the language is faster, than the quadratic sort (O(n^2)), which you have implemented.

Count Number of Teams - Javascript

I'm trying to solve the problem below in JavaScript.
There are n soldiers standing in a line. Each soldier is assigned a
unique rating value.
You have to form a team of 3 soldiers amongst them under the following
rules:
Choose 3 soldiers with index (i, j, k) with rating (rating[i],
rating[j], rating[k]). A team is valid if: (rating[i] < rating[j] <
rating[k]) or (rating[i] > rating[j] > rating[k]) where (0 <= i < j <
k < n). Return the number of teams you can form given the conditions.
(soldiers can be part of multiple teams).
Example:
Input: rating = [2,5,3,4,1]
Output: 3
Explanation: We can form three teams given the conditions. (2,3,4), (5,4,1), (5,3,1).
My idea is to keep an array of possible combinations, when I reach 3 elements in the combinations, I increment my res variable. My attempt:
var numTeams = function(rating) {
let res = 0;
if(!rating || rating.length < 3) return res;
let combinations = [];
for(let i=0; i<rating.length; i++) {
const size = combinations.length;
for(let j=0; j<size; j++) {
const temp = combinations[j];
if(temp.length === 1 && temp[0] !== rating[i]) {
temp.push(rating[i]);
combinations.push(temp);
} else {
if(temp[0] < temp[1] && temp[1] < rating[i])
res++;
else if(temp[0] > temp[1] && temp[1] > rating[i])
res++;
}
}
combinations.push([rating[i]]);
}
return res;
};
It is returning 2 for the example input, not 3.
I suggest to use three encapsulated for loops. By initializing the indexes with the value of the current value of the index of the outer loop, e.g. k = i, the condition (0 <= i < j < k < n) is implicitly fulfilled.
Within the innermost loop, you can then check if the conditions to be a combination is true for the current set of soldiers by checking if rating[i] < rating[k] && rating[k] < rating[l] or rating[i] > rating[k] && rating[k] > rating[l] and add the values to the array holding the valid combinations.
At the end, the length of the array holding the valid combinations is printed:
var rating = [2,5,3,4,1];
var combinations = [];
for (i = 0; i < rating.length; i = i+1) {
for (k = i; k < rating.length; k = k+1) {
for (l = k; l < rating.length; l = l+1) {
if (rating[i] < rating[k] && rating[k] < rating[l]) {
combinations.push([rating[i], rating[k], rating[l]]);
}
if (rating[i] > rating[k] && rating[k] > rating[l]) {
combinations.push([rating[i], rating[k], rating[l]]);
}
}
}
}
console.log(combinations);
console.log(combinations.length);
Output:
[ [ 2, 3, 4 ], [ 5, 3, 1 ], [ 5, 4, 1 ] ]
3
For better performance compared to the three for loops provided in answer https://stackoverflow.com/a/64921918/9240674, i also iterate over the rating array only once now. An array variations holds all variations that may become a valid combination. The entries consist of the function which is used to determine whether the variation is a valid combination and the ratings. With each iteration over the rating array, the new rating[i] is checked against all variations if it might complete the currently checked variation to become a valid combination. If so, the ratings of the current variation[k] are stored in combinations array.
var rating = [2,5,3,4,1];
var variations = [];
var combinations = [];
function less_than(a, b) {
return a < b;
}
function greater_than(a, b) {
return a > b;
}
for (i = 0; i < rating.length; i = i + 1) {
var duplications = [];
for (k = 0; k < variations.length; k = k + 1) {
if (variations[k].ratings.length < 3) {
if (variations[k].comparemethod(rating[i],variations[k].ratings[variations[k].ratings.length-1])) {
// Duplicate current (incomplete) variation
duplications.push({"comparemethod": variations[k].comparemethod, "ratings": variations[k].ratings.slice()});
// Add the current rating to the current variation
variations[k].ratings.push(rating[i]);
}
if (variations[k].ratings.length == 3) {
// we found a valid combination
combinations.push(variations[k].ratings);
}
}
}
// add entries which needed to be duplicated to the variations
variations = variations.concat(duplications);
// add the current rating to the variations
variations.push({comparemethod: less_than, ratings: [rating[i]]});
variations.push({comparemethod: greater_than, ratings: [rating[i]]});
}
console.log(JSON.stringify(combinations));
console.log(combinations.length);
Output:
[[2,3,4],[5,3,1],[5,4,1]]
3

Loop from 0 to a number and loop through all numbers in array (javascript)

Here is the idea:
var a = [4, 5, 6];
for (var m = 0; m < a[0]; m++)
for (var n = 0; n < a[1]; n++)
for (var p = 0; p < a[2]; p++)
console.log(`${m} + ${n} + ${p} = ${m+n+p}`);
Live Copy:
// This just tells the Stack Snippets in-snippet console not
// to throw away entries once it reaches a max (the default max
// is just the last 50 logs).
console.config({maxEntries: Infinity});
var a = [4, 5, 6];
for (var m = 0; m < a[0]; m++)
for (var n = 0; n < a[1]; n++)
for (var p = 0; p < a[2]; p++)
console.log(`${m} + ${n} + ${p} = ${m+n+p}`);
/* This just makes the console take up the full output area */
.as-console-wrapper {
max-height: 100% !important;
}
The code would get longer if the array a has more indexes. Could the code be shorten using Array.map or filter or a function?
We can do this without taking up massive amounts of memory, and fairly simply, by using recursion:
const process = (array, n, numbers) => {
if (n < array.length) {
// Not done yet, recurse once for each number at this level
const max = array[n];
for (let i = 0; i < max; ++i) {
process(array, n + 1, [...numbers, i]);
}
} else {
// Done with this level, process the numbers we got
console.log(`${numbers.join(" + ")} = ${numbers.reduce((s, e) => s + e)}`);
}
}
process([4, 5, 6], 0, []);
Live Copy, with cross-checking against your results to ensure the above does the same thing:
// This just tells the Stack Snippets in-snippet console not
// to throw away entries once it reaches a max (the default max
// is just the last 50 logs).
console.config({maxEntries: Infinity});
function thisSolution() {
const results = [];
const process = (array, n, numbers) => {
if (n < array.length) {
// Not done yet, recurse once for each number at this level
const max = array[n];
for (let i = 0; i < max; ++i) {
process(array, n + 1, [...numbers, i]);
}
} else {
// Done with this level, process the numbers we got
const result = numbers.reduce((s, e) => s + e);
results.push(result);
console.log(`${numbers.join(" + ")} = ${result}`);
}
}
process([4, 5, 6], 0, []);
return results;
}
function yourSolution() {
const results = [];
var a = [4, 5, 6];
for (var m = 0; m < a[0]; m++)
for (var n = 0; n < a[1]; n++)
for (var p = 0; p < a[2]; p++)
results.push(m + n + p);
return results;
}
const thisResult = thisSolution();
const yourResult = yourSolution();
if (thisResult.some((entry, index) => entry !== yourResult[index])) {
console.log("WRONG");
} else {
console.log("RIGHT");
}
/* This just makes the console take up the full output area */
.as-console-wrapper {
max-height: 100% !important;
}
This never goes deep into the stack (a.length + 1 stack frames, to be precise, so four in the example case). It builds up a number of temporary arrays (145 in the example case) that max out at a.length entries, releasing them as soon as they aren't needed anymore (a max of four are retained at any given time). Here's the quick and dirty metrics on that:
let maxStack = 0;
let stack = 0;
let totalArrays = 0;
let maxArrays = 0;
let arrays = 0;
// A wrapper for counting stack frames
const process = (...args) => {
if (++stack > maxStack) {
maxStack = stack;
}
const result = process2(...args);
--stack;
return result;
};
const process2 = (array, n, numbers) => {
if (n < array.length) {
// Not done yet, recurse once for each number at this level
const max = array[n];
for (let i = 0; i < max; ++i) {
++totalArrays;
if (++arrays > maxArrays) {
maxArrays = arrays;
}
process(array, n + 1, [...numbers, i]);
--arrays;
}
} else {
// Done with this level, process the numbers we got
//console.log(`${numbers.join(" + ")} = ${numbers.reduce((s, e) => s + e)}`);
}
}
process([4, 5, 6], 0, []);
++maxArrays; // To account for the one in the last argument above
++totalArrays; // "
console.log(`Max stack: ${maxStack}, max arrays: ${maxArrays}, total arrays: ${totalArrays}`);
It's easier if you break it down. First, you need to create a series per every element of your array.
let series = num => Array.from({ length: num + 1 }, (n, i) => i); //creates an array with nums from 0 to num.
That's the first part of your question. Then you need to do a cross product of your series.
Basically for two series [1, 2, 3] and [1, 2, 3, 4] you'll end up with a set of 12 elements:
[2, 3, 4, 5, 3, 4, 5, 6, 4, 5, 6, 7]
And for that you could do:
let crossProduct = (a1, a2) => Array.prototype.concat.call(...a1.map(n1 => a2.map(n2 => n1 + n2)));
Now all you need to do is have a crossProduct for every series.
let final = numbers.map(series).reduce(crossProduct);
And there you have it:
let numbers = [4, 5, 6];
let series = num => Array.from({ length: num + 1 }, (n, i) => i);
let crossProduct = (a1, a2) => Array.prototype.concat.call(...a1.map(n1 => a2.map(n2 => n1 + n2)));
let final = numbers.map(series).reduce(crossProduct);
console.log(final);
Edit: If it's from 0 to the number before (e.g. 4 is [0, 1, 2, 3]) then just take the + 1 in the series function.
2nd Edit: Less objects created for your crossProduct:
let crossProduct = (a1, a2) => {
let resultingSet = [];
for(let i = 0; i < a1.length; i++)
for(let j = 0; j < a2.length; j++)
resultingSet.push(a1[i] + a2[j]);
return resultingSet;
} //only one array is created
And if you want to avoid having the series on memory all the time:
let numbers = [4, 5, 6];
let series = function* (num){
for(let i = 0; i < num; i++){
yield i;
}
}
let crossProduct = (set, num) => {
let resultingSet = [];
for(let i = 0; i < set.length; i++){
for(let j of series(num)){
resultingSet.push(set[i] + j);
}
}
return resultingSet;
}
let final = numbers.reduce(crossProduct, [0]);
console.log(final);
Another solution that doesn't consume alot of memory and fairly efficient is by using an array that represnt the value of the indexes and update it each iteration.
first you create an array that represent in each element the amount of iterations you need to run in order to update the indexes respectively for example for this array [1, 2, 3 ,4 ,5] you will get:
[280, 140, 20, 5, 1] this means that index[0] will be updated each 280 iterations, index[1] will be updated each 140 iterations and so on..
totally you will run arr[n] * arr[n-1] * arr[n-2] * .... * arr[0] iterations as you did with ordinary nested for loop.
var arr = [1, 2, 7, 4, 5];
var indexes = Array.from({length: arr.length}, () => 0);
iterationsPerElement = arr.map((_, i) => arr.slice(i+1).reduce((acc, elem) => acc * elem, 1));
var totalIterations = iterationsPerElement[0] * arr[0];
for(var iteration = 1; iteration <= totalIterations; iteration++) {
// sum those indexes
console.log(`sum = ${indexes.reduce((acc, index) => acc + index, 0)}`);
// update indexes
for(i = 0; i < indexes.length; i++) {
if(iteration % iterationsPerElement[i] == 0) {
indexes[i]++;
// empty the indexes on the right
for(var j=i+1; j <indexes.length; j++) {
indexes[j] = 0;
}
}
}
}

How can I determine all possible ways a subsequence can be removed from a sequence?

Given two sequences, A and B, how can I generate a list of all the possible ways that B can be removed from A?
For example, In JavaScript, if I had a function removeSubSeq taking two array arguments that did what I want, it would work as follows:
removeSubSeq([1,2,1,3,1,4,4], [1,4,4]) would return [ [2,1,3,1], [1,2,3,1], [1,2,1,3] ] because the 4s at the end would match, and there are three possible places for the 1 to match
removeSubSeq([8,6,4,4], [6,4,8]) would return [] because the second argument is not actually a subsequence
removeSubSeq([1,1,2], [1]) would return [ [1,2], [1,2] ] because there's two ways that the 1 could be removed, even though it results in duplicates
This problem can be solved in O(n*m + r) time, where r is the total length of the results, using the classic longest common subsequence algorithm.
Once the table is made, as in Wikipedia's example, replace it with a list of the cells with a diagonal arrow that also have a value corresponding with their row. Now traverse backwards from each cell with a diagonal in the last row, accumulating the relevant index in the string and duplicating and splitting the accumulation such that each cell with a diagonal arrow will have a continuation to all cells with a diagonal in the preceding row that are to the left of it (store that count as well, as you build the matrix) and one less in value. When an accumulation reaches a zero cell, splice the accumulated indexes from the string and add that as a result.
(The arrows correspond with whether the LCS so far came from LCS(X[i-1],Y[j]) and/or LCS(X[i],Y[j-1]), or LCS(X[i-1],Y[j-1]), see the function definition.)
For example:
0 a g b a b c c
0 0 0 0 0 0 0 0 0
a 0 ↖1 1 1 ↖1 1 1 1
b 0 1 1 ↖2 2 ↖2 2 2
c 0 1 1 2 2 2 ↖3 ↖3
JavaScript code:
function remove(arr,sub){
var _arr = [];
arr.forEach(function(v,i){ if (!sub.has(i)) _arr.push(arr[i]); });
return _arr;
}
function f(arr,sub){
var res = [],
lcs = new Array(sub.length + 1),
nodes = new Array(sub.length + 1);
for (var i=0; i<sub.length+1;i++){
nodes[i] = [];
lcs[i] = [];
for (var j=0; j<(i==0?arr.length+1:1); j++){
// store lcs and node count on the left
lcs[i][j] = [0,0];
}
}
for (var i=1; i<sub.length+1;i++){
for (var j=1; j<arr.length+1; j++){
if (sub[i-1] == arr[j-1]){
lcs[i][j] = [1 + lcs[i-1][j-1][0],lcs[i][j-1][1]];
if (lcs[i][j][0] == i){
// [arr index, left node count above]
nodes[i].push([j - 1,lcs[i-1][j-1][1]]);
lcs[i][j][1] += 1;
}
} else {
lcs[i][j] = [Math.max(lcs[i-1][j][0],lcs[i][j-1][0]),lcs[i][j-1][1]];
}
}
}
function enumerate(node,i,accum){
if (i == 0){
res.push(remove(arr,new Set(accum)));
return;
}
for (var j=0; j<node[1]; j++){
var _accum = accum.slice();
_accum.push(nodes[i][j][0]);
enumerate(nodes[i][j],i - 1,_accum);
}
}
nodes[sub.length].forEach(function(v,i){
enumerate(nodes[sub.length][i],sub.length - 1,[nodes[sub.length][i][0]]);
});
return res;
}
console.log(JSON.stringify(f([1,2,1,3,1,4,4], [1,4,4])));
console.log(JSON.stringify(f([8,6,4,4], [6,4,8])));
console.log(JSON.stringify(f([1,1,2], [1])));
console.log(JSON.stringify(f(['a','g','b','a','b','c','c'], ['a','b','c'])));
You can use recursion. Build a new subsequence C by walking through A and pushing elements in order. Whenever you encounter an element that matches the head of B, you will fork the recursion into two paths: one in which you remove (i.e. skip) the element from A and B, and another in which you ignore it and continue business as usual.
If you exhaust all of B (meaning that you "removed" all elements in B from A), then appending the rest of A to C will produce a valid subsequence. Otherwise, if you reach the end of A without exhausting all of B, C is not a valid subsequence and should be discarded.
function removeSubSeq(a, b) {
function* remove(i, j, c) {
if (j >= b.length) {
yield c.concat(a.slice(i));
} else if (i >= a.length) {
return;
} else if (a[i] === b[j]) {
yield* remove(i + 1, j + 1, c);
yield* remove(i + 1, j, c.concat(a.slice(i, i + 1)));
} else {
yield* remove(i + 1, j, c.concat(a.slice(i, i + 1)));
}
}
if (a.length < b.length) {
return [];
}
return Array.from(remove(0, 0, []));
}
The inner helper function can be made slightly more efficient by replacing the use of Array.concat in each recursive branch with a simple push()/pop() pair, although, this makes the control flow a little harder to grok.
function* remove(i, j, c) {
if (j >= b.length) {
yield c.concat(a.slice(i));
} else if (i >= a.length) {
return;
} else {
if (a[i] === b[j]) {
yield* remove(i + 1, j + 1, c);
}
c.push(a[i]);
yield* remove(i + 1, j, c);
c.pop();
}
}
This problem can be solved using the bottom-up Dynamic Programming approach with backtracking.
Let's consider a recurrence relation f(i1, i2), which helps to check whether the tail of the sequence arr2 can be removed from the tail of the sequence arr1:
f(i1, i2) = true, if(i1 == length(arr1) AND i2 == length(arr2))
f(i1, i2) = f(i1 + 1, i2) OR f(i1 + 1, i2 + 1), if(arr1[i1] == arr2[i2])
f(i1, i2) = f(i1 + 1, i2), if(arr1[i1] != arr2[i2])
solution = f(0, 0)
I use the term tail to denote the subsequence of arr1 which starts at the index i1 and spans to the end of arr1 (and the same for arr2 - tail of arr2 starts at the index i2 and spans to the end of arr2).
Let's start with top-down implementation of the given recurrence relation (yet without memoization, in order to keep the explanation simple). Below is the snippet of Java code, which prints all possible subsequences of arr1 after removal of arr2:
void remove(int[] arr1, int[] arr2) {
boolean canBeRemoved = remove(arr1, arr2, 0, 0, new Stack<>());
System.out.println(canBeRemoved);
}
boolean remove(int[] arr1, int[] arr2, int i1, int i2, Stack<Integer> stack) {
if (i1 == arr1.length) {
if (i2 == arr2.length) {
// print yet another version of arr1, after removal of arr2
System.out.println(stack);
return true;
}
return false;
}
boolean canBeRemoved = false;
if ((i2 < arr2.length) && (arr1[i1] == arr2[i2])) {
// current item can be removed
canBeRemoved |= remove(arr1, arr2, i1 + 1, i2 + 1, stack);
}
stack.push(arr1[i1]);
canBeRemoved |= remove(arr1, arr2, i1 + 1, i2, stack);
stack.pop();
return canBeRemoved;
}
Th provided snippet of code does not use any memoization technique, and has the exponential runtime complexity for all instances of given problem.
However, we can see that the variable i1 can only have the value from the interval [0..length(arr1)], also the variable i2 can only have the value from the interval [0..length(arr2)].
Hence, it is possible to check, whether arr2 can be removed from arr1 with polynomial runtime complexity: O(length(arr1) * length(arr2)).
On the other hand, even if we find with polynomial runtime complexity that arr2 can be removed from arr1 - there still might be an exponential amount of possible ways to remove arr2 from arr1.
For example, consider the instance of the problem: when it is needed to remove arr2 = [1,1,1] from arr1 = [1,1,1,1,1,1,1]. There are 7!/(3! * 4!) = 35 ways to do it.
Nevertheless, below is the bottom-up Dynamic Programming solution with backtracking, which is still for many instances of given problem will have better runtime complexity than exponential:
void remove_bottom_up(int[] arr1, int[] arr2) {
boolean[][] memoized = calculate_memoization_table(arr1, arr2);
backtrack(arr1, arr2, 0, 0, memoized, new Stack<>());
}
/**
* Has a polynomial runtime complexity: O(length(arr1) * length(arr2))
*/
boolean[][] calculate_memoization_table(int[] arr1, int[] arr2) {
boolean[][] memoized = new boolean[arr1.length + 1][arr2.length + 1];
memoized[arr1.length][arr2.length] = true;
for (int i1 = arr1.length - 1; i1 >= 0; i1--) {
for (int i2 = arr2.length; i2 >= 0; i2--) {
if ((i2 < arr2.length) && (arr1[i1] == arr2[i2])) {
memoized[i1][i2] = memoized[i1 + 1][i2 + 1];
}
memoized[i1][i2] |= memoized[i1 + 1][i2];
}
}
return memoized;
}
/**
* Might have exponential runtime complexity.
*
* E.g. consider the instance of the problem, when it is needed to remove
* arr2 = [1,1,1] from arr1 = [1,1,1,1,1,1,1].
*
* There are 7!/(3! * 4!) = 35 ways to do it.
*/
void backtrack(int[] arr1, int[] arr2, int i1, int i2, boolean[][] memoized, Stack<Integer> stack) {
if (!memoized[i1][i2]) {
// arr2 can't be removed from arr1
return;
}
if (i1 == arr1.length) {
// at this point, instead of printing the variant of arr1 after removing of arr2
// we can just collect this variant into some other container
// e.g. allVariants.add(stack.clone())
System.out.println(stack);
return;
}
if ((i2 < arr2.length) && (arr1[i1] == arr2[i2])) {
backtrack(arr1, arr2, i1 + 1, i2 + 1, memoized, stack);
}
stack.push(arr1[i1]);
backtrack(arr1, arr2, i1 + 1, i2, memoized, stack);
stack.pop();
}
JavaScript Implementation of described solution
function remove_bottom_up(base_arr, removed_arr) {
// Initialize memoization table
var memoized = new Array(base_arr.length + 1);
for (var i = 0; i < base_arr.length + 1; i++) {
memoized[i] = new Array(removed_arr.length + 1);
}
memoized[base_arr.length][removed_arr.length] = true;
// Calculate memoization table
for (var i1 = base_arr.length - 1; i1 >= 0; i1--) {
for (var i2 = removed_arr.length; i2 >= 0; i2--) {
if ((i2 < removed_arr.length) && (base_arr[i1] == removed_arr[i2])) {
memoized[i1][i2] = memoized[i1 + 1][i2 + 1];
}
memoized[i1][i2] |= memoized[i1 + 1][i2];
}
}
// Collect all variants
var all_variants = [];
backtrack(base_arr, removed_arr, 0, 0, memoized, [], all_variants);
return all_variants;
}
function backtrack(base_arr, removed_arr, i1, i2, memoized, stack, all_variants) {
if (!memoized[i1][i2]) {
// arr2 can't be removed from arr1
return;
}
if (i1 == base_arr.length) {
all_variants.push(stack.slice(0));
return;
}
if ((i2 < removed_arr.length) && (base_arr[i1] == removed_arr[i2])) {
backtrack(base_arr, removed_arr, i1 + 1, i2 + 1, memoized, stack, all_variants);
}
stack.push(base_arr[i1]);
backtrack(base_arr, removed_arr, i1 + 1, i2, memoized, stack, all_variants);
stack.pop();
}
console.log(JSON.stringify(remove_bottom_up([1, 2, 1, 3, 1, 4, 4], [1, 4, 4])));
console.log(JSON.stringify(remove_bottom_up([8, 6, 4, 4], [6, 4, 8])));
console.log(JSON.stringify(remove_bottom_up([1, 1, 2], [1])));
console.log(JSON.stringify(remove_bottom_up([1, 1, 1, 1, 1, 1, 1], [1, 1, 1])));
The algorithm:
Recursively build a tree of nodes, starting from the first element in B. Each node's value is the index of the subsequence item matching its level and its descendants are the indices of the next item -- so for [1,2,1,3,1,4,4], [1,4,4] the tree would be [ [ 0, [5, [6]], [6] ], [ 2, [5, [6]], [6] ], [ 4, [5, [6]], [6] ].
Walk this tree and build up subsequences of items to delete, i.e. find all paths in the tree that are as long as the subsequence. This would result in a list like [ [ 0, 5, 6 ], [ 2, 5, 6 ], [ 4, 5, 6 ] ].
For each list thus developed, append the list that results from the elements at those indices being deleted: [ [ 2, 1, 3, 1 ], [ 1, 2, 3, 1 ], [ 1, 2, 1, 3 ] ].
The code to do this, which matches all your test cases:
#!/usr/bin/env node
var _findSubSeqs = function(outer, inner, current) {
var results = [];
for (var oi = current; oi < outer.length; oi++) {
if (outer[oi] == inner[0]) {
var node = {
value: oi,
children: _findSubSeqs(outer, inner.slice(1), oi+1)
};
results.push(node);
}
}
return results;
}
var findSubSeqs = function(outer, inner) {
var results = _findSubSeqs(outer, inner, 0);
return walkTree(results).filter(function(a) {return (a.length == inner.length)});
}
var _walkTree = function(node) {
var results = [];
if (node.children.length) {
for (var n = 0; n < node.children.length; n++) {
var res = _walkTree(node.children[n])
for (r of res) {
results.push([node.value].concat(r))
}
}
} else {
return [[node.value]]
}
return results
}
var walkTree = function(nds) {
var results = [];
for (var i = 0; i < nds.length; i++) {
results = results.concat(_walkTree(nds[i]))
}
return results
}
var removeSubSeq = function(outer, inner) {
var res = findSubSeqs(outer, inner);
var subs = [];
for (r of res) {
var s = [];
var k = 0;
for (var i = 0; i < outer.length; i++) {
if (i == r[k]) {
k++;
} else {
s.push(outer[i]);
}
}
subs.push(s);
}
return subs
}
console.log(removeSubSeq([1,2,1,3,1,4,4], [1,4,4]))
console.log(removeSubSeq([8,6,4,4], [6,4,8]) )
console.log(removeSubSeq([1,1,2], [1]))
First I would use string. It's easier to manipulate:
var results = [];
function permute(arr) {
var cur, memo = [];
for (var i = 0; i < arr.length; i++) {
cur = arr.splice(i, 1);
if (arr.length === 0) {
results.push(memo.concat(cur));
}
permute(arr.slice(), memo.concat(cur));
arr.splice(i, 0, cur[0]);
}
return results;
}
function removeSub(arr, sub) {
strArray = arr.join(' ');
if(strArray.includes(sub)){
return strArray.replace(sub.join(' ')).split(' ');
}
return [];
}
function removeSubSeq(arr, sub) {
return permute(removeSub(arr, sub));
}
I have not commented the code, but do not hesitate to ask for clarification. It's not tested, but the idea is in it...
My aim was to create and call functions as little as possible. This seems to work. Could definitely be cleaned up. Something to play around with...
function removeSubSeq( seq, sub ) {
var arr,
sub_v,
sub_i = 0,
seq_i,
sub_len = sub.length,
sub_lenm1 = sub_len - 1,
seq_len = seq.length,
pos = {},
pos_len = [],
c_pos,
map_i = [],
len,
r_pos,
sols = [],
sol;
do {
map_i[ sub_i ] = 0;
sub_v = sub[ sub_i ];
if( pos[ sub_v ] ) {
pos_len[ sub_i ] = pos_len[ sub_i - 1 ];
continue;
}
arr = pos[ sub_v ] = [];
c_pos = 0;
seq_i = seq_len;
while( seq_i-- ) {
if( seq[ seq_i ] === sub_v ) {
arr[ c_pos++ ] = seq_i;
}
}
pos_len[ sub_i ] = arr.length;
} while( ++sub_i < sub_len );
len = pos[ sub[ 0 ] ].length;
while( map_i[ 0 ] < len ) {
sub_i = 0;
arr = [];
do {
r_pos = pos[ sub[ sub_i ] ][ map_i[ sub_i ] ];
if( sub_i && r_pos <= arr[ sub_i - 1] ) break;
arr.push( r_pos );
} while( ++sub_i < sub_len );
if( sub_i === sub_len ) {
sol = seq.slice( 0 );
while( sub_i-- ) sol.splice( arr[ sub_i ], 1 );
sols.push( sol );
}
sub_i = sub_lenm1;
while( ++map_i[ sub_i ] === pos_len[ sub_i ] ) {
if( sub_i === 0 ) break;
map_i[ sub_i-- ] = 0;
}
} while( map_i[ 0 ] < len );
return sols;
}
console.log(JSON.stringify(removeSubSeq([1,2,1,3,1,4,4], [1,4,4])));
console.log(JSON.stringify(removeSubSeq([8,6,4,4], [6,4,8])));
console.log(JSON.stringify(removeSubSeq([1,1,2], [1])));
console.log(JSON.stringify(removeSubSeq(['a','g','b','a','b','c','c'], ['a','b','c'])));

push numbers that repeats only 2 times in sequence in a new array

I want my array puma to contain 2,3,5,7,13,17,19,23 at the end of the code. (They are the ones that repeat 02 times in sequence in the number array) Thanks
<!DOCTYPE html>
<html>
<head>
<title>Trial</title>
</head>
<body>
<script>
var number = [1,2,2,3,3,4,4,4,5,5,6,6,6,6,7,7,8,8,8,8,
9,9,9,10,10,10,10,10,11,12,12,12,12,12,12,
12,13,13,14,14,14,14,14,15,15,15,15,15,
16,16,16,16,16,17,17,18,18,18,18,18,18,
18,18,19,19,20,20,20,20,20,20,20,21,
21,21,21,22,22,22,22,23,23];
var puma=[];
for (k=0; k<number.length; k++)
for (i=1; i<number.length; i++)
for (j=2; j<number.length; j++)
{
if (number[k]==number[i] && number[k]!==number[j])
{
puma.push(number[k]);
}
};
document.write(puma);
</script>
</body>
</html>
We can use counting sorting concept for this purpose. So we can firstly count number of each element. And then get elements whose count is equal to two.
var number = [1,2,2,3,3,4,4,4,5,5,6,6,6,6,7,7,8,8,8,8,9,9,9,10,10,10,10,10,11,12,12,12,12,12,12,12,13,13,14,14,14,14,14,15,15,15,15,15,16,16,16,16,16,17,17,18,18,18,18,18,18,18,18,19,19,20,20,20,20,20,20,20,21,21,21,21,22,22,22,22,23,23],
counts = [],
puma=[],
i;
for(i = 0; i < number.length; i++) {
counts[number[i]] = !!counts[number[i]] ? counts[number[i]] + 1 : 1;
}
function getNumbers(counts, n) {
return counts.reduce(function(a, c, i) {
if(c === n) {
a.push(i);
}
return a;
}, []);
}
document.write(getNumbers(counts, 2) + '</br>');
document.write(getNumbers(counts, 3) + '</br>');
document.write(getNumbers(counts, 4));
Update
If it is needed to get numbers only by sequences, then we have two possible situations:
When there is only one sequence and its length = N
When there are several sequences and length of one of them is equal N.
var number = [0, 0, 1, 1, 2, 1, 1, 1],
counts = [],
puma=[],
prev, i, currentCount = 1;
for(i = 1, prev = number[0]; i < number.length; i++) {
var current = number[i];
if(prev === current) {
currentCount++;
}
if(prev !== current || i === number.length - 1) {
console.log(prev)
counts[prev] = (counts[prev] || []).concat([currentCount]);
prev = current;
currentCount = 1;
}
}
// (2nd situation) Get every number if it was found N times in a sequence
function getNumbers1(counts, n) {
return counts.reduce(function(a, c, i) {
if(c.indexOf(n) > -1) {
a.push(i);
}
return a;
}, []);
}
// (1st situation) Get number only if there is only one sequence of this number and it's length is equal to N
function getNumbers2(counts, n) {
return counts.reduce(function(a, c, i) {
if(c.length === 1 && c[0] === n) {
a.push(i);
}
return a;
}, []);
}
document.write(getNumbers1(counts, 2) + '</br>');
document.write(getNumbers2(counts, 2));
In this code we firstly get all the lengths of every sequence of every number. And then get the sequences that we need.
In last example counts array will be equal to [ [ 2 ], [ 2, 3 ], [ 1 ] ] after

Categories

Resources