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(''))
Related
so what I have been trying to acheive is that if I iterate over arr and string and if they have the same letters then some action should be perfomed, for example - if ("both string and array have the same letters"){ "some action" }
const word = "hello";
const arr = ["o", "l", "e", "h"];
const word = "hello";
const arr = ["o", "l", "e", "h"];
const uWord = [...new Set(word)].sort().join()
const uArr = [...new Set(arr)].sort().join()
if (uWord === uArr) {
// do something
}
Here is with Linear time O(n). Check whether both items has same chars and same chars count.
const isMatch = (arr, word, track = {}) => {
arr.forEach((char) => (track[char] = (track[char] ?? 0) + 1));
[...word].forEach((char) => (track[char] = (track[char] ?? 0) - 1));
return !Object.values(track).some((val) => val < 0);
};
console.log(isMatch(["o", "l", "e", "h"], "hello"));
console.log(isMatch(["o", "l", "e", "h", "l"], "hello"));
console.log(isMatch(["o", "o"], "lo"));
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);
Say, I have an array of option values, like:
var arr = ["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"];
And I need to translate that into array of formatted strings, like:
var result = ['1-st option is "a"', '2-nd option is "b", '3-rd option is "c"', '4-th option is "d"',...];
I managed to achieve most part of it with:
var result = [];
for(var i = 0; i < arr.length; i++){
result.push((i+1)+' option is "'+arr[i]+'"');
}
It produces the strings, like 1 option is "a", so on.
But I can't seem to cope with those suffixes ('-st', '-nd', '-rd', '-th'). Would you, guys, help me out with the issue? Thanks!
You may put necessary suffixes into array and pick the one that corresponds to your index:
const arr = [...'abcdefghijklmnopqrstuvwxyz'];
const suffixes = ['th', 'st', 'nd', 'rd'];
const result = arr.map((item, i) =>
(idx = ~~(i % 10) + 1 > 3 || ~~(i / 10) == 1 ? 0 : ~~(i % 10) + 1,
`${i+1}-${suffixes[idx]} options is '${item}'`));
console.log(result);
.as-console-wrapper {min-width: 100%}
Try this Code it works :
https://en.wikipedia.org/wiki/Ordinal_indicator#English
-st is used with numbers ending in 1 (e.g. 1st, pronounced first)
-nd is used with numbers ending in 2 (e.g. 92nd, pronounced ninety-second)
-rd is used with numbers ending in 3 (e.g. 33rd, pronounced thirty-third)
As an exception to the above rules, all the "teen" numbers ending with 11, 12 or 13 use -th (e.g. 11th, pronounced eleventh, 112th, pronounced one hundred [and] twelfth)
-th is used for all other numbers (e.g. 9th, pronounced ninth).
var arr = ["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"];
function addSuffix(i) {
var j = i % 10,
k = i % 100;
if (j == 1 && k != 11) {
return i + "-st";
}
if (j == 2 && k != 12) {
return i + "-nd";
}
if (j == 3 && k != 13) {
return i + "-rd";
}
return i + "-th";
}
var result = [];
for (var i = 0; i < arr.length; i++) {
result.push(`${addSuffix(i + 1)} option is '${arr[i]}'`);
}
console.log(result);
You can create a function with modulo % that handles that for you, for instance:
function calc_suffix(number) {
let rest = number % 10;
if(rest === 1 && number != 11) { return 'st'; }
else if(rest === 2 && number != 12) { return 'nd'; }
else if(rest === 3 && number != 13) { return 'rd'; }
else { return 'th'; }
}
The end result would look like:
var result = [];
for(var i = 0; i < arr.length; i++){
result.push((i+1) + "-" + calc_suffix(i+1) + " option is "'+arr[i]+'"');
}
try this:
const arr= ["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 ordinal_suffix_of = (n) => (["st","nd","rd"][((n+90)%100-10)%10-1] || "th")
const result = arr.map((res,i)=> `${i+1}-${ordinal_suffix_of(i+1)} option is '${res}'`);
console.log(result);
For those familiar with this quiz, I'm trying to take a string argument and convert each letter to the letter that follows in the alphabet. EG, the argument "abc" should become "bcd".
The first portion of my code works. It takes the first letter of the argument and converts it. Now I'm trying to do this for each letter of the argument and then concatenate the results into one string as the output. This part isn't working. I'm getting the error, "SyntaxError: Unexpected token ;"
function LetterChanges(str) {
var string = str.toLowerCase()
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"];
var n = 0;
var output = "";
var currentLetter = string[n];
var currentAlphaPos = alphabet.indexOf(currentLetter);
var nextAlphaPos = currentAlphaPos + 1;
var nextAlpha = alphabet[nextAlphaPos];
//the code above this works. The code below results in an error
while (i = 1;i < string.length; i++){
output += nextAlpha;
n += 1;
};
return output;
}
I'm a beginner, so thanks in advance.
You have confused the while and for loops.
You are trying to do for (iterator; condition; step); the while syntax is simply while (condition).
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.