Related
I am trying to complete a codewars challenge as my practice coding since I will have a beginner tech test to enter a coding training program. In case you would like to know what the challenge is: https://www.codewars.com/kata/530e15517bc88ac656000716/train/javascript
I have written code which does what is expected. I will quote Codewars.com below:
ROT13 is a simple letter substitution cipher that replaces a letter
with the letter 13 letters after it in the alphabet. ROT13 is an
example of the Caesar cipher.
Create a function that takes a string and returns the string ciphered
with Rot13. If there are numbers or special characters included in the
string, they should be returned as they are. Only letters from the
latin/english alphabet should be shifted, like in the original Rot13
"implementation".
My code grabs the test string "grfg" and converts it to the word "test" which would be the equivalent to 13 letters ahead in the alphabet, however, if I pass the string as "Grfg" with the capital "G"it returns "gest" meaning that it will not replace capital letters.
If I pass "test" in lower case it will return "grfg", so it works backwards too, however, if I pass "Test" it will return "trfg" not replacing the capital again but returning the same letter.
Please find the code I wrote below:
function rot13(message){
let abc = ["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", "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 msg = message.split("");
for (let i = 0; i < abc.length; i++){
for (let j = 0; j < abc.length; j++) {
if (msg[j] === abc[i]) {
ind = parseInt(abc.indexOf(i), 0) + 13;
msg[j] = abc.slice(i + 13, i + 14);
};
};
};
return msg.join("").toLowerCase();
};
rot13("test");
What is my mistake or what I should know to make sure that my code will convert the strings regardless of capitals or lowercase?
Have you tried making the message all lower-case first, before splitting it?
let msg = message.toLowerCase().split("");
I know there have been a few posts about Caesar Ciphers in the past, which I have had a look at, but I haven't found an answer which has helped me to solve this kata, hence my post.
The language is JavaScript. I've written 3 tests, 2 of which are passing so far, but the third is not. I tried to use a nested for loop to loop over the alphabet and the str, and compare them, then shift the alphabet index up/down according to whatever the number was, then push that letter into a new array, and return the joined array at the end.
It's working for positive numbers, but not negatives. (I should also point out that I haven't thought of how to handle spaces yet, I just wanted to get it working for single words first, then take it from there, thanks!)
Any pointers in the right direction would be appreciated.
Kata Instructions:
The function caesarCipher should take a string and a number (n) and return a new string with a Caesar cipher applied. A Caesar cipher replaces each plaintext letter with a different one a fixed number of places up or down the alphabet. N represents the number of shifts up or down the alphabet should be applied. It may be negative or positive.
E.g.
caesarCipher('hello', 2)
--> 'jgnnq'
caesarCipher('hello world!', -3)
--> 'ebiil tloia!'
My tests:
const caesarCipher = require("../katas/caesar-cipher");
const { expect } = require("chai");
describe.only("caesarCipher", () => {
it("returns an empty string when passed an empty string", () => {
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"
];
const str = "";
const num = 2;
const actualResults = caesarCipher(alphabet, str, num);
const expectedResults = "";
expect(actualResults).to.equal(expectedResults);
});
it("returns a string with the letters replaced by the number of shifts up the alphabet", () => {
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"
];
const str = "hi";
const num = 2;
const actualResults = caesarCipher(alphabet, str, num);
const expectedResults = "jk";
expect(actualResults).to.equal(expectedResults);
});
it("returns a string with the letters replaced by the number of shifts down the alphabet", () => {
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"
];
const str = "dog";
const num = -3;
const actualResults = caesarCipher(alphabet, str, num);
const expectedResults = "ald";
expect(actualResults).to.equal(expectedResults);
});
});
My Solution:
function caesarCipher(alphabet, str, num) {
const strToArray = str.split("");
console.log(strToArray);
const cipheredStr = [];
for (let i = 0; i < strToArray.length; i++) {
for (let j = 0; j < alphabet.length; j++) {
if (strToArray[i] === alphabet[j] && Math.sign(num) === 1) {
console.log(Math.sign(num));
cipheredStr.push(alphabet[(j += num)]);
} else if (strToArray[i] === alphabet[j] && Math.sign(num) === -1) {
console.log(Math.sign(num));
console.log(alphabet[(j -= num)]);
cipheredStr.push(alphabet[(j -= num)]);
}
}
}
console.log(cipheredStr.join(""));
return cipheredStr.join("");
}
The Results:
caesarCipher
[]
✓ returns an empty string when passed an empty string
[ 'h', 'i' ]
1
1
jk
✓ returns a string with the letters replaced by the number of shifts up the alphabet
[ 'd', 'o', 'g' ]
-1
g
-1
r
-1
j
jum
1) returns a string with the letters replaced by the number of shifts down the alphabet
2 passing (15ms)
1 failing
1) caesarCipher
returns a string with the letters replaced by the number of shifts down the alphabet:
AssertionError: expected 'jum' to equal 'ald'
+ expected - actual
-jum
+ald
at Context.<anonymous> (spec/caesar-cipher.spec.js:108:30)
at processImmediate (internal/timers.js:456:21)
The problem is that when num is negative, you are doing the mapping:
cipheredStr.push(alphabet[(j -= num)]);
But num is negative. When you subtract a negative number what you are doing is adding its absolute value.
Instead, you should do:
cipheredStr.push(alphabet[j + num]);
Also, note that to compute the index in the alphabet you don't need to put the = in there.
Side notes
I understand that your solution is a work in progress. You have to take into consideration:
What happens when you sum j + num to do the translation and it goes out of boundaries of your alphabet. Same thing could happen with a negative value of num.
In the declaration of the problem, it states that caesarCipher should only accept two parameters, but you are passing the alphabet as first parameter!
Good luck with your code and keep trying :)
let alphabets = 'abcdefghijklmnopqrstuvwxyz';
let arr = alphabets.split('')
// Cipher Get
function getCipher(str= alphabets, shift=3){
return arr.reduce((a, c, i) => {
let result = [...a]
let tIndex = ( i + shift) % arr.length
result[i]=arr[tIndex]
return result;
},[])
}
// Encrypt
let stringToEnc = 'danger'
console.log('Plain Text -', stringToEnc)
let cipheredAlphabets = getCipher()
let encryptedStr = stringToEnc
.toLowerCase()
.split('')
.map(function(p, i){
let indexInAlphabets = arr.findIndex(c => p == c)
return (cipheredAlphabets[indexInAlphabets])
})
let encryptedText = encryptedStr.join('')
console.log('encrypted text - ', encryptedText)
// Decrypt
let cipherForDecrypt = getCipher(alphabets, -3)
let decryptedStr = encryptedText
.toLowerCase()
.split('')
.map(function(p, i){
let indexInAlphabets = cipheredAlphabets.findIndex(c => p == c)
return (arr[indexInAlphabets])
})
console.log('decrypted text - ', decryptedStr.join(''))
I'm new to fairly new to Javascript and I need some help solving the 804. Unique Morse Code Words - Leetcode problem.
I figured how to search return the morse code by using the index of each letter from a word and using it to concatenate the codes at those specific index in the morse code array. The problem is I can't store the results into an Set array excluding the duplicates and returning the length of the Set array.
var letters = ["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"];
var morseCode = [".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."];
var words = ["gin", "zen", "gig", "msg"]
var uniqueMorseRepresentations = function(words) {
for (i = 0; i < words.length; i++) {
let word = words[i];
var result = "";
for (j = 0; j < word.length; j++) {
let letter = word.charAt(j);
let index = letters.indexOf(letter);
result += morseCode[index];
};
console.log(result);
};
};
uniqueMorseRepresentations(words);
The console.log method return the results in 4 separate strings but I don't know how to store them into an array while verifying if there are duplicate.
I'm sorry if the question is sloppy. It's my first one.
Thanks in advance!
Inside your function, create a Set:
const resultSet = new Set();
Then when each result is built up (when you log it), add the resulting morse code to that Set:
resultSet.add(result);
Then you can finally return that Set, or its .size.
I think this should solve your problem. Take an obj, and store the result in that and check if the result is repeating then don't push that result into that array. And the time complexity in this operation would be O(1), so you don't have to worry about it if you want to scale your algorithm.
var letters = ["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"];
var morseCode = [".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."];
var words = ["gin", "zen", "gig", "msg","gig"]
var array = [];
var uniqueMorseRepresentations = function(words) {
let obj = {};
for (i = 0; i < words.length; i++) {
let word = words[i];
var result = "";
for (j = 0; j < word.length; j++) {
let letter = word.charAt(j);
let index = letters.indexOf(letter);
result += morseCode[index];
};
if(!obj[result]){
obj[result] = result;
array.push(result);
}
console.log(result);
};
console.log(array)
};
uniqueMorseRepresentations(words);
I am working on creating a program to encrypt a message. One of the functions I was planning on running the code through is a circle cipher. It hasn't been working and I'm unsure as to why. At certain times the program will return the correct letters and at others it won't change anything and returns the same character that was input. Any suggestions are appreciated.
function circle(message, rotate) {
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"];
for(i = 0; i < message.length; i++) {
for (i = 0; i < alphabet.length; i++) {
if (message[i] == alphabet[i]) {
message[i] = alphabet[i + rotate];
break;
}
}
}
return message;
}
I spot at least three bugs in your code:
You use the variable i twice. You should use another variable name for the inner loop.
You have to use remainder (modulus) operator to get the value i + rotate modulo 26.
Strings are immutable. So you can't change individual characters of a string.
My random generator makes names predominately to the large side. From what I read that can be a factor if you use .round, .ceil or .floor. It also it supposed to make a name no smaller than 3 and no larger than 13.
After adding the title to the name, some of the names were 2 and 14 and such, so I modified it again. Am I going the wrong way about this?
also how do you empty a var for recycling. everytime I try to recycle this and rerun it, it keeps adding to the original var
var nameLength = Math.floor(Math.random() * (13 - 3) + 3); /* min 3 max 13*/
var compareLetter = 0;
var randomLetter = "";
var randomName = "";
var capitolLetter = "";
var checkLetter = 0;
var nameTitle = "XXX"; /* titles add _ between title and name*/
var nameLetters = [
["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", "e", "i", "o", "u"],
["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z"]
];
if (nameTitle !== "") { /* my attempt to subtract title length +1 from name length if over 13*/
nameLength = nameLength + (nameTitle.length + 1);
if (nameLength > 13) {
nameLength = nameLength - (nameLength - 13);
}
}
for (i = 0; i < nameLength; i++) {
randomLetter = nameLetters[compareLetter][Math.floor(Math.random() * nameLetters[compareLetter].length)];
checkLetter = nameLetters[1].indexOf(randomLetter);
if (checkLetter > -1) { /* my version of keeping consanants and vowels from stacking up */
compareLetter = 2;
} else {
compareLetter = 1;
}
if (randomName.length < 1) { /* my version to keep first letter capitolized*/
capitolLetter = randomLetter.toUpperCase();
randomName = randomName + capitolLetter;
} else {
randomName = randomName + randomLetter;
}
}
if (nameTitle !== "") {
alert(nameTitle + "_" + randomName);
} else {
alert(randomName);
}
alert(nameLength);
alert(randomName.length);
Any guidance would be appreciated.
I'm not yet able to comment, so this will be a comment disguised as an answer...
1) Math.random() will return a zero, but will not return a 1 -- it returns a number between 0 (inclusive) and 1 (exclusive). So using floor in this manner will never result in your upper bound being reached. Math.round would be a better choice. (This also goes for your later use of Math.random in selecting letters -- I don't think you'll get a "z" with this, but I haven't checked your math.)
2) The initial random nameLength calc is a bit tortured -- I follow it, but embedding (13-3) in the calculation is just awkward. Just make the calculation straightforward and comment accordingly.
var nameLength = Math.round(Math.random() * 10) + 3
3) You may want to explain the nameTitle interaction with nameLength. I can't figure out what you're trying to accomplish there -- let's say, for example, your above calculation results in a nameLength of 8. Your nameTitle right now is hardwired as "XXX", so your calculation just alters the nameLength from 8 to 12. Basically, you're adding 4 to the nameLength until you would get to 13. That may be what you want, but why?
I don't know that any of the above are the reason you're seeing variable length values, but they may be contributors.