[Confused about 2 points] Caesars Cipher

Hi everyone,

I was able to solve the project Caesars Cipher but there are two things I still don’t quite understand and was wondering if anyone could help explain what is causing the issues, thanks in advance.

Link to challenge: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/javascript-algorithms-and-data-structures-projects/caesars-cipher

I’ve blurred out the code below to avoid spoiling it for anyone.

My solution:

function rot13(str) {
  
  const alphabet = [ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", 
                     "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", 
                     "W", "X", "Y", "Z"  ];
  console.log(alphabet);

  let arrStr = str.split("");
  
  // Array(11) [ "S", "E", "R", "R", " ", "C", "V", "M", "M", "N", … ]
  console.log(arrStr); 

  // This regex will search for all letters in the alphabet, uppercase and 
  // lowercase, and also numbers. The "g" flag indicates that the regex should 
  // be tested against all possible matches in a string.
  
  // Don't understand why I can't have /[A-Za-z0-9]/g; --> which is what I initially tried
  let regex = /[A-Za-z0-9]/; 

  let result = [];
  
  console.log(arrStr.length);

  for (let i = 0; i < arrStr.length; i++) {
    let placeInArray;
    if (regex.test(arrStr[i])) {
      placeInArray = alphabet.indexOf(arrStr[i]); // "S" should be at 18
      if (placeInArray + 13 <= 25) {
        result.push(alphabet[placeInArray + 13]); 
      } else if (placeInArray + 13 > 25) {
        result.push(alphabet[placeInArray + 13 - 26]);   
      } 
    } else {
      result.push(arrStr[i]);
    }
  }

  result = result.join("");
  console.log(result);

  return result;
}

// Testing this instead of the default in the challenge since it has an exclamation mark.
rot13("SERR CVMMN!");

The thing I don’t understand is why adding a “g” to the regex results in “FEER PVZMA!”.

From reading MDN, the “g” flag in regex means the regex should be tested against all possible matches in a string. Since in the code I have:

if (regex.test(arrStr[i]))

I’m testing against each letter in the array “arrStr”. I understand at each index there’s only a single letter, so that means we don’t really need the “g” flag to find multiple matches. Having said that, I still don’t understand why we can’t have a “g” anyway, even if it’s not necessary.

My understanding is when we go through “arrStr[i]”, at each index there’s only a single letter. If we have “g” in the regex, we’re looking to see if there are multiple matches when looking at each individual letter. It can’t have more than one match given it’s a single letter, but I’m surprised that adding the “g” breaks the code.

I’d be most grateful if someone could shed some light on why adding the “g” doesn’t work, thanks in advance.

The second thing I’m confused about is while I was trying to come up with the solution, I tried the following (just for testing purposes) and was surprised by the result:

function rot13(str) {
 
  const alphabet = [ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K",
                 	"L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
                 	"W", "X", "Y", "Z"  ];
  console.log(alphabet);

  let arrStr = str.split("");
  console.log(arrStr); // Array(11) [ "S", "E", "R", "R", " ", "C", "V", "M", "M", "N", … ]

  // Don't understand why I can't have /[A-Za-z0-9]/g;
  let regex = /[A-Za-z0-9]/g;

  // This doesn't work! Very strange...
  for (let i = 0; i < arrStr.length; i++) {
	if (regex.test(arrStr[i])) {
  	console.log(arrStr[i]);
	}
  }

}

rot13("SERR CVMMN!") // console: S
                 	// console: R
                 	// console: C
                 	// console: M
                 	// console: N

I was expecting the console to log exactly what is in the array “arrStr”, each letter at a time, ie “S”, “E”, “R”, “R”, “C”, “V” etc.

But it turns out it would only log the first letter and every other letter, skipping letters in between.

I’d be most grateful if someone could help explain what is happening here, thanks in advance.

This is actually a fun feature with the global flag and regex.test().

The global flag makes your regex stateful - JavaScript will keep track of the last index of a successful match in your test() calls. When a test() call returns true, JavaScript updates the lastIndex property of your regex to the index of the end of the match.

In this case, you’re testing a single letter, so when it matches, the match ends at index 1. lastIndex then gets set to 1. On the next test() call, JavaScript starts looking at the lastIndex of the string - because it is 1, and you are testing a single character, the string at index 1 is undefined and the test() returns false. And when a test returns false, lastIndex is reset to 0.

If you want to see how the loop runs, I’ve added a log statement that might help:

function rot13(str) {
 
  const alphabet = [ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K",
                 	"L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
                 	"W", "X", "Y", "Z"  ];

  let arrStr = str.split("");
  console.log(arrStr); // Array(11) [ "S", "E", "R", "R", " ", "C", "V", "M", "M", "N", … ]

  // Don't understand why I can't have /[A-Za-z0-9]/g;
  let regex = /[A-Za-z0-9]/g;

  // This doesn't work! Very strange...
  for (let i = 0; i < arrStr.length; i++) {
  	console.log(`Last index is currently: ${regex.lastIndex}. The array index is ${i}, with a value of ${arrStr[i]}. The test result is: ${regex.test(arrStr[i])} - last index is now: ${regex.lastIndex}`);
  }
}

rot13("SERR CVMMN!")
2 Likes

@nhcarrigan – Wow, that is an incredible explanation, thank you so much for taking the time to explain it in so much detail. I learned so much from the answer you provided.

I hadn’t come across “stateful” as a concept or the property “lastIndex” before. This is brilliant.

So basically (I’m trying to get my head around this), for any regex with a global flag, it always tries to find a match, and once it does, it’ll start searching for the next match from the “lastIndex” (ie the very next place after the match) – I was wondering if you could confirm whether my understanding is correct, thanks in advance.

Also regarding the console log code that you provided, I’m still a little confused by this. I tried logging it and the first log is:

Last index is currently: 1. The array index is 0, with a value of S. The test result is: false - last index is now: 0

I would have thought at the very start, the “lastIndex” would be “0” not 1. I also would have thought since “S” should be a match based on the regex, it should show test result is “true” instead of “false”, and only after that, it will show the “lastIndex” is now “1” (since it matched at 0), instead of “0” as is displayed.

I also would have thought now that “lastIndex” is 1, when it looks at the next letter “E” (which, while it is at index 1 of “arrStr”, it is actually at index 0 from a regex standpoint as a new string, and since “lastIndex” is 1, it doesn’t find a match (since there’s nothing there). As such, I would imagine “E” wouldn’t be logged (as in my original code; it skipped the letter “E”). But then I would have thought the same would be true when checking the next letter, “R”, and that “lastIndex” would still be 1 and “R” would be at index 0 from a regex standpoint, and therefore, I would have thought after “S”, none of the letters would match (and be logged).

Sorry if I’m making this really convoluted. I’m just not sure why in my original code it skips every other letter, and from the code you kindly provided, I don’t quite understand what is happening. I think what’s also a bit confusing for me is that during the first loop, the console log says “last index is now: 0” and then in the next loop, it starts with “Last index is currently: 1”.

I’d be most grateful for any additional help when you happen to have a free moment, thanks again.

Ya, this is confusing stuff. @nhcarrigan did an amazing job of explaining it and I’m still not sure it makes complete sense to me :slight_smile:

I just wanted to point out that you really don’t need the regex at all. You have the alphabet array with all the possible letters you care about. You can just check whether each letter (arrStr[i]) is in the alphabet array. You can do that with Array.includes or Array.indexOf.

2 Likes

This sounds correct, yes.

Make sure you have copied the code exactly - if, for example, you added the log inside your if (regex.test(arrStr[i]) block, you’d get different results.

1 Like

It’s a fun “feature” that has bitten us a few times when writing curriculum content and tests.

2 Likes

You quoted “feature”.

I would have quoted “fun”.

:smile_cat: :smiley_cat:

1 Like

2 Likes

@nhcarrigan and @bbsmooth thanks so much.

@bbsmooth – ahhh of course, right, no need to complicate things by bringing in regex (at least complicated for me). I think your approach makes much more sense, thanks for the comment.

@nhcarrigan – Oops… I copied the code incorrectly the first time around :flushed:, I see different results now :rofl:, thanks again, appreciate all the help.

This actually made me laugh out loud, that’s hilarious.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.