Caesars Cipher in JavaScript - Why does 'A' turn into '[' here? - javascript

I'm currently going through a FreeCodeCamp challenge that requests you to create a ROT13 cipher (a very simple cipher that shifts each letter to be the letter 13 letters ahead of it in a never-ending alphabet). My code is below:
function rot13(str) {
let lettersRegex = /[A-Z]/;
let alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
let charCodes = [];
for(let i = 0; i < str.length; i++) {
if(str.charAt(i).match(lettersRegex)) {
charCodes.push(str.charCodeAt(i));
} else {
charCodes.push(str.charAt(i));
}
}
let ret = '';
console.log(`charCodes is currently ${charCodes}`);
for(let i = 0; i < charCodes.length; i ++) {
let temp = 0;
if(lettersRegex.test(String.fromCharCode(charCodes[i]))) {
if((alphabet.indexOf(String.fromCharCode(charCodes[i])) + 13) > alphabet.length) {
temp = charCodes[i] + alphabet.indexOf(charCodes[i]) - 12;
}
else {
temp = charCodes[i] + 13;
}
ret += String.fromCharCode(temp);
} else {
ret += charCodes[i];
}
}
console.log(ret);
return ret;
}
rot13("GUR DHVPX OEBJA SBK WHZCF BIRE GUR YNML QBT.");
//THE QUICK BROWN FOX JUMPS OVER THE L[ZY DOG.
Basically, every letter but 'A' shifts to the correct answer after the cipher. What could be causing 'A' to turn into '[' instead of 'N' in this code?
Any comments or tips on my code would also be much appreciated.

It's a simple fix, changing > to >= Don't forget that arrays are zero-indexed.
if((alphabet.indexOf(String.fromCharCode(charCodes[i])) + 13) >= alphabet.length)

Related

Why is my output not including spaces when I have an IF statement specifically for it?

I am trying to make a Caesar's Cipher for a challenge, and there are specific rules I must follow:
The algorithm uses a numeric "shift" value. You should use a shift of 8 in your code.
You should IGNORE any characters that are not a letter (this includes symbols such as #*!$^) and they should not be in your output (however a space will remain a space in the encrypted string).
Your output should take into account both uppercase and lowercase letters. That is, both a lowercase 'a' and uppercase 'A' will have the same shift value.
Your final answer should be in all capital letters.
JS code:
function shift8(string){
string = string.toUpperCase();
const alphabetArray = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
let shiftedString = '';
for (i = 0; i < string.length; i++) {
var currentIndex = alphabetArray.indexOf(string[i]);
var newIndex = currentIndex + 8;
var currentCharacter = alphabetArray[currentIndex];
var shiftedCharacter = alphabetArray[newIndex];
if (currentCharacter == ' ') {
shiftedString += ' ';
} else if ('ABCDEFGHIJKLMNOPQRSTUVWXYZ '.includes(shiftedCharacter)) {
shiftedString += shiftedCharacter;
}
}
return shiftedString;
}
var output = shift8('The quick brown fox jumps over the lazy dog');
console.log(output);
Is there something I am missing? Is my logic incorrect?
alphabetArray.indexOf(string[i]) will return -1 for all values not part of alphabetArray, such as spaces. This means that currentCharacter will be undefined for any such values.
To fix this, do var currentCharacter = string[i]; instead.
Your variable currentCharacter is just a member of alphabetArray, which doesn't contain any ' ' (spaces), that's why it's never triggered.
Here, I refactored your code. There is one important thing - you need to loop those indexes, as their max value should be of alphabetArray.length.
function shift8(string){
string = string.toUpperCase();
const alphabetArray = 'ABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGHIJKLMNOPQRSTUVWXYZA'.split('');
let shiftedString = '';
for (i = 0; i < string.length; i++) {
let oldCharacter = string[i];
if( !alphabetArray.includes(oldCharacter) ){
if( oldCharacter == ' ' ) shiftedString += oldCharacter;
} else{
let currentIndex = alphabetArray.indexOf(oldCharacter);
let newIndex = (currentIndex + 8) % alphabetArray.length;
let shiftedCharacter = alphabetArray[newIndex];
shiftedString += shiftedCharacter;
}
}
return shiftedString;
}
var output = shift8('The quick brown fox jumps over the lazy dog');
console.log(output);
For an space the following will return -1
var currentIndex = alphabetArray.indexOf(string[i]);
If you should use a shift of 8 in your code alphabetArray doesn't need to have repeated values after the first eight characters in the alphabet because you will never use these values.
The code should be:
function shift8(string){
string = string.toUpperCase();
const alphabetArray = 'ABCDEFGHIJKLMNOPQRSTUVXYZABCDEFGH'.split('');
let shiftedString = '';
for (i = 0; i < string.length; i++) {
const element = string[i]
if (element == ' ') {
shiftedString += ' ';
} else {
const currentIndex = alphabetArray.indexOf(string[i]);
if(currentIndex === -1){ // To not include symbols such as #*!$^
continue
}
const newIndex = currentIndex + 8;
const currentCharacter = alphabetArray[currentIndex];
const shiftedCharacter = alphabetArray[newIndex];
shiftedString += shiftedCharacter
}
}
return shiftedString;
}
var output = shift8('The quick brown fox jumps over the lazy dog');
console.log(output); // CPM ZDQKS JAXHV NXF RDUYB XEMA CPM TIHG LXO

Comparison operator not working(Java Script)

I am trying to replace all the letters of a string by the next letter in the alphabet.
For example: a --> b or i --> j.
My program is ignoring the if statement that checks a letter against the alphabet array. When I try running the code it replaces all letters by "A", the last element in the alphabet array.
Although inefficent, I cannot find any errors with this algorithm. So why is the program ignoring the if statement?
function LetterChanges(str){
var 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","a"];
str = str.toLowerCase();
var ans = str.split("");
for(i = 0; i < ans.length; i ++)//Running through for each letter of the input string
{
for(a = 0; a < 26; a++)//Checking each letter against the alphabet array
{
if(alphabet[a] == ans[i])
{
ans[i] = alphabet[a+1];
}
}
}
return ans;
}
LetterChanges("Argument goes here");
The reason why it is not working, is because the ans array is modified, whilst you are still checking it.
In this loop:
for(a = 0; a < 26; a++)//Checking each letter against the alphabet array
{
if(alphabet[a] == ans[i])
{
ans[i] = alphabet[a+1];
}
}
If the if statement is found to be true, ans[i] will be updated, but then on the next loop of the iteration, it will likely be true again, as you are checking against the updated ans[i] variable.
As #xianshenglu suggested, you can fix this issue by adding a break to escape from the inner loop once a correct match is found.
for(a = 0; a < 26; a++) {
if(alphabet[a] == ans[i]) {
ans[i] = alphabet[a+1]
// escape from the inner loop once a match has been found
break
}
}
For an alternative way to do this, you could do the following:
var result = str.toLowerCase().split('').map(ch => {
var pos = alphabet.indexOf(ch)
return pos >= 0 ? alphabet[pos + 1] : ch
}).join('')
And if you want to get rid of the alphabet array, you can use char codes. For example:
var result = str.toLowerCase().split('').map(ch => {
var code = ch.charCodeAt(0)
if(code < 96 || code > 122){ return ch }
return String.fromCharCode((code - 96) % 26 + 97)
}).join('')
you lost break when if executed
function LetterChanges(str){
var 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","a"];
str = str.toLowerCase();
var ans = str.split("");
for(i = 0; i < ans.length; i ++)//Running through for each letter of the input string
{
for(a = 0; a < 26; a++)//Checking each letter against the alphabet array
{
if(alphabet[a] == ans[i])
{
ans[i] = alphabet[a+1];
break;
}
}
}
return ans;
}
console.log(LetterChanges("Argument goes here"));

How do you iterate over an array every x spots and replace with letter?

/Write a function called weave that accepts an input string and number. The function should return the string with every xth character replaced with an 'x'./
function weave(word,numSkip) {
let myString = word.split("");
numSkip -= 1;
for(let i = 0; i < myString.length; i++)
{
numSkip += numSkip;
myString[numSkip] = "x";
}
let newString = myString.join();
console.log(newString);
}
weave("weave",2);
I keep getting an infinite loop. I believe the answer I am looking for is "wxaxe".
Here's another solution, incrementing the for loop by the numToSkip parameter.
function weave(word, numToSkip) {
let letters = word.split("");
for (let i=numToSkip - 1; i < letters.length; i = i + numToSkip) {
letters[i] = "x"
}
return letters.join("");
}
Well you need to test each loop to check if it's a skip or not. Something as simple as the following will do:
function weave(word,numSkip) {
var arr = word.split("");
for(var i = 0; i < arr.length; i++)
{
if((i+1) % numSkip == 0) {
arr[i] = "x";
}
}
return arr.join("");
}
Here is a working example
Alternatively, you could use the map function:
function weave(word, numSkip) {
var arr = word.split("");
arr = arr.map(function(letter, index) {
return (index + 1) % numSkip ? letter : 'x';
});
return arr.join("");
}
Here is a working example
Here is a more re-usable function that allows specifying the character used for substitution:
function weave(input, skip, substitute) {
return input.split("").map(function(letter, index) {
return (index + 1) % skip ? letter : substitute;
}).join("");
}
Called like:
var result = weave('weave', 2, 'x');
Here is a working example
You dont need an array, string concatenation will do it, as well as the modulo operator:
function weave(str,x){
var result = "";
for(var i = 0; i < str.length; i++){
result += (i && (i+1)%x === 0)?"x":str[i];
}
return result;
}
With arrays:
const weave = (str,x) => str.split("").map((c,i)=>(i&&!((i+1)%x))?"x":c).join("");
You're getting your word greater in your loop every time, so your loop is infinite.
Try something like this :
for(let k = 1; k <= myString.length; k++)
{
if(k % numSkip == 0){
myString[k-1]='x';
}
}
Looking at what you have, I believe the reason you are getting an error is because the way you update numSkip, it eventually becomes larger than
myString.length. In my code snippet, I make i increment by numSkip which prevents the loop from ever executing when i is greater than myString.length. Please feel free to ask questions, and I will do my best to clarify!
JSFiddle of my solution (view the developer console to see the output.
function weave(word,numSkip) {
let myString = word.split("");
for(let i = numSkip - 1; i < myString.length; i += numSkip)
{
myString[i] = "x";
}
let newString = myString.join();
console.log(newString);
}
weave("weave",2);
Strings are immutable, you need a new string for the result and concat the actual character or the replacement.
function weave(word, numSkip) {
var i, result = '';
for (i = 0; i < word.length; i++) {
result += (i + 1) % numSkip ? word[i] : 'x';
}
return result;
}
console.log(weave("weave", 2));
console.log(weave("abcd efgh ijkl m", 5));
You can do this with fewer lines of code:
function weave(word, numSkip) {
word = word.split("");
for (i = 0; i < word.length; i++) {
word[i] = ((i + 1) % numSkip == 0) ? "x" : word[i];
}
return word.join("");
}
var result = weave("weave", 2);
console.log(result);

Encryption... almost works

I wrote a simple script for a website called Codewars (here: https://www.codewars.com/kata/57814d79a56c88e3e0000786). The purpose of the function was to encrypt a string such that every second character would appear first, and then the rest of them. I tested many random strings of text; it worked for a while. But then, I tested a specific case with 17 characters: "maybe do i really", and it resulted in a character being dropped (notably a space). Initially, I thought the issue was that the .join method didn't allow a double space in a row, so I attempted to make my own function to mimic its functionality: it did not solve the problem. Could anyone answer why this specific string loses a character and returns a wrong encryption? My jsfiddle is here: https://jsfiddle.net/MCBlastoise/fwz62j2g/
Edit: I neglected to mention that it runs a certain number of times based on parameter n, encrypting the string multiple times per that value.
And my code is here:
function encrypt(text, n) {
if (n <= 0 || isNaN(n) === true || text === "" || text === null) {
return text;
}
else {
for (i = 1; i <= n; i++) {
if (i > 1) {
text = encryptedString;
}
var evenChars = [];
var oddChars = [];
for (j = 0; j < text.length; j++) {
if (j % 2 === 0) {
evenChars.push(text.charAt(j));
}
else {
oddChars.push(text.charAt(j));
}
}
var encryptedString = oddChars.join("") + evenChars.join("");
}
return encryptedString;
}
}
function decrypt(encryptedText, n) {
if (n <= 0 || encryptedText === "" || encryptedText === null) {
return encryptedText;
}
else {
for (i = 1; i <= n; i++) {
if (i > 1) {
encryptedText = decryptedString;
}
var oddChars = [];
var evenChars = [];
for (j = 0; j < encryptedText.length; j++) {
if (j < Math.floor(encryptedText.length / 2)) {
oddChars.push(encryptedText.charAt(j));
}
else {
evenChars.push(encryptedText.charAt(j));
}
}
var convertedChars = []
for (k = 0; k < evenChars.length; k++) {
convertedChars.push(evenChars[k]);
convertedChars.push(oddChars[k]);
}
var decryptedString = convertedChars.join("");
}
return decryptedString;
}
}
document.getElementById("text").innerHTML = encrypt("maybe do i really", 1);
document.getElementById("text2").innerHTML = decrypt("ab oiralmyed ely", 1)
<p id="text"></p>
<p id="text2"></p>
Nothing wrong with the code itself. Basically HTML doesn't allow 2 or more spaces. You can use <pre> tag for the case like this.
document.getElementById("text").innerHTML = "<pre>" + encrypt("maybe do i really", 1) + "</pre>";

Palindrome function returns true when word isn't a palindrome

Could someone be kind enough to tell me why "almostomla" returns true in my code.
I have searched and have seen there are simpler versions but im so deep into this code now i need to make it work if at all possible.
Please excuse the terrible variable names, i was frustrated.
function palindrome(str) {
str = str.toLowerCase();
str = str.replace(/ /g, '').replace(/\./g, '').replace(/,/g, '');
for (var i = 0; i < str.length / 2; i++) {
for (var j = str.length - 1; j > str.length / 2 - 1; j--) {
var iDntKnow = str.charAt(i);
var iDntKnowEither = str.charAt(j);
if (iDntKnow === iDntKnowEither) {
return true;
} else {
return false;
}
}
}
}
Appreciate all answers.
While I can understand the frustration of wanting to make something work if you have put time into it, there is also something to be said for starting from the drawing board and not driving yourself crazy. The main problem I see with your code is that you have two loops when you only need one. The second loop is actually sabotaging you. I would suggest running a debugger (type "debugger" into your code and run) to see why.
I believe this is what you are trying to accomplish:
var palindrome = function(str) {
// Put any additional string preprocessing here.
for(var i = 0; i < str.length/2; i++) {
var j = str.length-i-1;
if (str[i] != str[j]) {
return false;
}
}
return true;
}
In this way you are comparing each mirrored element in the string to confirm if the string is a palindrome.
Your question seems to be answered by now.
If performance isn't an issue, why not just use this?
function palindrome(str) {
str = str.toLowerCase();
return (str.split().reverse().join() === str)
}
It splits the string into an array, reverses that and joins it back together. The result is compared to the original string.
You can only know if it's NOT a palindrome in each iteration.
Also, why using nested loops?
function palindrome(str) {
str = str.toLowerCase();
str = str.replace(/ /g, '').replace(/\./g, '').replace(/,/g, '');
for (var i = 0; i < str.length / 2; i++) {
if (str.charAt(i) !== str.charAt(str.length - i - 1)) {
return false;
}
}
return true;
}
This works:
function palindrome(string) {
string = string.toLowerCase();
for (var i = 0; i < Math.ceil(str.length/2); i++) {
var character1 = string.charAt(i);
var character2 = string.charAt(string.length-1-i);
if (character1 !== character2) {
return false;
}
}
return true;
}
Here is a version that omits spaces and commas:
var removeLetterFromString = function(string,letterPos){
var returnString = "";
for(var i = 0; i < string.length; i++){
if(i!==letterPos){
returnString=returnString+string.charAt(i);
}
}
return returnString;
};
var palindrome = function(string) {
string = string.toLowerCase();
var stringCheck="";
var recheck = true;
while(recheck){
recheck=false;
for(var i = 0; i < string.length; i ++){
if(string.charAt(i)===" "||string.charAt(i)===","){
string=removeLetterFromString(string,i);
}
}
for(var i = 0; i < string.length; i ++){
if(string.charAt(i)===" "||string.charAt(i)===","){
recheck=true;
}
}
}
if(string.length===0){
return false;
}
for (var i = 0; i < Math.ceil(string.length/2); i++) {
var j = string.length-1-i;
var character1 = string.charAt(i);
var character2 = string.charAt(j);
if (character1 !== character2) {
return false;
}
}
return true;
};

Categories

Resources