10
\$\begingroup\$

Below is a bit of code which divides an alphanumeric string to a map of numbers and strings (assume numbers are single digits and non-repeating).

Example input:

String alphaNumericStr = "1abcdef2xyz9aaa6xxx";        

Output:

Map<Integer, String> map = {1=abcdef, 2=xyz, 9=aaa, 6=xxx}

This is the code i wrote. While it gets the work done, this does not look like a clean solution to me. How can I make this better?

    String alphaNumericStr = "1abcdef2xyz9aaa6xxx";

    Map<Integer, String> map = new LinkedHashMap<Integer, String>();

    Character key = null;
    StringBuffer s = new StringBuffer();

    for (int i = 0; i < alphaNumericStr.length(); i++) {
        Character c = alphaNumericStr.charAt(i);

        if (Character.isDigit(c)) {
            if (s.length() > 0) {
                map.put(Character.getNumericValue(key), s.toString());
                s = new StringBuffer();

            }
            key = c;

        } else {
            s.append(c);
            if (i == alphaNumericStr.length() - 1) {
                map.put(Character.getNumericValue(key), s.toString());
            }
        }
    }

    System.out.println(map);
\$\endgroup\$

3 Answers 3

13
\$\begingroup\$

You can use this regex (\d+)(\D+) which will match two groups, one is to match the number and the second to match the non digit part, beside If you are using Java 9+ Pattern, you can use simply :

String input = "1abcdef2xyz9aaa6xxx";
String regex = "(\\d+)(\\D+)";

Map<Integer, String> map = Pattern.compile(regex)
        .matcher(input)
        .results()
        .collect(Collectors.toMap(
                m -> Integer.parseInt(m.group(1)), m -> m.group(2)
        ));

System.out.println(map);

Result

{1=abcdef, 2=xyz, 9=aaa, 6=xxx}

If you are using Java 8 or lower, you can use :

String alphaNumericStr = "1abcdef2xyz9aaa6xxx";
String regex = "(\\d+)(\\D+)";
Matcher matcher = Pattern.compile(regex).matcher(alphaNumericStr);

Map<Integer, String> map = new HashMap<>();
while (matcher.find()) {
    map.put(Integer.parseInt(matcher.group(1)), matcher.group(2));
}
System.out.println(map);

But you will lose some information if the input contain the same number, in this case I would like to create a class which hold a number and a String and use a list in this case instead.

\$\endgroup\$
3
  • \$\begingroup\$ @ssubr check now the second solution! \$\endgroup\$ May 7, 2018 at 19:31
  • 1
    \$\begingroup\$ You could simply make a List<Map.Entry<Integer, String>> instead of creating a custom class for the list items. \$\endgroup\$
    – Stingy
    May 8, 2018 at 10:36
  • \$\begingroup\$ This is also correct @Stingy \$\endgroup\$ May 8, 2018 at 10:39
5
\$\begingroup\$

Here are few changes from performance perspective. Actually they are not needed with your code as it invokes only once, but in case of frequent invocation, like an View.onDraw() method in Android, can really improve performance.

    String alphaNumericStr = "1abcdef2xyz9aaa6xxx";

    Map<Integer, String> map = new LinkedHashMap<>(); // diamond with a constructor of a generic class

    Character key = null;
    final StringBuilder s = new StringBuilder(); // StringBuilder is faster if multithreading not used

    // `size = alphaNumericStr.length()` actually not needed here but can
    // speed up performance in case of huge amount of invocations.
    // actually it's a default loop pattern in Android source code.
    for (int i = 0, size = alphaNumericStr.length(); i < size; i++) {
        Character c = alphaNumericStr.charAt(i);

        if (Character.isDigit(c)) {
            if (s.length() > 0) {
                map.put(Character.getNumericValue(key), s.toString());
                // do not create new object in a loop. less work for GC
                s.delete(0, s.length());
            }
            key = c;
        } else {
            s.append(c);
        }
    }

    // move this out of the loop as it's needed only once.
    if (s.length() > 0) {
        map.put(Character.getNumericValue(key), s.toString());
    }

    System.out.println(map);
}
\$\endgroup\$
0
\$\begingroup\$

I see a potential bug in your code.

If I had the input 12asdasd3xyz I would have expected :

[12] : asdasd

[3]: xyz

But right now it would be :

[2] : asdasd

[3]: xyz

If you plan/need to fix this, you should keep the key as an int instead of a Character and in your if, find a way to "concat" your ints with something like this :

key = key * 10 + Character.getNumericValue(c)
\$\endgroup\$
1
  • 2
    \$\begingroup\$ The question defined the numbers in the string to be single digits, so this is not a bug. \$\endgroup\$
    – Stingy
    May 8, 2018 at 14:08

Your Answer

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

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