# Divide an alphanumeric string into a map of numbers and strings

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);


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.

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

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);
}


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)

• The question defined the numbers in the string to be single digits, so this is not a bug. – Stingy May 8 '18 at 14:08