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;
}
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'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());
I am trying to solve a problem . Hoping for some help here.
Objective: Find a number which is immediate next to input and contains only 4 and 7 in it.
Input : 1234
Output: 4444
Input : 4476
output: 4477
Input : 8327
Output : 44444
I am not looking for incrementing number and each time checking string characters for the pattern. That would be too slow for large numbers.
static String toOutput (int a) {
// I am trying here all the possible other ways
}
Check this answer. Hope this helps :)
private static String toOutput(int n) {
String input = String.valueOf(n+1);
// create input character array and output character array of one more in size
char[] inputChars = input.toCharArray();
char[] outputChars = new char[inputChars.length + 1];
boolean extra = false; //carry forward
// traverse input array from last position to first position
for (int i = inputChars.length - 1; i >= 0; i--) {
// for all other positions except last position check whether number is changed
// (i.e. apart from 4 or 7),
// change all higher digits in output array to 4
if ((i + 1) < inputChars.length) {
if (inputChars[i] != '4' && inputChars[i] != '7') {
for (int j = i + 1; j < inputChars.length; j++) {
outputChars[j + 1] = '4';
}
}
}
// if extra is true that means it is carry forward
if (extra == true) {
inputChars[i] = (char) ((int) inputChars[i] + 1);
}
// if input digit is less than equal to 4 output digit is 4 , extra is false
if (inputChars[i] <= '4') {
outputChars[i + 1] = '4';
extra = false;
}
// if input digit is between 4 to 7 output digit is 7 , extra is false
else if (inputChars[i] <= '7') {
outputChars[i + 1] = '7';
extra = false;
}
// if input digit is more than 7 output digit is 4 , extra is true
else {
outputChars[i + 1] = '4';
extra = true;
}
}
// if carry forward is true, make extra digit to 4 otherwise it is not required
if (extra == true) {
outputChars[0] = '4';
} else {
outputChars[0] = ' ';
}
return new String(outputChars).trim();
}
static String toOutput (long a) {
LinkedList<Long> q = new LinkedList<Long>();
q.add(4L);
q.add(7L);
while(!q.isEmpty()){
Long curr = q.pop();
if(curr>a)
return String.valueOf(curr);
q.add(curr*10+4);
q.add(curr*10+7);
}
return "";
}
This will solve the problem in close to O(LogN)
Since this is fundamentally a manipulation of character strings, a plausible solution is to use string functions, particularly regular expressions. Here's a compact solution:
class Incrementer {
Pattern p;
public Incrementer() {
p = Pattern.compile("(?:([47]*)([0-6]))?(.*)");
}
public String next(String s) {
Matcher m = p.matcher(s);
m.lookingAt();
return (m.group(1)==null
? '4'
: m.group(1) + (m.group(2).charAt(0) >= '4' ? '7' : '4'))
+ m.group(3).replaceAll(".", "4");
}
}
See it here.
(I'm not at all a Java programmer. Coding suggestions welcome.)
The regular expression matches the prefix of any sequence of legal digits (4 or 7) followed by an incrementable digit ( < 7). If that prefix is not matchable, the answer must be one digit longer, so it must start with the smallest legal digit (4). If the prefix is matchable, the prefix must be modified by bumping the last digit to the next legal digit. In both cases, all the digits following the (possibly empty) prefix are replaced with the smallest legal digit.
Of course, this could be done without actual regular expressions. The following essentially uses a state machine which implements the regular expression, so it might be faster. (Personally I find the regex version easier to verify, but YMMV):
public static String next(String s)
{
int toinc = -1;
for (int i = 0; i < s.length(); ++i) {
char c = s.charAt(i);
if (c < '7') {
toinc = i;
if (c != '4') break;
} else if (c > '7') break;
}
char[] outChars;
// Copy the prefix up to and including the character to be incremented
if (toinc < 0) {
outChars = new char[s.length() + 1];
} else {
outChars = new char[s.length()];
for (int i = 0; i < toinc; ++i)
outChars[i] = s.charAt(i);
// Increment the character to be incremented
outChars[toinc] = s.charAt(toinc) >= '4' ? '7' : '4';
}
// Fill with 4's.
for (int i = toinc + 1; i < outChars.length; ++i)
outChars[i] = '4';
return new String(outChars);
}
See it here.
*
public class PatternTest {
private static final char FOUR = '4';
private static final char SEVEN = '7';
public static void main(String[] args) {
Scanner scanner = new Scanner(new InputStreamReader(System.in));
String value = scanner.next();
char startChar = value.charAt(0);
Result result;
if (startChar == FOUR || startChar == SEVEN) {
result = getStartWith4Or7(value);
} else {
result = getNotStartWith4Or7(value);
}
System.out.println("Final value is : " + result.getValue());
}
private static Result getNotStartWith4Or7(String value) {
Result result = new Result();
char startChar = value.charAt(0);
if (startChar < FOUR) {
result.value = value.replaceAll(".", String.valueOf(FOUR));
} else if (startChar > SEVEN) {
result.value = value.replaceAll(".", String.valueOf(FOUR));
result.flag = FOUR;
} else if (startChar > FOUR && startChar < SEVEN) {
result.value = getSubString(value).replaceAll(".", String.valueOf(FOUR));
result.value = String.valueOf(SEVEN) + result.value;
}
return result;
}
private static Result getStartWith4Or7(String value) {
Result result = new Result();
if (value != null && !value.equalsIgnoreCase("")) {
char startChar = value.charAt(0);
if (startChar == FOUR || startChar == SEVEN) {
value = getSubString(value);
result = getStartWith4Or7(value);
result.value = getStartCharUpdate(startChar, result) + result.value;
} else {
result = getNotStartWith4Or7(value);
}
}
return result;
}
private static String getStartCharUpdate(char startChar, Result result) {
String newValue = String.valueOf(startChar);
if (result.flag == FOUR) {
if (startChar == FOUR) {
newValue = String.valueOf(SEVEN);
result.flag = 0;
} else {
newValue = String.valueOf(FOUR);
}
}
return newValue;
}
private static String getSubString(String value) {
int len = value.length();
String finalValue = "";
if (len > 1) {
finalValue = value.substring(1, len);
}
return finalValue;
}
static class Result {
String value = "";
char flag;
public String getValue() {
if (flag == FOUR) {
value = FOUR + value;
}
return value;
}
}
}
*
You can try something like this.
String num = "4476";
double immediateVal = 0;
int length = num.length();
StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
for (int i = 0; i < length; i++) {
sb1.append("4");
sb2.append("7");
}
double valNum = Double.parseDouble(num);
double val1 = Double.parseDouble(sb1.toString());
double val2 = Double.parseDouble(sb2.toString());
if (valNum < val1) {
immediateVal = val1;
} else if (val1 <= valNum && valNum < val2) {
if(num.indexOf("4")==0){
int firstIndexOf7=-1;
for(int a=0;a<length;a++){
firstIndexOf7=num.indexOf("7");
if(firstIndexOf7!=-1){
break;
}
}
if(firstIndexOf7!=-1){
StringBuilder sb3=new StringBuilder();
for(int b=0;b<firstIndexOf7;b++){
sb3.append("4");
}
for(int b=firstIndexOf7;b<length;b++){
sb3.append("7");
}
immediateVal=Double.parseDouble(sb3.toString());
}
} else{
immediateVal = val2;
}
}else if(valNum>=val2){
immediateVal=Double.parseDouble(sb1.append("4").toString());
}
System.out.println(immediateVal);
this can help you
static String toOutput(String input){
char[] values = input.toCharArray();
StringBuilder result = new StringBuilder();
int length = values.length;
int currentPosition = 0;
for(char current: values){
Integer currentInt = new Integer(current);
if(currentInt<4){
//fill the string with 4, best result
for(currentPosition;currentPosition<length;currentPosition++){
result.append(4);
}
break;
}
else if(currentInt==4){
result.append(4);
currentPosition++;
continue;
}
else if(currentInt<=7){
result.append(7);
currentPosition++;
continue;
}
else if(currentInt>7){
if(currentPosition=0){
//fill the string with 4 but with length +1, best result
for(currentPosition;currentPosition<=length;currentPosition++){
result.append(4);
}
break;
}
else{
// you need work this case, change last 4 to 7 and fill the rest with 4. enjoy it.
}
}
return result.toString();
}
One approach would be reading the digits right to left and check if that is less than 4 or 7 and add 4 or 7 respectively.
One optimization would be check if first(from left) digit is >7 then its sure that you will have all 4's +1 extra 4`.
You need to take extra care at the left most digit. If the left most digit is greater than 7 you need to add two 4s.
EX: 1234
right to left `4` is `4`
`3` is `4`
`2` is `4`
`1` is `4`
This approach wont work if all the digits in the number are 4 or 7. You need to have one condition and change one or two chars accordingly.
private static String toOutput(int a) {
String s = Integer.toString(a);
StringBuilder sb = new StringBuilder();
if (Integer.valueOf(s.charAt(0)) > 7) {
for(int i=0; i<= s.length(); i++) {
sb.append("4");
}
return sb.toString();
}
for(int i=s.length()-1; i>=0; i--) {
Integer x = Integer.valueOf(i);
if(x <=4) {
sb.append(4);
} else {
sb.append(7);
}
}
return sb.reverse().toString();
}
This is not checking if the number has all 4's or 7's.
I don't think you need to worry about performance for converting a number into a String. You're evaluating a number as if it's a string, so it only makes sense to cast it to string to do the evaluation.
Something like this works and is reasonably fast.
public static int closestNumber(int num) {
for (int i = num + 1; i < Integer.MAX_VALUE; i++) {
if ((i+"").matches("^[47]+$")) {
return i;
}
}
throw new RuntimeException("Your number was too close to Integer's max value!");
}
Ok, I wrote it using mod and no strings:
public static int closestNumber(int num) {
for (int i = num + 1; i < Integer.MAX_VALUE; i++) {
if (containsOnly(i,4,7)) {
return i;
}
}
throw new RuntimeException("Your number was too close to Integer's max value!");
}
public static boolean containsOnly(int evaluate, int numeralA, int numeralB) {
while (evaluate > 0) {
int digit = evaluate % 10;
if (digit != numeralA && digit != numeralB) return false;
evaluate = evaluate / 10;
}
return true;
}
It looks like looking for the first digit that is not 4 or 7 counting from left to right.
Set up a index pointer to record the last index that hold digit "4" and check every digit from left to right.
Initial current pointer index (c) to -1 and set last "4" index (l) to -1
For every digit from left to right
update c (c+=1)
check digit value
digit = 7 -> do nothing
digit = 4 -> l = c
digit < 4 -> this digit change to "4", all remaining digits sets to "4", end check
4 < digit < 7 -> this digit change to "7", all remaining digits sets to "4", end check
digit > 7 -> do necessary change and end check
l = -1 => 444....444 (no. of digit = n+1)
l > -1 => digit at l change to "7", all digits after l change to "4"
Idea
For a n-digit value, if it contains only "4" or "7", you do nothing.
Then, if there is any non "4" or "7", what should it be?
Analyzing the pattern, we need to know the first occurrence of non "4"/"7" digit (from left to right) only and all digits after the digit will change to "4" to minimize the value since 444...444 is the least k-digit value for combination of "4" and "7" for all k.
Consider case Xcccccccc , c is any value
If X in {4,7}, consider case 2.
If X in {1,2,3}, the next value should be 444444444.
If X in {5,6}, the next value should be 744444444.
If X in {8,9}, the next value should be 4444444444
Consider case aaaaXcccc, if a are "4" or "7"
If X in {4,7}, consider case aaaaaXccc.
If X in {0,1,2,3}, the next value should be aaaa44444.
If X in {5,6}, the next value should be aaaa74444.
If X in {8,9}, the next value should be bbbb44444 or bbbbb44444.(b are "4" or "7")
then how to deduce bbbb or bbbbb?
if aaaa does not have any "4", you get bbbbb = 44444 (since aaaa=7777)
if aaaa have "4", you get bbbb ("4" will be replaced by "7", e.g. 474779 => 477444)
Consider case aaaaaaaaX, this should be same as case 2 except no remaining digit need to be handle
Combine case 1-3, when the first occurrence of non "4"/"7" digit is in {8,9}, the difference in change of value depends on whether there is any "4" before the digit.
I have this loop which splits a Boolean LinkedList by 8 bits and return the ASCII value of each byte in a buffer. The function return the string buffer.
This code is extremely slow if the LinkedList's size is big. I try to change the Iterator with a simple looping, but it's still slow.
How can this algorithm be really fast ? Maybe with multi-threading ?
Note: The size of the linkedList is not always divisible by 8.
public String toString(){
String buffer = "";
String output = "";
LinkedList<Boolean> bits = this.bits;
for(Iterator it = this.bits.iterator(); it.hasNext();){
if(buffer.length() >= 8){
output += (char)Integer.parseInt(buffer, 2);
buffer = "";
}
buffer += ((Boolean)it.next() == false) ? "0" : "1";
}
if(buffer != "")
output += (char)Integer.parseInt(buffer, 2);
return output;
}
These suggestions will give you enough performance still keeping the code simple and readable. First test using these changes and if doesn't meet your performance requirements then slowly introduce optimization techniques suggested in other answers
Use BitSet instead of LinkedList<Boolean>
use StringBuilder output; instead of String output;
use StringBuilder buffer; instead of String buffer;
Use Integer.valueOf() instead of Integer.parseInt. valueOf uses cache for values below 128 i think.
Use StringBuilder initialized with expected capacity for output:
StringBuilder out = new StringBuilder(bits.size() / 8 + 1);
Use bitwise operations instead of parseInt(), something like this:
int i = 0;
int c = 0;
for(Boolean b: bits){
if (i > 0 && i % 8 == 0){
out.append((char) c);
c = 0;
}
c = (c << 1) | (b ? 1 : 0);
i++;
}
out.append((char) c); // Not sure about the desired behaviour here
String concatenation is slow especially for large lists (since strings are immutable they have to be copied around which takes some time and each copy requires more space as well). Use a StringBuilder instead of a String to append to. In other words: buffer and output should be StringBuilder instances.
As others suggested - use BitSet. For the rest, I think the method below is pretty efficient:
public String toString() {
char[] bytes = new char[bits.size() / 8 + ((bits.size() % 8 > 0) ? 1 : 0)];
int bitCounter = 0;
int word = 0;
int byteCounter = 0;
for (boolean b : bits) {
word = (word << 1) | (b ? 1 : 0);
if (bitCounter == 7) {
bytes[byteCounter] = (char) word;
++byteCounter;
bitCounter = 0;
word = 0;
} else {
++bitCounter;
} // else
} // foreach
bytes[byteCounter] = (char) word;
return new String(bytes);
} // toString() method
Here is possibly a better alternative that does not use byte counter:
public String toString() {
int size = bits.size() / 8 + ((bits.size() % 8 > 0) ? 1 : 0);
if (size == 0) {
return "";
} // if
char[] bytes = new char[size];
int bitCounter = 0;
int word = 0;
for (boolean b : bits) {
if (bitCounter % 8 == 0
&& bitCounter > 0) {
bytes[(bitCounter - 1) / 8] = (char) word;
word = 0;
} // if
word = (word << 1) | (b ? 1 : 0);
++bitCounter;
} // foreach
bytes[size - 1] = (char) word;
return new String(bytes);
} // toString() method
Try to keep buffer as an int. I mean
buffer = buffer << 1 + (((Boolean)it.next() == false) ? 0 : 1);
instead of
buffer += ((Boolean)it.next() == false) ? "0" : "1";
Also use StringBuilder for output. This is a small change here but always a bit.
Try the following:
StringBuilder b = new StringBuilder();
int ch = 0;
int n = 0;
for (Boolean bit : bits) {
ch <<= 1;
if (bit) {
ch++;
}
if (++n == 8) {
b.append((char)ch);
n = 0;
ch = 0;
}
}
if (n > 0) {
b.append((char)ch);
}
System.out.println(b.toString());
Use StringBuffer or stringBuilder instead of String for your buffer and output vars.
String vars are immutable, so every operations creates a new instance in heap, while StringBuilder and StringBuffer are not.
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?