-4

I wrote the algorithm below and asked an AI to evaluate my solution.

//TESTS:

console.log(firstNonRepeatingCharacter('abacabaz')) //c
console.log(firstNonRepeatingCharacter('aabbccdeefgghijj')) //d
console.log(firstNonRepeatingCharacter('abcdefgggghhhhhiiiii')) //a  
console.log(firstNonRepeatingCharacter('kkllmnnopqestuvxyzzz')) //m


function firstNonRepeatingCharacter(str){
  const charMap = {}
  let result = ''

  for(let i=0; i < str.length; i++) {
    if(!charMap[str[i]]) {
      charMap[str[i]] = 1
    } else {
      charMap[str[i]]++ 
    }
  }

  for(let char in charMap) { // <-- Problem detected by IA
    if(charMap[char] == 1) {
      result = char;
      return result;
    }
  }

  //console.log(charMap)
}

It gave me the following feedback about using for...in over an object:

"Your solution works, but there's a small conceptual issue: the order of keys when using for...in over the charMap object is not guaranteed across all JavaScript environments. As a result, it might not return the first non-repeating character in the original string order — only the first according to the key order of the object, which doesn't always match the string's order."

I've always done it this way, and it has always worked. All the test cases I tried passed.

How accurate is this information given by the AI? Could someone clarify this for me? Was the AI possibly mistaken?

Note: I'm not looking for an improved solution or suggestions. I've already learned how to solve this more efficiently. I’d just like clarification on the specific concern the AI raised.

0

3 Answers 3

4

Yes

The inconsistent iteration is basically not an issue. All relevant environments will use the order of keys that was codified by the specifications a decade ago: Does ES6 introduce a well-defined order of enumeration for object properties?

There might be a few environments where the iteration order will not be guaranteed but very few and very rare. Most notably writing embedded JavaScript code for a PDF document will use very old specifications. But, again, that is rare.

However, there is still an issue and it is exactly with the well-defined enumeration of properties. That goes in insertion order except for non-negative integers which will be ahead of everything else and sorted in ascending order.

const obj = {};

obj["foo"] = "hello";
obj["bar"] = "wolrd";

console.log(obj); // keys: first "foo" then "bar"

obj[2] = "this will be second";
obj[1] = "this will be first";

console.log(obj); // keys: first "1" then "2", then "foo", then "bar"

Therefore, since the algorithm relies on the property order in an object, if a string contains any numbers then the smallest of them will be reported as the "first" non-repeating character regardless of where it is found in the string:

console.log(firstNonRepeatingCharacter('abbccc9'))  //9
console.log(firstNonRepeatingCharacter('abbccc91')) //1


function firstNonRepeatingCharacter(str){
  const charMap = {}
  let result = ''

  for(let i=0; i < str.length; i++) {
    if(!charMap[str[i]]) {
      charMap[str[i]] = 1
    } else {
      charMap[str[i]]++ 
    }
  }

  for(let char in charMap) { // <-- Problem detected by IA
    if(charMap[char] == 1) {
      result = char;
      return result;
    }
  }

  //console.log(charMap)
}

Sign up to request clarification or add additional context in comments.

Comments

4

In short, the AI is incorrect. Let's pretend that ChatGPT doesn't exist and do some old-fashioned research, starting with the page for...in on MDN. It reads:

The traversal order, as of modern ECMAScript specification, is well-defined and consistent across implementations. Within each component of the prototype chain, all non-negative integer keys (those that can be array indices) will be traversed first in ascending order by value, then other string keys in ascending chronological order of property creation.

So, the concern that the order of the keys is inconsistent between JS environments seems to be invalid, at least in the current day. Other reliable resources such as this page seem to suggest that keys have been well-ordered as part of the specification since ES2015; and caniuse reports that 96.57% of browsers implement that version of the spec.

I'm not looking for an improved solution or suggestions. I've already learned how to solve this more efficiently.

The code looks fine. You could make some optimizations based on the values passed to the function, however for arbitrary input this is the obvious approach. Here's my attempt at an unecessary optimization:

function firstNonRepeatingCharacter(str) {
    if (str.length === 0) return null;
    let chars = [];
    
    function push(code) {
        const curLength = chars.length;
        let data;
        let idx = 0;

        for (let i=0; i < curLength; i++) {
            data = chars[i];
            if (data.code === code) {
                data.count++;
                return;
            }
            if (data.count === 1) {
                idx = i + 1;
            }
        }

        if (idx < curLength) {
            for (let i = idx + 1; i <= curLength; i++) {
                chars[i] = chars[i - 1];
            }
        }
        chars[idx] = { code, count: 1 };
    }

    for (let i=0; i < str.length; i++) {
        push(str.charCodeAt(i));
    }

    const c0 = chars[0];
    return (c0.count === 1) ?
        String.fromCharCode(c0.code) :
        null;
}

To my horror, it does seem to be about 15% faster in my testing, though I deem the code quality to be lesser.

1 Comment

Despite the traversal order being well-defined, that order is not suitable for OP's task as you can see in VLAZ'es answer.
0

So, considering @VLAZ's answer, if you suspect numbers within the string to inspect, the AI is right.

Here is a possible solution for that:

console.log(`firstNonRepeatingCharacter('9abbccc22'): ${
  firstNonRepeatingCharacter('9abbccc2')}`);
console.log(`firstNonRepeatingCharacter('abbccc91'): ${
  firstNonRepeatingCharacter('abbccc91')}`);
console.log(`firstNonRepeatingCharacter('aabb42ccc'): ${
  firstNonRepeatingCharacter('aabb42ccc')}`);
console.log(`firstNonRepeatingCharacter('aabbccc99'): ${
  firstNonRepeatingCharacter('aabbccc99')}`);

function firstNonRepeatingCharacter(str) {
  const charMap = {};
  
  for (const chr of str) {
    charMap[chr] = (charMap[chr] || 0) + 1;
  }
  
  for (const chr of str) {
    if (charMap[chr] === 1) { return chr; }
  }
  
  return `no single characters in "${str}"`;
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.