Algorithm for organizing pagination to ensure consistent page sizes and prevent excessively large or small pages

My goal is to create a pagination algorithm that distributes elements evenly per page, with specified minimum and maximum thresholds. I aim to maximize the number of elements per page while adhering to the minimum rule.

I've attempted to develop my own version of this algorithm but struggle with arranging elements on a page in an aesthetically pleasing way. Below is the code for my test cases:


describe('paginate()', () => {
    it('places all elements on one page if 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 elements evenly without remainders on max limit', () => {
      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 last page if there's any leftover elements', () => {
      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 adjust page sizes to distribute elements evenly', () => {
      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)
      );
    });
  });

Below is my implementation 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);
};

One of my tests is failing, where the output differs from expected. Despite several attempts at solving the issue, it remains unresolved. If anyone has insights on how to make these tests pass or can identify overlooked edge cases, please share your thoughts. I am open to refining the function signature if necessary, such as reconsidering the need for a minimum requirement.

Answer №1

When approaching this task, I would consider implementing it in the following manner:

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 main concept here is to set a maximum number of elements per page maxPerPage, calculate the total number of pages numPages by dividing the array length by the maximum and rounding up if needed.

Then, distribute the elements into these pages based on the calculations mentioned above. The idea is to have some pages with one extra element, called "big" pages, while others have the normal amount, known as "small" pages.

To handle the distribution, we prioritize allocating the extra elements to big pages first, followed by the remaining elements to small pages sequentially.


We can test this implementation using 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]]

It appears to produce the desired outcome as expected.


Note that there are various ways to refine and customize this algorithm further, such as utilizing libraries like lodash or adjusting the approach for array manipulation. Ultimately, the core principle aligns with your requirements. Best of luck with your project!

Access Playground link to code

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the process for including a new button in the infowindow attached to a marker?

This is a JavaScript code snippet that initializes a map using the Google Maps API. It creates a marker at Uluru in Australia and opens an info window with some content when the marker is clicked. function initMap() { var uluru = {lat: -25.363, lng: 131 ...

A comprehensive guide on creating translation files using grunt angular-translate from original JSON files containing translations

I have a unique angular application that requires support for multiple languages. To achieve this, I have implemented the angular translate task in the following manner. My goal is to create separate language files which can be loaded later using the useSt ...

JS form validation malfunctioning

XHTML <form name="suggestion" method="post" action="suggestion.php" class="elegant-aero" onSubmit="return validate()" > <label> <span>Message :</span> <textarea id="Message" name="m ...

The variable req.body.username is not defined in the context of JavaScript

I am completely new to JS, Angular.js and node.js. I am currently working on a login-register project but facing a minor issue. Below is my code: login.ctrl.js: var app = angular.module('login-register', []); app.factory('UserLog', ...

What is the proper way to expand a TypeScript class?

I'm facing a dilemma with the code snippet below. The line m.removeChild(m.childNodes[0]) is causing an issue with the TypeScript compiler. I'm unsure if childNodes: BaseNode[]; is the correct approach in this scenario. class BaseNode { childNo ...

Creating an array dynamically in response to an AJAX call

Is there a way to dynamically create an array after receiving an AJAX response? Upon getting the AJAX response, the variable data.villages contains the data. To iterate over its values, the jQuery each function can be used: $.each(data.villages, functio ...

CSS3 Animation: Facing issue with forwards fill behavior in Safari when using position and display properties

After creating a CSS3 animation to fade out an element by adjusting its opacity from 1 to 0 and changing the position to absolute and display to none in the last frames, I encountered an issue. In Safari, only the opacity is maintained while the position a ...

The type 'IProduct' cannot be assigned to type '[]'

Looking for help with dispatching events between parent and child components. Interfaces: export interface IProduct { id: string; name: string; price: string; image: string; inStock: number; fastDelivery: bo ...

Calculate the total price from a combination of HTML and JavaScript options without altering the values

I'm currently facing an issue in my form where the final price needs to be updated when a different option is selected. I've searched extensively for the problem without success. I've used similar code before, just with different variables a ...

The useQueries function is unable to simultaneously query data as Next.js mandates that the server component must be asynchronous

The Next.js-powered app is up and running smoothly. Within my code, I utilize a useQueries hook: const userQueries = useQueries({ queries: user.contacts.map((contactId: string) => ({ queryKey: ['contact', contactId], ...

Utilizing a string as an argument in a function and dynamically assigning it as a key name in object.assign

Within my Angular 5 app written in TypeScript, I have a method in a service that requires two arguments: an event object and a string serving as the key for an object stored in the browser's web storage. This method is responsible for assigning a new ...

Completing the initial task before moving on to the next task: Leveraging Ionic 2 Promises

Currently in my Ionic 2 project, I am facing an issue where two functions are executing one after another but the second function starts before the first one is completed. Both functions involve making API calls and I want to ensure that the first function ...

Upload dojo.js to my server for use

Check out this link for a working example: It's functioning perfectly. I then copied the HTML and tried running it on my local Apache server. Surprisingly, it worked! However, when I attempted to load the same dojo.js file on my web server, it ...

What is the best way to divide an array of objects into three separate parts using JavaScript?

I am looking to arrange an array of objects in a specific order: The first set should include objects where the favorites array contains only one item. The second set should display objects where the favorites array is either undefined or empty. The third ...

Adjust the size of the div to fit its content while maintaining the transition animation

I am aiming for the DIV height to automatically adjust to the text within it, while also maintaining a minimum height. However, when the user hovers their mouse over the DIV, I want the height to change smoothly without losing the transition effect. When ...

Align watermark content to the far left side

Having trouble getting my watermark to align properly on the left side of my website's main content. Here is how it currently looks: https://i.sstatic.net/Nfhh5.png The issue arises when I resize the screen or switch to mobile view, as the watermark ...

What is the best way to retrieve a class property within an arrow function that is passed to a constructor?

On a React login page, I created a class for rendering authentication methods. The class includes a default render function that I need to customize. class AuthMethod { constructor(initializer: Pick<AuthMethod, 'id'> & Partial<A ...

The view "skills.ejs" could not be found in the views directory

Just one month into my full stack program, I am facing a challenge while trying to render index and details pages on a local server. It's been a frustrating experience so far. :) Despite all my efforts and days of troubleshooting, I can't seem t ...

Setting the state of a nested array within an array of objects in React

this is the current state of my app this.state = { notifications: [{ from: { id: someid, name: somename }, message: [somemessage] }, {..}, {..}, ] } If a n ...

Retrieve a particular item using the NGXS selector and incorporate it into the template

I am retrieving stored configuration settings from an API. Each setting includes an 'ID' and several properties like 'numberOfUsers', etc. I am utilizing NGXS for managing the state. My goal is to specifically fetch the 'numberOf ...