最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Testing whether you reach an array's last item when using reduce() in Javascript - Stack Overflow

programmeradmin2浏览0评论

I am trying to implement the equivalent of .join() using .reduce() to answer a quiz on executeprogram. My code passes the tests, but it seems ugly. The specific issue that I have solved in an ugly way is this: The constructed join() function takes an array and a separator as arguments, but the separator should not be inserted after the last item of the array when constructing the resulting joined output. This means I have to test, when iterating over the array with reduce(), whether I am at the last item (so that I can skip using the separator) [oops: see note below]. Using indexOf or lastIndexOf doesn't work if there are duplicate values in the array. So I created a counter. I don't like it. I would love suggestions about how to do this better. Here's the code:

 function join(arr, separator) {
  let counter = 0;
  if (arr.length === 0) { return(''); }
  else { 
    return (
    arr.reduce((accumulator, item) =>
      {
        if (counter === arr.length - 1) {
          return accumulator + item;
        } else {
          counter += 1; 
          return accumulator + item + separator;
        }
      }, 
      '')
    );
  }
} 

The tests and their expected output are below. Again, the function above passes all the tests, but I think there has to be a better way.

> join(['a'], ',')
Expected: 'a'
> join(['a', 'b'], ',')
Expected: 'a,b' 
> join(['a', 'b', 'c'], '')
Expected: 'abc'
> join(['a', 'b', 'c', 'd'], 'x')
Expected: 'axbxcxd'
> join(['a', 'b'], 'COMMA')
Expected: 'aCOMMAb'
> join(['', '', ''], ',')
Expected: ',,'
> join([], ',')
Expected: ''

NOTE: After seeing all the helpful answers, I realized that the real cause for this ugly code was my mistaken assumption that the separator comes "after" an item. Actually, the separator comes between items, and you can make it come between every item if you insert it before every item except the first one. Then you don't need to test for the last item. When you remove the assumption that you need to test for the last item, everything is much simpler. So basically, I stated the problem wrong in the first place. Thanks to everyone for the answers!

I am trying to implement the equivalent of .join() using .reduce() to answer a quiz on executeprogram.com. My code passes the tests, but it seems ugly. The specific issue that I have solved in an ugly way is this: The constructed join() function takes an array and a separator as arguments, but the separator should not be inserted after the last item of the array when constructing the resulting joined output. This means I have to test, when iterating over the array with reduce(), whether I am at the last item (so that I can skip using the separator) [oops: see note below]. Using indexOf or lastIndexOf doesn't work if there are duplicate values in the array. So I created a counter. I don't like it. I would love suggestions about how to do this better. Here's the code:

 function join(arr, separator) {
  let counter = 0;
  if (arr.length === 0) { return(''); }
  else { 
    return (
    arr.reduce((accumulator, item) =>
      {
        if (counter === arr.length - 1) {
          return accumulator + item;
        } else {
          counter += 1; 
          return accumulator + item + separator;
        }
      }, 
      '')
    );
  }
} 

The tests and their expected output are below. Again, the function above passes all the tests, but I think there has to be a better way.

> join(['a'], ',')
Expected: 'a'
> join(['a', 'b'], ',')
Expected: 'a,b' 
> join(['a', 'b', 'c'], '')
Expected: 'abc'
> join(['a', 'b', 'c', 'd'], 'x')
Expected: 'axbxcxd'
> join(['a', 'b'], 'COMMA')
Expected: 'aCOMMAb'
> join(['', '', ''], ',')
Expected: ',,'
> join([], ',')
Expected: ''

NOTE: After seeing all the helpful answers, I realized that the real cause for this ugly code was my mistaken assumption that the separator comes "after" an item. Actually, the separator comes between items, and you can make it come between every item if you insert it before every item except the first one. Then you don't need to test for the last item. When you remove the assumption that you need to test for the last item, everything is much simpler. So basically, I stated the problem wrong in the first place. Thanks to everyone for the answers!

Share Improve this question edited Oct 6, 2020 at 1:54 bhagerty asked Oct 5, 2020 at 21:51 bhagertybhagerty 6107 silver badges11 bronze badges 1
  • The reducer function takes a third argument, the current index, and a fourth, the source array. You could clean it up a bit by using those instead of your own counter. – ray Commented Oct 5, 2020 at 21:55
Add a comment  | 

4 Answers 4

Reset to default 8

The reduce callback is called with 4 params:

arr.reduce(callback( accumulator, currentValue, index, array)

Example:

function join(arr, separator) {
  if (arr.length === 0) { return(''); }
  
  else { 
    return (
    arr.reduce((accumulator, item, counter) =>
      {
        if (counter === arr.length - 1) {
          return accumulator + item;
        } else {
          counter += 1; 
          return accumulator + item + separator;
        }
      }, 
      '')
    );
  }
}
    
    
console.log(join(['a'], ','))
console.log(join(['a', 'b'], ','))
console.log(join(['a', 'b', 'c'], ''))
console.log(join(['a', 'b', 'c', 'd'], 'x'))
console.log(join(['a', 'b'], 'COMMA'))
console.log(join(['', '', ''], ','))
console.log(join([], ','))

You can shorter the code a bit by:

  1. Skip the empty check - if the array is empty, reduce will return the initial value ('').
  2. Use a template string, and a ternary. If it's not the last item add the separator, if it is, use an empty string.
  3. Since the counter (i) starts with a 0, you can skip the separator for the 1st item (0 is casted to false). This allows you to skip the comparison in the ternary.

const join = (arr, separator) => arr.reduce((accumulator, item, i) =>
  `${accumulator}${i ? separator : ''}${item}`
, '')
    
    
console.log(join(['a'], ','))
console.log(join(['a', 'b'], ','))
console.log(join(['a', 'b', 'c'], ''))
console.log(join(['a', 'b', 'c', 'd'], 'x'))
console.log(join(['a', 'b'], 'COMMA'))
console.log(join(['', '', ''], ','))
console.log(join([], ','))

Another idea when changing the order (based on @JCFord arr[0] idea), is to destructure the array, take the 1st element (first), and assign it a default if it's empty. Then reduce the rest of the array, and use first as the initial value.

const join = ([first = '', ...arr], separator) => 
  arr.reduce((accumulator, item, i) => `${accumulator}${separator}${item}`, first)
    
    
console.log(join(['a'], ','))
console.log(join(['a', 'b'], ','))
console.log(join(['a', 'b', 'c'], ''))
console.log(join(['a', 'b', 'c', 'd'], 'x'))
console.log(join(['a', 'b'], 'COMMA'))
console.log(join(['', '', ''], ','))
console.log(join([], ','))

As others have pointed out you could use the index and array args in the callback.

But there is another approach. You don't have to know when you've encountered the last array element. You already know what the first element is and you can just use that.

const join = (arr, sep=',') => arr.slice(1).reduce((acc, item) => acc + sep + item, arr[0]);

To further refine the function, take advantage of the little used default initial value of the accumulator. If you do not pass an initial value to the reduce() function, first execution of the callback will be given the first and second elements of the array.

const join = (arr, sep=',') => arr.reduce((acc, item) => acc + sep + item);

And to handle empty array without error...

const join = (arr, sep=',') => arr[0] ? arr.reduce((acc, item) => acc + sep + item) : '';

Here's how I do it.

Let's say you want to separate this array by commas but end it with a period.

let names = ['Amber', 'Bobby', 'Chris', 'Dennis', 'Edward'];

You can use the third index parameter "i" and compare it with the length of the array being reduced as a variable.

names.reduce((a, c, i) => {
   const isLast = i == names.length -1;
   return `${a}${c}` + (isLast ? '.' : ', '); 
}, "")

Result: 'Amber, Bobby, Chris, Dennis, Edward.'

You can simplify your current function by removing the initial value from reduce. This will then use the first element as initial value. You've already set up a guard clause to handle empty arrays so you should be fine in that regard.

function join(arr, separator) {
  if (arr.length == 0) return '';
  return arr.reduce((accumulator, item) => accumulator + separator + item);
}

console.log(join(['a'], ','))
console.log(join(['a', 'b'], ','))
console.log(join(['a', 'b', 'c'], ''))
console.log(join(['a', 'b', 'c', 'd'], 'x'))
console.log(join(['a', 'b'], 'COMMA'))
console.log(join(['', '', ''], ','))
console.log(join([], ','))

Another easy way to join an array of strings using reduce would be to simply not care about the additional separator it produces and slice it off after reducing the array.

function join(arr, separator) {
  return arr.reduce((accumulator, item) => accumulator + separator + item, '')
            .slice(separator.length);
}

console.log(join(['a'], ','))
console.log(join(['a', 'b'], ','))
console.log(join(['a', 'b', 'c'], ''))
console.log(join(['a', 'b', 'c', 'd'], 'x'))
console.log(join(['a', 'b'], 'COMMA'))
console.log(join(['', '', ''], ','))
console.log(join([], ','))

发布评论

评论列表(0)

  1. 暂无评论