Как разбить строку, но также сохранить разделители? - PullRequest
209 голосов
/ 05 февраля 2010

У меня есть многострочная строка, которая разделена набором различных разделителей:

(Text1)(DelimiterA)(Text2)(DelimiterC)(Text3)(DelimiterB)(Text4)

Я могу разбить эту строку на части, используя String.split, но кажется, что я не могу получить фактическую строку, которая соответствует регулярному выражению разделителя.

Другими словами, вот что я получаю:

  • Text1
  • Text2
  • Text3
  • Text4

Это то, что я хочу

  • Text1
  • DelimiterA
  • Text2
  • DelimiterC
  • Text3
  • DelimiterB
  • Text4

Есть ли какой-нибудь способ JDK разбить строку с помощью регулярного выражения-разделителя, но также оставить разделители?

Ответы [ 23 ]

2 голосов
/ 01 января 2016

Я также опубликую свои рабочие версии (первая действительно похожа на Markus).

public static String[] splitIncludeDelimeter(String regex, String text){
    List<String> list = new LinkedList<>();
    Matcher matcher = Pattern.compile(regex).matcher(text);

    int now, old = 0;
    while(matcher.find()){
        now = matcher.end();
        list.add(text.substring(old, now));
        old = now;
    }

    if(list.size() == 0)
        return new String[]{text};

    //adding rest of a text as last element
    String finalElement = text.substring(old);
    list.add(finalElement);

    return list.toArray(new String[list.size()]);
}

А вот второе решение и его раунд на 50% быстрее первого:

public static String[] splitIncludeDelimeter2(String regex, String text){
    List<String> list = new LinkedList<>();
    Matcher matcher = Pattern.compile(regex).matcher(text);

    StringBuffer stringBuffer = new StringBuffer();
    while(matcher.find()){
        matcher.appendReplacement(stringBuffer, matcher.group());
        list.add(stringBuffer.toString());
        stringBuffer.setLength(0); //clear buffer
    }

    matcher.appendTail(stringBuffer); ///dodajemy reszte  ciagu
    list.add(stringBuffer.toString());

    return list.toArray(new String[list.size()]);
}
2 голосов
/ 30 ноября 2017

Другой вариант решения с использованием регулярных выражений. Сохраняет порядок токенов, правильно сопоставляет несколько токенов одного типа в строке. Недостатком является то, что регулярное выражение является противным.

package javaapplication2;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JavaApplication2 {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        String num = "58.5+variable-+98*78/96+a/78.7-3443*12-3";

        // Terrifying regex:
        //  (a)|(b)|(c) match a or b or c
        // where
        //   (a) is one or more digits optionally followed by a decimal point
        //       followed by one or more digits: (\d+(\.\d+)?)
        //   (b) is one of the set + * / - occurring once: ([+*/-])
        //   (c) is a sequence of one or more lowercase latin letter: ([a-z]+)
        Pattern tokenPattern = Pattern.compile("(\\d+(\\.\\d+)?)|([+*/-])|([a-z]+)");
        Matcher tokenMatcher = tokenPattern.matcher(num);

        List<String> tokens = new ArrayList<>();

        while (!tokenMatcher.hitEnd()) {
            if (tokenMatcher.find()) {
                tokens.add(tokenMatcher.group());
            } else {
                // report error
                break;
            }
        }

        System.out.println(tokens);
    }
}

Пример вывода:

[58.5, +, variable, -, +, 98, *, 78, /, 96, +, a, /, 78.7, -, 3443, *, 12, -, 3]
1 голос
/ 24 марта 2010

Если вы можете себе позволить, используйте Java метод replace (цель CharSequence, замена CharSequence) и заполните другой разделитель для разделения. Пример: Я хочу разделить строку «boo: and: foo» и оставить строку «:» в правой строке.

String str = "boo:and:foo";
str = str.replace(":","newdelimiter:");
String[] tokens = str.split("newdelimiter");

Важное примечание: это работает, только если у вас нет больше "newdelimiter" в вашей строке! Таким образом, это не общее решение. Но если вам известна последовательность CharSequence, в которой вы можете быть уверены, что она никогда не появится в строке, это очень простое решение.

1 голос
/ 09 ноября 2008

Я не знаю о существующей функции в Java API, которая делает это (что не означает, что она не существует), но вот моя собственная реализация (один или несколько разделителей будут возвращены как один токен; если вы хотите, чтобы каждый разделитель возвращался в виде отдельного токена, потребуется немного адаптации):

static String[] splitWithDelimiters(String s) {
    if (s == null || s.length() == 0) {
        return new String[0];
    }
    LinkedList<String> result = new LinkedList<String>();
    StringBuilder sb = null;
    boolean wasLetterOrDigit = !Character.isLetterOrDigit(s.charAt(0));
    for (char c : s.toCharArray()) {
        if (Character.isLetterOrDigit(c) ^ wasLetterOrDigit) {
            if (sb != null) {
                result.add(sb.toString());
            }
            sb = new StringBuilder();
            wasLetterOrDigit = !wasLetterOrDigit;
        }
        sb.append(c);
    }
    result.add(sb.toString());
    return result.toArray(new String[0]);
}
1 голос
/ 05 февраля 2010

Я не думаю, что это возможно с String#split, но вы можете использовать StringTokenizer, хотя это не позволит вам определить разделитель как регулярное выражение, а только как класс однозначных символов :

new StringTokenizer("Hello, world. Hi!", ",.!", true); // true for returnDelims
1 голос
/ 05 февраля 2010

Я предлагаю использовать Pattern и Matcher, которые почти наверняка достигнут того, что вы хотите. Ваше регулярное выражение должно быть несколько сложнее, чем то, что вы используете в String.split.

0 голосов
/ 08 апреля 2016
    String expression = "((A+B)*C-D)*E";
    expression = expression.replaceAll("\\+", "~+~");
    expression = expression.replaceAll("\\*", "~*~");
    expression = expression.replaceAll("-", "~-~");
    expression = expression.replaceAll("/+", "~/~");
    expression = expression.replaceAll("\\(", "~(~"); //also you can use [(] instead of \\(
    expression = expression.replaceAll("\\)", "~)~"); //also you can use [)] instead of \\)
    expression = expression.replaceAll("~~", "~");
    if(expression.startsWith("~")) {
        expression = expression.substring(1);
    }

    String[] expressionArray = expression.split("~");
    System.out.println(Arrays.toString(expressionArray));
0 голосов
/ 16 июля 2013

Вот отличная версия, основанная на коде выше, на случай, если это поможет. Во всяком случае, это коротко. Условно включает в себя голову и хвост (если они не пусты). Последняя часть представляет собой демонстрационный пример / тестовый пример.

List splitWithTokens(str, pat) {
    def tokens=[]
    def lastMatch=0
    def m = str=~pat
    while (m.find()) {
      if (m.start() > 0) tokens << str[lastMatch..<m.start()]
      tokens << m.group()
      lastMatch=m.end()
    }
    if (lastMatch < str.length()) tokens << str[lastMatch..<str.length()]
    tokens
}

[['<html><head><title>this is the title</title></head>',/<[^>]+>/],
 ['before<html><head><title>this is the title</title></head>after',/<[^>]+>/]
].each { 
   println splitWithTokens(*it)
}
0 голосов
/ 18 декабря 2012

Чрезвычайно наивное и неэффективное решение, которое, тем не менее, работает. Дважды разбейте строку и затем объедините два массива

String temp[]=str.split("\\W");
String temp2[]=str.split("\\w||\\s");
int i=0;
for(String string:temp)
System.out.println(string);
String temp3[]=new String[temp.length-1];
for(String string:temp2)
{
        System.out.println(string);
        if((string.equals("")!=true)&&(string.equals("\\s")!=true))
        {
                temp3[i]=string;
                i++;
        }
//      System.out.println(temp.length);
//      System.out.println(temp2.length);
}
System.out.println(temp3.length);
String[] temp4=new String[temp.length+temp3.length];
int j=0;
for(i=0;i<temp.length;i++)
{
        temp4[j]=temp[i];
        j=j+2;
}
j=1;
for(i=0;i<temp3.length;i++)
{
        temp4[j]=temp3[i];
        j+=2;
}
for(String s:temp4)
System.out.println(s);
0 голосов
/ 05 июля 2011

Tweaked Pattern.split () для включения сопоставленного шаблона в список

Добавлено

// add match to the list
        matchList.add(input.subSequence(start, end).toString());

Полный источник

public static String[] inclusiveSplit(String input, String re, int limit) {
    int index = 0;
    boolean matchLimited = limit > 0;
    ArrayList<String> matchList = new ArrayList<String>();

    Pattern pattern = Pattern.compile(re);
    Matcher m = pattern.matcher(input);

    // Add segments before each match found
    while (m.find()) {
        int end = m.end();
        if (!matchLimited || matchList.size() < limit - 1) {
            int start = m.start();
            String match = input.subSequence(index, start).toString();
            matchList.add(match);
            // add match to the list
            matchList.add(input.subSequence(start, end).toString());
            index = end;
        } else if (matchList.size() == limit - 1) { // last one
            String match = input.subSequence(index, input.length())
                    .toString();
            matchList.add(match);
            index = end;
        }
    }

    // If no match was found, return this
    if (index == 0)
        return new String[] { input.toString() };

    // Add remaining segment
    if (!matchLimited || matchList.size() < limit)
        matchList.add(input.subSequence(index, input.length()).toString());

    // Construct result
    int resultSize = matchList.size();
    if (limit == 0)
        while (resultSize > 0 && matchList.get(resultSize - 1).equals(""))
            resultSize--;
    String[] result = new String[resultSize];
    return matchList.subList(0, resultSize).toArray(result);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...