3

My browser is crashing from this loop that doesn't appear to be unterminated.

function checkLetters(word){
    for(i=0;i<5;i++){
        for(j=i+1;j<5;j++){
            if(word.charAt(i) == word.charAt(j)){
                return false;
                break;
            } 
        }
    }
    return true;
}
var compLibrary = [];
for (var k=0;k<library.length;k++) {
    if(checkLetters(library[k]) == true) {
        compLibrary.push(library[k]);
    }
}

I am trying to search the library for words with no repeating letters and pushing it into a new array The whole library is five letter words.

5
  • 1
    I doubt that code freezes the browser, the checkLetters has constant (C ~ 25) time, so the entire snippet is O(n), where n is the size of library.length .. unless library.length is something ridiculously large, it should be complete in a very small amount of time. Commented Aug 9, 2013 at 0:53
  • I guess, following with the above: if only checking the first, say, 100 words (;k<Math.min(library.length,100);), does it still "crash" the browser? Commented Aug 9, 2013 at 1:00
  • Yes it's only 8000 words but this loop is freezing my browser and firebug is telling me it's "if(checkLetters(library[k]) == true)". I have many other statements where I am looping through the library and they all work its just it won't put words with no repeating letters into "var compLibrary" and my browser stops the script. Commented Aug 9, 2013 at 1:01
  • Can you post a minimal jsfiddle.net (with the word list or appropriate AJAX to fetch it) that shows the behavior? Commented Aug 9, 2013 at 1:04
  • I've added an "answer" showing that the posted code is likely not the problem. Please provide a supporting minimal example showing my counter-example wrong. Here is the fiddle (again): jsfiddle.net/FqdX7 Commented Aug 9, 2013 at 1:18

6 Answers 6

3

It's not an infinite loop, but it does look like a pretty expensive operation. There's not any really elegant way to detect an infinite loop (or recursion) so most engines just resort to either

  1. Not trying to detect it, and running forever until some lower-level controller (like, the kernel) kills it.
  2. Automatically killing itself when it gets to a certain recursion depth, loop count, or run time.

Your algorithm loops 5 * 4 * library.length times, so depending on how long your library actually is, your code could certainly trigger #2. But there are faster ways to find duplicate letters:

function checkLetters(word) {
    var i=word.length;
    var seenChars={};
    var c;
    while (i-->0) {
      c = word.CharAt(i); # The current character
      if (c in seenChars) return false;
      seenChars[c] = 1;
    }
    return true;
}
var compLibrary = [];
for (var k=0; k < library.length; k++) {
    if (checkLetters(library[k]) == true) {
        compLibrary.push(library[k]);
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

I have faith in modern CPUs. Even though there are nested loops, it's still in simple linear bounds due to the (small) fixed word size. I would suspect the browser/impl. in question would make a difference if a lookup like this was faster than the nested loops (because of the fixed bounds of the checkLetters loops). Although, since I don't have benchmarks on both code snippets in question I have no supporting evidence if this will or will not perform (suitably) better.
Also, nit: I would recommend writing [stackoverflow] example code like i-- > 0 so as to not confuse people about the --> "operator" xD
I reject that this answer benefits the question, as posted. I've created some counter fiddles to support my position.
This works but I don't exactly know how, do you mind giving me an explanation?
@ThomasNocera Your original code works just fine as well. I've provided fiddle cases showing that something else is the problem. What it is, I don't know. But the onus is now on you to providing a minimal example of it "crashing" the browser. Anyway, this answer uses a object to "remember" which letters have already been seen - c in seenChars (it's almost the same as seenChars[c] !== undefined but checks for existence, not value) check this (returning false immediately if a seen letter is encountered), and seenChars[c] = 1 adds a seen character to the lookup.
0

Shouldn't this loop

for(i=0;i<5;i++){
        for(j=i+1;j<5;j++){

be something in these lines

for(var i=0; i< word.length ;i++){
        for(j=i+1; j<word.length/2; j++){

5 Comments

all of the words are 5 letters so "word.length" doesn't matter, and the object of this loop is to compare each letter with the rest of the letters to make sure that they aren't the same so you end up with a word with all distinct letters like "proxy"
Also you would not need a break statement
You don't need to but it saves the computer a few miliseconds lol
But the function will always return false before hitting that statement.
@ThomasNocera your break statement saves the computer no time at all - if anything it costs the computer some (very small) time for the parser to read it just so the dead code eliminator can remove it.
0

Can't see what your issue is but here's the solution I suggest for your problem:

var words = ['hello', 'bye', 'foo', 'baz'];

function hasUniqLetters(word){
  var uniq = word.split('').filter(function(letter, idx){
    return word.indexOf(letter) == idx;
  });
  return uniq.length == word.length;
}

var result = words.filter(hasUniqLetters);

console.log(result); //=> ["bye","baz"]

Comments

0
function checkLetters(word){
    for(i=0;i<5;i++){      //both i and j were not instantiated
        for(j=i+1;j<5;j++){
            if(word.charAt(i) == word.charAt(j)){ //It could be crashing if
                return false;                     //word <= 5
                break;//a break after a return
            }         //statement is redundant.
        }
    }
    return true;
}

You must put var before declaring a variable.

word.charAt(i) can be written like word[i]

1 Comment

Neither of these things answers the question why the browser would crash.
0

Try this:

function checkLetters(word){
  for(var i=0,j=1,l=word.length-1; i<l; i++,j++){
    if(word.charAt(i) == word.charAt(j)){
      return false;
    } 
  }
  return true;
}
var compLibrary = [];
for(var i=0,l=library; i<l; i++){
  if(checkLetters(library[i]) == true){
    compLibrary.push(library[i]);
  }
}

1 Comment

Getting this syntax error "SyntaxError: missing ) after for-loop control"
0

tldr; The code originally posted should not crash the browser.

The following explains why nested loops are not always bad for efficiency and shows a counter-example where the original code works successfully without crashing the browser when running over 100,000 simulated words.

The complexity of the posted code is low and it should run really fast. It executes here in a fraction of a second (under 20ms!), even at all "20 * 8000" - i.e. C * O(n). Note that the time complexity is linear because the nested loops in checkLetters have a constant time: due to this small fixed limit ("20 loops" max each call) it does not represent a performance problem here.

As such, I maintain that it is not an issue wrt it being an efficiency problem. I assert that the original code will not "crash" a modern browser environment. For longer or unbound words then using a (presumably) lower complexity probe attempt may pay off - but the inner loop runs in small constant time here. (Actually, due to distribution of letters within words and word lengths I would imagine that the constant rarely exceeds "90 loops" for a natural language like English.)

See http://jsfiddle.net/FqdX7/6/

library = []
for (w = 0; w < 100000; w++) {
    library.push((w + "12345").slice(0,5))
}
function checkLetters(word){
    for(i=0;i<5;i++){
        for(j=i+1;j<5;j++){
            if(word.charAt(i) == word.charAt(j)){
                return false;
            } 
        }
    }
    return true;
}
$('#time').text("running")
start = +(new Date)
var compLibrary = [];
for (var k=0;k<library.length;k++) {
    if(checkLetters(library[k]) == true) {
        compLibrary.push(library[k]);
    }
}
time = +(new Date) - start
$('#time').text(time + "ms")

On my machine (in Safari) the code runs in ~30 milliseconds (~40ms if the "return false" is removed) for an input of 100,000 words!

In comparison, the answer with a probe (seenChars lookup) actually runs worse in Safari/Chrome. See http://jsfiddle.net/Hw2wr/5/, where for 100k words it takes about 270ms - or about 9x slower. However, this is highly browser dependent and the jsperf in the comments shows that in Firefox the probing approach is faster (by about 2x) but is slower again in IE (say 4-5x).

YMMV. I think the original code is acceptable for the given situation and the "crashing" problem lies elsewhere.

2 Comments

I made my answer a CW so feel free to edit it. Also, here's some ammo. jsperf.com/is-jsperf-just-broken/3
@kojiro Thanks for the jsperf. It made me completely have to revert my assumptions about browser performance (and last paragraph) and work on calling out different browsers specifically. It is amazing the Firefox fairs so much better (or webkit-based so much worse) for this specific use of probing.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.