SEB
SEB

Reputation: 43

Letter Count - How to return the first word with the highest amount of repeating letters?

For example: "Today, is the greatest day ever!" should return greatest because it has 2 e's (and 2 t's) and it comes before ever which also has 2 e's. If there are no words with repeating letters return -1. Words will be separated by spaces. Input:"Hello Apple Pie" Output should be:"Hello" Do not understand what is wrong with my code, also if you think there is a simpler and shorter way to solve I would love to hear it. Thanks in advance for the help.

  function LetterCountI(str) { 
    let unique=[... new Set(str)]
    if(unique==str){ 
      return -1}
    
    let arr= str.split(" ") 
    
    let array=arr.map(item=>{ 

    let temparr=item.split("")

      return temparr.reduce((acc,curr)=>{ 
    acc[curr]=acc[curr]? acc[curr]+1:1 
    if(acc[curr]>acc.max){ 
      acc.max=acc[curr]}
return acc},{max:1, word:item})}) 
    
    let amount=1
    let largest=""
    
    for(let item of arr){ 
      if( item.max>amount){ 
        amount=item.max 
        largest=item.word
      }
    } 
    
    return largest
    
   }

Upvotes: 0

Views: 1003

Answers (3)

Scott Sauyet
Scott Sauyet

Reputation: 50797

My initial approach would involve separating out a maxBy function that accepts a function extracting a comparison number from an input value and returns a function from a list of values to the largest one of those.

Then we could write letterCount to count the occurrences of various letters in a string, and wrap that up with a function, maxLetterCount that calculates the maximum letter count for a string, using Math .max on the values from letterCount, and write our main function to split your initial string into words and call maxBy using maxLetterCount against the list of words. It could look like this:

// DO NOT USE -- Doesn't meet all requirements!

const maxBy = (fn) => (xs) => xs .reduce (
  ({m, r}, x) => {const v = fn(x); return v > m ? {m: v, r: x} : {m, r}}, 
  {m: -Infinity}
) .r

const letterCount = ([...cs]) => 
  cs .reduce ((a, c) => {a [c] = (a [c] || 0) + 1; return a}, {})

const maxLetterCount = (cs) => 
  Math .max (... Object .values (letterCount (cs)))

const letterCountI = (s) =>
  maxBy (maxLetterCount) (s .split (/\s+/))

console .log (letterCountI ("Today, is the greatest day ever!"))
console .log (letterCountI ("Hello Apple Pie"))

But there is a problem with this approach. It doesn't take into account your requirement that if there are no duplicated letters, we must return -1. Right now, letterCountI ("Today is the day") will return "Today".

One fix for this problem might involve pairing the words with their max letter counts, filtering these pairs to only those which have multiply-occurring letters, then use maxBy again on the pairs, and finally pull the word out of the winning pair. To handle the -1 case we could insert a dummy pair into the filtered list with -1 for the word and -Infinity for the letter count. If the list is otherwise empty, then we will choose this one.

And doing this might lead us to add some defaulting behavior to maximumBy, which right now is limited to numbers, but ideally should work with anything we can compare using <. If we defaulted our lower bound of -Infinity, but allowing override, and paired that with a default value for an empty list, then we could probably do the above in a fairly simple way.1

But that feels a bit overcomplicated. Perhaps a simpler approach would be to simply perform the code as above, then test if the resulting word has any repeated letter. While we could track this through the function, I think the simplest version would be to call letterCount again on it. So this is how I would probably choose to write this function:

const maxBy = (fn) => (xs) => xs .reduce (
  ({m, r}, x) => {const v = fn(x); return v > m ? {m: v, r: x} : {m, r}}, 
  {m: -Infinity}
) .r

const letterCount = ([...cs]) => 
  cs .reduce ((a, c) => {a [c] = (a [c] || 0) + 1; return a}, {})

const maxLetterCount = (cs) => 
  Math .max (... Object .values (letterCount (cs)))

const letterCountI = (s, target = maxBy (maxLetterCount) (s .split (/\s+/))) =>
  maxLetterCount (target) > 1 ? target : -1

console .log (letterCountI ("Today, is the greatest day ever!"))
console .log (letterCountI ("Hello Apple Pie"))
console .log (letterCountI ("Today, is the day!"))


1 That might look like this (untested) version:

const maximumBy = (fn, {dfltVal = '', lowBound = -Infinity} = {}) => (xs) => xs .reduce (
  ({m, r}, x) => {const v = fn(x); return v > m ? {m: v, r: x} : {m, r}}, 
  {m: lowBound, r: dfltVal}
) .r

Upvotes: 1

Peter Seliger
Peter Seliger

Reputation: 13377

Staying with the OP's reduce based approach one would provide a single function which incorporates two specific nested reduce tasks responsible for 1) aggregating the word specific statistics of a sentence and 2) the letter specific statistics for each word. This approach also ensures the result of '' (suggested) or -1 (the OP'S requirement) in case there are only words of unique (non repeating) letters/characters.

function getFirstOccurringWordOfMaximumSameCharacterCount(value) {

  function aggregateCharCountData(wordStats, char, idx, arr) {
    const { charStats } = wordStats;

    // aggregate character specific array.
    (charStats[char] ??= []).push(char);

    if (idx >= (arr.length - 1)) {
      // console.log({ charStats });

      // aggregate maximum character count data
      // at the end of the last character reduce step.
      const maxCharList = Object
        .values(charStats)
        .sort((aCharList, bCharList) =>
          bCharList.length - aCharList.length
        )[0];

      wordStats.maxCharCount = maxCharList.length;
      wordStats.maxCountChar = maxCharList[0];
    }
    return wordStats;
  }
  function aggregateWordSpecificCharCountData(collector, word, idx, arr) {
    const { statistics } = collector;

    statistics.push(
      word
        .split('')
        .reduce(aggregateCharCountData, { word, charStats: {} })
    );
    if (idx >= (arr.length - 1)) {

      // find the first occurring word of maximum same character count
      // at the end of the last word reduce step.
      const wordStats = statistics
        .sort((aWordStats, bWordStats) =>
          bWordStats.maxCharCount - aWordStats.maxCharCount
        )[0];
      // console.log({ statistics });

      collector.result = (wordStats.maxCharCount >= 2)
        ? wordStats.word
        : ''; // : -1;
    }
    return collector;
  }

  const {

    // statistics,
    result,

  } = String(value)
    .split(/\s+/)

    .reduce(aggregateWordSpecificCharCountData, {
      statistics: [],
      result: '',
    });
  // console.log({ statistics });

  return result;
}

console.log([

  'Today is the day.',                  // ''
  'Today, is the greatest day ever!',   // 'greatest'
  'Hello Apple Pie',                    // 'Hello'
  'She sells seashells by the seashore' // 'seashells'

].map(getFirstOccurringWordOfMaximumSameCharacterCount));
.as-console-wrapper { min-height: 100%!important; top: 0; }

Upvotes: -1

danh
danh

Reputation: 62676

Start with the simpler problem of getting the frequency of each letter in a word...

// given "sassy", will return { s:3, a:1, y:1 }
function letterFrequency(word) {
  return word.split('').reduce((acc, letter) => {
    if (!acc[letter]) acc[letter] = 0;
    acc[letter]++;
    return acc;
  }, {});
}

A simple problem to get the max frequency...

// given { s:3, a:1, y:1 }, will return 3
function maxLetterFrequency(word) {
  return Math.max(...Object.values(letterFrequency(word)));
}

Another simple problem to sort a sentence by frequency...

// given "She sells seashells by the seashore"
// returns ["seashells", "sells", "seashore", "She", "by", "the"]
function wordsOrderedByMaxLetterFrequency(sentence) {
  let words = sentence.split(' ');
  words.sort((a, b) => {
    return maxLetterFrequency(b) - maxLetterFrequency(a);
  });
  return words;
}

The first word in the resulting array is the answer. You can retest max frequencies in that word to determine of the answer should be -1.

Demo...

// given "sassy", will return { s:3, a:1, y:1 }
function letterFrequency(word) {
  return word.split('').reduce((acc, letter) => {
    if (!acc[letter]) acc[letter] = 0;
    acc[letter]++;
    return acc;
  }, {});
}

// given { s:3, a:1, y:1 }, will return 3
function maxLetterFrequency(word) {
  return Math.max(...Object.values(letterFrequency(word)));
}

// given "She sells seashells by the seashore"
// returns ["seashells", "sells", "seashore", "She", "by", "the"]
function wordsOrderedByMaxLetterFrequency(sentence) {
  let words = sentence.split(' ');
  words.sort((a, b) => {
    return maxLetterFrequency(b) - maxLetterFrequency(a);
  });
  return words;
}

const sentence = "She sells seashells by the seashore";
const sorted = wordsOrderedByMaxLetterFrequency(sentence);
console.log(sorted);
console.log('best word:', sorted[0]);
console.log('max freq in best word:', maxLetterFrequency(sorted[0]));

Upvotes: 2

Related Questions