Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Here is a practice question I am solving:

Write a function to find the first non-repeated character in a string. For instance, the first non-repeated character in 'total' is 'o' and the first non-repeated character in 'teeter' is 'r'.

How can I improve the efficiency of this algorithm?

function repeater(string){
    var charCount = {};
    for(var i = 0; i < string.length; i++){
        if(charCount[string[i]]){
            charCount[string[i]] = 'More Than One';
        } else {
            charCount[string[i]] = 'One Time';
        }
    }    
    for(var j = 0; j < string.length; j++){
        if(charCount[string[j]] === 'One Time'){
          return string.charAt(j);      
        }
    }

    return 'Everything is repeated';
}

http://repl.it/QUf/1

I also solved this using a nested loop:

var nonRepeater = function(str) {
  var index = [];
  var count;
  str.split('').forEach(function(letter, i) {
    count = 0;
    str.split('').forEach(function(latter) {
      if (letter === latter) {
        count += 1;
      }
    });
    index.push(count);
  });
//   console.log(index.indexOf(1));
  return str[index.indexOf(1)];
};

http://repl.it/QVI/2

I am in the process of learning about Big-O Notation and from my understanding, worst case scenario these both have the same run time O(n2).

I am trying to find a way to increase the efficiency of this algorithm. I am toying with ways to use a RegEx.

Does anyone know how to write this more efficiently in JavaScript? I have found a few guides in C but I do not know the language at all. I haven't found much help on this in JavaScript.

share|improve this question
    
first solution is O(n) so as good as it gets, but instead of 'More than one' and 'One Time' I would change the values to a boolean or int, checking if charCount[string[j]] === 'One Time' on each index is probable slower than multiple[string[j]] === true –  Rodolfo 3 hours ago
    
Interesting: in a higher-level language (R example here), this would be (rle is "run-length encoder" ) foo<- rle(sort(my.data)); bar<- foo$values[foo$lengths==1][1]; which(my.data==bar) –  Carl Witthoft 2 hours ago
add comment

4 Answers

The runtime of the 1st solution you posted is not O(n2). You are just traversing the string twice, which makes it O(2n) => O(n).

share|improve this answer
1  
This doesn't look like a solution - it's more a comment –  Antonio 6 hours ago
3  
@Antonio I'd consider this an allowable answer under the "your code seems fine" rule. –  200_success 5 hours ago
1  
@200_success I thought the reviewer then ought to state why the code is fine ? meta.codereview.stackexchange.com/questions/850/… –  konijn 5 hours ago
    
@konijn That would have made a better answer, but I had to comment in defense to save it from deletion. –  200_success 5 hours ago
    
@200_success Fair, though personally I would have voted to convert this to a comment. –  konijn 5 hours ago
show 1 more comment

After playing a lot with jsperf and having to admit that the regex solution is actually faster, which annoys me.

  • Your first approach is far superior than your second approach (O(2n) -> O(n^2)) as per Venu

  • you should cache string.length, looking up the value of a property slows things down

    for(var i = 0, length = string.length; i < length; i++){
    
  • You can assign More than one and One time with a ternary, also you should cache string[i] and not look it up 3 times:

    charCount[c] = charCount[c] ? 'More Than One' : 'One Time';
    
  • You do not need var j, just use i again

  • You used string[i] everywhere else, your return statement should be return string[i];

  • repeater is a terrible name if you are planning to return a non-repeater ;)

  • from a design perspective, I would return '' instead of 'Everything is repeated', because really, '' aka nothing is repeating

On the whole I think your code is fine, I am not sure ( besides the magical regex ) how it could be done much faster. You need something to track the character count and I am not sure how you can avoid the second loop.

share|improve this answer
add comment

I'm proposing a solution that in some cases can be better than your first algorithm, but not always - it mostly depends on the input string. I think that on average they have similar performance.

The idea is to use a regex to search and replace with an empty string all occurrences of the first character. If the resulting string has length equal to the length at the previous iteration minus 1, then that character is not repeated. The search is case insensitive

The advantage of this code is that it's more compact and readable, plus it immediately breaks as soon as a non repeating character is found.

var repeater = function(string) {
    var result = false;

    while (string) {
        var len = string.length;
        var char = string[0];
        var regex = new RegExp(char, "gi");
        string = string.replace(regex, "");
        if (string.length == len - 1) {
            result = char;
            break;
        }
    }
    return result;
}

Update: I ran a few benchmarks, and on average it takes less than your first algorithm (using code runner and node.js). This is the benchmarking code:

var start = Date.now();

for (var count = 0; count < 10000; ++count) {
    repeater('toTal');
    repeater('teEter');
    repeater('erttreert');
    repeater('repeaterqetyhgerdfcvvgfnk');
}

var end = Date.now();

console.log("\nTime: " + (end - start) + "ms");

and the results are around 60ms for my algorithm, 110-120ms for yours.

share|improve this answer
    
jsperf.com/first-non-repeated I dont think so, it would not make sense, the regex is O(n), so unless the first non-repeater is the first or the second character, your code should be slower. –  konijn 4 hours ago
    
Hmm... tested that, but it tells me repeater2 is better. repeater1 is reported as 56% slower. I'm on mac, using chrome. Are you using a different browser? –  Antonio 4 hours ago
    
Also tried with Safari, FF and IE, same outcome (but ofc with different numbers) –  Antonio 4 hours ago
    
Chrome, windows ( Chrome 35.0.1897 (1)), which is funny, the only combo where the OP code is faster, most interesting. –  konijn 4 hours ago
    
To add more fuel to the fire, I tried Chrome on windows and the result is the opposite than yours. I swear I did not hack jsperf.com ;-) –  Antonio 2 hours ago
show 1 more comment

This solution has an upper bound of O(n^2) and an lower bound of O(n). It has a good average performance for short strings and strings with many repeated characters.

function repeater(string)
{
  if(string.length==0)
    return false;

  var char = string.charAt(0);
  if(string.lastIndexOf(char) == 0)
    return char;

  for(var i = 1; i < string.length-1; ++i)
  {
    char = string.charAt(i);
    if(string.lastIndexOf(char)==i && string.indexOf(char)==i)
      return char;
  }

  char = string.charAt(string.length-1);
  if(string.indexOf(char)==string.length-1)
    return char;

  return false;
}
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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