2

I've been on this problem for several hours now and have done all I can to the best of my current newbie javaScript ability to solve this challenge but I just can't figure out exactly what's wrong. I keep getting "UNEXPECTED TOKEN ILLEGAL on here: http://jsfiddle.net/6n8apjze/14/

and "TypeError: Cannot read property 'length' of null": http://goo.gl/LIz89F

I think the problem is the howManyRepeat variable. I don't understand why I'm getting it can't read the length of null when clearly word is a word from str...

I got the idea for:

word.toLowerCase().split("").sort().join("").match(/([.])\1+/g).length

...here: Get duplicate characters count in a string

The Challenge:
Using the JavaScript language, have the function LetterCountI(str) take the str parameter being passed and return the first word with the greatest number of repeated 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.

function LetterCountI(str){
  var wordsAndAmount={};
  var mostRepeatLetters="-1";
  var words=str.split(" ");

     words.forEach(function(word){
       // returns value of how many repeated letters in word.
       var howManyRepeat=word.toLowerCase().split("").sort().join("").match(/([.])\1+/g).length;       
        // if there are repeats(at least one value).
        if(howManyRepeat !== null || howManyRepeat !== 0){ 
          wordsAndAmount[word] = howManyRepeat;
        }else{
         // if no words have repeats will return -1 after for in loop.
         wordsAndAmount[word] = -1; 
         }
     });

     // word is the key, wordsAndAmount[word] is the value of word.
     for(var word in wordsAndAmount){ 
        // if two words have same # of repeats pick the one before it.
        if(wordsAndAmount[word]===mostRepeatLetters){ 
          mostRepeatLetters=mostRepeatLetters;
        }else if(wordsAndAmount[word]<mostRepeatLetters){ 
          mostRepeatLetters=mostRepeatLetters;
        }else if(wordsAndAmount[word]>mostRepeatLetters){
          mostRepeatLetters=word;
        }
      } 

  return mostRepeatLetters;
}

// TESTS
console.log("-----");   
console.log(LetterCountI("Today, is the greatest day ever!"));   
console.log(LetterCountI("Hello apple pie"));    
console.log(LetterCountI("No words"));    

Any guidance is much appreciated. Thank you!! ^____^

Community
  • 1
  • 1
Mar
  • 115
  • 1
  • 3
  • 12
  • `[.]` matches a literal dot symbol, not any letter but a newline. – Wiktor Stribiżew Dec 07 '15 at 09:38
  • oh about the regex. yeah I'm still learning those. So if I wanted to match if the word had any repeating letters can I do /([a-z])\1+/g ? I think I tried that and I got an error, about an hour ago. – Mar Dec 07 '15 at 09:46
  • Does [this code](http://jsfiddle.net/v3czs6zm/) work as expected? http://jsfiddle.net/v3czs6zm/. It outputs `-----, greatest, apple, -1`. – Wiktor Stribiżew Dec 07 '15 at 09:47
  • oh you're right it almost works now! I didn't get what you got. I got ---- greatest apple -1. I see you changed the (.) It's really different from mine. I'm supposed to get --- greatest Hello and -1...I think I need to change something in my for in loop. Nope...That's not it... – Mar Dec 07 '15 at 09:52
  • How is (.) different from [.]? – Mar Dec 07 '15 at 09:54
  • *`[.]` matches a literal dot symbol, not any letter but a newline.* – Wiktor Stribiżew Dec 07 '15 at 09:58
  • But what does "." do? And why did you remove length? I still cant figure out why I am getting apple instead of hello. Both have 2 letters that repeat. But I set up the for in loop so that the first word would go first right? – Mar Dec 07 '15 at 10:00
  • If I knew that I would post an answer :) – Wiktor Stribiżew Dec 07 '15 at 10:02
  • So you just guessed? – Mar Dec 07 '15 at 10:03
  • Now it says match is undefined: http://goo.gl/K4Qzh5 I think the problem here is the regex...I need to figure that out now. – Mar Dec 07 '15 at 10:05
  • I guess this is it: http://jsfiddle.net/v3czs6zm/2/ If you confirm it is working as expected, I will explain in an answer. – Wiktor Stribiżew Dec 07 '15 at 10:07
  • It works! Thank you! But what about the -1? That's important for the challenge. Please explain how you did it stribizhev :) – Mar Dec 07 '15 at 10:17
  • I noticed you added in length in the for in loop. Does /(.)\1+/g return letters not a number? – Mar Dec 07 '15 at 10:23

4 Answers4

1

Here is the working code snippet:

/*
Using the JavaScript language, have the function LetterCountI(str) take the str 
parameter being passed and return the first word with the greatest number of 
repeated 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. 

console.log(LetterCountI("Today, is the greatest day ever!") === "greatest");
console.log(LetterCountI("Hello apple pie") === "Hello");
console.log(LetterCountI("No words") === -1);

Tips: 
This is an interesting problem. What we can do is turn the string to lower case using String.toLowerCase, and then split on "", so we get an array of characters.

We will then sort it with Array.sort. After it has been sorted, we will join it using Array.join. We can then make use of the regex /(.)\1+/g which essentially means match a letter and subsequent letters if it's the same.

When we use String.match with the stated regex, we will get an Array, whose length is the answer. Also used some try...catch to return 0 in case match returns null and results in TypeError.

/(.)\1+/g with the match method will return a value of letters that appear one after the other. Without sort(), this wouldn't work.
*/

function LetterCountI(str){
  var wordsAndAmount={};
 var mostRepeatLetters="";
  var words=str.split(" ");
  
  words.forEach(function(word){
    
      var howManyRepeat=word.toLowerCase().split("").sort().join("").match(/(.)\1+/g);
      
   if(howManyRepeat !== null && howManyRepeat !== 0){ // if there are repeats(at least one value)..
          wordsAndAmount[word] = howManyRepeat;
     } else{
           wordsAndAmount[word] = -1; // if no words have repeats will return -1 after for in loop.
        }
  });
  
//  console.log(wordsAndAmount);
 for(var word in wordsAndAmount){ // word is the key, wordsAndAmount[word] is the value of word.
   // console.log("Key = " + word);
   // console.log("val = " + wordsAndAmount[word]);
     if(wordsAndAmount[word].length>mostRepeatLetters.length){ //if two words have same # of repeats pick the one before it.
        mostRepeatLetters=word; 
    }
  } 
  return mostRepeatLetters ? mostRepeatLetters :  -1;
}

// TESTS
console.log("-----");
console.log(LetterCountI("Today, is the greatest day ever!"));
console.log(LetterCountI("Hello apple pie"));
console.log(LetterCountI("No words"));

/*
split into words

var wordsAndAmount={};
var mostRepeatLetters=0;
  
  
loop through words
 Check if words has repeated letters, if so
   Push amount into object
    Like wordsAndAmount[word[i]]= a number
  If no repeated letters...no else.
  
Loop through objects
  Compare new words amount of repeated letters with mostRepeatLetters replacing whoever has more.
  In the end return the result of the word having most repeated letters
  If all words have no repeated letters return -1, ie. 
*/

The changes made:

  • [.] turned into . as [.] matches a literal period symbol, not any character but a newline
  • added closing */ at the end of the code (the last comment block was not closed resulting in UNEXPECTED TOKEN ILLEGAL)
  • if(howManyRepeat !== null || howManyRepeat !== 0) should be replaced with if(howManyRepeat !== null && howManyRepeat !== 0) since otherwise the null was testing for equality with 0 and led to the TypeError: Cannot read property 'length' of null" issue. Note that .match(/(.)\1+/g).length cannot be used since the result of matching can be null, and this will also cause the TypeError to appear.
  • The algorithm for getting the first entry with the greatest number of repetitions was wrong since the first if block allowed subsequent entry to be output as a correct result (not the first, but the last entry with the same repetitions was output actually)
  • -1 can be returned if mostRepeatLetters is empty.
Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
  • OMG Thank you!! You're beautiful!! I have one last question: I noticed you added in length in the for in loop. Does /(.)\1+/g return letters not a number? – Mar Dec 07 '15 at 10:30
  • Regexps work with strings only. If you use `String#match` with a global regex, it will return an array of strings. Or null. – Wiktor Stribiżew Dec 07 '15 at 10:30
  • But isnt word a string in an array? So why wouldn't it work? And why do you say global if the variable is declared inside the function? – Mar Dec 07 '15 at 10:33
  • I do not get you, sorry. "Global" regex means a regex with `/g` modifier, meaning it will be looking for all matching substrings in a larger string. – Wiktor Stribiżew Dec 07 '15 at 10:39
  • oooh ok. I thought you meant global scope. Ok got it. But I see that you used /g anyway? So how come it doesn't return null? – Mar Dec 07 '15 at 10:40
  • In `apple`, there is a match (=> `[pp]`, 1-element array), in `today`, there is no match (=> `null`). – Wiktor Stribiżew Dec 07 '15 at 10:44
1

Hope you dont mind if I rewrite this code. My code may not be that efficient. Here is a snippet

function findGreatest() {
   // ipField is input field
    var getString = document.getElementById('ipField').value.toLowerCase();
    var finalArray = [];
    var strArray = [];
    var tempArray = [];
    strArray = (getString.split(" "));
    // Take only those words which has repeated letter
    for (var i = 0, j = strArray.length; i < j; i++) {
        if ((/([a-zA-Z]).*?\1/).test(strArray[i])) {
            tempArray.push(strArray[i]);
        }
    }
    if (tempArray.length == 0) {       // If no word with repeated Character
        console.log('No such Word');
        return -1;
    } else {                 // If array has words with repeated character
        for (var x = 0, y = tempArray.length; x < y; x++) {
            var m = findRepWord(tempArray[x]);  // Find number of repeated character in it
            finalArray.push({
                name: tempArray[x], 
                repeat: m
            })
        }
      // Sort this array to get word with largest repeated chars
        finalArray.sort(function(z, a) {
            return a.repeat - z.repeat
        })
        document.getElementById('repWord').textContent=finalArray[0].name;
    }
}

// Function to find the word which as highest repeated character(s)
    function findRepWord(str) {
        try {
            return str.match(/(.)\1+/g).length;
        } catch (e) {
            return 0;
        } // if TypeError
    }

Here is DEMO

brk
  • 48,835
  • 10
  • 56
  • 78
0

function LetterCountI(str) { 

var word_arr = str.split(" ");
  var x = word_arr.slice();
  
  for(var i = 0; i < x.length; i ++){
    var sum = 0;
    for(var y = 0; y < x[i].length; y++){
      var amount = x[i].split("").filter(function(a){return a == x[i][y]}).length;
      if (amount > 1){
        sum += amount
      }
      
    }
    x[i] = sum;
  }
   var max = Math.max.apply(Math,x);
  if(max == 0)
    return -1;
  var index = x.indexOf(max);
  return(word_arr[index]);
};

Here is another version as well.

DanielSD
  • 73
  • 8
0

You could use new Set in the following manner:

const letterCount = s => {
    const res = s.split(' ')
        .map(s => [s, (s.length - new Set([...s]).size)])
        .reduce((p, c) => (!p.length) ? c
            : (c[1] > p[1]) ? c : p, []);

    return !res[1] ? -1 : res.slice(0,1).toString()
}

Note: I have not tested this solution (other than the phrases presented here), but the idea is to subtract unique characters from the total characters in each word of the phrase.

John Swindin
  • 79
  • 1
  • 1