Я пытаюсь разбить строку на список строк, разделенных на изменение того, может ли символ быть приведен численно или нет. Другими словами, я хочу разбить свою строку на отдельные группы цифр и букв. Для большего удовольствия я также пытаюсь обрезать все начальные 0 из каждой группы чисел. Рассмотрим следующий пример.
Скажем, вы получили "aoeu01234stnh0987"
в качестве своего вклада. Результат, который я хочу, это ["aoeu", "1234", "stnh", "987"]
Я сделал рабочий пример ниже, но он несколько длинный и запутанный. Кажется, должен быть лучший, более лаконичный способ добиться этого.
private static List<String> fragmentString(String string) {
char[] charArr = string.toCharArray();
StringBuilder tempStr = new StringBuilder();
StringBuilder tempInt = new StringBuilder();
List<String> tempList = new ArrayList<>();
boolean wasPrevNum = false;
for (char c : charArr) {
boolean isNum = Character.isDigit(c);
if (isNum) {
tempInt.append(c);
if (!wasPrevNum) {
wasPrevNum = true;
tempList.add(tempStr.toString());
tempStr = new StringBuilder();
}
} else {
tempStr.append(c);
if (wasPrevNum) {
while (tempInt.charAt(0) == '0') tempInt.deleteCharAt(0);
tempList.add(tempInt.toString());
tempInt = new StringBuilder();
wasPrevNum = false;
}
}
}
if (tempInt.length() > 0) while (tempInt.charAt(0) == '0') tempInt.deleteCharAt(0);
tempList.add(wasPrevNum ? tempInt.toString() : tempStr.toString());
return tempList;
}
Я видел этот пост об использовании метода split()
, но это решение работает только для их очень конкретного случая и здесь не применяется. Метод split()
был первым, с чем я поигрался, чтобы решить эту проблему, но я не смог понять регулярное выражение, и теперь я задаюсь вопросом, возможно ли это вообще с помощью split()
.
Очень простым решением может быть использование регулярного выражения. Для поиска подстрок можно использовать регулярное выражение \p{L}+|[0-9]+
, которое означает последовательность букв или последовательность цифр. Затем попробуйте разобрать найденную подстроку. Если это целое число, начальные нули будут удалены в результате синтаксического анализа, а если синтаксический анализ не удался, просто напечатайте подстроку.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class Main {
public static void main(String[] args) {
String str = "aoeu01234stnh0987";
Matcher matcher = Pattern.compile("\\p{L}+|[0-9]+").matcher(str);
while (matcher.find()) {
String substring = matcher.group();
try {
System.out.println(Integer.parseInt(substring));
} catch (NumberFormatException e) {
System.out.println(substring);
}
}
}
}
Выход:
aoeu
1234
stnh
987
Этот пример не намного более краток, чем код, опубликованный OP. Лучшее, что я могу сказать, это то, что я не использую исключение как часть своей обработки.
Вот результаты одного тестового прогона.
aoeu01234stnh0987
[aoeu, 1234, stnh, 987]
Вот полный исполняемый пример кода.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class StringSplitter {
public static void main(String[] args) {
StringSplitter ss = new StringSplitter();
String input = "aoeu01234stnh0987";
System.out.println(input);
List<String> output = ss.splitString(input);
String[] output2 = output.toArray(new String[output.size()]);
System.out.println(Arrays.toString(output2));
}
public List<String> splitString(String input) {
List<String> output = new ArrayList<>();
if (input == null || input.length() < 1) {
return output;
}
char c = input.charAt(0);
boolean isDigit = Character.isDigit(c);
StringBuilder builder = new StringBuilder();
builder.append(c);
for (int i = 1; i < input.length(); i++) {
c = input.charAt(i);
if (isDigit == Character.isDigit(c)) {
builder.append(c);
} else {
addToList(output, builder, isDigit);
builder.delete(0, builder.length());
builder.append(c);
isDigit = !isDigit;
}
}
addToList(output, builder, isDigit);
return output;
}
private void addToList(List<String> output,
StringBuilder builder, boolean isDigit) {
if (isDigit) {
output.add(Integer.toString(
Integer.valueOf(builder.toString())));
} else {
output.add(builder.toString());
}
}
}
Вы можете добавить несколько символов-разделителей в каждую группу символов, а затем разделить строку вокруг этих символов:
String str = "aoeu01234stnh0987";
String[] arr = str.replaceAll("\\d+|\\D+", "$0::::").split("::::", 0);
System.out.println(Arrays.toString(arr)); // [aoeu, 01234, stnh, 0987]
// trim leading zeros from numbers,
// i.e. parse the integer value
// and return it back to the string
IntStream.range(0, arr.length)
.filter(i -> arr[i].replaceAll("\\d+", "").length() == 0)
.forEach(i -> arr[i] = Integer.valueOf(arr[i]).toString());
System.out.println(Arrays.toString(arr)); // [aoeu, 1234, stnh, 987]
See also: How to split the string into string and integer in java?
Я публикую код, который в итоге использовал в продакшне, на всякий случай, если это кому-то принесет пользу; Я знаю, что уже есть несколько отличных ответов, и я использовал информацию из некоторых ответов здесь, чтобы придумать это.
private static List<List<String>> fragmentArr(String[] inputArr) {
List<List<String>> fragArr = new ArrayList<>();
Arrays.stream(inputArr).forEach(string -> {
List<String> listToAdd = new ArrayList<>();
Matcher matcher = Pattern.compile("[^0-9]+|[0-9]+").matcher(string);
while (matcher.find()) {
StringBuilder substring = new StringBuilder(matcher.group());
while (substring.charAt(0) == '0') substring.deleteCharAt(0);
listToAdd.add(substring.toString());
}
fragArr.add(listToAdd);
});
return fragArr;
}
Я использовал цикл while для обрезки 0 вместо преобразования в int и преобразования обратно в строку по двум причинам.
Сложность времени. Если вы конвертируете типы данных для этой задачи, даже используя Big Integer
или каким-либо другим способом, вы теряете эффективность. Обратитесь к этому посту о временной сложности преобразования в int и обратно. И parseInt
, и toString
— операции O(n), где n — вся длина строки. Моя реализация цикла while - O (n), где n - количество ведущих нулей.
Исключение числового формата. Если вам передается строка, например "0000000000000000000001000000000000000000000"
, будет выдано исключение, если вы попытаетесь преобразовать значение в целое число, чтобы обрезать начальные 0, потому что это значение слишком велико для целочисленного типа данных в Java. Так что это крайний случай для рассмотрения.
Вот модульный тест.
@Test
public void fragmentTest() {
assertEquals(
Arrays.asList(
Arrays.asList("abc", "123", "dce", "456"),
Arrays.asList("123", "abcde", "444", "a")
),
fragmentArr(new String[]{"abc123dce456", "123abcde444a"})
);
assertEquals(
Arrays.asList(
Arrays.asList("abc", "1000000000000000000000", "def", "29")
),
fragmentArr(new String[]{"abc0000000000000000000001000000000000000000000def29"})
);
}
Хорошо, что вы опубликовали свой собственный ответ в дополнение к существующим ответам. Чем больше альтернативных решений, тем полезнее страница становится для будущих посетителей. Однако нехорошо то, что теперь вы не приняли первоначально принятый ответ и принимаете свой собственный ответ. Ни одно сообщество этого не оценит. Тем не менее, вы можете это сделать. В любом случае, желаю вам успехов!
К вашему сведению, я потерял очки репутации, чтобы принять свой собственный ответ, так что это не в мою пользу. Никакие другие ответы оптимально или полностью не решают мою подсказку. Принятый ответ до (вашего) использует попытку для известного ввода и использует преобразование типов для обрезки 0, а регулярное выражение не обрабатывает некоторые альфа-символы, например «-», правильно. Тот, что Гилберт, тоже использует преобразование типов. Lorem, который использует поток для обрезки 0, интересен, но также использует преобразование типов. Во всех ответах не учитываются целые числа, превышающие 4 байта. Я с уважением считаю, что мой ответ лучше всего решает проблему, и я очень ценю ваш ответ.
Ссылаясь на страницу справки , «Выберите один ответ, который, по вашему мнению, является лучшим решением вашей проблемы», я пытаюсь быть объективным.
Я возражаю против использования обработки исключений для управления потоком. Да, это работает. Нет, совершенно ожидаемые входные данные не должны обрабатываться как исключения.