I understand that this is more of a problem solving than a coding question as such, so my apologies if this post breaks any rules here, but does anyone have any idea how best to go about this?
I am trying to solve the problem, but there is a logical error in my code, or rather I have not considered all the conditions, tell me how to find it.
The problem: An adventurer found himself in a dungeon full of treasures. However, before entering he activated a trap, which in t minutes will flood the entire room.
You are given an array of chests, where chests[i] is the number of treasures in the chest. The explorer can either pick up treasure i, taking one minute, or move to the next chest (i+1), which also takes one minute. He starts at position zero, it is not necessary to reach the end of the array.
Determine the maximum amount of treasure the hero can collect. by writing function getResult(chests,t):Integer
Input:
chests - number of treasures in chests, 2<length(chests)<20, 0<chests[i]<100
t - number of minutes to flood, 0<t<20
Output:
Integer - maximum number of treasures collected
Example 1:
chests = [1, 4, 2] t = 3
getResult(chests, t) = 5 // In the first minute the hero collected treasure from the first chest,
in the second minute, he moved to the next chest, in the third minute, he gets the treasure from it
Example 2:
chests = [7, 8, 9] t = 2
getResult(chests, t) = 8 // In the first minute, the hero goes to the second chest and gets the treasure from it,
than taking the treasure in the first chest
below are my reasons, and code.
the horizontal side of the matrix is moves and captures. They don't differ, because it takes the same amount of time to move or capture an item.
1 unit per move or capture. The chests are arranged vertically, in order of increasing number of moves to the chest, so we can say
If n (number of chests)=4, the values in the chests are in order of distance of moves with the contents of 1, 4, 3, 5
It is possible to take any[j,i] chest in i moves. In 10 moves it is possible to take all items, the point is that the number of moves n for taking
chest is a triangular number, i.e. the sum of the first n natural numbers. The formula for calculating a triangular number is: 1/2 * n * (n+1)
We build a matrix, put the inputs [1, 4, 3, 5] there, and place all the sums of those there, too, as chests.
If one cell of the matrix contains more than 1 chest, we choose maximum.
all combinations without regard to direction, (i.e. 2+3=3+2 without regard to such permutations)
summary matrix: chests and their combinations and steps to get
1__2__3__4__5__6__7__8__9_10
first chest 1, | | | | | | | | |
second chest 0, 4, 5 | | | | | | |
third chest 0, 0, 3, 4, 7, 8, | | | |
fourth chest 0, 0, 0, 5, 6, 9,10, 9 12 13
there are combinations not included in the matrix,
i.e. 4c+1c,2c>4c+3 (throw out the equal in moves option 4+3 chest, this is not the maximum)
So, form a one-dimensional array to select the best (maximal) combinations for each move
maxs_in_onerow=[1,4,5,5,7,9,10,9,12,13]
count sum of elements up to t-1
compare with the element with the number t
ANSWER: sumofchests(0,t-1)>maxs_in_onerow(t) ? return sumofchests(0,t-1) : maxs_in_onerow(t)
// fill in the backpack, output the result
function getResult(chests, t) {
function transpose(a) { //helper func
// Calculate the width and height of the Array
let w = a.length || 0;
let h = a[0] instanceof Array ? a[0].length : 0;
// In case it is a zero matrix, no transpose routine needed.
if(h === 0 || w === 0) { return []; }
let i, j, t = [];
// Loop through every item in the outer array (height)
for(i=0; i<h; i++) {
// Insert a new row (array)
t[i] = [];
// Loop through every item per item in outer array (width)
for(j=0; j<w; j++) {
// Save transposed data.
t[i][j] = a[j][i];
}
}
return t;
}
function sumofsteps(c = chests) {
if (!Array.isArray(c)) c=Array.from({length:c})
return (c.length * (c.length + 1)) / 2;
}
function sumofchests(c = chests) {
return c.reduce((partialSum, a) => partialSum + a, 0);
}
const J = sumofsteps(chests);
const I = (chests.length);
// console.log(`${chests.length}, ${J}, ${I}`);
//create a matrix with the size of the number of chests
//for as many moves as it takes to get all the chests=J
let matrix = Array.from({ length: I }, () => new Array(J).fill(0));
let maxs_in_onerow = [];
// fill with values
let prevstepI = 0;
chests.forEach((element,index) => {
let cchests=chests.slice(0,index)
//the side of the matrix, the moves for moving and taking chests, grows like half a square
for (let i = prevstepI; i <=cchests.length; i++) {
// matrix side, chests,
// nothing before the diagonal, skip
if (i<index) continue
if (i===index) { //diagonal, minimum moves to take
prevstepI=i
matrix[index][i]=element
}
let _x=0
while (_x<i) {
matrix[_x].forEach((el , ind ) => { /* ... */
if (el > 0) {matrix[index][i+ind+1]=element + el}
})
//create combinations of chests
_x+=1
if (_x===i) break
}
}
});
// form maxs_in_onerow=[1,4,5,5,7,9,10,9,12,13]
let jmartix=[]
jmartix=transpose(matrix)
for (let j = 0; j < J; j++) {
let cur=Math.max.apply(null, jmartix[j])
maxs_in_onerow.push(cur);
}
// fill in the backpack, output the result
let res;
if (t === 1) res = chests[0];
if (t >= J) res = sumofchests(chests);
if (t<J) {
let res1=Math.max.apply(null,maxs_in_onerow.slice(0,t))
let res2=sumofchests(maxs_in_onerow.slice(0,t-1))
res = res1>res2 ? res1 : res2
}
// console.log( `${matrix}, ${totalsteps()}, t: ${t}, maxs: ${maxs_in_onerow}, res: ${res} ` );
return res;
}
console.log(` input: [1, 4, 2], 3 \n response: ${getResult([1, 4, 2], 3)}`);
console.log(` input: [7, 8, 9], 2 \n response: ${getResult([7, 8, 9], 2)}`);
My sleep-deprived brain is not up trying to interpret your code or your reasoning. Instead, here's a simple recursive solution:
const maxTreasure = ([c, ...cs], t) =>
t <= 0 || c == undefined
? 0
: c == 0
? maxTreasure (cs, t - 1)
: Math. max (c + maxTreasure ([0, ...cs], t - 1), maxTreasure (cs, t - 1) )
console .log (`maxTreasure ([1, 4, 2], 3) //=> ${maxTreasure ([1, 4, 2], 3)}`);
console .log (`maxTreasure ([7, 8, 9], 2) //=> ${maxTreasure ([7, 8, 9], 2)}`);
We check whether the time has run out or if there are no more chests found, and if so, simply return 0. If the first chest is empty, we have no reasonable alternative than to move on to the next one, so we reduce the time by one and recur with the remaining chests. Otherwise we have to choose the better of two possibilities: taking the current chests' treasures or moving on to the next one. We use Math .max to select one of these, and calculate them by recursion. In one case, we include the current chest (c) and recur with a list of chests that replaces the current chest's value with zero. In the other, we move on to the remaining chests. In either case, we reduce the time by one.
So we have base cases, and three potential recursive calls. In each of those calls, we're reducing the time by 1, so we will eventually reach the case t <= 0.
That same foggy brains isn't going to do the analysis of time complexity here. I wouldn't be surprised if this is horribly inefficient; it's likely of exponential complexity in the number of chests. But it's simple and a good start at thinking of the problem logically. If it turns out too inefficient for real world use (ha!) we can come back at a solution using bottom-up dynamic programming... and that may be what you're attempting.
But I would always start simply, and often recursion is simplest.
Related
I have two arrays and for each number in the first array I need to find the largest number from the second array that fits into that number and break the first number into its components. Since 25 from the second array is the largest number that fits into 80 of the first, I need to transform 80 into two numbers - 75, 5. Likewise for 6 and 5 the result should be 5, 1. So the end result should be an array [75, 5, 5, 1]
let arr = [80, 6]
let arr2 = [25, 5]
for (let x of arr) {
for (let y of arr2) {
if (x / y > 1 && x / y < 4) {
let mlt = Math.floor(x / y)
largestFit = y * mlt
arr.splice(arr.indexOf(x), 1, largestFit)
}
}
}
console.log(arr)
The code above gives [75, 5] so thought I could add one more splice operation to insert the remainders, but doing this arr.splice(arr.indexOf(x + 1), 0, x - largestFit) just crashes the code editor. Why isn't this working and what is the solution? Thank you.
It is not advised to splice an array that is being iterated, and it is the reason why your loop got suck sometimes.
Instead build a new array, so it doesn't affect the iteration on the input array.
If you first sort the second array in descending order, you can then find the first value in that array that fits the amount, and be sure it is the greatest one. For sorting numerically in descending order, you can use sort with a callback function.
Once you have found the value to subtract, you can use the remainder operator (%) to determine what is left over after this subtraction.
function breakDown(amounts, coins) {
// Get a sorted copy (descending) of denominations:
coins = [...coins].sort((a, b) => b - a);
const result = []; // The results are stored here
for (let amount of amounts) {
for (let coin of coins) {
if (coin <= amount) {
result.push(amount - amount % coin);
amount %= coin;
}
}
if (amount) result.push(amount); // remainder
}
return result;
}
// Example 1
console.log(breakDown([80, 6], [25, 5]));
// Example 2
console.log(breakDown([100, 90, 6], [100, 75, 15, 5, 1]));
Explanations
The coins are sorted in descending order so that when we search for a fitting coin from left to right, we can be sure that if we find one, it will be the greatest one that fits, not just any. So for instance, if our amount is 7 and we have coins 2 and 5, we don't want to use coin 2 just yet -- we want to use coin 5 first. So if we sort those coins into [5, 2], and then start looking for the first coin that is smaller than our amount, we will be sure to find 5 first. The result would not be as expected if we would have found 2 first.
We can calculate the remainder of a division with the % operator, and there is a shortcut for when we want to assign that remainder back to the amount: it is the %= operator. amount %= coin is short for amount = amount % coin.
When the inner loop completes, it might be that amount is not zero, i.e. there is still an amount that remains that cannot be consumed by any available coin. In that case we want to still have that remainder in the result array, so we push it.
Often, the amount will already be zero when the loop ends. This will be ensured when the smallest coin is the smallest unit of amount one can expect. For instance if the amount is expressed as an integer, and the smallest coin is 1, then there will never be any remainder left when the inner loop has completed. If however the smallest coin would be 2, and we are left with an a amount of 1, then there is no way to reduce that amount to zero, so after the loop ends, we could be stuck with that remainder. And so we have this code to deal with that:
if (amount) result.push(amount)
Floating point
Be careful with using non-integers: floating point arithmetic does not always lead to expected results, because numbers like 0.1 cannot be accurately represented in floating point. You can end up with a non-zero amount after the inner loop finishes, when zero would have been expected. That amount will be tiny like 1e-15, and really indicates there was such an inaccuracy at play.
When calculating with monetary amounts, it is common practice to do that in number of cents, so to make all calculations based on integers. This will give reliable results.
I found the issue. After the first splice() operation indexOf(x) was returning -1, since x's are being replaced, so the solution is to assign indexOf(x) to a variable and use that variable for both splice() operations.
let arr = [80, 6]
let arr2 = [25, 5]
for (let x of arr) {
for (let y of arr2) {
if (x / y > 1 && x / y < 4) {
let mlt = Math.floor(x / y)
largestFit = y * mlt
let idx = arr.indexOf(x)
arr.splice(idx, 1, largestFit)
arr.splice(idx + 1, 0, x - largestFit)
}
}
}
console.log(arr)
I'm trying to understand how to solve Leetcode Problem #740: Delete and Earn
I recently was given this problem as part of a pre-interview assessment and was unable to complete it in the allotted time. I've been working on it today to try and wrap my head around it, but I'm kinda spinning in circles at the moment. I've checked numerous resources, videos, tutorials, etc, but I'm working in vanilla JS and a lot of the guides are in C++, Python, or Typescript which I don't currently know. (I plan on learning Python and Typescript at minimum, but I'm working with my current set of knowledge for the time being). This is leading to confusion and frustration, as an accurate translation of sample python/c++ code, etc continues to elude me.
The problem is as follows:
You are given an integer array nums. You want to maximize the number of points you get by performing the following operation any number of times:
Pick any nums[i] and delete it to earn nums[i] points. Afterwards, you must delete every element equal to nums[i] - 1 and every element equal to nums[i] + 1.
Return the maximum number of points you can earn by applying the above operation some number of times.
Example 1
Input: nums = [3,4,2]
Output: 6
Explanation: You can perform the following operations:
- Delete 4 to earn 4 points. Consequently, 3 is also deleted. nums = [2].
- Delete 2 to earn 2 points. nums = [].
You earn a total of 6 points.
Example 2
Input: nums = [2,2,3,3,3,4]
Output: 9
Explanation: You can perform the following operations:
- Delete a 3 to earn 3 points. All 2's and 4's are also deleted. nums = [3,3].
- Delete a 3 again to earn 3 points. nums = [3].
- Delete a 3 once more to earn 3 points. nums = [].
You earn a total of 9 points.
What I have so far:
const deleteAndEarn = (nums) => {
if(!nums || nums.length === 0) return 0;
if(nums.length === 1) return nums[0];
if(nums.length === 2) return nums[1];
const freq = makeDict(nums);
let prevNum
let [keep, avoid] = [0, 0];
for(const num of [...Object.keys(freq)].sort()){
let max = Math.max(keep, avoid)
if(parseInt(num) - 1 !== prevNum){
[keep, avoid] = [
(freq[num] * parseInt(num)) + max,
max
]
}else{
[keep, avoid] = [
parseInt(num) * freq[num] + avoid,
max
]
}
prevNum = parseInt(num)
}
return Math.max(keep, avoid)
};
const makeDict = (nums) => {
const dict = {}
for(const num of nums){
dict[num] = !!dict[num] ? dict[num]++ : 1
}
return dict
}
Provided Python Solution
This is what I've tried to model my code off of, but I don't actually know Python syntax so I'm sure I'm missing something.
class Solution(object):
def deleteAndEarn(self, nums):
count = collections.Counter(nums)
prev = None
avoid = using = 0
for k in sorted(count):
if k - 1 != prev:
avoid, using = max(avoid, using), k * count[k] + max(avoid, using)
else:
avoid, using = max(avoid, using), k * count[k] + avoid
prev = k
return max(avoid, using)
I really don't understand at all why this code isn't working, and I've even gone as far as to run sample cases step by step. Please help me understand how to do this so I can get a job!
Many thanks
I figured it out! The problem is twofold.
Bug Number One
First, shoutout to David Eisenstat for catching the bug in my makeDict() function.
The incorrect line of code reads:
dict[num] = !!dict[num] ? dict[num]++ : 1
Whereas the correct syntax is as follows:
dict[num] = !!dict[num] ? ++dict[num] : 1
or alternatively
dict[num] = !!dict[num] ? dict[num] + 1 : 1
The issue comes from how postfix vs prefix increment operators work in Javascript.
From the MDN docs:
If used postfix, with operator after operand (for example, x++), the increment operator increments and returns the value before incrementing.
If used prefix, with operator before operand (for example, ++x), the increment operator increments and returns the value after incrementing.
Bug Number Two
The second issue comes from my initial guard clauses.
if(nums.length === 2) return nums[1];
I think this was a remnant from when I was sorting the provided array at the very start, but even then automatically selecting the last element doesn't really make any sense. I deleted this line and, combined with the adjustment to the previous makeDict() function, the code passed all the provided tests.
My working solution is provided below. Open to any suggestions as to how to improve the code for both readability, or efficiency.
Appreciate the help!
const deleteAndEarn = (nums) => {
if(!nums || nums.length === 0) return 0;
if(nums.length === 1) return nums[0];
const freq = makeDict(nums);
let prevNum
let [keep, avoid] = [0, 0];
for(const num of Object.keys(freq)){
let max = Math.max(keep, avoid)
if(parseInt(num) - 1 !== prevNum){
[keep, avoid] = [
(freq[num] * parseInt(num)) + max,
max
]
}else{
[keep, avoid] = [
parseInt(num) * freq[num] + avoid,
max
]
}
prevNum = parseInt(num)
}
return Math.max(keep, avoid)
};
const makeDict = (nums) => {
const dict = {}
for(const num of nums){
dict[num] = !!dict[num] ? ++dict[num] : 1
}
return dict
}
One bug in your existing code is that
[...Object.keys(freq)].sort()
will not sort numbers in order - see here.
Another bug is that your algorithm doesn't have any backtracking - you don't want to greedily choose 3 when given [3, 4, 4, 4].
I think the best way to approach this is to understand that it's only strings of consecutive numbers in the input that need to be considered. For example, given
[1, 2, 3, 6, 7, 8]
Separate it out into all the consecutive strings of integers:
[1, 2, 3]
[6, 7, 8]
Then decide the optimal picks for each sequence.
You can't just pick all odd numbers or all even numbers in the sequence, because that would fail to pick, eg, 1 and 4 for [1, 1, 1, 1, 1, 1, 2, 3, 4, 4, 4, 4, 4]. The best approach I can see is to use a recursive function: when checking a sequence, getBestSequenceSum, starting with N, return the maximum of:
Sum of N plus getBestSequenceSum(seq.slice(2)) (skipping the next item in the sequence), OR
Sum of getBestSequenceSum(seq.slice(1)) (using the next item in the sequence)
to adequately cover all possibilities.
There may be more efficient algorithms, but this is relatively simple and intuitive.
const getBestSequenceSum = (seq) => {
if (seq.length === 0) return 0;
// Include the lowest value in the sequence, or not?
const sumOfLowestVal = seq[0].num * seq[0].count;
return Math.max(
sumOfLowestVal + getBestSequenceSum(seq.slice(2)),
getBestSequenceSum(seq.slice(1))
);
};
const deleteAndEarn = (nums) => {
nums.sort((a, b) => a - b);
let lastNum;
const sequences = [];
for (const num of nums) {
if (num !== lastNum && num !== lastNum + 1) {
// New sequence
sequences.push([]);
}
const thisSequence = sequences[sequences.length - 1];
if (num !== lastNum) {
thisSequence.push({ num, count: 0 });
}
thisSequence[thisSequence.length - 1].count++;
lastNum = num;
}
return sequences.reduce((a, seq) => a + getBestSequenceSum(seq), 0);
};
console.log(deleteAndEarn([10,8,4,2,1,3,4,8,2,9,10,4,8,5,9,1,5,1,6,8,1,1,6,7,8,9,1,7,6,8,4,5,4,1,5,9,8,6,10,6,4,3,8,4,10,8,8,10,6,4,4,4,9,6,9,10,7,1,5,3,4,4,8,1,1,2,1,4,1,1,4,9,4,7,1,5,1,10,3,5,10,3,10,2,1,10,4,1,1,4,1,2,10,9,7,10,1,2,7,5]));
The number of calculations could be reduced somewhat by changing the { num, count: 0 } objects to a single number instead, but that would be more difficult to understand when reading the code.
You could also reduce the number of calculations by caching already-optimized sequences so as not to recalculate them multiple times, but that'd make the code significantly longer.
I am trying the "CountDistinctSlices" codility question. I tried my best scored 30% so tried to look up on someone who did it for insights. and basically what I don't get in the answer is the use of the initialized seen array(and M for that matter) and how its being used can someone who get it kindly walk me through this code.
THis is the Answer I found without explanation
function solution(M, A) {
// write your code in JavaScript (Node.js 8.9.4)
let sum = 0;
let front = 0;
let back = 0;
const seen = new Array(M+1).fill(false);
while (front < A.length && back < A.length){
while (front < A.length && seen[A[front]] !== true){
sum += (front-back+1);
seen[A[front]] = true;
front += 1;
}
while (A[back] !== A[front]){
seen[A[back]] = false;
back += 1;
}
seen[A[back]] = false;
back += 1;
}
return Math.min(sum, 1000000000);
}
This is the full question
An integer M and a non-empty array A consisting of N non-negative
integers are given. All integers in array A are less than or equal to
M.
A pair of integers (P, Q), such that 0 ≤ P ≤ Q < N, is called a slice
of array A. The slice consists of the elements A[P], A[P + 1], ...,
A[Q]. A distinct slice is a slice consisting of only unique numbers.
That is, no individual number occurs more than once in the slice.
For example, consider integer M = 6 and array A such that:
A[0] = 3
A[1] = 4
A[2] = 5
A[3] = 5
A[4] = 2
There are exactly nine distinct slices: (0, 0), (0, 1), (0, 2), (1,
1), (1, 2), (2, 2), (3, 3), (3, 4) and (4, 4).
The goal is to calculate the number of distinct slices.
Write a function:
function solution(M, A);
that, given an integer M and a non-empty array A consisting of N
integers, returns the number of distinct slices.
If the number of distinct slices is greater than 1,000,000,000, the
function should return 1,000,000,000.
For example, given integer M = 6 and array A such that:
A[0] = 3
A[1] = 4
A[2] = 5
A[3] = 5
A[4] = 2
the function should return 9, as explained above.
Write an efficient algorithm for the following assumptions:
N is an integer within the range [1..100,000];
M is an integer within the range [0..100,000];
each element of array A is an integer within the range [0..M].
Lets go through the algorithm first:
You first start from the beginning and traverse until you find a duplicate. Now you have a range = [ back - front ]
The code called this range [back, front] where "back" is beginning and "front" is your moving pointer.
How many distinct slices are there in this range? There are slices of size 1, 2, .... front - back + 1, so it is sum = 1 + 2 + 3 + ... [front - back + 1]
Now that you encountered a duplicate what you should do ? To understand lets take the example in the question : [3,4,5,5,2]. Now front reached 5. Now we should bring the back pointer to 5 but also at the same time remove the elements 3, 4, 5 from the set because those may be present after the current front. So back comes to 5 which is currently pointed by front.
Lets take another example [1,2,1] , for this front will reach 1 at the index 2 because that is the first duplicate found. Now where should back come to? It should come to 2 because that will be the position where set won't have any duplicates when you delete the elements in the set while you move the back pointer.
Do this until the front reaches the end of the array.
Your question about seen:
How do you find a duplicate in an array? You could use either a Set or you could use a boolean array. I think the only purpose of M in this question is to specify the maximum value that an element in the array can have. So the code used a boolean array of size M.
If you use the boolean array, once you find an element of value say v you can just say boolean_arr[v] = true that means it was "seen".
Or you could use a set and create a new one when needed without having to clear your whole boolean array everytime you find a duplicate - by letting the JavaScript to handle the garbage collection - something like below ( not fully tested ):
function solution(M, A) {
let sum = 0;
let front = 0;
let back = 0;
let set = new Set();
while (front < A.length) {
while (front < A.length && !set.has(A[front])){
sum += (front-back+1);
set.add(A[front]);
front += 1;
}
while (A[back] != A[front]) {
set.delete(A[back]);
back += 1;
}
set.delete(A[back]);
back += 1;
}
return Math.min(sum, 1000000000);
}
const dice = [1,3,4,5,6]
const score = straightScore(dice,4)
function straightScore(dice, sizeOfStraight) {
let score = 0
sizeOfStraight > 4 ? score = 40 : score = 30
dice.sort( (a, b) => a-b )
// filter to eliminate duplicates
const filteredDice = dice.reduce( (acc, die) => {
if ( acc.indexOf(die) === -1 ) acc.push(die)
return acc
}, [] )
//determine straight
const straightFinder = filteredDice.reduce( (acc, die) => {
const lastNumInArray = acc.slice(-1)[0]
// here is my hack if the small straight starts with 3 and the last die is a 1
if ( die - lastNumInArray === 2) acc = [die]
if ( lastNumInArray === undefined || die - lastNumInArray === 1 ) acc.push(die)
return acc
}, [] )
if (straightFinder.length >= sizeOfStraight) return score
return 0
}
console.log(score)
I got this to work, but it feels hacky. Any thoughts would be appreciated.
I am trying to make a basic Yahtzee game. Determining if a straight was rolled is where I got stuck. It basically worked, but if it was a small straight (4 in a row out of five dice) going from 3 - 6 AND the fifth die was a 1 my reduce would not work right. It always resolved to a 1 item array. I can see the problem in my logic, so I threw a little hack in there to make it work, but I feel like there must be a dozen better ways. Here is my code. Thanks for looking.
The dice are an array of 5 numbers 1-6 representing dice, the sizeOfStraight is just so I can reuse it for the large straight. The problem only came up with the small straight though, so you could just put a 4 in for that.
input dice = [1, 3, 4, 5, 6]
output (none, just returns because straightFinder.length = 1)
You don't really need reduce in that final operation. Just check the sorted dice at index 0 with index 3. If the latter has a value of 3 more than the first, the first 4 sorted dice represent a small straight. The same can be repeated at index 1 and index 4. There are no other positions possible for a small straight. A large straight can only start at index 0, so that only needs one check.
NB: extracting unique values can be done with the help of a temporary Set.
const dice = [1, 3, 4, 5, 6];
const score = straightScore(dice, 4);
function straightScore(dice, sizeOfStraight) {
let score = 0;
sizeOfStraight > 4 ? score = 40 : score = 30;
dice.sort( (a, b) => a-b );
// duplicates can be eliminated with a Set:
const filteredDice = [...new Set(dice)];
// determine straight: only two locations are of interest (at most)
if (filteredDice[sizeOfStraight-1] - filteredDice[0] === sizeOfStraight-1) return score;
if (filteredDice[sizeOfStraight] - filteredDice[1] === sizeOfStraight-1) return score;
return 0;
}
console.log(score);
Maybe this answer is a little overkill for Your needs, but may inspire You. Generally, I would create a "histogram" of rolled values and then look for the longest sequence of consecutive "hits" to find the longest straight.
Approach 1:
Let's say, we rolled dice = [6, 3, 1, 4, 3]. The "histogram" would look like this:
0 - false (there's no dice roll 0)
1 - true
2 - false
3 - true
4 - true
5 - false
6 - true
7 - false (there's no dice roll 7)
Indexes 0 and 7 are useful as boundaries, which are guaranteed to be always false (which simpifies detecting straights starting at 1 or ending at 6). It can be created like this:
Array(8).fill(false).map((_, i) => dice.indexOf(i) >= 0)
-> [false, true, false, true, true, false, true, false]
Now we need to find length of the longest sequence of trues. Let's traverse through the histogram and detect changes from false to true (straight start) and back (straight end):
function findStraights(dice) {
const histogram = Array(8).fill(false).map((_, i) => dice.indexOf(i) >= 0)
let straights = [];
let start = 0;
for (let i = 1; i < 8; i++) {
if (histogram[i - 1] === histogram[i])
continue;
if (histogram[i])
start = i
else
straights.push({ start: start, length: i - start })
}
return straights
}
console.log(findStraights([]))
console.log(findStraights([5]))
console.log(findStraights([3, 2, 1]))
console.log(findStraights([4, 5, 6, 1]))
console.log(findStraights([6, 3, 1, 4, 3]))
Now we have array of all found straights and finding the longest one is trivial (sort(...)[0]).
Approach 2:
Let's represent the histogram as a binary number (instead of the array shown above) and use a little bit-twiddling magic to determine longest sequence length:
function findLongestStraight(dice) {
let bits = dice.reduce((a, v) => a | (1 << v), 0)
let longest = 0
while (bits !== 0) {
bits &= bits << 1
longest++
}
return longest
}
console.log(findLongestStraight([]))
console.log(findLongestStraight([5]))
console.log(findLongestStraight([3, 2, 1]))
console.log(findLongestStraight([4, 5, 6, 1]))
console.log(findLongestStraight([6, 3, 1, 4, 3]))
The first line creates binary number bits, where every bit represents if given number was rolled on at least one die (bit 0 is always 0, bit 1 represents rolled 1 etc.) In our case bits = 0b01011010 = 90.
The while part uses a bit-twiddling trick, that shortens every sequence of consecutive 1s by one on every iteration. All we do is count number of iterations needed to zero all sequeces.
I have an array with 8 numbers, 1-8
arr = [1, 2, 3, 4, 5, 6, 7, 8]
each number in the array is part of a group
1 & 2 - group a
3 & 4 - group b
5 & 6 - group c
7 & 8 - group d
What I need to do is match each number in my array, with another number from the same array, but they can not be in the same group
1 & 2 may not be matched with 1 or 2
3 & 4 may not be matched with 3 or 4
5 & 6 may not be matched with 5 or 6
7 & 8 may not be matched with 7 or 8
Conditions
May not be predetermined, given the same inputs, different solutions must be possible
no repeat pairings for example if 2 pairs with 8, 4 can not also pair with 8
They must be paired one at a time, with the ability to leave some pairings unfinished and come back at a later date to complete more pairings
Pairings cannot be reset or undone, once paired it is permanent
One pairing can not run both ways in all cases. For example, if 2 is paired to 3 then 3 can not always pair with 2. It is acceptable if this happens from time to time, but it can not be an intended feature.
We can not assume the pairings will be made in any specific order. For example, the first pairing may be made by 1, or maybe 7 or 3 and so on. Any number may need to be paired at any time.
My problem is that if you pick it in just the right order, you can get stuck on the last number where the only pairing left would be to pair with itself or it's groupmate.
I want to stress a condition here because it keeps being overlooked in answers. I want pairings to be made one at a time. This means that you should be able to space making each pairing out as far as you want. I want to be able to make a pairing on day 0, and then I can come back day 1, week 2, or year 2750 to make the second pairing. This is necessary. Each and every single pairing must be made completely independent of each other, and at the end, the last number must be still able to make a valid pairing.
example...
6 with 8
7 with 6
5 with 7
3 with 5
8 with 4
2 with 3
4 with 2
1 with _
This order leaves 1 with no option but itself.
What can I do to make it so no matter what the last number always has a viable pairing?
update: I have added a fairly good solution in the answers section. If you are still struggling to understand what I want to accomplish try reading my answer in the answers section. The attached code is outdated since I have found a currently usable answer.
antiquated code below
function selectGiftee(req, res, db){
const {user_id, group_id} = req.body
db.select('name', 'user_id', 'giftee_id', 'group_id').from('users')
.then (data => {
if (data.length) {
// only sending available giftees
const taken = [];
const fullList = [];
let available = [];
// figure out which giftees are taken
data.forEach( user => {
if (user.giftee_id !== null){
taken.push(user.giftee_id)
}
if (user.group_id === group_id) {
taken.push(user.user_id)
}
})
// add all giftees to a list
data.forEach( user => {
fullList.push(user.user_id)
})
// only add available giftees to the available list
available = fullList.filter(val => !taken.includes(val));
// respond with only giftees that are not taken
res.json(available)
Lets take our groups as one long list:
1 2|3 4|5 6
Now lets divide that in the middle, and move one part below the other one:
1 2|3
4|5 6
Now every element got a pair (each column) that is not from the group itself, you could turn it into a continous list by appending all the columns to one:
(1 -> 4) -> (2 -> 5) -> (3 -> 6) ->
Now to get different combinations, we just shuffle the array of groups and the groups itself before.
// stolen from https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array
function shuffle(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
const groups = [[1, 2], [3, 4], [5, 6], [7, 8, 9]];
groups.forEach(shuffle);
shuffle(groups);
console.log("shuffled:", groups.join(" | "));
const list = [].concat(...groups);
console.log("list:", list);
// Now pair every element by matching the first and the second half:
const pairs = [];
for(let i = 0; i < Math.floor(list.length / 2); i++) {
pairs.push([
list[i],
list[i + Math.floor(list.length / 2)]
]);
}
if(list.length % 2)
pairs.push([list.pop()]);
console.log("pairs:", pairs.join(" | "));
const result = [].concat(...pairs);
for(let i = 0; i < result.length; i++)
console.log(result[i] + " -> " + result[(i + 1) % result.length]);
So here is the method I came up with to answer my own question. What I've done is to split the groups into two separate groups first. This means that group a and b are in metagroup 1 and group c and d are in metagroup 2.
second I have added in a weighting system. so when a pair is trying to be made i collect all the secondary numbers in the pairs that have already been taken and I add a weight to their group. for example, if 4 has been paired with 6 then group c gets +1 to their weight. This is only the first step of the weighting though.
Now, in the current example, 4 has already paired with 6, and therefore group c has a weight of 1. Now we want to pair 3. 3 is in the same group as 4, which is group b. So now group 3 will look at 4, and see that it has already got a pair, which is 6. 6 is part of metagroup 2, and so now both group c and d are given +10 to their weight. This leaves group c with 11, and d with 10.
Edit: these two conditions were added to clear up some less common errors I found. first I added a negative weight (-1) for any number that has not been paired yet. This makes it so that numbers without pairs are chosen before numbers with pairs. I had to do this because I was still on rare occasion getting stuck with one number that could not pair at the end. Second I changed the way numbers in the same group were handled. previously I simply removed them from the available list. This however caused an issue if their group had the lowest weight. The algorithm would suggest picking a number from that group because it's weight was lowest, but there were no numbers in the group so it resulted in a deadlock. Now i add 20 weight to the group a number is in so that it can never be the lowest weight.
So now we have our weights set and 3 is still trying to pair. we have a look at all our weights and see that group a and b have a 0 for their score and c has 11 and d has 10. 3 is part of group b and pairing with self is specifically blocked so that is not possible, so this leaves only group a to choose from, so 3 will pair with either 1 or 2.
this is the only method I was able to find that would allow me to form pairs 1 at a time on demand. Below is my code, it may be a bit confusing since I'm just pulling it straight out of a program, but if anyone needs clarificaion on how it works I'll be happy to explain.
function chooseGiftee(avail){
const int = avail.length;
const index = Math.floor((Math.random() * int) + 1);
return avail[index-1];
}
function getCandidates(weights){
return candidates;
}
function selectGiftee(req, res, db){
const {user_id, spouse_id, group_id} = req.body;
if (!user_id || !spouse_id || !group_id){
return res.status(400).json('Missing Credentials')
}
db.select('user_id', 'spouse_id', 'giftee_id', 'group_id').from('users')
.then (data => {
if (data.length) {
// only sending available giftees
let newGiftee;
const taken = [];
const fullList = [];
let available = [];
let filteredAvailable = [];
let nullCount = 0;
let nullArr = [];
let a = 0;
let b = 0;
let c = 0;
let d = 0;
//for the love of god man please refactor all this stuff!!!!!!!
// figure out which giftees are taken and set weight for already picked groups
data.forEach( user => {
if (user.giftee_id === null){
switch (user.user_id){
case 1:
case 2:
a--;
break;
case 3:
case 4:
b--;
break;
case 5:
case 6:
c--;
break;
case 7:
case 8:
d--;
break;
}
}
if (user.giftee_id !== null){
taken.push(user.giftee_id);
}
switch (user.giftee_id){
case 1:
case 2:
a++;
break;
case 3:
case 4:
b++;
break;
case 5:
case 6:
c++;
break;
case 7:
case 8:
d++;
break;
}
if (user.group_id === group_id) {
switch(user.giftee_id){
case 1:
case 2:
case 3:
case 4:
a += 10;
b += 10;
break;
case 5:
case 6:
case 7:
case 8:
c += 10;
d += 10;
break;
}
switch(user.group_id){
case 1:
a += 10;
break;
case 2:
b += 10;
break;
case 3:
c += 10;
break;
case 4:
d += 10;
break;
}
}
})
// add all giftees to a list
data.forEach( user => {
fullList.push(user.user_id)
})
// only add available giftees to available list
available = fullList.filter(val => !taken.includes(val));
// Choose from what is available based on groupWeight
let lowWeight = Math.min(a, b, c, d);
let candidates = [];
if(lowWeight === a){
candidates.push(1, 2);
}
if(lowWeight === b){
candidates.push(3, 4);
}
if(lowWeight === c){
candidates.push(5, 6);
}
if(lowWeight === d){
candidates.push(7, 8);
}
filteredAvailable = available.filter(val => candidates.includes(val));
// check if there is three or less choices left, and if so we need to prevent a deadlock
if (nullCount <= 4){
filteredAvailable = filteredAvailable.filter(val => !nullArr.includes(val))
}
newGiftee = chooseGiftee(filteredAvailable);
If each of the group contains 2 values exactly (I will address if not too) then we can keep punch the numbers at odd index to keep creating a wall in between them. for eg
Groups
[ [1,2], [3,4], [5, 6], [7, 8] ]
Starts with [1, 2] the initial array
process next -> [3, 4] punch at index 1 and 4 at index 3 so it became [1, 3, 2, 4]
Now process next ([5,6]) and do the same, so it became: [1, 5, 3, 6, 2, 4]
and then process next and continue.
Now, If there are groups that can have arbitrary length! SO, now here is the problem (Bang On! this is why I came here)
First we need to understand when (under which condition) it can be done! If there exists a group having length L and summation of length of all other groups is M then
N - M should be <= 1
WHY?
lets assume a group have 3 members, in order to separate them out you need minimum 2 wall. so, all other group combining must have length 2.
So, If condition satisfied now we need to create pairs (because now possible)
First step, sort the array of groups desc with respect to their length
We need to start with the first one, but we need a continuation of
odd indexes if the second group did not spoiled the first group
completely.
Let's assume Groups are:
[[1, 2, 3, 4, 5, 6], [7,8,9], [10, 11, 12], [...], [.....]]
As the first group have length 6 and 5 elements is needed to dismiss it completely the next group [7, 8, 9] have length 3 so definitely it needs 2 more elements, so when we will start processing the next group after placing the current group components at 1, 3, 5 index, we need to start placing at 7, 9, ...
Once it dismissed the first array we can start from index 1 and
keeping odd indexes.
I can produce a working code on demand, but once it's clear what we have to do the coding part all of us can do easily. Focused mainly in the algorithm part. However I can write a code if that helps!
ADDED example of implementation
var groups1 = [
[7, 8, 9],
[10, 11],
[1, 2, 3, 4, 5, 6],
[12, 13]
],
groups2 = [
[1, 2],
[3, 4],
[5, 6],
[7, 8]
];
function punchGroups(groups) {
groups.sort((a, b) => b.length - a.length);
let firstLen = (groups[0] || []).length,
oic = 0, //odd index increment counter
covered = false; //covered the largest array flag
return groups.reduce((acc, e) => {
if (!acc.length) {
return acc.concat(e)
}
e.forEach((n, i) => {
let idx = (covered ? (i * 2) : (oic++) * 2) + 1;
acc.splice(idx, 0, n);
covered = oic >= (firstLen - 1)
})
return acc;
}, []);
}
console.log('Groups: ', groups2);
console.log('Pairs: ', punchGroups(groups2));
console.log('-------------------------------');
console.log('Groups: ', groups1);
console.log('Pairs: ', punchGroups(groups1));
Well, Now to have different combinations of pairs (obviously within one of the possible pairs) you just need to manage groups and member of groups in all possible combinations (just use recursion to have all the combo) and process each to have all different possible pairs. You can generate all the outputs and pick one from them each day with a index or some factor which will make your call different may be timestamp and calculate it to a number within index range of all possible output array.
Your numbers are graph nodes, and edges exist from every node to all others but group neighbor. You need to cover all n nodes with n/2 edges while no node is shared by two edges. This covering is called perfect matching (not maximal as I wrote before, but perfect is maximal)
Python example using networkx library finds maximal matching (here is also perfect, but we cannot expect this fact always):
import networkx as nx
lst = [1, 2, 3, 4, 5, 6, 7, 8]
G = nx.Graph()
for i in range(0, len(lst)):
for j in range(i + 1, len(lst)):
if ((i // 2) != (j // 2)): //make edge for items from different pairs
G.add_edge(lst[i], lst[j])
#print(G.edges)
m = nx.maximal_matching(G)
print(m)
>>> {(4, 2), (1, 3), (6, 8), (5, 7)}