Formatting a list with commas and occasional line breaks

Posted on

Problem

I have an array mapped to make a typical result string separated by commas and with numbers a bit beautified with array.map(formatNumber).

Expected format is

number [quantity], number [quantity], number [quantity]

So

1234 [3], 1235 [2], 2234 [1]

My mapping function was like this:

function formatNumberSelected(number, i) {
    return number.number + " [" + number.amount() + "]" + (i % 5 === 4 ? ',<br />' : ', ');
}

But to avoid comma (or comma and <br />) in the last iteration now is:

function formatNumberSelected(number, i, array) {
  var toReturn = number.number + " [" + number.amount() + "]";
  if ((i + 1) < array.length) {
    if (i % 5 === 4) toReturn += ',<br />';
    else toReturn += ', ';
  }
  return toReturn;
}

There is a better way to make this code more readable? Seems like passing array to each iteration just to find the last index is a bit too much.

If not I find better this solution not using Array.prototype.map:

function formatNumberArray(array) {
  var SEPARATOR = '';
  var toReturn = '';
  for (var index in array) {
    toReturn += SEPARATOR + array[index].number + ' [' + array[index].amount() + ']';
    if (index % 5 === 4) SEPARATOR = ',<br />';
    else SEPARATOR = ', ';
  }
  return toReturn;
}

Solution

You have serval ways to achieve this task. You can pass the length of array as well.
About the implementation you can use forEach method like this.

 function formatNumberArray(arr) {
        let output = "";
        arr.forEach((v, i, a) => {
            output += v.number + "[" + v.amount + "]" + ((i + 1) % a.length == 0 ? "" : i % 5 == 4 ? ',<br />' : ', ');
        });
        return output;
    }

or you can iterate the array to the last index – 1 and then simply add the last value

function formatNumberArray(arr) {
        let output = "";
        for (let i = 0; i < arr.length - 1; ++i) {
            output += arr[i].number + "[" + arr[i].amount + "]" + ((i % 5 === 4) ? ',<br />' : ', ');
        }
        return output + arr[arr.length - 1].number + "[" + arr[arr.length - 1].amount + "]"
    }

I assume that the array is something like this.

  let arr = [{ number: 123, amount: 24 }, { number: 16, amount: 10 }, { number: 16, amount: 10 }, { number: 16, amount: 10 }, { number: 16, amount: 10 }, { number: 16, amount: 10 }, { number: 16, amount: 10 }, { number: 16, amount: 10 }, { number: 16, amount: 10 }, { number: 16, amount: 10 }, { number: 16, amount: 10 }, { number: 16, amount: 10 }, { number: 16, amount: 10 }]

I assume you need a <br/> on the last line as well but not the comma so I would separate the logic for the two:

function formatNumberSelected(number, i, array) {
  var toReturn = number.number + " [" + number.amount() + "]";
  if (i+1 < array.length)
    toReturn = toReturn + ',';
  toReturn = toReturn + (i+1 == array.length || i % 5 === 4) ? '<br/>' : '';
  return toReturn; 
}

An alternative might be:

function formatNumberArray(array) {
  var formatted = array.map( function(el) { 
        return number.number + " [" + number.amount() + "]" 
         } );
  var lines = [];
  while (formatted.length > 0)
    lines.push( formatted.splice(0,4).join(', ');
  return lines.join(', <br/>');
}

I would consider using array.reduce() for this. I would also consider explicit code paths for:

  • item at the end of the array, which would not need a comma, but may or may not need a line break (this is unclear in your original code – for my answer, I assume you would want one)
  • item at the end of the output line, which need end of line and comma
  • and all other items, which just need commas

For example:

function reduceNumberArray(numberArray) {
    var arrayLength = numberArray.length;
    return numberArray.reduce(
        function(str, number, index) {
            str = str + number.number + ' [' + number.amount() + ']';
            // is last element in array?
            if (index === arrayLength - 1) {
                return str + '<br />';
            }
            // is last element in chunk?
            if ((index + 1) % 5 === 0) {
                return str + ',<br />';
            }
            return str + ', '; 
        },
        ''
    );
}

Or, if you you want the function behavior to be configurable:

function reduceNumberArray(numberArray, chunkSize, glue, endOfLine, baseString) {
    chunkSize = chunkSize || 5;
    glue = glue || ',';
    endOfLine = endOfLine || '<br />';
    baseString = baseString || '';
    var arrayLength = numberArray.length;
    return numberArray.reduce(
        function(str, number, index) {
            str = str + number.number + ' [' + number.amount() + ']';
            // is last element in array?
            if (index === arrayLength - 1) {
                return str + endOfLine;
            }
            // is last element in chunk?
            if ((index + 1) % chunkSize === 0) {
                return str + glue + endOfLine;
            }
            return str + glue + ' '; 
        },
        baseString
    );
}

Or, in ES6:

function reduceNumberArray(
    numberArray,
    chunkSize = 5,
    glue = ',',
    endOfLine = '<br />',
    baseString = '',
) {
    const arrayLength = numberArray.length;
    return numberArray.reduce( (str, number, index) => {
            str = str + number.number + ' [' + number.amount() + ']';
            // is last element in array?
            if (index === arrayLength - 1) {
                return str + endOfLine;
            }
            // is last element in chunk?
            if ((index + 1) % chunkSize === 0) {
                return str + glue + endOfLine;
            }
            return str + glue + ' '; 
        },
        baseString
    );
}

Your formatNumberSelected needs to understand that the number is part of an array, there are other numbers, and depending on the position of this number, I need to format it differently.

Whoa, whoa, whoa. Slow down, you’re trying to do too much.

formatNumberSelected() should format a number.

function formatNumberSelected(number) {
    return number.number + " [" + number.amount() + "]"
}

formatArray() could format an array of numbers and understand how to piece them together and know where in the array we are, etc.

function formatArray(numbers) {
    var result = "";
    var len = numbers.length;
    var separator = "";
    for( i = 0; i < len; i++ ){
        result += formatNumberSelected(numbers[i]);
        if( i == len-1 ){
            separator = "";
        }
        else if( i % 5 == 4 ){
            separator = ",<br />";
        } else {
            separator = ",";
        }            
        result += separator;
    }
    return result;
}

You could take it a step farther and create determineSeparator()

function determineSeparator(index, arrayLength){
    if( index == arrayLength-1 ){
        return "";
    }
    else if( index % 5 == 4 ){
        return ",<br />";
    } else {
        return ",";
    }
}

Then your simplified formatArray would be

function formatArray(numbers) {
    var result = "";
    var len = numbers.length;
    for( i = 0; i < len; i++ ){
        result += formatNumberSelected(numbers[i]);
        result += determineSeparator(i, len);
    }
    return result;
}

Leave a Reply

Your email address will not be published. Required fields are marked *