pondigi
pondigi

Reputation: 906

How to convert integers to base64 (0-9A-Za-z)

I have been trying to reduce the length of the way. I represent some integer ID's in my program. For Example

2
3
15
26
63
...
151564852

I would like them to be represented as such (0-9A-Za-z only)

2
3
F
Q
z
...
vDF25a   //For example

The approach I thought of is to have 63 if statements which each of the mappings from 0-63 to 0-z respectively and for anything above 64 do a recursion on the value minus 63.

Needless to say, I think my approach is very flawed and impractical. What would be a more appropriate way of doing it?


Update:

Following fge's suggestion I've got the encoder to work correctly, however my decode function only works for up-to length 2 strings, in cases where the string is larger the sum becomes erroneous. For example for 3840 to 3845 this is the output

// Encoded 
zw
x
zy
zz
100

// Decoded
3840
3841
3842
3843
124         //Invalid decoding

Here is my code for the decode function

public static int decode(String value)
{
    String revStr = new StringBuilder(value).reverse().toString();

    int sum = 0;



    for (int i=1; i < revStr.length(); i++)
    {
        for (int j=0; j < ALPHABET.length; j++)
        {
            if (ALPHABET[j] == revStr.charAt(i))
            {
                sum += (ALPHABET.length * j) * i;
                break;
            }
        }
    }

    for (int j=0; j < ALPHABET.length; j++)
    {
        if (ALPHABET[j] == revStr.charAt(0))
        {
            sum += j;
            break;
        }
    }

    return sum;
}

Upvotes: 5

Views: 23423

Answers (4)

Igor
Igor

Reputation: 688

My thanks to the @fge answer. Using it with some changes I've get it to support much larger integers with BigInteger, added support of negative integers and changed Arrays.binarySearch() with HashMap.

BTW, it should be called base62 encoding because [0-9A-Za-z] contains just 62 chars.

public class Base62{

    private static final char[] ALPHABET = new char[ 62 ];
    private static final Map<Character, Integer> ALPHABET_MAPPING = new HashMap<>();
    private static final BigInteger ENCODE_LENGTH = BigInteger.valueOf( ALPHABET.length );

    static{
        int position = 0;
        // numbers
        for( int i = 48; i <= 57; i++ ){
            ALPHABET[ position++ ] = (char)i;
        }
        // uppercase letters
        for( int i = 65; i <= 90; i++ ){
            ALPHABET[ position++ ] = (char)i;
        }
        // lowercase letters
        for( int i = 97; i <= 122; i++ ){
            ALPHABET[ position++ ] = (char)i;
        }
        for( int i = 0; i < ALPHABET.length; i++ ){
            ALPHABET_MAPPING.put( ALPHABET[ i ], i );
        }
    }

    public static String encode( final BigInteger in ){
        final List<Character> list = new ArrayList<>();
        boolean negative = in.signum() == -1;
        BigInteger use;
        if( negative ){
            use = in.negate();
        } else {
            use = in;
        }

        do{
            BigInteger[] divisionResultAndReminder = use.divideAndRemainder( ENCODE_LENGTH );
            list.add( ALPHABET[ divisionResultAndReminder[ 1 ].intValue() ] );
            use = divisionResultAndReminder[ 0 ];
        } while( use.equals( BigInteger.ZERO ) == false );

        Collections.reverse( list );

        char[] res = new char[ list.size() ];
        for( int i = 0; i < list.size(); i++ ){
            res[ i ] = list.get( i );
        }

        return ( negative ? "-" : "" ) + new String( res );
    }

    public static BigInteger decode( final String encoded ){
        BigInteger res = BigInteger.ZERO;
        char c;
        boolean negative;
        String use;
        if( '-' == encoded.charAt( 0 ) ){
            negative = true;
            use = encoded.substring( 1 );
        } else {
            negative = false;
            use = encoded;
        }

        for( int index = 0; index < use.length(); index++ ){
            c = use.charAt( index );
            res = res.multiply( ENCODE_LENGTH );
            res = res.add( BigInteger.valueOf( ALPHABET_MAPPING.get( c ) ) );
        }
        return negative ? res.negate() : res;
    }
}

Upvotes: 1

fge
fge

Reputation: 121810

This is not base64; base64 encodes binary data.

Anyway, you don't need a s*load of if statements; use an array:

public final class AlphabetEncoder
{
    private static final char[] ALPHABET = { '0', '1', '2', ...., 'z' };
    private static final int ENCODE_LENGTH = ALPHABET.length;

    public static String encode(int victim)
    {
        final List<Character> list = new ArrayList<>();

        do {
            list.add(ALPHABET[victim % ENCODE_LENGTH]);
            victim /= ENCODE_LENGTH;
        } while (victim > 0);

        Collections.reverse(list);
        return new String(list.toArray(new char[list.size()],
            StandardCharsets.UTF_8);
    }

    public int decode(final String encoded)
    {
        int ret = 0;
        char c;
        for (int index = 0; index < encoded.length(); index++) {
            c = encoded.charAt(index);
            ret *= ENCODE_LENGTH;
            ret += Arrays.binarySearch(ALPHABET, c);
       }
       return ret;
    }
}

NOTE ABOUT THE DECODE FUNCTION: it is possible to use Arrays.binarySearch() here since the alphabet has the nice property of being naturally sorted (0 < 1 < 2 < ... < z). However, a test should probably be added that its return code not be negative!

Upvotes: 5

Uday Shankar
Uday Shankar

Reputation: 844

You can refer to already existing logic for converting from Decimal [0-9] to Hexadecimal conversion present in Integer class and extend the logic for your Base 64 converison. Refer

Integer.toHexString(int i)

This maybe the efficient implementation for conversion.

Upvotes: 4

ctutte
ctutte

Reputation: 131

Depending on the language you use, there should already be a module which converts a string from/to base64. Check this other post: Base64 Encoding in Java

Upvotes: 2

Related Questions