I had a requirement of encoding a 3 character string(always alphabets) into a 2 byte[] array of 2 integers.
This was to be done to save space and performance reasons.
Now the requirement has changed a bit. The String will be of variable length. It will either be of length 3 (as it is above) or will be of length 4 and will have 1 special character at beginning. The special character is fixed i.e. if we choose # it will always be # and always at the beginning. So we are sure that if length of String is 3, it will have only alphabets and if length is 4, the first character will always be '#' followed by 3 alphabets
So I can use
charsAsNumbers[0] = (byte) (locationChars[0] - '#');
instead of
charsAsNumbers[0] = (byte) (chars[0] - 'A');
Can I still encode the 3 or 4 chars to 2 byte array and decode them back? If so, how?
Not directly an answer, but here's how I would do the encoding:
public static byte[] encode(String s) {
int code = s.charAt(0) - 'A' + (32 * (s.charAt(1) - 'A' + 32 * (s.charAt(2) - 'A')));
byte[] encoded = { (byte) ((code >>> 8) & 255), (byte) (code & 255) };
return encoded;
}
The first line uses Horner's Schema to arithmetically assemble 5 bits of each character into an integer. It will fail horribly if any of your input chars fall outside the range [A-`].
The second line assembles a 2 byte array from the leading and trailing byte of the integer.
Decoding could be done in a similar manner, with the steps reversed.
UPDATE with the code (putting my foot where my mouth is, or something like that):
public class TequilaGuy {
public static final char SPECIAL_CHAR = '#';
public static byte[] encode(String s) {
int special = (s.length() == 4) ? 1 : 0;
int code = s.charAt(2 + special) - 'A' + (32 * (s.charAt(1 + special) - 'A' + 32 * (s.charAt(0 + special) - 'A' + 32 * special)));
byte[] encoded = { (byte) ((code >>> 8) & 255), (byte) (code & 255) };
return encoded;
}
public static String decode(byte[] b) {
int code = 256 * ((b[0] < 0) ? (b[0] + 256) : b[0]) + ((b[1] < 0) ? (b[1] + 256) : b[1]);
int special = (code >= 0x8000) ? 1 : 0;
char[] chrs = { SPECIAL_CHAR, '\0', '\0', '\0' };
for (int ptr=3; ptr>0; ptr--) {
chrs[ptr] = (char) ('A' + (code & 31));
code >>>= 5;
}
return (special == 1) ? String.valueOf(chrs) : String.valueOf(chrs, 1, 3);
}
public static void testEncode() {
for (int spcl=0; spcl<2; spcl++) {
for (char c1='A'; c1<='Z'; c1++) {
for (char c2='A'; c2<='Z'; c2++) {
for (char c3='A'; c3<='Z'; c3++) {
String s = ((spcl == 0) ? "" : String.valueOf(SPECIAL_CHAR)) + c1 + c2 + c3;
byte[] cod = encode(s);
String dec = decode(cod);
System.out.format("%4s : %02X%02X : %s\n", s, cod[0], cod[1], dec);
}
}
}
}
}
public static void main(String[] args) {
testEncode();
}
}
Yes, it is possible to encode an extra bit of information while maintaining the previous encoding for 3 character values. But since your original encoding doesn't leave nice clean swaths of free numbers in the output set, mapping of the additional set of Strings introduced by adding that extra character cannot help but be a little discontinuous.
Accordingly, I think it would be hard to come up with mapping functions that handle these discontinuities without being both awkward and slow. I conclude that a table-based mapping is the only sane solution.
I was too lazy to re-engineer your mapping code, so I incorporated it into the table initialization code of mine; this also eliminates many opportunities for translation errors :) Your encode() method is what I call OldEncoder.encode().
I've run a small test program to verify that NewEncoder.encode() comes up with the same values as OldEncoder.encode(), and is in addition able to encode Strings with a leading 4th character. NewEncoder.encode() doesn't care what the character is, it goes by String length; for decode(), the character used can be defined using PREFIX_CHAR . I've also eyeball checked that the byte array values for prefixed Strings don't duplicate any of those for non-prefixed Strings; and finally, that encoded prefixed Strings can indeed be converted back to the same prefixed Strings.
package tequilaguy;
public class NewConverter {
private static final String[] b2s = new String[0x10000];
private static final int[] s2b = new int[0x10000];
static {
createb2s();
creates2b();
}
/**
* Create the "byte to string" conversion table.
*/
private static void createb2s() {
// Fill 17576 elements of the array with b -> s equivalents.
// index is the combined byte value of the old encode fn;
// value is the String (3 chars).
for (char a='A'; a<='Z'; a++) {
for (char b='A'; b<='Z'; b++) {
for (char c='A'; c<='Z'; c++) {
String str = new String(new char[] { a, b, c});
byte[] enc = OldConverter.encode(str);
int index = ((enc[0] & 0xFF) << 8) | (enc[1] & 0xFF);
b2s[index] = str;
// int value = 676 * a + 26 * b + c - ((676 + 26 + 1) * 'A'); // 45695;
// System.out.format("%s : %02X%02X = %04x / %04x %n", str, enc[0], enc[1], index, value);
}
}
}
// Fill 17576 elements of the array with b -> #s equivalents.
// index is the next free (= not null) array index;
// value = the String (# + 3 chars)
int freep = 0;
for (char a='A'; a<='Z'; a++) {
for (char b='A'; b<='Z'; b++) {
for (char c='A'; c<='Z'; c++) {
String str = "#" + new String(new char[] { a, b, c});
while (b2s[freep] != null) freep++;
b2s[freep] = str;
// int value = 676 * a + 26 * b + c - ((676 + 26 + 1) * 'A') + (26 * 26 * 26);
// System.out.format("%s : %02X%02X = %04x / %04x %n", str, 0, 0, freep, value);
}
}
}
}
/**
* Create the "string to byte" conversion table.
* Done by inverting the "byte to string" table.
*/
private static void creates2b() {
for (int b=0; b<0x10000; b++) {
String s = b2s[b];
if (s != null) {
int sval;
if (s.length() == 3) {
sval = 676 * s.charAt(0) + 26 * s.charAt(1) + s.charAt(2) - ((676 + 26 + 1) * 'A');
} else {
sval = 676 * s.charAt(1) + 26 * s.charAt(2) + s.charAt(3) - ((676 + 26 + 1) * 'A') + (26 * 26 * 26);
}
s2b[sval] = b;
}
}
}
public static byte[] encode(String str) {
int sval;
if (str.length() == 3) {
sval = 676 * str.charAt(0) + 26 * str.charAt(1) + str.charAt(2) - ((676 + 26 + 1) * 'A');
} else {
sval = 676 * str.charAt(1) + 26 * str.charAt(2) + str.charAt(3) - ((676 + 26 + 1) * 'A') + (26 * 26 * 26);
}
int bval = s2b[sval];
return new byte[] { (byte) (bval >> 8), (byte) (bval & 0xFF) };
}
public static String decode(byte[] b) {
int bval = ((b[0] & 0xFF) << 8) | (b[1] & 0xFF);
return b2s[bval];
}
}
I've left a few intricate constant expressions in the code, especially the powers-of-26 stuff. The code looks horribly mysterious otherwise. You can leave those as they are without losing performance, as the compiler folds them up like Kleenexes.
Update:
As the horror of X-mas approaches, I'll be on the road for a while. I hope you'll find this answer and code in time to make good use of it. In support of which effort I'll throw in my little test program. It doesn't directly check stuff but prints out the results of conversions in all significant ways and allows you to check them by eye and hand. I fiddled with my code (small tweaks once I got the basic idea down) until everything looked OK there. You may want to test more mechanically and exhaustively.
package tequilaguy;
public class ConverterHarness {
// private static void runOldEncoder() {
// for (char a='A'; a<='Z'; a++) {
// for (char b='A'; b<='Z'; b++) {
// for (char c='A'; c<='Z'; c++) {
// String str = new String(new char[] { a, b, c});
// byte[] enc = OldConverter.encode(str);
// System.out.format("%s : %02X%02X%n", str, enc[0], enc[1]);
// }
// }
// }
// }
private static void testNewConverter() {
for (char a='A'; a<='Z'; a++) {
for (char b='A'; b<='Z'; b++) {
for (char c='A'; c<='Z'; c++) {
String str = new String(new char[] { a, b, c});
byte[] oldEnc = OldConverter.encode(str);
byte[] newEnc = NewConverter.encode(str);
byte[] newEnc2 = NewConverter.encode("#" + str);
System.out.format("%s : %02X%02X %02X%02X %02X%02X %s %s %n",
str, oldEnc[0], oldEnc[1], newEnc[0], newEnc[1], newEnc2[0], newEnc2[1],
NewConverter.decode(newEnc), NewConverter.decode(newEnc2));
}
}
}
}
public static void main(String[] args) {
testNewConverter();
}
}
In your alphabet, you use only 15 of the 16 available bits of the output. So you could just set the MSB (most significant bit) if the string is of length 4 since the special char is fixed.
The other option is to use a translation table. Just create a String with all valid characters:
String valid = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ";
The index of a character in this string is the encoding in the output. Now create two arrays:
byte encode[] = new byte[256];
char decode[] = new char[valid.length ()];
for (int i=0; i<valid.length(); i++) {
char c = valid.charAt(i);
encode[c] = i;
decode[i] = c;
}
Now you can lookup the values for each direction in the arrays and add any character you like in any order.
You would find this a lot easier if you just used the java.nio.charset.CharsetEncoder class to convert your characters to bytes. It would even work for characters other than ASCII. Even String.getBytes would be a lot less code to the same basic effect.
If the "special char" is fixed and you're always aware that a 4 character String begins with this special char, then the char itself provides no useful information.
If the String is 3 characters in length, then do what you did before; if it's 4 characters, run the old algorithm on the String's substring starting with the 2nd character.
Am I thinking too simply or are you thinking too hard?
Related
I need to build a Caesar cipher that only encrypts letters, but no special characters. My concept was, to compare the input char[] with two alphabet char[]. If there is no match in a char, the char should be added to the String without being changed. The problem is that the not-changed char will be added to the String until the the for-loop ends. How do I fix this?
public static String encrypt(String text, int number) {
String str = "";
char[] chars = text.toCharArray();
char[] al = "abcdefghijklmnopqrstuvwxyz".toCharArray();
char[] ab = "abcdefghijklmnopqrstuvwxyz".toUpperCase().toCharArray();
for (char c : chars) {
boolean match = false;
for (int i = 1; i < chars.length - 1; i++) {
for (int k = 0; (k < al.length || k < ab.length) && !match; k++) {
match = (c == al[k] || c == ab[k]);
if (match) {
c += number;
str += c;
}
}
if (!match) {
str += c;
}
}
}
return str;
}
I already tried to put the case for not changing the string within the other for-loop, but it will be added until the for-loop has reached it's end.
I would tackle the problem by iterating through the String and considering the possible cases for each letter
Uppercase Letter
Lowercase Letter
Special Character
public static String encrypt(String text, int number) {
//String to hold our return value
String toReturn = "";
//Iterate across the string at each character
for (char c : text.toCharArray()){
if (Character.isUpperCase(c)){
/* If uppercase, add number to the character
If the character plus number is more than 90,
subtract 25 [uppercase letters have ASCII 65 to 90] */
toReturn += c + number > 90 ? (char)(c + number - 25) : (char)(c + number);
} else if (Character.isLowerCase(c)){
/* If lowercase, add number to the character
If the character plus number is more than 122,
subtract 25 [uppercase letters have ASCII 97 to 122] */
toReturn += c + number > 122 ? (char)(c + number - 25) : (char)(c + number);
} else {
// For other characters, just add it onto the return string
toReturn += c;
}
}
return toReturn;
}
Explanation of Code
You might be wondering what the following code does
toReturn += c + number > 90 ? (char)(c + number - 25) : (char)(c + number)
The structure is
toReturn += CONDITION ? A : B
It basically reads as
IF CONDITION IS TRUE, toReturn += A, ELSE toReturn += B
The CONDITION is simply c + number > 90 since we want to make sure that we are sticking with uppercase letters only
When this is true (A), we subtract 25 from c + number, otherwise (B) we just keep it as c + number (B)
We then cast this value into a char since it is initially an int
I want to invert a value of bit in digit.
The method should invert value by number of bit, like this:
public static void main(String[] args) {
int res = flipBit(7,1);
}
public static int flipBit(int value, int bitIndex) {
String bin = Integer.toBinaryString(value);
char newChar = (char) (bin.charAt(bitIndex) ^ bin.charAt(bitIndex));
//pseudo code
bin[bitIndex] = newChar;
return Integer.parseInt(bin);
}
Mixing mix bitwise operations and strings will not improve the performance and reduces the redubility of code.
Assuming that bitIndex is zero-based, it might be done using XOR operator like that (credits to #Iłya Bursov since he has pointed out it earlier in the comments):
public static int flipBit(int value, int bitIndex) {
if (bitIndex < 0 || bitIndex > 31) {
throw new IllegalArgumentException();
}
return value ^ 1 << bitIndex;
}
Online Demo
A quick recap on how XOR works.
1 ^ 1 => 0
0 ^ 1 => 1
1 ^ 0 => 1
0 ^ 0 => 0
That means zeros 0 in the bit-mask 1 << bitIndex, created by shifting the value of 1 by the given index, will have no impact on the result while applying XOR.
Only a single significant bit of the mask would interact with the value: if it would encounter 1, this bit would be turned into 0, or if there would be 0 at the same position it would result into 1.
Example:
value = 7, index = 2
111 - value
^
100 - bit-mask `1 << bitIndex`
011 - result is `3`
value = 0, index = 0
000 - value
^
001 - bit-mask `1 << bitIndex`
001 - result is `1`
This solution refers to:
I have string of binary in bin like "111" = 7. I need to change a bit in position bitIndex.
Currently bitIndex is zero-based and counted from the front of the string. (This might not be desired and could be changed with the use of bitIndex = binaryText.length() - 1 - bitIndex;)
public class Main
{
public static void main(String[] args) {
String bin = Integer.toBinaryString(7);
int bitIndex = 2;
System.out.println("Original string: " + bin);
System.out.println("String with flipped bit: " + flipBit(bin, 2));
try {
System.out.println("Flipping using number: " + flipBitViaNumber(bin, 2));
} catch (NumberFormatException ex) {
System.err.println("Oops! Not a number: " + bin);
}
System.out.println("Flipping via char array: " + flipBitViaCharArray(bin, 2));
}
public static String flipBit(String binaryText, int bitIndex) {
StringBuilder sb = new StringBuilder(binaryText.length());
for (int i = 0; i < binaryText.length(); i++) {
if (i == bitIndex) {
sb.append(binaryText.charAt(i) == '1' ? '0' : '1');
} else {
sb.append(binaryText.charAt(i));
}
}
return sb.toString();
}
public static String flipBitViaNumber(String binaryText, int bitIndex)
throws NumberFormatException {
int value = Integer.parseInt(binaryText, 2);
int pattern = 1 << (binaryText.length() - 1 - bitIndex);
value = value ^ pattern;
return Integer.toBinaryString(value);
}
public static String flipBitViaCharArray(String binaryText, int bitIndex)
throws NumberFormatException {
char[] chars = binaryText.toCharArray();
chars[bitIndex] = chars[bitIndex] == '1' ? '0' : '1';
return new String(chars);
}
}
Original string: 111
String with flipped bit: 110
Flipping using number: 110
Flipping via char array: 110
Additional information
A Java int consists of 32 bits, see https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html. Java uses the *two's complement as binary representation. Bitwise operations on ints are handled on the Java stack with particular bytecodes which are very fast as neither objects nor method invocations are used.
How about:
String bin = Integer.toBinaryString( value );
char newChar = (char) (bin.charAt(bitIndex) ^ bin.charAt(bitIndex));
StringBuilder sb = new StringBuilder( bin );
sb.setCharAt( bitIndex, newChar );
return sb.toString();
I am using StringBuilder to change a String input and shift it depending on input. This is for the META coding practice website and I am running into an issue with their two Test cases. One is passing and the other is not.
The expected output is stuvRPQrpq-999.#and the input is abcdZXYzxy-999.# with a shift of 200.
Here is my code
String rotationalCipher(String input, int rotationFactor) {
// Write your code here
int shift = rotationFactor % 26;
StringBuilder output = new StringBuilder();
for (char character : input.toCharArray()) {
if (character >= 'a' && character <= 'z') {
character = (char) (character + shift);
if (character > 'z') {
character = (char) (character + 'a' - 'z' - 1);
}
output.append(character);
} else if (character >= 'A' && character <= 'Z') {
character = (char) (character + shift);
if (character > 'Z') {
character = (char) (character + 'A' - 'Z' - 1);
}
output.append(character);
} else if (character >= '0' && character <= '9') {
character = (char) (character + shift);
if (character > '9') {
character = (char) (character + '0' - '9' - 1);
}
output.append(character);
} else {
output.append(character);
}
}
return output.toString();
}
My issue is that I am somehow outputting AAA instead of 999 as far as I can tell from tracing my algo seems solid. I looked through JAVA docs StringBuilder page to see if there was any issue with how I was using it. As far as I can tell it should be good to go.
Could anyone lend me an idea of why my output is the way it is?
Here is the test cases code:
String input_1 = "All-convoYs-9-be:Alert1.";
int rotationFactor_1 = 4;
String expected_1 = "Epp-gsrzsCw-3-fi:Epivx5.";
String output_1 = rotationalCipher(input_1, rotationFactor_1);
check(expected_1, output_1);
String input_2 = "abcdZXYzxy-999.#";
int rotationFactor_2 = 200;
String expected_2 = "stuvRPQrpq-999.#";
String output_2 = rotationalCipher(input_2, rotationFactor_2);
check(expected_2, output_2);
Check your maths
200 % 26 = 18 (shift)
'9' + 18 = 57 + 18 = 75 ('K')
75 + '0' = 75 + 48 = 123 ('{')
123 - '9' = 123 - 57 = 66 ('B')
66 - 1 = 65 ('A')
Now, the problem is, between '9' and 'A' there are 7 other characters, so character = (char) (character + ('0' - '9') - 1); would have to become character = (char) (character + ('0' - '9') - 9); to shift 9 back to 9, but that would screw up you other test case
I don't think ASCII manipulation is the way to go here, as there are characters in-between the digits and the upper and lower cased characters which are going to mess things up as the rotation increases.
In fact, for the digits, you really want to rotate using a factor % 10 instead.
A different approach would be to generate a list of characters and apply a shift to those instead. Now if I was doing this, I'd use List and Collections, but lets assume you can't do that for second, instead, we're going to need to apply a shift to an array, for example...
public String[] rotate(String[] original, int offset) {
if (offset >= 0) {
return positiveRotate(original, offset);
}
return negativeRotate(original, Math.abs(offset));
}
public String[] positiveRotate(String[] original, int offset) {
String[] results = new String[original.length];
int count = original.length - offset;
System.arraycopy(original, count, results, 0, offset);
System.arraycopy(original, 0, results, offset, count);
return results;
}
public String[] negativeRotate(String[] original, int offset) {
String[] results = new String[original.length];
System.arraycopy(original, offset, results, 0, original.length - offset);
System.arraycopy(original, 0, results, original.length - offset, offset);
return results;
}
Now, this has two different methods, one of a "positive" (or "right" shift) and one for a "negative" (or "left" shift). During testing, I found that the you want to "left shift" the array.
Next, we need what we want to shift over...
private String[] digits = "0123456789".split("");
private String[] characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
I've cheated here, you may need to create the array by long hand, but I can't be bothered typing it out.
Please note
I'm using a String array instead of a char array, not hard to change, but I'm been lazy
You could actually do this on the String directly, using things like contains and split to perform the shifting
And then the rotational cipher might look something like...
public String rotationalCipher(String input, int rotationFactor) {
int shift = rotationFactor % 26;
String[] shiftedDigits = rotate(digits, -(rotationFactor % 10));
String[] shiftCharacters = rotate(characters, -(rotationFactor % 26));
StringBuilder output = new StringBuilder();
for (char character : input.toCharArray()) {
String value = Character.toString(character);
int index = 0;
if ((index = indexOf(value, digits)) > -1) {
output.append(shiftedDigits[index]);
} else if ((index = indexOf(value, characters)) > -1) {
output.append(shiftCharacters[index]);
} else if ((index = indexOf(value.toUpperCase(), characters)) > -1) {
output.append(shiftCharacters[index].toLowerCase());
} else {
output.append(value);
}
}
return output.toString();
}
protected int indexOf(String value, String[] array) {
for (int index = 0; index < array.length; index++) {
if (array[index].equals(value)) {
return index;
}
}
return -1;
}
Then you could just execute it something like...
System.out.println(" --> All-convoYs-9-be:Alert1.");
System.out.println(" Got " + rotationalCipher("All-convoYs-9-be:Alert1.", 4));
System.out.println("Want Epp-gsrzsCw-3-fi:Epivx5.");
System.out.println("");
System.out.println(" --> abcdZXYzxy-999.#");
System.out.println(" Got " + rotationalCipher("abcdZXYzxy-999.#", 200));
System.out.println("Want stuvRPQrpq-999.#");
Which outputs
--> All-convoYs-9-be:Alert1.
Got Epp-gsrzsCw-3-fi:Epivx5.
Want Epp-gsrzsCw-3-fi:Epivx5.
--> abcdZXYzxy-999.#
Got stuvRPQrpq-999.#
Want stuvRPQrpq-999.#
As I said above, you could just use a String instead of an array, in which case it might look something more like...
private String digits = "0123456789";
private String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public String rotationalCipher(String input, int rotationFactor) {
int shift = rotationFactor % 26;
String shiftedDigits = rotate(digits, -(rotationFactor % 10));
String shiftCharacters = rotate(characters, -(rotationFactor % 26));
StringBuilder output = new StringBuilder();
for (char character : input.toCharArray()) {
String value = Character.toString(character);
int index = 0;
if ((index = digits.indexOf(value)) > -1) {
output.append(shiftedDigits.charAt(index));
} else if ((index = characters.indexOf(value)) > -1) {
output.append(shiftCharacters.charAt(index));
} else if ((index = characters.indexOf(value.toUpperCase())) > -1) {
output.append(Character.toLowerCase(shiftCharacters.charAt(index)));
} else {
output.append(value);
}
}
return output.toString();
}
public String rotate(String original, int offset) {
if (offset >= 0) {
return positiveRotate(original, offset);
}
return negativeRotate(original, Math.abs(offset));
}
public String positiveRotate(String original, int offset) {
String prefix = original.substring(original.length() - offset);
String sufix = original.substring(0, original.length() - offset);
return prefix + sufix;
}
public String negativeRotate(String original, int offset) {
String prefix = original.substring(offset);
String sufix = original.substring(0, offset);
return prefix + sufix;
}
This is what I have:
class encoded
{
public static void main(String[] args)
{
String s1 = "hello";
char[] ch = s1.toCharArray();
for(int i=0;i<ch.length;i++)
{
char c = (char) (((i - 'a' + 1) % 26) + 'a');
System.out.print(c);
}
}
}
So far I've converted the string to an array, and I've worked out how to shift, but now I'm stuck.
What I want is for the code to start at ch[0], read the character, shift it one to the right (h to i) and then do the same for each character in the array until it reaches the end.
Right now, my code outputs opqrs. I want it to output ifmmp. If I replace the int i = 0 in the for loop with int i = ch[0], it does start at i, but then it just inputs ijklmno...
I want it to read h, output as i, read e, output as f, and so on until it reaches the end of the array.
You are using the loop index i instead of the ith character in your loop, which means the output of your code does not depend the input String (well, except for the length of the output, which is the same as the length of the input).
Change
char c = (char) (((i - 'a' + 1) % 26) + 'a');
to
char c = (char) (((ch[i] - 'a' + 1) % 26) + 'a');
Replace i - 'a' + 1 with ch[i] - 'a' + 1
class encoded {
public static void main(String[] args)
{
String s1 = "hello";
char[] ch = s1.toCharArray();
for(int i=0;i<ch.length;i++)
{
char c = (char) (((ch[i] - 'a' + 1) % 26) + 'a');
System.out.print(c);
}
}
}
I'm trying to implement a basic Caesar Shift Cipher for Java to shift all the letters by 13. Here's my code so far.
public static String cipher(String sentence){
String s = "";
for(int i = 0; i < sentence.length(); i++){
char c = (char)(sentence.charAt(i) + 13);
if (c > 'z')
s += (char)(sentence.charAt(i) - 13);
else
s += (char)(sentence.charAt(i) + 13);
}
return s;
}
However, the program also changes the values of numbers and special characters and I don't want that.
String sentence = "abc123";
returns "nop>?#"
Is there a simple way to avoid the special characters and only focus on letters?
Edit: I should mention I want to keep all the other bits. So "abc123" would return "nop123".
In the following example I encrypt just the letters (more precisely A-Z and a-z) and added the possibility to use any offset:
public static String cipher(String sentence, int offset) {
String s = "";
for(int i = 0; i < sentence.length(); i++) {
char c = (char)(sentence.charAt(i));
if (c >= 'A' && c <= 'Z') {
s += (char)((c - 'A' + offset) % 26 + 'A');
} else if (c >= 'a' && c <= 'z') {
s += (char)((c - 'a' + offset) % 26 + 'a');
} else {
s += c;
}
}
return s;
}
Here some examples:
cipher("abcABCxyzXYZ123", 1) // output: "bcdBCDyzaYZA123"
cipher("abcABCxyzXYZ123", 2) // output: "cdeCDEzabZAB123"
cipher("abcABCxyzXYZ123", 13) // output: "nopNOPklmKLM123"
Note: Due to your code, I assumed that you just want to handle/encrypt the "ordinary" 26 letters. Which means letters like e.g. the german 'ü' (Character.isLetter('ü') will return true) remain unencrypted.
Problem is that you add 13 as a fixed number, and that will for some letters (in second half of alphabet mostly and digits) produce characters that aren't letters.
You could solve this by using array of letters and shifting through those characters. (similar for digits) So something like this
List<Character> chars = ... // list all characters, separate lists for upper/lower case
char c = chars.get((chars.indexOf(sentence.charAt(i)) + 13)%chars.size());