BattlefyBlogHistoryOpen menu
Close menuHistory

How to avoid math and profit from it

Ronald ChenDecember 20th 2021

Off-by-one error is one of the most common bugs. We can reduce this category of bugs with how we choose to write code. The more math present in our code, the more opportunities for errors.

This blog post will run through several scenarios that can be solved with typical math heavier patterns and alternative reduced math solutions. By doing less math in our code, we are allowing computers to do what they are meant for.

The code samples are written in JavaScript and may include lodash (imported as _).

Access every element in an array

// BEFORE  
for (let i = 0; i < array.length; i++) {  
  const element = array[i];  
  ..  
}
// AFTER  
for (const element of array) {  
  ..  
}

Common errors

  • using var instead of let (causes i to be visible outside of for loop)
  • using const instead of let (loop fails on i++)
  • typo , instead of ; (comma surprisingly is an operator)
  • typo i <= array.length
  • typo i < array.length — 1
  • typo j instead of i (hard to detect if j has been defined before the loop)
  • typo array(i) instead of array[i] (especially hard to detect if array is typeof function which happens to be used in an array-like fashion)

Access every element in an array with an index

// BEFORE  
for (let i = 0; i < array.length; i++) {  
  const element = array[i];  
  ..  
}
// AFTER  
array.forEach((element, i) => {  
 ..  
});

Access every element in an array in reverse

// BEFORE  
for (let i = array.length - 1; i >= 0; i--) {  
  const element = array[i];  
  ..  
}
// AFTER  
for (const element of [...array].reverse()) {  
  ..  
}

Common errors (omits one similar to “Access every element in an array”)

  • typo let i = array.length
  • typo i > 0

Transform every element into a new array

// BEFORE  
const result = [];  
for (let i = 0; i < array.length; i++) {  
  result[i] = someFunction(array[i]);  
}
// AFTER  
const result = array.map((element) => someFunction(element));

Alternatively, if someFunction ignores parameters after the first, shorten to array.map(someFunction).

Concatenate two arrays

// BEFORE  
const result = [];  
for (let i = 0; i < array1.length; i++) {  
  result[i] = array1[i];  
}  
for (let i = 0; i < array2.length; i++) {  
  result[array1.length + i] = array2[i];  
}
// AFTER  
const result = [...array1, ...array2];

Combine two arrays pair-wise with some function

// BEFORE  
const result = [];  
for (let i = 0; i < Math.max(array1.length, array2.length); i++) {  
  result[i] = someFunction(array1[i], array2[i]);  
}
// AFTER  
const result = _.zip(array1, array2).map(([element1, element2]) => someFunction(element1, element2));

Note someFunction will receive undefined items from the shorter array.

Take first n elements from an array

// BEFORE  
const result = [];  
for (let i = 0; i < Math.min(n, array.length); i++) {  
  result[i] = array[i];  
}
// AFTER  
const result = array.slice(0, n);

Drop first n elements from an array

// BEFORE  
const result = [];  
for (let i = n; i < array.length; i++) {  
  result[i - n] = array[i];  
}
// AFTER  
const result = array.slice(n);

Take last n elements from an array

// BEFORE  
const result = [];  
const init = Math.max(0, array.length - n);  
for (let i = init; i < array.length; i++) {  
  result[i - init] = array[i];  
}
// AFTER  
const result = array.slice(-n);

Drop last n elements from an array

// BEFORE  
const result = [];  
for (let i = 0; i < Math.max(0, array.length - n); i++) {  
  result[i] = array[i];  
}
// AFTER  
const result = array.slice(0, -n);

Take subarray from start to end index

// BEFORE  
const subarray = [];  
for (let i = start; i < Math.min(end, array.length); i++) {  
  subarray[i - start] = array[i];  
}
// AFTER  
const subarray = array.slice(start, end);

Drop subarray from start to end index

// BEFORE  
const subarray = [];  
for (let i = 0; i < Math.min(start, array.length); i++) {  
  subarray[i] = array[i];  
}  
for (let i = end; i < array.length; i++) {  
  subarray[start + i] = array[i];  
}
// AFTER  
const subarray = [...array.slice(0, start), ...array.slice(end)];

Take subarray from start index to length n

// BEFORE  
const subarray = [];  
for (let i = start; i < Math.min(start + n, array.length); i++) {  
  subarray[i - start] = array[i];  
}
// AFTER  
const subarray = array.slice(start);  
subarray.length = n;

Drop subarray from start index to length n

// BEFORE  
const subarray = [];  
for (let i = 0; i < Math.min(start, array.length); i++) {  
  subarray[i] = array[i];  
}  
for (let i = start + n; i < array.length; i++) {  
  subarray[start + i] = array[i];  
}
// AFTER  
const subarray = [...array];  
subarray.splice(start, n);

Note the splice, not to be confused with slice.

Create array of length n with fill value

// BEFORE  
const result = [];  
for (let i = 0; i < n; i++) {  
  result[i] = value;  
}
// AFTER  
const result = Array(n).fill(value);

If someFunction needs the index, use Array.from({length: n}, (_, i) => someFunction(i)).

Pad end of array to length n with fill value

// BEFORE  
const result = [];  
const paddedLength = Math.max(0, n - array.length);  
for (let i = 0; i < array.length; i++) {  
  result[i] = array[i];  
}  
for (let i = 0; i < paddedLength; i++) {  
  result[array.length + i] = value;  
}
// AFTER  
const result = Object.assign(Array(n).fill(value), ...array);

Pad start of array to length n with fill value

// BEFORE  
const result = [];  
const paddedLength = Math.max(0, n - array.length);  
for (let i = 0; i < paddedLength; i++) {  
  result[i] = value;  
}  
for (let i = 0; i < array.length; i++) {  
  result[paddedLength + i] = array[i];  
}
// AFTER  
const result = Object.assign(Array(n).fill(value), [...array].reverse()).reverse();

Oof, that’s not great. But neither is [...Array(n).fill(value), ...array].slice(-(Math.max(n, array.length)). Leave a response if you have a better solution.

Extract head and tail from an array

// BEFORE  
const head = array[0];  
const tail = [];  
for (let i = 1; i < array.length; i++) {  
  tail[i - 1] = array[i];  
}
// AFTER  
const [head, ...tail] = array;

Extract initial and last from an array

// BEFORE  
const initial = [];  
for (let i = 0; i < array.length - 1; i++) {  
  initial[i] = array[i];  
}  
const last = array[array.length - 1];
// AFTER  
const initial = array.slice(0, -1);  
const last = array.at(-1);

Chunk by size n

// BEFORE  
const result = [];  
for (let i = 0; i < array.length; i += n) {  
  const chunk = [];  
  for (let j = 0; j < n && i + j < array.length; j++) {  
    chunk[j] = array[i + j];  
  }  
  result.push(chunk);  
}
// AFTER  
const result = [];  
while (array.length) {  
  result.push(array.splice(0, n)); // modifies array  
}

If one cannot modify the existing array, use lodash’s chunk.

Access matrix by columns

// BEFORE  
for (let i= 0; i < matrix[0].length; i++) {  
  const column = [];  
  for(let j = 0; j < matrix.length; j++) {  
    column[i] = matrix[j][i];  
  }  
  // use column  
  ..  
}
// AFTER  
for (const column of _.unzip(matrix)) { // _.unzip is R.transpose
  // use column  
  ..  
}

This assumed matrix is rectangular.

Be careful with mathless code in production

While the examples show how to write less math, some are definitely harder to read and understand. Do not use these mathless forms it made your production code worse.

Do you want to write less math? You’re in luck, Battlefy is hiring.

2022

Powered by
BATTLEFY