totalnoob
totalnoob

Reputation: 2741

refactoring a function using reduce - javascript

I've written a function to turn a string such as 'aaazeeeee' into a new string of 'aaa z eeeee'

this is the code I've tried that works

const groupCharacters = signature => {
  let newSignature = "", arr = [];

  let result = [...signature].reduce((accumulator, element, index) => {
    // check if last element in accumulator matches current element
    if (accumulator[accumulator.length -1] !== element) {
      // push accumulator into array
      arr.push(accumulator);
      // set newSignature as new element
      newSignature = element;
    } else {
      // else add element to newSignature
      newSignature = accumulator += element;
    }
    
    // if is last item, push to array
    if (index === signature.length - 1) arr.push(element);

    return newSignature;
  })

  return arr;
}

console.log(groupCharacters('aabaaaaa'));

how can I refactor it so that I don't need the new string or array? I've tried something like this

const groupCharacters = str => [...str].reduce((accumulator, element) => accumulator[accumulator.length - 1] !== element ? `${accumulator} ` : accumulator + element)

it outputs 'aaa '

how do I fix it or use something like map instead?

Upvotes: 2

Views: 182

Answers (2)

CertainPerformance
CertainPerformance

Reputation: 370689

To change your existing code but still use reduce, I'd suggest reduceing into a string, rather than array: on each iteration, concatenate with the current character, and also concatenate with a space if the next character is both defined and is not equal to the current character:

const groupCharacters = str => (
  [...str].reduce((a, char, i) => a + char + (
    str[i + 1] === char || str[i + 1] === undefined
    ? ''
    : ' '
  ), '')
);

console.log(groupCharacters('aaazeeeee'));

Or, you could use a simple regular expression - capture a word character in a group, then backreference that group as many times as possible, and replace with the whole match plus a space:

const groupCharacters = signature => signature.replace(/(\w)\1*(?!\1|$)/g, '$& ');
console.log(groupCharacters('aaazeeeee'));

To break it down:

  • (\w) - Match any word character, capture it in the first group (so it can be backreferenced later using \1)

  • \1* - Greedily repeat the character that was just matched zero or more times (repeat as much as possible)

  • (?!\1|$) - Checks that the substring that was just matched is not followed by either the end of the string, or by another of the same character. This ensures that the final repeated substring doesn't get a space appended to it as well (that is, you don't want 'aaa z eeeee ').

As a side note, the regex \G(\w)\1*+(?!$) would accomplish the same thing, is nicer to read, and is notably more efficient (the \G matches the end of the last match or the beginning of the string, and the + in \1*+ makes the repetition possessive, which means that, on the final substring, the engine will be unable to backtrack, and thus fail immediately once the final full substring is checked, rather than iterating through each of its characters first). But, unfortunately, native JS doesn't support possessive quantifiers, nor the \G anchor.

Upvotes: 5

vibhor1997a
vibhor1997a

Reputation: 2376

I would do this using reduce with something like this as the OP wants to do with reduce:-

function myFunc(str) {
    return str.split('').reduce(function (acc, cv, ci) {
        if (acc.lastElem != cv) {
            acc.str += (acc.lastElem == "") ? cv : ' ' + cv;
        }
        else {
            acc.str += cv;
        }
        acc.lastElem = cv;
        return acc;
    }, { lastElem: '', str: '' }).str;
}
console.log(myFunc('aaabbbcc'));
console.log(myFunc('aaazeeeee'))

How it works?

The accumulator is an object with keys lastElem(the previous element than the current element) and str(the string forming).

Upvotes: 1

Related Questions