Niroda
Niroda

Reputation: 318

Calculating the size of an element depending on the number of elements and the total width

Let X be a known value which represents how many columns will be displayed in a row (possible columns are 1, 2, 3 or 4)

Let Y be the number of the elements elements (from 1 to infinity)

Using Javascript, is there any way/algorithm to calculate the width of each element in Y such that we fulfil X (regardless how many rows we use) and the max size of each element is 2 columns?

Example 1: let X be 4 (widescreen) and Y be 5 elements, the first row will contain 3 elements such that one of the elements takes 2 columns and the second row will contain 2 elements such that both take 2 columns. enter image description here

Example 2: let X be 4 (widescreen) and Y be 7 elements, the first row will contain 4 elements such that every element takes 1 column and the second row will contain 3 elements such that one of the elements takes 2 columns. (The order doesn't matter)

enter image description here

Upvotes: 0

Views: 640

Answers (2)

Alex L
Alex L

Reputation: 4241

A slightly different approach than @trincot. (Who's is more elegant I believe):

Demo: https://codepen.io/Alexander9111/pen/povMXOG

HTML:

<input placeholder="enter number of cells"/>

<div id="holder">

</div>

JS:

const holder = document.querySelector('#holder');
function calculateGrid(Y) {
  const xMax=4;
  let flag = void(0);
  let remainder = Y % xMax;
  let rows = Math.floor(Y / xMax, 0);
  //console.log(rows, remainder);
  if (Y > 1 && remainder == 1){
    flag = rows - 1;
    remainder = 2;
  }
  let output = [];
  let temp = [];
  let tempMax = 0;
  for (let i=0; i<=rows; i++){
    temp = [];
    if (i == flag){
      tempMax = (xMax-1);
    } else if (i == rows){
      tempMax = remainder;
    } else {
      tempMax = xMax;
    }
    for (let j=0; j<tempMax; j++){
      temp.push(1);
    }
    for (let j=0; j<temp.length; j++){
      if (temp.reduce((a,b)=>a+b) == xMax){
        break;
      } else {
        temp[j] += 1;
      }
    }
    output.push(temp);
  }
  return output;
}    

function draw(grid){
  holder.innerHTML = "";
  num = 1;
  for (let i=0; i<grid.length; i++){
    let row = document.createElement('div');
    row.id = "row"+i;
    row.classList.add('row');
    holder.appendChild(row);

    for (let j=0; j<grid[i].length; j++){
      let cell = document.createElement('div');
      cell.id = "row"+i+"_col"+j;
      cell.classList.add('cell');
      cell.classList.add('w'+grid[i][j]);
      cell.innerText = num;
      row.appendChild(cell);
      num +=1;
    }
  }
}

const input = document.querySelector('input');

input.addEventListener('change', (e) => {
  const val = parseInt(e.target.value);
  const grid = calculateGrid(val);
  console.log(grid);
  draw(grid);
});

Upvotes: 0

trincot
trincot

Reputation: 350212

As the number of possibilities is quite limited, you could just identify those. It is clear that it does not matter how many rows you have to produce, only the last or last two rows will need to have double sized boxes to make it fit. All the other rows can just have 1-column sized boxes.

Here is a function that will list the widths of the last few cells, starting with the first one that will have double width:

function tail(x, y) {
    let mod = y % x;
    if (mod == 0) return []; // All items have size 1
    if (x == 2 || y == 1) return [2]; // Only last item has size 2
    if (x == 3) return [[2, 1, 2, 1], [2, 1]][mod-1];
    if (x == 4) return [[2, 1, 1, 2, 2], [2, 2], [2, 1, 1]][mod-1];
}

Now we have done the hard work. All the rest becomes simple. We can create another function that will return a full array of widths, one for every item, of length y:

function widths(x, y) {
    // Get the special rows that have to appear at the very end
    let arrTail = tail(x, y);
    // Prepend the number of normal cells (all having size 1):
    return Array(y - arrTail.length).fill(1).concat(arrTail);
}

We can think of yet another function that will use the above to generate the HTML for each of the y boxes, giving each a class that defines its width:

function toHtml(x, y) {
    let i = 1;
    return widths(x, y).map(w => `<span class="w${w}_${x}">${i++}<\/span>`)
                       .join("");
}

All that remains is to define the corresponding CSS and container element.

Here is a snippet that allows you to define x and y interactively, showing the result in real-time:

function tail(x, y) {
    let mod = y % x;
    if (mod == 0) return []; // All items have size 1
    if (x == 2 || y == 1) return [2]; // Last row consists of one item of size 2
    if (x == 3) return [[2, 1, 2, 1], [2, 1]][mod-1];
    if (x == 4) return [[2, 1, 1, 2, 2], [2, 2], [2, 1, 1]][mod-1];
}

function widths(x, y) {
    // Get the special rows that have to appear at the very end
    let arrTail = tail(x, y);
    // Prepend the number of normal cells (all having size 1):
    return Array(y - arrTail.length).fill(1).concat(arrTail);
}

function toHtml(x, y) {
    let i = 1;
    return widths(x, y).map(w => `<span class="w${w}_${x}">${i++}<\/span>`)
                       .join("");
}

// I/O management

let [inputX, inputY] = document.querySelectorAll("input");
let container = document.querySelector("#container");

function refresh() {
    let x = +inputX.value;
    let y = +inputY.value;
    container.innerHTML = toHtml(x, y);
}

refresh();
document.addEventListener("input", refresh);
#container > span {
    box-sizing: border-box;
    border: 1px solid;
    display: inline-block;
    text-align: center;
}

#container > span:nth-child(even) { background: #eee }

.w1_1, .w2_2 { width: 100% }
.w1_3 { width: 33.33% }
.w1_4 { width: 25%; }
.w2_3 { width: 66.66% }
.w2_4, .w1_2 { width: 50%; }
Columns: <input type="number" min="1" max="4" value="4">
Items: <input type="number" min="1" value="7">
<p>
<div id="container"></div>

Upvotes: 3

Related Questions