2
\$\begingroup\$

Inspired by The Beauty and the Strings question, I tried to implement the "Maximum Beauty of String" in a stateless fashion using ES6. Unfortunately, I ran into two places where I had to assign variables.

The questions I have:

  • How can I remove the state in the reducer for mapLettersByCount?
  • How can one use generators without assigning it to a variable? (see points variable in the maxBeauty function) Is this even considered state in this situation?

Code:

const EMPTY_STRING = '';
const LETTERS_ONLY = /[a-z]/i;

function mapLettersByCount(sentence) {
  return sentence.split(EMPTY_STRING)
    .filter(letter => LETTERS_ONLY.test(letter))
    .map(letter => [letter.toUpperCase(), 1])
    .reduce((letterCount, [key, count]) => {
      letterCount[key] = (letterCount[key] || 0) + count;
      return letterCount;
    }, {});
}

function * downCounter(maxValue) {
  yield maxValue;
  yield * downCounter(maxValue > 0 ? maxValue - 1 : 0);
}

export function maxBeauty(sentence = "") {
  const points = downCounter(26);
  return Object.values(mapLettersByCount(sentence))
    .sort().reverse()
    .reduce((score, count) => score + count * points.next().value, 0);
}

Test Cases:

import {maxBeauty} from './beauty';

describe('The maximum beauty', () => {
  it('should calculate for a word', () => {
    expect(maxBeauty('AbBCcC')).toBe(152);
  });

  it('should calculate ignoring spaces', () => {
    expect(maxBeauty('This is from Facebook Hacker Cup 2013.'))
      .toBe(551);
  });

  it('should calculate ignoring punctuation', () => {
    expect(maxBeauty('Ignore punctuation, please :)'))
      .toBe(491);
  });
});
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

How can I remove the state in the reducer for mapLettersByCount?

Return a new object on each iteration. Not optimal, but we're talking functional anyways. You can use object destructuring to do a shallow copy of the object. A more verbose version would be Object.assign. Then use computed keys to assign a dynamic key.

function mapLettersByCount(sentence) {
  return sentence.split(EMPTY_STRING)
                 .filter(letter => LETTERS_ONLY.test(letter))
                 .map(letter => [letter.toUpperCase(), 1])
                 .reduce((letterCount, [key, count]) => ({
                   ...letterCount,
                   [key]: (letterCount[key] || 0) + count
                 }), {});
}

How can one use generators without assigning it to a variable? (see points variable in the maxBeauty function) Is this even considered state in this situation?

I believe that it is stateful since one call to points.next() does not return the same result as the next call. But I may be wrong.

One way you could do this is compute the points by subtracting the reducer's index from a constant 26. I believe it won't violate the rules of functional programming because

  1. this 26 is a constant value
  2. it's technically a closure scenario, similar to your other constants and
  3. index is provided as an argument to the callback.

And it would look like this:

const POINTS = 26;

export function maxBeauty(sentence = "") {
  return Object.values(mapLettersByCount(sentence))
               .sort()
               .reverse()
               .reduce((score, count, index) => score + count * (POINTS - index), 0);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.