How to split nested array into "rows" with limited size - javascript

I have an array of arrays of different sizes. The goal is to generate "rows" where each row can contain a max of 12 elements.
For example:
Input data can be something like this:
const groups = [[1,2,3,4],[1,2,3,4,5,6], [1,2,3,4,5,6,7,8,9,10,11,12], [1,2,3,4,5,6,7], [1,2,3],[1,2,3]]
groups[0].length + groups[1].length = 10 -> row0
groups[2].length = 12 -> row1
groups[3].length + groups[4].length = 10 -> row3
groups[5].length = 3 -> row4
Output for such array should be:
[[[1,2,3,4], [1,2,3,4,5,6]], [[1,2,3,4,5,6,7,8,9,10,11,12]], [[1,2,3,4,5,6,7], [1,2,3]], [[1,2,3]]]
I was thinking of a recursive function for this but couldn't figure out how to solve it.

You can use Array#reduce() to do this.
The code first checks if the current last element (last "row") has more than 12 numbers in it if you add the next group:
(acc[acc.length - 1].flat().length + cv.length) <= 12
if it will be less than 12, the elements will get pushed into the "row":
acc[acc.length - 1].push(cv)
and if not, a new "row" will be added to the outer array:
acc.push([cv])
const groups = [[1, 2, 3, 4],[1, 2, 3, 4, 5, 6],[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],[1, 2, 3, 4, 5, 6, 7],[1, 2, 3],[1, 2, 3]];
const rows = groups.reduce((acc, cv) => {
(acc[acc.length - 1].flat().length + cv.length) <= 12 ?
acc[acc.length - 1].push(cv) :
acc.push([cv])
return acc
}, [[]]);
console.log(JSON.stringify(rows))

Here's one way to solve it recursively:
const regroup = (max, [g, ...gs], filled = [], curr = [], cc = 0) =>
g == undefined
? filled .concat ([curr])
: g .length + cc <= max
? regroup (max, gs, filled, curr .concat ([g]), cc + g.length)
: regroup (max, gs, filled .concat ([curr]), [g], g .length)
const groups = [[1, 2, 3, 4], [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
[1, 2, 3, 4, 5, 6, 7], [1, 2, 3], [1, 2, 3]]
console .log (regroup (12, groups))
.as-console-wrapper {max-height: 100% !important; top: 0}
We pass the maximum size and the list of items, and then we default three parameters:
filled will track the output rows we've filled; it starts with an empty array
curr stores the row we're working on; it also starts with an empty array
cc stores the count of all elements in the current row; it starts with zero
On each recursive call, we have one of three possibilities:
There are no more arrays to process, and we return all the filled rows with the current row appended.
The next array is small enough to fit in the current row, and we update the current to include it, and the current count to accommodate it.
The next array is too large, and we add the existing current row to the filled ones, and start a new current row with this array, setting the count appropriately.

Related

Sorting two arrays according specific order

What I'm trying to do is ordering 2 arrays lets say...
let C = [10, 5, 6, 7]
let M = [8, 9, 2, 5]
The first array should stay as it is, but the second needs to be orderer so the output should go like this
[10, 5, 6, 7]
[9, 2, 5, 8]
Basically, i need to order the second array so that the max value matches the max value of the other array.
Please take into account that the first array should stay the same, so i cant reorder both, just the second
To clarify, each array item is a box with that amount of coins and multipliers. One array is coins and the other multipliers.
You can't change the position of the coin boxes, but you can move the multiplier so you can get the max amount of coins. But you can't move the coin boxes from their position.
What i've discover is that in order to get the max amount of coins, you need to multiply the highest possible value, with the highest posible value of the other table. so te correct order would be something like
[1,3,2,4]
[5,7,6,8]
and the values in example are
[5 3 7 1]
[1 4 3 9]
//expected output
[4 3 9 1]
You need to correlate the two arrays by matching the highest number from C to the highest at M, and so on until the smallest.
To do so create a new array of indexes (order), map arr1, add the index to each item, an then sort the array (I chose descending order).
Now clone arr2 and sort it in the same sort order you've used for C (descending order) to get the sorted array.
Now reduce the order array to another array, take the item with the same index i from sorted, and put it in the index idx taken from the order array.
const fn = (arr1, arr2) => {
const order = arr1
.map((n, i) => [n, i])
.sort(([n1], [n2]) => n2 - n1)
.map(([, i]) => i)
const sorted = [...arr2].sort((n1, n2) => n2 - n1)
return order.reduce((acc, idx, i) => (acc[idx] = sorted[i], acc), [])
}
console.log(JSON.stringify(fn(
[10, 5, 6, 7],
[8, 9, 2, 5]
))) // [9, 2, 5, 8]
// [2,0,1,3]
console.log(JSON.stringify(fn(
[5, 3, 7, 1],
[1, 4, 3, 9]
))) // [4, 3, 9, 1]
console.log(JSON.stringify(fn(
[5, 4],
[7, 2]
))) // [7, 2]
console.log(JSON.stringify(fn(
[11, 17, 39],
[7, 5, 3]
))) // [3, 5, 7]

Pagination algorithm that doesn't allow a page to be too big or small

I want to write a pagination algorithm that evenly distributes the number of elements per page, with a specified min and max elements per page. When possible, I wanted to have the maximum number of elements per page that it can have without violating the minimum rule.
I've been trying to write my own version of this for a while, but I get stuck on it when it comes to distributing the elements on the page in a manner that I consider to be aesthetically pleasing. Here's the code of my tests:
describe('paginate()', () => {
it('puts everything on one page is less than or equal to maximum', () => {
expect(paginate([1, 2], 1, 8)).toEqual([[1, 2]]);
expect(paginate([1, 2], 3, 8)).toEqual([[1, 2]]);
expect(paginate([1, 2], 1, 2)).toEqual([[1, 2]]);
});
it('divides everything evenly if there are no remainders on the max', () => {
expect(paginate([1, 2, 3, 4, 5, 6], 1, 3)).toEqual([
[1, 2, 3],
[4, 5, 6],
]);
expect(paginate([1, 2, 3, 4, 5, 6], 1, 2)).toEqual([
[1, 2],
[3, 4],
[5, 6],
]);
});
it('merges the last page if there is one left over', () => {
let outcome = paginate([1, 2, 3, 4, 5, 6, 7], 2, 4);
expect(outcome).toEqual([
[1, 2, 3, 4],
[5, 6, 7],
]);
outcome = paginate([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 2, 4);
console.log('outcome', outcome);
expect(outcome).toEqual([
[1, 2, 3, 4],
[5, 6, 7],
[8, 9, 10],
]); // THIS TEST FAILS
});
it('can reduce page sizes if it makes elements evenly distributed', () => {
let outcome = paginate(_.range(1, 12), 6, 10);
expect(outcome).toEqual(
[
[1, 2, 3, 4, 5, 6],
[7, 8, 9, 10, 11],
],
JSON.stringify(outcome)
);
outcome = paginate(_.range(1, 22), 6, 10);
expect(outcome).toEqual(
[
[1, 2, 3, 4, 5, 6, 7],
[8, 9, 10, 11, 12, 13, 14],
[15, 16, 17, 18, 19, 20, 21],
],
JSON.stringify(outcome)
);
});
});
Here's my code:
import _ from 'lodash';
export const paginate = <T>(content: T[], min: number, max: number): T[][] => {
const length = content.length;
for (let i = max; i > min; i--) {
if (length % i === 0 || length % i >= min) {
const result = _.chunk(content, i);
console.log(result);
return result;
}
}
console.log('end');
return _.chunk(content, min);
};
there is only one of my tests failing here, but I don't know how to get it passing:
outcome = paginate([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 2, 4);
console.log('outcome', outcome);
expect(outcome).toEqual([
[1, 2, 3, 4],
[5, 6, 7],
[8, 9, 10],
]); // THIS TEST FAILS
the output is [ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10 ] ] instead of what i want. Every time I think of a solution to this problem, it breaks another test. I've been toiling for a few hours now and I'm stuck. I don't know how to get all these test to pass. Also, are there any edge cases I may not have thought of?
I'm not tied to the signature I've come up with for this function, so if it makes sense to change it, feel free. For example, I'm not entirely convinced that I need to provide a min at all.
My inclination would be to do something like this:
function paginate<T>(arr: T[], maxPerPage: number): T[][] {
const numPages = Math.ceil(arr.length / maxPerPage);
const minPerPage = Math.floor(arr.length / numPages);
const numBigPages = arr.length % numPages;
console.log(numPages, minPerPage, numBigPages)
const ret: T[][] = [];
for (let pageNum = 0, curElem = 0; pageNum < numPages; pageNum++) {
const numOnThisPage = minPerPage + (pageNum < numBigPages ? 1 : 0);
ret.push(arr.slice(curElem, curElem + numOnThisPage));
curElem += numOnThisPage;
}
return ret;
}
The idea is to only ask for the maximum number of elements per page maxPerPage, and determine the number of pages numPages by dividing the array length by this maximum, rounding up to a whole number if necessary.
Then the task is to divide the elements into these pages. Again, you can divide the array length by number of pages (arr.length / numPages) and put that many elements into each page. If that's not a whole number, then some "small" pages will have the whole number less than this, minPerPage = Math.floor(arr.length / numPages), and some "big" pages will have one more, minPerPage + 1. You can calculate the number of big pages numBigPages by taking the remainder when dividing the array length by the number of pages. (If this is confusing, imagine distributing into numPages pages one element at a time; you'd end up putting in minPerPage into each page, and then you'd have some left over. You'd likely want no more than one of these leftovers per page, so the number of leftovers equals the number of big pages numBigPages.)
So, how do we distribute the leftovers into big pages? It sounds like you want all the big pages to come first and then all the small pages. So we just make sure that if the current page number is less than the number of big pages, we make it a big page, and otherwise it's small.
Let's see how it does on your examples:
const range = (n: number) => Array.from({ length: n }, (_, i) => i + 1);
console.log(JSON.stringify(paginate(range(2), 8))); // [[1,2]]
console.log(JSON.stringify(paginate(range(2), 2))); // [[1,2]]
console.log(JSON.stringify(paginate(range(6), 3))); // [[1,2,3],[4,5,6]]
console.log(JSON.stringify(paginate(range(6), 2))); // [[1,2],[3,4],[5,6]]
console.log(JSON.stringify(paginate(range(7), 4))); // [[1,2,3,4],[5,6,7]]
console.log(JSON.stringify(paginate(range(10), 4))); // [[1,2,3,4],[5,6,7],[8,9,10]]
console.log(JSON.stringify(paginate(range(11), 10))); // [[1,2,3,4,5,6],[7,8,9,10,11]]
console.log(JSON.stringify(paginate(range(21), 10)));
// [[1,2,3,4,5,6,7],[8,9,10,11,12,13,14],[15,16,17,18,19,20,21]]
That looks like what you wanted, right?
Obviously the exact implementation of that algorithm can change; if you want to use lodash or to make the array via purely functional methods instead of iterating and using push(), that's up to you. But the basic concept seems to match up with what you want. Anyway, hope this lets you proceed. Good luck!
Playground link to code

javascript Delete from array between 2 indices

With an array of: [1, 2, 3, 4, 5, 6]
I would like to delete between 2 indices such as 2 and 4 to produce [1, 2, null, null, 5, 6]. What's the easiest way to do this?
Hopefully better than this:
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
let i = 2;
const rangeEnd = 9;
while (i < rangeEnd) {
delete array[i];
i++;
}
console.log(array)
If you want to use some native API you can actually do this with splice(). Otherwise, you should iterate a for loop through your array and change the value in each iteration.
Here is an example of how it would be done:
const array = [1, 2, 3, 4, 5, 6]
array.splice(3, 2, null, null) // the First element is beginning index and the second is count one will indicate how many indexes you need to traverse from the first one, then you should provide replace element for each of them.
console.log(array)
Note: For more info about it you can read more here.
There is a possible workaround for large scale replacement, so I will give it a touch here:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var anotherArr = Array(2).fill(null); // or you can simply define [null, null, ...]
Array.prototype.splice.apply(arr, [3, anotherArr.length].concat(anotherArr));
console.log(arr);
As you mean the range (2, 4] so you can follow this:
The range is: lower limit exclusive and the upper limit inclusive.
const arr = [1, 2, 3, 4, 5, 6];
const deleteRange = (arr, f, t) => {
return arr.map((item, i) => {
if (i + 1 > f && i + 1 <= t) {
return null;
}
return item;
})
}
console.log(deleteRange(arr, 2, 4));

javascript custom sorting function to place lower occurrence first

Need to create a custom sort for an array --
-- first separate numbers by frequency
--- subset numbers of having frequency 1
--- subset numbers of having frequency 2
partially sorted data - with respect to asc order of frequency
- then we sort each subset of elements of having the same frequencey in non desc order
function cSort(arr) {
if(typeof arr !== "undefined") {
arr.sort(function(a, b) {
return a - b;
});
return arr
}
}
needs to satisfy the test cases
-- test 1
input
[5, 3, 1, 2, 2, 4]
output
[1, 3, 4, 2, 2]
-- test 2
input
[10, 8, 5, 5, 5, 5, 1, 1, 1, 4, 4]
output
[8, 4, 4, 1, 1, 1, 5, 5, 5, 5]
--
current jsfiddle 1
http://jsfiddle.net/6mekdn8h/
new fiddle 2
http://jsfiddle.net/6mekdn8h/1/
You could take a sorting with the count of the occurences of each item.
var array = [5, 3, 1, 2, 2, 4].slice(1),
hash = array.reduce((h, v, i) => (h[v] = (h[v] || 0) + 1, h), {});
array.sort((a, b) => hash[a] - hash[b] || a - b);
console.log(array);

Lodash difference between _.remove() and _.pullAt()

What is the difference between lodash _.remove() and _.pullAt() functions?
var arr1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
_.remove(arr1, function (item) {
return item == 1
});
var arr2 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
_.pullAt(arr2, 1);
console.log(arr1.toString() + '\n' + arr2.toString());
// both result to [0,2,3,4,5,6,7,8,9,]
I've crated fiddle and read the description on the lodash site that states that _.remove()
Removes all elements from array that predicate returns truthy for and returns an array of the removed elements
and _.pullAt()
Removes elements from array corresponding to the given indexes and returns an array of the removed elements
Is there any difference at all? Or am I missing something?
Even your example made different things:
remove splices element by value, while pullAt by index.
Let's check it with different array [0, 3, 1, 1, 5]:
remove: [0, 3, 5] - all 1 items removed
pullAt: [0, 1, 1, 5] - arr[1] was spliced
You also can write other filters than compare by value with remove:
_.remove(arr, item => item % 2); // removes all odd numbers
_.remove(arr, user => user.deleted); // splice deleted users
_.remove(arr, item => item < 5); // and etc.

Categories

Resources