6
\$\begingroup\$

I had an interview recently and discovered that I'd forgotten some of the basics. I've been playing around again and have written a function that will take a binary string (there is no validation yet) and returns the ASCII representation of said string.

I'm looking for advice or tips on how it could be improved. I don't want to use any of the API functions, this is more of a playground scenario in which I might be able to learn something.

public static String convertBinaryStringToString(String string){
    StringBuilder sb = new StringBuilder();
    char[] chars = string.replaceAll("\\s", "").toCharArray();
    int [] mapping = {1,2,4,8,16,32,64,128};

    for (int j = 0; j < chars.length; j+=8) {
        int idx = 0;
        int sum = 0;
        for (int i = 7; i>= 0; i--) {
            if (chars[i+j] == '1') {
                sum += mapping[idx];
            }
            idx++;
        }
        System.out.println(sum);//debug
        sb.append(Character.toChars(sum));
    }
    return sb.toString();
}

Sample output:

01101000 01100101 01101100 01101100 01101111 
104
101
108
108
111
hello
\$\endgroup\$

2 Answers 2

8
\$\begingroup\$
int [] mapping = {1,2,4,8,16,32,64,128};

This seems avoidable - array access could be replaced with a bitshift. It'd also clear up what you're doing...

for (int j = 0; j < chars.length; j+=8) {
    int idx = 0;
    int sum = 0;
    for (int i = 7; i>= 0; i--) {
        if (chars[i+j] == '1') {
            sum += 1 << idx;
        }
        idx++;
    }
    System.out.println(sum);//debug
    sb.append(Character.toChars(sum));
}

It's only after making this change that I understand what you're doing! You're splitting the input string into segments that represent a single character (I got that far already), but after that, you start at the back of the represented character, and check for each bit representing character that it is '1'. If it's '1' (and thus not '0'), you add the value that the bit would represent to sum. Once you've gone through a single character representation, you let Character.toChars cast the int-value of the character back to a character. Then you add it to the result buffer, which will form the full string.

Phew.

You need more comments, so that other developers can read and understand directly how this method works.

Specifically, 1 comment to explain what your replaceAll does (regex are not obvious to me and I have to look them up), and 1 comment to say that you're parsing bits in reverse per character.

Maybe like so:

//for each character
for (int j = 0; j < chars.length; j+=8) {
    int idx = 0;
    int sum = 0;
    //for each bit in reverse
    for (int i = 7; i>= 0; i--) {
        if (chars[i+j] == '1') {
            sum += 1 << idx;
        }
        idx++;
    }
    System.out.println(sum);//debug
    sb.append(Character.toChars(sum));
}

Getting rid of all the built-in functions (for fun only)

So you want to do everything yourself?

This is bad style, since everything in java.lang (and in generally, java.util as well) can be relied on without turning this into a puzzle for your fellow programmers, but I'll humor you.

First, you can get rid of the String.split via Thomas Junk's answer, incrementing by 9 each time instead.

public static String convertBinaryStringToString(String string){
    StringBuilder sb = new StringBuilder();
    char[] chars = string.toCharArray();

    //for each character
    for (int j = 0; j < chars.length; j+=9) {
        int idx = 0;
        int sum = 0;
        //for each bit in reverse
        for (int i = 7; i>= 0; i--) {
            if (chars[i+j] == '1') {
                sum += 1 << idx;
            }
            idx++;
        }
        System.out.println(sum);//debug
        sb.append(Character.toChars(sum));
    }
    return sb.toString();
}

Leaves us with this bit of code.

We can get rid of the StringBuilder by writing to a char[]. We can get rid of Character.toChars(sum) with (char) sum. We do have to make a new string out of the character array, though.

public static String convertBinaryStringToString(String string){
    char[] chars = string.toCharArray();
    char[] transcoded = new char[(chars.length / 9)+1];

    //for each character (plus one for spacing)
    for (int j = 0; j < chars.length; j+=9) {
        int idx = 0;
        int sum = 0;

        //for each bit in reverse
        for (int i = 7; i>= 0; i--) {
            if (chars[i+j] == '1') {
                sum += 1 << idx;
            }
            idx++;
        }
        transcoded[j/9] = (char) sum;
    }
    return new String(transcoded);
}

And voila, we're rid of most of the built-ins. There's no way to get the contents of a String without use of built-ins, however, so string.toCharArray or string.charAt is required.

\$\endgroup\$
7
  • 1
    \$\begingroup\$ thankyou! bitswise ops are something I'm not great at so this certainly helps, thank you. Have you any suggestion for how I might go about removing "Character.toChars(sum)"? \$\endgroup\$
    – null
    Apr 30, 2015 at 13:19
  • 1
    \$\begingroup\$ @SteveGreen It's pretty nasty, but (char) sum would do. I tried System.out.println((char)104); just now and got h. \$\endgroup\$
    – Pimgd
    Apr 30, 2015 at 13:21
  • \$\begingroup\$ that is pretty rough :) \$\endgroup\$
    – null
    Apr 30, 2015 at 13:24
  • 1
    \$\begingroup\$ @SteveGreen updated my answer to get rid of most if not all of the built-in calls... but that's really not recommended, it took nice code and butchered it. \$\endgroup\$
    – Pimgd
    Apr 30, 2015 at 13:33
  • \$\begingroup\$ thanks for that, I really appreciate it. I think I'll stop at your first suggestion of using bitwise and +=9. Butchering the code as you pointed out was never my intention but I have a much better understanding now. Thanks for solving the puzzle :D \$\endgroup\$
    – null
    Apr 30, 2015 at 13:37
4
\$\begingroup\$

That's okay. You could improve on your current design by these minor changes:

1) If you know, that the binaries are in blocks of 8 chars each, you could simply omit filtering the noise and incement by 9 instead of 8 characters.

2) You do not need to split the string into an array. String.charAt() does, what need.

public static String convertBinaryStringToString(String string){
    StringBuilder sb = new StringBuilder();
    int [] mapping = {1,2,4,8,16,32,64,128};
    for (int j = 0; j < string.length(); j+=9) {
        int idx = 0;
        int sum = 0;
        for (int i = 7; i>= 0; i--) {
            if (string.charAt(i+j) == '1') {
                sum += mapping[idx];
            }
            idx++;
        }
        System.out.println(sum);//debug
        sb.append(Character.toChars(sum));
    }
    return sb.toString();
}

Perhaps you could go one step further: Instead of iterating over each block, you could make use of the fact, that between each block is a delimiter ( in your case). So you could get blocks by simply chopping up the string with string.split(" "). This makes it possible to do a forEach on the resulting blocks. You could refactor the inner for-loop out.

public static String convertBinaryStringToString(String string){
    StringBuilder sb = new StringBuilder();
    List<String> blocks = Arrays.asList(string.split(" "));

    for (String block:blocks){
        int result=convertBlock(block);
        System.out.println(result);
        sb.append(Character.toChars(result));
    }
    return sb.toString();
}

private static int convertBlock(String block) {
    int [] mapping = {128,64,32,16,8,4,2,1};
    int sum = 0;
    int blockPosition= block.length()-1;
    while(blockPosition>0){
        if(block.charAt(blockPosition)=='1') sum+=mapping[blockPosition];
        blockPosition--;
    }
    return sum;
}

If you take this as a starting point, you could do it in Java8 as follows:

public static String convertBinaryStringToString(String string){
    int sum=0;
    List<String> blocks = Arrays.asList(string.split(" "));
    List<Integer> numbers=blocks.stream().map(block -> {
        int[] mapping = {128, 64, 32, 16, 8, 4, 2, 1};
        return IntStream.range(0, block.length()).reduce(0, (o, n) -> {
            if (block.charAt(n) == '1') o += mapping[n];
            return o;
        });
    }).collect(Collectors.toList());
    numbers.forEach(System.out::println);
    StringBuilder result= new StringBuilder();
    numbers.forEach(x->{
       result.append(Character.toChars(x));
    });
    return result.toString();
}
\$\endgroup\$
3
  • \$\begingroup\$ them skills :D thank you for the suggestsions and for taking the time to reply. I intend on amalgamating both the answers to some degree. I like the idea of bitshifting but I'd also very much like to get that inner loop removed. Thanks! Any suggestions for how to rewrite the Character.charAt call? \$\endgroup\$
    – null
    Apr 30, 2015 at 13:23
  • \$\begingroup\$ I saw the solution with bitshifting after posting this one. That's ouf course nicer ;) \$\endgroup\$ Apr 30, 2015 at 13:36
  • \$\begingroup\$ it was nice to see some Java 8 code, we're still using 6 at the moment and I doubt that'l change anytime soon. :) \$\endgroup\$
    – null
    Apr 30, 2015 at 13:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.