Caesar Cipher Shift Letters Issue - javascript

I was trying to program a caesar cipher using the technique of converting letter by letter with an ASCII Converter then shift the numbers and convert them again in text but it works only if the letters don't go over the z with the shift.
const checkPunctuation = (text) => {
return /[.,#!$%^&*;:{}=\-_`~()]/g.test(text);
};
const ASCIIConverter = (text) => {
return text.split("").map((char) => {
return checkPunctuation(char) ? char : char.charCodeAt();
});
};
const caesarCipher = (text, shift) => {
return ASCIIConverter(text)
.map((code) => (code + shift) % 26)
.map((charCode) => String.fromCharCode(charCode))
.join("");
};
console.log(caesarCipher("pizza!", 2));
Expected: wpggh!
Received: (Box Characters with a mark)

Some issues:
There are thousands of characters that are not letters and are not in your punctuation regex. It is better to do a check for letters: if it is not a character from the English alphabet, then leave it unaltered.
return checkPunctuation(char) ? char : char.charCodeAt() is a bad idea: this means that the mapping is to a mixed type: either string (the original character) or a number (it's code point). This affects the further processing of these values. See next point
.map((code) => (code + shift) % 26) expects code to be a number, but it can also be a character (punctuation, see previous point), and then + shift will perform string concatenation and % 26 will produce NaN in that case. This obviously is not what is intended.
.map((code) => (code + shift) % 26) results in numbers between 0 and 25 (and NaN), which are not the ASCII codes of letters but of control characters.
.map((charCode) => String.fromCharCode(charCode)) will convert control codes (and NaN) to their character representations, which are those boxes you see. You need to add the ASCII code of the letter 'a' back to it before converting to a string.
The example call is wrong. A shift of 2 would not give "wpggh!" as result. For that you would need to use a shift of 7 (as "w" occurs 7 letters after the letter "p" in the alphabet).
Here is the proposed correction:
const convertRange = (text, regex, a, shift) =>
text.replace(regex, letter =>
String.fromCharCode(
(letter.charCodeAt() - a.charCodeAt() + shift) % 26 + a.charCodeAt()
)
);
const caesarCipher = (text, shift) => {
text = convertRange(text, /[A-Z]/g, 'A', shift); // Cypher uppercase
return convertRange(text, /[a-z]/g, 'a', shift); // Cypher lowercase
};
console.log(caesarCipher("pizza!", 7));

charCodeAt probably simply returns the index in ASCII. What you need is the index in the range [0..n), where n in this case is 26. Same problem with fromCharCode of course, which receives the tiny values after the %26 is applied. Generally you can subtract / add the value of A or a to get the values into the correct range.
Generally I propose to add methods to convert the ASCII index into the index of the alphabet and back again. Those can be tested separately. The problem with this highly concise code is that it is horrid to debug; too much happens when mapping the values in the "stream".

One problem is that the code is decreasing values incorrectly.
For example, the char code of A is 65 but becomes 20.
If capitalization doesn't matter, you could use
String.fromCharCode((charCode - 65)%26 + 65)
This puts values in the correct range for upper-case letters.

Here is a working Caesar's Cypher function that will rotate only the ASCII characters [A-Z] and [a-z]:
function cc(str,n){
return str.split("").map(c=>{
let a=c.charCodeAt(0)-1
if (a>63&&a<90||a>95&&a<122)
a=(a%32+n)%26+64+(a>95?32:0)
return String.fromCharCode(a+1);
}).join("");
}
console.log(cc("Most fabulous - our Pizzas!",7));

Related

Use Regex for phone number

I'm working on a Regex.
let phones = ['321-1234567','+355 321 1234567','0103 1234500', '00 355 3211234567' ]
I want to have the following results:
3211234567
+3553211234567
+3551031234500
+3553211234567
I have implemented this:
phones.forEach(phone => {
phone = phone.replace(/^0+/,'+355').replace(/[^+\d]+/g, '')
console.log(phone)
})
Output:
3211234567
+3553211234567
+3551031234500
+3553553211234567 --->wrong , it should be: +3553211234567
and it works only for the three first elements of array, but it doesn't work for the last one (the case when we should replace those two zeros with + ).
So, when the phone number starts with a zero, replace that first zero with +355, when it starts with 00, replace those two zeros with + .
How can I do that using a Regex, or should I use conditions like if phone.startsWith()?
My question is not a duplication of: Format a phone number (get rid of empty spaces and replace the first digit if it's 0)
as the solution there doesn't take in consideration the case when the phone number starts with 00 355 .
let phones = ['321-1234567','+355 321 1234567','0103 1234500', '00 355 3211234567' ]
phones = phones.map(r => r
.replace(/^00/,'+')
.replace(/^0/,'+355')
.replace(/[^+\d]+/g, '')
)
console.log(phones)
You can use
let phones = ['321-1234567','+355 321 1234567','0103 1234500', '00 355 3211234567' ]
for (const phone of phones) {
console.log(
phone.replace(/^0{1,2}/, (x) => x=='00'?'+':'+355')
.replace(/(?!^\+)\D/g, ''))
}
Details:
.replace(/^0{1,2}/, (x) => x=='00'?'+':'+355') - matches 00 or 0 at the start of string, and if the match is 00, replacement is +, else, replacement is +355 (here, x stands for the whole match value and if ? then : else is a ternary operator)
.replace(/(?!^\+)\D/g, '') removes any non-digit if it is not + at the start of string.
Regex details:
^0{1,2} - ^ matches start of string and 0{1,2} matches one or two zero chars
(?!^\+)\D - (?!^\+) is a negative lookahead that fails the match if the char immediately to the right is + that is located at the start of the string (due to ^ anchor), and \D matches any char other than a digit.
your only problem is in this line replace(/^0+/,'+355') replace this with replace(/^0+/,'+')
phones.forEach(phone => {
phone = phone.replace(/^0+/,'+').replace(/[^+\d]+/g, '')
console.log(phone)
})

Regex search and replacement in a long string

I have a long string which represents CSS applied to a page;
const string = '.blog{background-color:#eff0f1;border color:#eff0f1} .header{background:rgba(33,37,41,0.125);}';
I am trying to replace rgba(33,37,41,0.125) in the string with appropriate hexcode.
My regex looks like this:
let rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/g;
When I am trying to replace occurrences of rgba code in the CSS string it does not do anything.
let anotherString = string.replace(regex, '#123123'); //does not work
However, if I try to match directly with 'rgba(33,37,41,0.125)' it works.
let anotherString = string.replace('rgba(33,37,41,0.125)', '#123123'); //works
My regex is correct, can anyone tell what else could be the issue ?
As Cuong points out in the comment, you used ^...$ which mean "the start of the string" and "the end of the string".
If you remove those signs you should be able to match the rgba part, so this should work:
let rgbRegex = /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/g;
Check this out: https://regex101.com/r/brO25z/1
Your regex will never match because you anchor the string at the beginning and end, but you want to match a pattern within the string.
You can use this regex:
const str = '.blog{background-color:#eff0f1;border color:#eff0f1} .header{background:rgba(33,37,41,0.125);}';
const regex = /\brgba?\(([^\)]*)\)/g;
let result = str.replace(regex, '#123123');
console.log(result);
Output:
.blog{background-color:#eff0f1;border color:#eff0f1} .header{background:#123123;}
Explanation:
\brgba? - match rgb or rgba using word boundary \b
\( - literal (
([^\)]*) - capture group with everything up to and just before the next )
\) - literal )
A benefit to using /\brgba?\(([^\)]*)\)/g is that it is computationally simple to match. The RegEx is processed in one direction and doesn't have multiple capture groups.
Super-charged replacement
String#replace() also supports using a function as the second parameter, this allows you to transform whatever the RegEx matches dynamically using the arguments passed to the function. The functions's first argument is the matched substring (which we don't need), but the second argument will contain the capture group we specified for everything inside the parentheses of rgb() and rgba().
const str = '.blog{background-color:#eff0f1;border color:#eff0f1} .header{background:rgba(33,37,41,0.125);}';
const regex = /\brgba?\(([^\)]*)\)/g;
let result = str.replace(regex, (match, g1) => {
const [r,g,b,a] = g1.split(/\s*,\s*/);
const parts = (a !== undefined ? [Math.floor(a * 255), r, g, b] : [r, g, b]);
return parts.reduce((hexStr, comp) => hexStr + `0${Number(comp).toString(16)}`.slice(-2), "#");
});
console.log(result);
Explanation:
const [r,g,b,a] = g1.split(/\s*,\s*/) - take the first capture group (the inside of the parentheses from our match), split it into parts separated by a comma, and use a destructuring assignment to save each respective part as r, g, b, and a.
The format of a basic hexadecimal color is #RRGGBB, whereas a hexadecimal color with an alpha value is #AARRGGBB
Math.floor(a * 255) - because a is a value between 0.0 and 1.0, we need to convert it to an integer in the range 0 to 255.
const parts = (a !== undefined ? [Math.floor(a * 255), r, g, b] : [r, g, b]); - When you match a string without an alpha value (like rgb(33,37,41)), a will be undefined. Here we create an array with the components in the right order, omitting a when it's not needed.
Number(comp).toString(16) - this takes the variable comp, converts it to a number and then back to a string, but this time in hexadecimal notation.
("0" + Number(comp).toString(16)).slice(-2) - this takes the result of the hexadecimal conversion, places a "0" in front of it and then takes the last two characters of that string. So Number("16").toString(16) (which is "f") will become "0f" and Number("17").toString(16) (which is "10") will still be "10".
return parts.reduce((hexStr, comp) => hexStr + COMP_AS_HEX, "#"); - Convert each component in parts to hex, flatten it to #AARRGGBB or #RRGGBB and return the value (replacing the original match).

JS - why does this Codewars challenge return undefined for one test while 104 other tests pass?

I'm solving the Highest Scoring Word challenge on Codewars, which states -
Given a string of words (x), you need to find the highest scoring
word.
Each letter of a word scores points according to it's position in the
alphabet. a=1, z=26 and everything inbetween.
You need to return the highest scoring word as a string.
If two words score the same, return the word that appears earliest in
the original string.
All letters will be lower case and all inputs will be valid.
My approach to the problem is as follows -
Construct an object, that maps alphabets to their corresponding integer values, like a - 1, b - 2, etc.
Split the input string on spaces, for each word - find it's score by-
creating an array of the word's letters
map over the array to get score of each letter
reduce this array with addition and get the total score of the word
The array returned in step 2 will have scores for each word in the original string. Find the maximum value in this array and get it's position, return the word from the original string at that particular position.
My code snippet (demo here ) -
function high(x) {
let myObj = {};
for (let i = 1; i <= 26; i++) {
myObj[String.fromCharCode(i + 96)] = i;
}
// console.log(myObj);
let scores = x.split(' ').map(word => [...word].map(a => myObj[a]).reduce((a, b) => a + b, 0));
return x.split(' ')[scores.indexOf(Math.max(...scores))];
}
console.log(high('take me to semynak'))
When I run this in codewars, the results show 104 passed tests, 1 failed, and 1 error. The only info. about the failures/errors shown is -
Expected: 'bintang', instead got: undefined
Since the actual input for this test case is not shown, this info. by itself is not very useful.
I tried to think of some edge cases, like two words having the same score, but even then .indexOf() should return the position of the earlier value, as you can see in this example -
let nums = [1, 2, 3, 3];
console.log(nums.indexOf(Math.max(...nums)));
Here's the screenshot -
The statement of the problem says that the words will consist of lowercase letters only, but it doesn't guarantee that the input string will contain only words and spaces.
In order to account for punctuation, numbers and other non-words, you need to extract all sequences of lowercase letters as words (instead of splitting the input string on spaces only).
function high(x) {
let words = x.split(/[^a-z]+/);
let scores = words.map(word => [...word].map(a => a.charCodeAt(0) - 96).reduce((a, b) => a + b, 0));
return words[scores.indexOf(Math.max(...scores))];
}
console.log(high('today is 24 august, 2017'));

Format a string in blocks of 3 digits

How would I go about reformating a string into blocks of 3 digits separated by dash (-) in JavaScript?
ex:
let myString = "00 64 33 3-44-23 982- 23-1-0"
produce desired output as:
myString = "006-433-344-239-822-310"
With regex:
("00 64 33 3-44-23 982- 23-1-0").replace(/\D/g,"").replace(/(\d{3})(?!$)/g,'$1-')
Explanation:
\D - remove all non numeric characters
(\d{3})(?!$)
(\d{3}) - match 3 numbers
(?!$) - but not the last 3
To turn 0 - 22 1985--324 into 022-198-53-24:
("0 - 22 1985--324")
.replace(/\D/g,"") // strip numbers
.replace( // do specific format
/^(\d{3})(\d{3})(\d{2})(\d{2})$/g,
'$1-$2-$3-$4'
)
From what you've said in the comments I believe you want groups of 3 where possible and groups of 2 at the end if there aren't enough numbers. I believe this will do it:
[
'1-2 3 45---67 890',
'12345678901',
'123456789012',
'1234567890123',
'12345678901234',
'1',
'12',
'123',
'1234',
'12345',
'123456',
'1234567'
].forEach(function(str) {
console.log(str.replace(/\D/g, '').replace(/(\d\d\d?)(?=\d\d)/g, '$1-'));
});
The /\D/g removes all the non-digits. The capture group (\d\d\d?) will attempt to grab 3 digits if it can (RegExps are greedy so they grab as much as possible) but if it can't grab 3 digits that ? makes the third digit optional. The lookahead (?=\d\d) requires there to be at least 2 more digits after the initial capture. The result of this is that if there are 5 or more digits remaining in the string this will result in 3 digits being included in the capture but if there are only 4 remaining it will apply the ? to grab just 2 digits.
When making multiple matches using the g flag the string is consumed from start to finish in one run. Each time it makes a match it doesn't go back to the start again, it just carries on. This means that matches can never overlap. Lookaheads provides a way to check what's coming up next without including it in the match, so that you can effectively rewind.
The key to understanding this RegExp is the observation that wherever we insert a - in the result there must always be at least 2 more digits after it. In English it translates as 'grab 3 digits if we can (or 2 if we can't) so long as there are 2 more digits yet to come'.
First you can use 'replace' to remove any non-digit characters with regex.
let myString = "00 64 33 3-44-23 982- 23-1-0"
myString = myString.replace(/\D/g, "")
// "006433344239822310"
Secondly, 'match' any digits (0-9) between 1 and 3 times. The match function conveniently returns an array with an item for each match.
myString = myString.match(/\d{1,3}/g)
// [006,433,344,239,822,310]
Lastly, join the array using "-" as the separator.
myString = myString.join("-");
// 006-433-344-239-822-310
And if you would like to chain each step together..
myString = myString.replace(/\D/g, "").match(/\d{1,3}/g).join("-");
Note that if the string has any leftover digits, they will be left in their own block on the end of the string. This is due to the 1-3 match.
For example..
"00 64 33 3-44-23 982- 23-1-0 24" // before - with 2 extra digits
"006-433-344-239-822-310-24" // after - the extra digits are maintained
Here's a non-regexp solution:
joinGroups(joinDigits(partition3(extract(string))))
joinGroups is just
// Join groups of digits with a hyphen.
const joinGroups = a => a.join('-');
joinDigits is easy enough too:
// Join elements of subarrays together into strings.
const joinDigits = groups = groups.map(group => group.join(''));
extract is easy too:
// Extract all digits from a string and return as array.
const extract = input => input.match(/\d/g);
partition3 is a little harder. There are many samples here on SO and elsewhere. See the implementation in the snippet for one idea.
const joinGroups = a => a.join('-');
const joinDigits = groups => groups.map(group => group.join(''));
const extract = input => input.match(/\d/g);
const partition = n =>
a => Array.from(
{length: Math.floor((a.length - 1) / n) + 1},
(_, i) => a.slice(i * n, (i + 1) * n));
const partition3 = partition(3);
const data = "00 64 33 3-44-23 982- 23-1-0";
console.log(joinGroups(joinDigits(partition3(extract(data)))));

Remove Any Non-Digit And Check if Formatted as Valid Number

I'm trying to figure out a regex pattern that allows a string but removes anything that is not a digit, a ., or a leading -.
I am looking for the simplest way of removing any non "number" variables from a string. This solution doesn't have to be regex.
This means that it should turn
1.203.00 -> 1.20300
-1.203.00 -> -1.20300
-1.-1 -> -1.1
.1 -> .1
3.h3 -> 3.3
4h.34 -> 4.34
44 -> 44
4h -> 4
The rule would be that the first period is a decimal point, and every following one should be removed. There should only be one minus sign in the string and it should be at the front.
I was thinking there should be a regex for it, but I just can't wrap my head around it. Most regex solutions I have figured out allow the second decimal point to remain in place.
You can use this replace approach:
In the first replace we are removing all non-digit and non-DOT characters. Only exception is first hyphen that we negative using a lookahead.
In the second replace with a callback we are removing all the DOT after first DOT.
Code & Demo:
var nums = ['..1', '1..1', '1.203.00', '-1.203.00', '-1.-1', '.1', '3.h3',
'4h.34', '4.34', '44', '4h'
]
document.writeln("<pre>")
for (i = 0; i < nums.length; i++)
document.writeln(nums[i] + " => " + nums[i].replace(/(?!^-)[^\d.]+/g, "").
replace(/^(-?\d*\.\d*)([\d.]+)$/,
function($0, $1, $2) {
return $1 + $2.replace(/[.]+/g, '');
}))
document.writeln("</pre>")
A non-regex solution, implementing a trivial single-pass parser.
Uses ES5 Array features because I like them, but will work just as well with a for-loop.
function generousParse(input) {
var sign = false, point = false;
return input.split('').filter(function(char) {
if (char.match(/[0-9]/)) {
return sign = true;
}
else if (!sign && char === '-') {
return sign = true;
}
else if (!point && char === '.') {
return point = sign = true;
}
else {
return false;
}
}).join('');
}
var inputs = ['1.203.00', '-1.203.00', '-1.-1', '.1', '3.h3', '4h.34', '4.34', '4h.-34', '44', '4h', '.-1', '1..1'];
console.log(inputs.map(generousParse));
Yes, it's longer than multiple regex replaces, but it's much easier to understand and see that it's correct.
I can do it with a regex search-and-replace. num is the string passed in.
num.replace(/[^\d\-\.]/g, '').replace(/(.)-/g, '$1').replace(/\.(\d*\.)*/, function(s) {
return '.' + s.replace(/\./g, '');
});
OK weak attempt but seems fine..
var r = /^-?\.?\d+\.?|(?=[a-z]).*|\d+/g,
str = "1.203.00\n-1.203.00\n-1.-1\n.1\n3.h3\n4h.34\n44\n4h"
sar = str.split("\n").map(s=> s.match(r).join("").replace(/[a-z]/,""));
console.log(sar);

Categories

Resources