简体   繁体   中英

Convert number in a certain base to a decimal number using recursion

My assignment is to create a recursive method makeDecimal that when passed a number (that is represented by a String) and its base, converts the number to base 10. You will need to use the method Integer.parseInt(str) . (Hint: use substrings.) This method takes a String and returns the integer form of it.

For example, Integer.parseInt("21"); will return the int 21.

Here are some examples of how makeDecimal works:

makeDecimal("11", 2) will return 3.

makeDecimal("100", 4) will return 16.

Here was my attempt at it:

public static double makeDecimal(String number, int base){
    int len = number.length();
    double f = 0;

    if(len <= 0)
        return 0;
    else{
        makeDecimal(number,base);

        double temp = Integer.parseInt(number.substring(len - 1, len + 1));
        f = f + temp * Math.pow(3, len-1);
    }

    len--;
    return f;
}

However, I get an "overflow error", and I don't know if it even is written correctly.

You are recursing with exactly the same arguments that were passed in. As a result, the call will itself recurse the same way, until the stack overflows. That's not how recursion is supposed to work. Instead, you need to figure out how to do one piece of the problem in the current call and then recurse to do a smaller problem.

In your code, it's not even clear what logic you are using. (What's the point of computing 3 len -1 ?) Try this instead:

  • If the input string has length 0, the answer is 0 (that part you got right)
  • Otherwise, take the last digit and parse it in the current base. Then the answer is that value plus base times the value of everything up to but not including the last digit of the input. (Hint: this is a good place to use recursion.)

You should be able to translate that description into the appropriate method calls and use of substring() .

Oh, one other thing: there's no reason to be using double values here. Just stick with int variables throughout. You won't be needing Math.pow() .

Here is simplest solution using recursion, substring and Integer.parseInt :

public int makeDecimal(String value, int base) {
    // exit from recursion
    if (value.length() < 1) 
        return 0;

    //parsing last character of string
    int charValue = Integer.parseInt(value.substring(value.length() - 1), base);

    //calling recursion for string without last character
    return makeDecimal(value.substring(0, value.length() - 1), base) * base + charValue;
} 

Edit: At first I've misted that recursion is obligated for this solution so my original answer without it could me four below.

Here is solution with recursion and without substring and Math.pow :

public double makeDecimal(String value, int base) {
    makeDecimal(value, base, value.length() - 1);
}

public double makeDecimal(String value, int base, int index) {
    double result = 0;

    if (index < 0)
        return result;

    double charValue = 0;
    char currentChar =  values.get(Character.toUpperCase(value.charAt(index));
    if (currentChar >= 0 && currentChar <= '9') {
       charValue = currentChar - '0';
    } else if (currentChar >= 'A' && currentChar <= 'Z') {
        charValue = currentChar - 'A';
    } else {
        throw new InvalidValueException("Unsupported character '" + currentChar + "'.");
    }

    if (charValue >= base) {
        throw new InvalidValueException("Wrong character '" + currentChar + "' for base '" base + "'.");
    }

    return makeDecimal(value, base, index - 1)) * base + charValue;
} 

Original Answer: Something like this should work for any base starting from 2 till 36:

private Map<Character, Integer> getCharValues(int base)
{
    Map<Character, Integer> values = new HashMap<Character, Integer>();
    for (int i = 0; i < base; i++){
        if (i < 10) {
            values.put('0' + i, i);
        } else if (i < 36) {
            values.put('A' + i - 10, i);
        } else {
            throw new InvalidValueException("Base '" + base + "' not supported");
        }
    }
    return values;
}

public double makeDecimal(String value, int base)
{
    Map<Character, Integer> charValues = getCharValues(base);
    double result = 0;
    for (int i = 0; i < value.length(); i++){
        result = result * base + charValues.get(Character.toUpperCase(Character.toUpperCase(value.charAt(i))));
    }
    return result;
}

If you need base more then 36 you can extend char set in method getCharValues . Also it will be a good idea do not create HasMap every time but just store it for maximum base and throw exception if char value exceed given base.

Here's my solution after writing the prototype in Python (if you are interested, I can also include the Python source code):

import java.util.HashMap;
import java.util.Map;

public class MakeDecimal {

    public static final Map<Character, Integer> alphabet = buildAlphabetTable();

    public static void main(String[] args) {

        // System.out.println(alphabet);
        System.out.println(makeDecimal("af10bb1", 16));
    }

    // pos refers to the position of the character in the string.
    // For example, if you have the following binary string 100
    // then 1 at the left is at position 2, 
    // the 0 in the middle is at position 1,
    // and the right most 0 is at position 0 
    // (you start counting from the right side).
    // In general, you would convert that string in the following way:
    // 2^2 * 1 + 2^1 * 0 + 2^0 * 0 = 4

    // If the base was n, then you would have
    // first * n^{pos + "length of string - 1"} + ... + penultimate * n^{pos + 1} + last * n^{pos}
    // Note that pos starts from 0.
    private static int makeDecimal(String s, int base, int pos) {
        if (s.length() == 0) {
            return 0;
        } else {
            int last = (int) Math.pow(base, pos) * alphabet.get(s.charAt(s.length() - 1));
            return makeDecimal(s.substring(0, s.length() - 1), base, pos + 1) + last;
        }
    }

    public static int makeDecimal(String s, int base) {
        if (s.length() == 0) {
            return 0;
        }

        if (base < 2 || base > 36) {
            throw new IllegalArgumentException("not base >= 2 and base <= 36");
        }

        return makeDecimal(s.toLowerCase(), base, 0);
    }


    // Creates a table that maps characters to their decimal value
    // the characters can be also '0' or '2' (or any other character number)
    // or they can be a character of the English alphabet
    private static Map<Character, Integer> buildAlphabetTable() {
        Map<Character, Integer> a = new HashMap<>(36);

        int i = 0;

        for (char c = '0'; c <= '9'; c++, i++) {
            a.put(c, i);
        }

        for (char c = 'a'; c <= 'z'; c++, i++) {
            a.put(c, i);
        }

        return a;
    }

}

My solution is based on the following post, which you should definitely read to refresh your ideas on how to convert between bases.

http://www.purplemath.com/modules/numbbase.htm

It does not accept bases that are smaller than 2 or greater than 36. It handles also when you pass English characters in upper case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM