15
\$\begingroup\$

I was working on a problem from Coderbyte. The challenge was to write a function that takes in a string and returns the longest word in the string. If two words are the same size then it asks to return the first one. The input will never be empty.

function longestword(str){
    var replaced = str.replace(/[^A-Za-z\s]/g,"");
    var final = replaced.split(" ").sort(function(a,b){return b.length - a.length})
    return final[0];
    }
longestword("This is a string theres two words that are the same size oh no!")
=>'string'

What do you think about my code?

\$\endgroup\$

5 Answers 5

16
\$\begingroup\$

longestword should be named using an interCaps naming convention. Also, replaced and final aren't exactly helpful variable names. However, if you were to have an intermediate result called words, it would be quite clear what that stands for.

It's good practice to terminate all statements with semicolons, even though JavaScript allows them to be omitted.

function longestWord(str){
    var words = str.replace(/[^A-Za-z\s]/g, "").split(" ");
    var wordsByDescendingLength = words.sort(function(a, b) {
        return b.length - a.length;
    });
    return wordsByDescendingLength[0];
}

Note that hyphenated words will have their hyphens dropped, both when comparing lengths and when returning the result.

\$\endgroup\$
4
  • \$\begingroup\$ not sure why, this format makes it look much better. \$\endgroup\$
    – zinking
    Commented Oct 8, 2014 at 9:05
  • 1
    \$\begingroup\$ Could be even more succinct with return words.sort(function(a, b) { return b.length - a.length; })[0];. :) \$\endgroup\$
    – Ben
    Commented Oct 8, 2014 at 12:28
  • \$\begingroup\$ Thanks! From this point on I will work on my style and variable name choice. \$\endgroup\$ Commented Oct 8, 2014 at 16:07
  • 1
    \$\begingroup\$ I am pretty certain that it's actually InterCaps, not interCaps and wikipedia seems to agree with me: en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms (what you seem to be talking about is either camel case (the most popular one as far as I know), camelBack, compoundNames, mixedCase (python), nerdCaps or simpyCaps) \$\endgroup\$ Commented Oct 9, 2014 at 13:40
12
\$\begingroup\$

Short and sweet and elegant. Unfortunately, there can be some issues with it.

Correctness

If two word are the same size then it asks to return the first one.

The implementation will only guarantee this if the sort implementation is stable. That is, during sort, the relative ordering of equal elements must not change, otherwise the original first longest string might get moved.

It seems modern browsers all use stable sort, but this was not always the case, and the relevant documentation can be hard to find.

Performance

The time complexity of sorting is usually \$O(n \log(n))\$ at best. Using sort makes your implementation elegant but inefficient because you don't actually need to sort, you can do better. You can walk through the elements in pass and record the longest string you find. That way the solution will be \$O(n)\$.

Non-English characters

The current implementation discards non-English characters like éèà, so the result can be incorrect for French, German, Hungarian texts, for example.

Formatting, coding style

These are just nitpicks, but the code would be somewhat easier to read if you follow these recommendations (borrowing from standards in other languages like Java, Python):

  • Put a space before opening braces
  • Put a space after commas
  • Align the closing brace with the start of its statement
  • Either use semicolon to end all statements always or never, do it consistently. (Related article)

Like this:

function longestword(str) {
    var replaced = str.replace(/[^A-Za-z\s]/g, "");
    var final = replaced.split(" ").sort(function(a, b) { return b.length - a.length; });
    return final[0];
}

Note: in addition, use better names as @200_success explained in his answer.

\$\endgroup\$
10
\$\begingroup\$

Just wanted to point out that @200_success's review is excellent, but personally I would go for reduce:

    function longestWord2(str){
        var words = str.replace(/[^A-Za-z\s]/g, "").split(" ");
        var longestWord = words.reduce( function( longestSoFar, currentWord ) {
          return currentWord.length > longestSoFar.length ? currentWord : longestSoFar;
        },"");
        return longestWord;
    }
    
    document.write( longestWord2( 'Code Review welcomes you' ) );

Using reduce will access each word only once. It will also return the first word of the largest length if there are multiple words with the same largest length;

\$\endgroup\$
2
  • \$\begingroup\$ Thanks. I will look into reduce. Sounds promising for other tasks I have. \$\endgroup\$ Commented Oct 8, 2014 at 16:05
  • \$\begingroup\$ I just assuming but what about length calculation for each word? \$\endgroup\$
    – CodeYogi
    Commented Dec 19, 2016 at 14:57
9
\$\begingroup\$

In many cases, the simple solutions are... simpler.

In this case, your solution of splitting the data on space , and locating the longest word, is accurate, but a simpler solution was probably intended. As an up-side, the simpler solution is actually better in terms of time-complexity (it is \$O(n)\$, which means the performance takes twice as long if the string is twice as long. Other solutions take more than twice as long if the input data is twice the size).

Bug: You have a bug in your string clean-up too, because you strip all punctuation, and replace it with the empty string "", you are vulnerable to input like This.is.a.long.string, and you will identify that as one word: Thisisalongstring. If you set the replacement as a space " ", you will be OK.

Note, this solution scans the data only once. Other solutions require that the data is scanned multiple times (once to split, once to sort).

function longestWord(str){
    str = str.replace(/[^A-Za-z\s]/g, " ");
    var maxoffset = 0, maxlen=0, wordoffset = 0;
    for (var i = 0; i < str.length; i++) {
        if (str[i] == " ") {
            wordoffset = i + 1;
        }
        if (i - wordoffset + 1> maxlen) {
            maxoffset = wordoffset;
            maxlen = i - wordoffset + 1;
        }
    }
    return str.substring(maxoffset, maxoffset + maxlen);
}

function textUpdated(source) {
    document.getElementById("outword").value = longestWord(source.value);
}
Enter text: <input type="Text" name="Input" id="data" onInput="textUpdated(this);"/>
<hr>
Longest Word: <input type="Text" id="outword" name="OutWord" value="none" />

\$\endgroup\$
2
  • 1
    \$\begingroup\$ +1 for finding the bug. I agree that this is faster, not sure I agree on your approach being simpler \$\endgroup\$
    – konijn
    Commented Oct 8, 2014 at 17:43
  • \$\begingroup\$ naming i - wordoffset + 1 as currentLength would make the code more readable. \$\endgroup\$
    – CodeYogi
    Commented Dec 19, 2016 at 16:01
6
\$\begingroup\$

As was pointed out by janos, non-Latin characters will be removed by your replace-call. Even English words with hyphens or apostrophes will be mangled. This may be completely intentional; depends on what you consider "a word" and what kind of input you expect.

Still, might be simpler to only split the string. For instance:

function longestWord(string) {
  return string.split(/[\s\d.,!?"_\/`]+/).reduce(function (longestSoFar, currentWord) {
    return currentWord.length > longestSoFar.length ? currentWord : longestSoFar;
  });
}

document.write(longestWord("The apostrophe's a tricky thing."));
document.write("<br>");
document.write(longestWord("But so is a naïve regex!"));

This is of course using konijn's reduce solution.

The above completely avoids replace letting split remove things we don't want. The regex will split on whitespace, digits, and different kinds of punctuation, which keeps words like "coöperation" and hyphenations intact.

But it's certainly not without fault; regexes are tricky. There are certainly things that the code above doesn't handle well. Again, it all comes down to what you consider to be a "word" in your context, and what sort of input you expect.

As a sidenote: Your current replace-regex could be written as /[a-z\s]/i - the i modifier flag mean "case-insensitive. No major difference, just an FYI.

\$\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.