Dynamic Programming Bottoms up approach clarification [duplicate] - javascript

This question already has answers here:
What is the difference between bottom-up and top-down?
(9 answers)
Closed 1 year ago.
So I have been really trying to grasp Dynamic Programming. I can say that I really understand the memoization top down approach, but the bottoms up approach is really confusing to me. I was able to solve rods cutting top down, but I had to seek the solution for the bottoms up. I just don't understand when to use a 1D array or a 2D array. Then the for loop within the bottoms up is just confusing. Can anyone help me understand the differences in these two codes conceptually?
// Top Down Memoizaton:
const solveRodCuttingTop = function(lengths, prices, n) {
return solveRodCuttingHelper(0, lengths, prices, n);
};
function solveRodCuttingHelper(idx, span, prices, n, memo = []) {
// BASE CASES
if (idx === span.length || n <= 0 || prices.length !== span.length) {
return 0;
}
let included = 0, excluded = 0;
memo[idx] = memo[idx] || [];
if (memo[idx][n] !== undefined) return memo[idx][n];
if (span[idx] <= n) {
included = prices[idx] + solveRodCuttingHelper(idx, span, prices, n - span[idx], memo);
}
excluded = solveRodCuttingHelper(idx + 1, span, prices, n, memo);
memo[idx][n] = Math.max(included, excluded);
return memo[idx][n];
}
// Bottoms up
const solveRodCuttingBottom = function(lengths, prices, n) {
const rods = Array.from({length: n + 1});
rods[0] = 0;
let maxRevenue = - Infinity;
for (let i = 1; i < rods.length; i++) {
for (let j = 1; j <= i; j++) {
maxRevenue = Math.max(maxRevenue, prices[j - 1] + rods[i - j])
}
rods[i] = maxRevenue
}
return rods[prices.length];
};
const lengths = [1, 2, 3, 4, 5];
const prices = [2, 6, 7, 10, 13];

This is an interesting problem. Maybe I'm over-simplifying it, but if you first calculate each price per length, you can determine the solution by selling as much as possible at the highest rate. If the remaining rod is too short to sell at the best rate, move onto the next best rate and continue.
To solve using this technique, we first implement a createMarket function which takes lenghts and prices as input, and calculates a price-per-length rate. Finally the market is sorted by rate in descending order -
const createMarket = (lengths, prices) =>
lengths.map((l, i) => ({
length: l, // length
price: prices[i], // price
rate: prices[i] / l // price per length
}))
.sort((a, b) => b.rate - a.rate) // sort by price desc
const lengths = [1, 2, 3, 4, 5]
const prices = [2, 6, 7, 10, 13]
console.log(createMarket(lengths, prices))
[
{ length: 2, price: 6, rate: 3 },
{ length: 5, price: 13, rate: 2.6 },
{ length: 4, price: 10, rate: 2.5 },
{ length: 3, price: 7, rate: 2.3333333333333335 },
{ length: 1, price: 2, rate: 2 }
]
Next we write recursive solve to accept a market, [m, ...more], and a rod to cut and sell. The solution, sln, defaults to [] -
const solve = ([m, ...more], rod, sln = []) =>
m == null
? sln
: m.length > rod
? solve(more, rod, sln)
: solve([m, ...more], rod - m.length, [m, ...sln])
const result =
solve(createMarket(lengths, prices), 11)
console.log(result)
[
{ length: 1, price: 2, rate: 2 },
{ length: 2, price: 6, rate: 3 },
{ length: 2, price: 6, rate: 3 },
{ length: 2, price: 6, rate: 3 },
{ length: 2, price: 6, rate: 3 },
{ length: 2, price: 6, rate: 3 }
]
Above, solve returns the rod lengths that sum to the maximum price. If you want the total price, we can reduce the result and sum by price -
const bestPrice =
solve(createMarket(lengths, prices), 11)
.reduce((sum, r) => sum + r.price, 0)
console.log(bestPrice)
32

Related

JS data transformation methods don't seem to be working

Create a function 'calcAverageHumanAge', which accepts an arrays of dog's
ages ('ages'), and does the following things in order:
Calculate the dog age in human years using the following formula: if the dog is
<= 2 years old, humanAge = 2 * dogAge. If the dog is > 2 years old,
humanAge = 16 + dogAge * 4
Exclude all dogs that are less than 18 human years old (which is the same as
keeping dogs that are at least 18 years old)
Calculate the average human age of all adult dogs (you should already know
from other challenges how we calculate averages )
Run the function for both test datasets
Test data:
Data 1: [5, 2, 4, 1, 15, 8, 3]
Data 2: [16, 6, 10, 5, 6, 1, 4]
My solution(idk why wont work):
const calcAverageHumanAge = function (ageList) {
const avgAge = ageList
.map(val => (val <= 2 ? 2 * val : 16 + val * 4))
.fliter(val => val >= 18)
.reduce((acc, val, i, list) => {
return acc + val / list.length;
}, 0);};
You have three issues. Your reduce doesn't return anything is the main problem, but you're also dividing by list.length in each callback which doesn't make any sense (actually it does and I'm dumb), and then you aren't returning anything from the function. You want something like this:
const calcAverageHumanAge = function (ageList) {
const filteredVals = ageList
.map(val => (val <= 2 ? 2 * val : 16 + val * 4))
.filter(val => val >= 18);
return filteredVals.reduce((acc, val) => acc + val) / filteredVals.length;
};
When run on your data:
calcAverageHumanAge([5, 2, 4, 1, 15, 8, 3]); // 44
calcAverageHumanAge([16, 6, 10, 5, 6, 1, 4]); // 47.333

how to split an array into equal chunks?

I've found a lot of answers to the question "how to split an array in multiple chunks", but I can't find a way to best repartition the array. For example,
let x = [1,2,3,4,5,6,7,8,9,10,11,12,13];
//#Source https://www.w3resource.com/javascript-exercises/fundamental/javascript-fundamental-exercise-265.php
const chunk = (arr, size) =>
Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
arr.slice(i * size, i * size + size)
);
const n = 10;
console.log(chunk(x,n))
This function gives me two arrays: [1,2,3,4,5,6,7,8,9,10] and [11,12,13]. But I would prefere n to be used as a "max" to obtain [1,2,3,4,5,6,7] and [8,9,10,11,12,13]. This way I would have two arrays of the same size. If it is possible for the selected n, they should be of equal size, otherwise, two arrays with a nearly identical size.
I broke it down into 3 steps.
Compute numChunks, how many chunks you need? E.g. if you have an array of 103 elements and a max size of 10, then you'll need 11 chunks.
Compute minChunkSize, the size of the smaller chunks. E.g. in the above example, the first 7 chunks will have 10 elements, while the other 3 chunks will have 11 elements (710 + 311 = 103).
Compute numSmallChunks, how many small chunks you can have. E.g. 3 in the above example.
Then you just splice the arr accordingly.
let chunk = (arr, maxSize) => {
let numChunks = parseInt((arr.length - 1) / maxSize) + 1;
let minChunkSize = parseInt(arr.length / numChunks);
let numSmallChunks = numChunks * (minChunkSize + 1) - arr.length;
arr = [...arr]; // avoid muckking the input
let arrays = [];
for (let i = 0; i < numChunks; i++)
if (i < numSmallChunks)
arrays.push(arr.splice(0, minChunkSize));
else
arrays.push(arr.splice(0, minChunkSize + 1));
return arrays;
};
let x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
for (let i = 1; i < x.length; i++)
console.log(i, JSON.stringify(chunk(x, i), null, ''));
Note, the other answers result in an unbalanced; e.g. they produce arrays of sizes 4, 4, 4, & 1 when n is 4. Whereas my approach produces arrays of sizes 3, 3, 3, & 4. I guess it's up to the situation which you need, but this is how I interpret the question's "equal chunks".
If you need n to be max, Then calculate size as below.
let x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
const chunk = (arr, max) => {
const size = Math.min(max, Math.ceil(arr.length / 2));
return Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
arr.slice(i * size, i * size + size)
);
};
const n = 10;
console.log(chunk(x, n));
You could slice with a variable size.
1 2 3 4 5 5
6 7 8 9 10 5
11 12 13 14 4
15 16 17 18 4
const
chunk = (array, max) => {
let
length = Math.ceil(array.length / max),
size = Math.ceil(array.length / length);
return Array.from(
{ length },
(_, i) => (
array.length === size * length - length + i && size--,
array.slice(size * i, size * (i + 1))
)
);
}
console.log(chunk([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], 5).map(a => a.join(' ')));

How to iterate over an array multiple times without repeating summed elements

I am trying to solve this problem but I don't know why I can't pass all test cases. I need some help and explanation, how can I count some array (in this example: variable s) multiple times and not repeat the same elements that I already summed.
Problem description:
Lily has a chocolate bar that she wants to share it with Ron for his
birthday. Each of the squares has an integer on it. She decides to
share a contiguous segment of the bar selected such that the length of
the segment matches Ron's birth month and the sum of the integers on
the squares is equal to his birth day. You must determine how many
ways she can divide the chocolate.
Consider the chocolate bar as an array of squares, s=[2,2,1,3,2].
She wants to find segments summing to Ron's birth day, d=4 with a
length equalling his birth month, m=2. In this case, there are two
segments meeting her criteria: [2,2] and [1,3].
Function Description
Complete the birthday function in the editor below. It should return
an integer denoting the number of ways Lily can divide the chocolate
bar.
birthday has the following parameter(s):
s: an array of integers, the numbers on each of the squares of
chocolate,
d: an integer, Ron's birth day, m: an integer, Ron's birth month
My code:
function birthday(s, d, m) {
let bars = 0;
if (m !== 1) {
s.reduce((acc, val) => (acc+val) === d ? ++bars : bars)
} else {
bars = 1;
}
return bars;
}
Some cases:
s = [2, 5, 1, 3, 4, 4, 3, 5, 1, 1, 2, 1, 4, 1, 3, 3, 4, 2, 1]
d = 18
m = 7
s = [4, 5, 4, 5, 1, 2, 1, 4, 3, 2, 4, 4, 3, 5, 2, 2, 5, 4, 3, 2, 3,
5, 2, 1, 5, 2, 3, 1, 2, 3, 3, 1, 2, 5]
d = 18
m = 6
s = [4, 5, 4, 2, 4, 5, 2, 3, 2, 1, 1, 5, 4]
d = 15
m = 4
My code works with this:
s = [1, 2, 1, 3, 2]
d = 3
m = 2
This can be found on HackerRank > Practice > Algorithms > Implementation
You just have to slice the array with the sliced length of m, and then compare that to d
As slice doc:
The slice() method returns a shallow copy of a portion of an array into a new array object selected from start to end (end not included) where start and end represent the index of items in that array. The original array will not be modified.
For example:
s = [1, 2, 1, 3, 2]
m = 2
d = 3
// We loop through s with index stop at s.length - m + 1 for slice to be in correct range
// Slices:
i=0: [1, 2] -> sum=3 -> res=0+1=1
i=1: [2, 1] -> sum=3 -> res=1+1=2
i=2: [1, 3] -> sum=4 -> do nothing
i=4: [3, 2] -> sum=5 -> do nothing
Below is a worked solution
function birthday(s, d, m) {
let res = 0
const sum = (arr) => arr.reduce((acc, el) => acc + el, 0)
for (let i = 0; i < s.length - m + 1; i++) {
if (sum(s.slice(i, i + m)) === d) {
res++
}
}
return res
}
Whenever you are looping over an array to get the summation or do a mathematical equation on it and you have to remove that specific element that you already calculated, You can use one of these built in function to remove an element from an array using a specific index.
Array.prototype.slice()
&& Array.prototype.splice()
Here's an easy to understand way with nested loops:
function birthday(s, d, m) {
var matches = 0; // Total matches found
// Look at chunks starting a position 0. Last chunk can't be m spots past end of array, so last chunk starts at 1 + s.length - m:
for ( let i=0; i < 1 + s.length - m; i++ ) {
var sum = 0; // What this chunk sums to
// Sum up the values of this chunk:
for ( let j=0; j < m; j++ ) {
sum += s[i+j];
}
if ( sum === d ) { // Does this chunk sum to d?
matches++; // Yes!
}
}
return matches;
}

I am looking for an algorithm for dividing N numbers into K groups and for each group to have S players

I am looking for an algorithm for dividing N numbers into K groups and for each group to have S players.
split(array, k, s);
var array = [5, 5, 5, 3, 3, 2, 1, 1, 1]; // sum : 26
var k = 3;// number of groups;
var s = 3;// number of players;
// 26/3 = 8.66 => sum in each group
// result :
//group 1 : { 5, 3, 1 } sum: 9
//group 2 : { 5, 3, 1 } sum: 9,
//group 3 : { 5, 2, 1 } sum: 8,
I think a simple greedy approach is optimal. Just grab next highest from the array and put it in the group that's the smallest.
const array = [5, 5, 5, 3, 3, 2, 1, 1, 1];
let groups = [[], [], []];
array.forEach(val => {
const counts = groups.map(group => group.reduce((acc, val) => acc + val, 0));
groups[counts.indexOf(Math.min(...counts))].push(val);
});
console.log(groups);
EDIT:
Oof, didn't read well enough. Simplest approach might be to even player count after: Just keep move the smallest value from the largest group (by size) to the smallest until groups are equal size.
You'll have to convince yourself if that's optimal, I'm not sure offhand.

Jumble numbers in an array such that no two adjacent numbers are same using JavaScript

The idea it to basically not have repeated values in the array with similar values.
An example input array:
input = [1,2,2,2,2,3,4,5,6,7,8,9]
Expected output to be something like this:
desiredOutput = [1,2,3,2,4,2,5,2,6,2,7,8,9]
I have tried putting this in a for loop where it checks with the next item and if it is same, swaps the values. The problem is when I have continuous similar values.
This proposal features
count of elements and store it in an appropriate object,
check whether spread is possible (e.g. not here [1, 1, 1, 1, 3, 3]),
round robin with the elements, so
maximum distance between the same elements.
How does it work?
As example I take this array: [1, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8, 9]
Build an object with the count of the elements, store it with the element as key.
length = {
"1": 1, "2": 4, "3": 1, "4": 1, "5": 1, "6": 1, "7": 1, "8": 1, "9": 1
}
Select the property with the largest value: length[2] = 4
Make a new array with the length of the previous value and fill it with empty arrays.
output = [[], [], [], [], []]
Check if a spreaded array is possible. If not, return.
Set k to the key of the biggest value of a property.
k = '2'
If truthy, proceed. Otherwise go to 11.
Set l to the value of length[k].
l = 4
Iterate over l and push k to the end of the array with the index of i % outputLength. Increase i.
Delete property k.
Proceed with 5.
Return the flat output array.
output first then continued
array 0: 2 1 6
array 1: 2 3 7
array 2: 2 4 8
array 3: 2 5 9
return: 2 1 6 2 3 7 2 4 8 2 5 9
distance | | | | is equal
function spread(input) {
function findMaxKey() {
var max = 0, key;
Object.keys(length).forEach(function (k) {
if (length[k] > max) {
max = length[k];
key = k;
}
});
return key;
}
var length = input.reduce(function (r, a) {
r[a] = (r[a] || 0) + 1;
return r;
}, {}),
i = 0, k = findMaxKey(), l,
outputLength = length[k],
output = Array.apply(Array, { length: outputLength }).map(function () { return []; });
if (input.length - outputLength < outputLength - 1 ) {
return; // no spread possible
}
while (k = findMaxKey()) {
l = length[k];
while (l--) {
output[i % outputLength].push(k);
i++;
}
delete length[k];
}
return output.reduce(function (r, a) { return r.concat(a) }, []);
}
console.log(spread([1, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8, 9]));
console.log(spread([1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2]));
console.log(spread([1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]));
console.log(spread([1, 1, 1, 1, 3, 3]));
console.log(spread([1, 1, 3]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
maybe that could help you:
for(var i = 1; i < input.length; i++) {
if(input[i-1] == input[i]) {
var j = i;
while(j < input.length && input[j] == input[i]) {
j++;
}
var el = input[j];
input[j] = input[i];
input[i] = el;
}
}
Greedy Approach Using Max Heap
The idea is to greedily put the highest frequency numbers first.
Construct a max heap where every node is a tuple that stores the number & it's frequency.
Then extract the head of the max heap (the highest frequency node) and add it's value to the resultant array.
If there's a previous element then add it back to the heap.
Decrement the frequency of the extracted node and store it in prev, so that it can be added back after one iteration.
Finally return the solution if it exists otherwise return the string "Not Possible".
function solution(arr) {
const maxHeap = Array.from(
arr.reduce((m, i) => m.set(i, (m.get(i) ?? 0) + 1), new Map())
).sort(([, a], [, b]) => b - a);
const res = [];
let prev = null;
while (maxHeap.length) {
const maxNode = maxHeap.shift();
res.push(maxNode[0]);
maxNode[1] -= 1;
if (prev) {
maxHeap.push(prev);
maxHeap.sort(([, a], [, b]) => b - a);
prev = null;
}
if (maxNode[1] > 0) {
prev = maxNode;
}
}
return res.length < arr.length ? "Not Possible" : res;
}
console.log(JSON.stringify(solution([1, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8, 9])));
console.log(JSON.stringify(solution([1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2])));
console.log(JSON.stringify(solution([1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3])));
console.log(JSON.stringify(solution([1, 1, 1, 1, 3, 3])));
console.log(JSON.stringify(solution([1, 1, 3])));
Note: I've not implemented a Max Heap (because it's tedious), I've simulated it with Array.prototype.sort.

Categories