多行字符串的 Java 子字符串,最多第 n 行和该行上的字符索引

Java substring of multi-line String up to nth line and character index on that line

提问人:amaidment 提问时间:11/9/2023 最后编辑:amaidment 更新时间:11/11/2023 访问量:78

问:

在 Java 中,给定一个多行,我想获取从开头到第 n 行的子字符串以及该行上的字符索引。(行索引和字符索引都是从零开始的。String

例如,如果我们要实现这样的方法:

  /**
   * Returns the substring of the given string up to the given character index on the given line index.
   *
   * @param text      input text
   * @param line      line index
   * @param character character index
   * @return substring
   */
  public static String substring(String text, int line, int character);

然后,考虑以下多行字符串:

你好,
世界,
你好

对于给定的输入,上述方法应返回

  • 子字符串(文本, 0, 2);

  • 子字符串(文本,1,3);

你好
沃尔

  • 子字符串(文本,3,0);

你好
,世界
如何


我考虑了几种方法:

  1. 通过对第 n 行进行操作来构造子字符串:
    一种方法是使用 string.lines() 并构建子字符串。 像这样:
    更新:根据厄立特里亚的回答,更新了改进和更整洁的实现:
    String
  public static String buildSubstring(String text, int line, int character) {
    long textLines = text.lines().limit(line + 1).count();
    if (line > textLines) {
      return text;
    } else {
      String[] rows = text.lines().toArray(String[]::new);
      return IntStream.range(0, line + 1)
          .mapToObj(i -> {
            String lineText = rows[i];
            return i == line ? lineText.substring(0, Math.min(character, lineText.length())) : lineText;
          })
          .collect(Collectors.joining(System.lineSeparator()));
    }
  }

但是,我主要担心的是过度创建字符串对性能的影响。

  1. 获取子字符串到原始字符串中的字符索引:
    更直观的方法可能是使用 string.substring(0, x),其中 x 是第 n 行和该行中位置的字符索引 - 在原始多行中。
    但是,对于在原始字符串中查找该字符索引的最佳方法是什么,我不清楚。
    一种方法是迭代使用 string.indexOf(System.lineSeparator(),lineIndex) 来标识该行在原始 String 中的位置,并在该行上添加字符索引。像这样的东西:
    String
  public static String indexSubstring(String text, int line, int character) {
    String separator = System.lineSeparator();
    int separatorLength = separator.length();

    int lineIndex = 0;
    if (line > 0) {
      lineIndex = text.indexOf(separator) + separatorLength;
      for (int i = 1; i < line; i++) {
        lineIndex = text.indexOf(separator, lineIndex) + separatorLength;
      }
    }
    return text.substring(0, lineIndex + character);
  }

但是,如果文本中的行分隔符与 ;在我的情况下就是这种情况 - 也就是说,原始文本可能来自 unix 或 Windows 环境和/或此功能可能在 unix 或 Windows 环境中执行,并且它们需要可互操作。
当然,可以做一个 ,但这会比第一种方法使用 做更多的创造。
System.lineSeparator()string.replaceAll("\\r?\\n, System.lineSeparator())Stringstring.lines()

注意:就此问题而言,我不处理错误情况 - 例如,行/字符索引超出了原始长度,或者字符索引超出了行的长度。一旦我决定了基本方法,这些将被考虑在内;或者,为了简单起见,我们可以假设它将返回行或输入文本中的所有内容。String

问题:

  1. 如何获取第 n 行的多行中的字符位置以及该行上的字符索引?
    即用于 string.substring(0, x)。
    String
  2. 有没有比我上面列出的任何一种方法更好的方法来获取子字符串?
java 子字符串 多行

评论

0赞 amaidment 11/9/2023
@Abra确定的 - 添加了示例。它们并不特定于我的特定用例(与实现协议有关,其中输入是源代码,行/字符索引是源代码中的插入符号位置),但希望能说明功能需求。

答:

0赞 Konstanius EU 11/9/2023 #1

使用现有的 System 类和方法总是会让你走得更远,它们更有效率,让你更精确地得到你的结果。

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        String text = """
                hello
                world
                how
                are
                you?""";
        System.out.println(substring(text, 0, 2)); // he
        System.out.println(substring(text, 1, 3)); // hello\nwor
        System.out.println(substring(text, 3, 0)); // hello\nworld\nhow\n
        try {
            System.out.println(substring(text, 6, 0)); // Line index out of bounds
        } catch (IndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        }
        try {
            System.out.println(substring(text, 0, 6)); // Range [0, 6) out of bounds for length 5
        } catch (IndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        }
    }

    /**
     * Returns the substring of the given string up to the given character index on the given line index.
     *
     * @param text      input text
     * @param line      line index (starting at 0 for the first line)
     * @param character character index (starting at 0 for the first character)
     * @return substring
     */
    public static String substring(String text, int line, int character) throws IndexOutOfBoundsException {
        Scanner scanner = new Scanner(text);
        int lineCount = 0;
        StringBuilder sb = new StringBuilder();
        while (scanner.hasNextLine()) {
            String lineText = scanner.nextLine();
            if (lineCount == line) {
                sb.append(lineText, 0, character);
                break;
            } else {
                sb.append(lineText);
                sb.append(System.lineSeparator());
            }
            lineCount++;
        }
        if (lineCount < line) {
            throw new IndexOutOfBoundsException("Line index out of bounds");
        }

        return sb.toString();
    }
}

评论

0赞 Community 11/9/2023
您的答案可以通过额外的支持信息得到改进。请编辑以添加更多详细信息,例如引文或文档,以便其他人可以确认您的答案是正确的。您可以在帮助中心找到有关如何写出好答案的更多信息。
1赞 Eritrean 11/9/2023 #2

假设您没有很大的输入,我将输入拆分为行并存储在数组中,并使用 IntStream 并将每行索引映射到整行,除非该行等于参数,然后映射到子字符串。像这样:line

public static String buildSubstring(String text, int line, int character){
    String[] rows = text.lines().toArray(String[]::new);

    return IntStream.range(0, line + 1)
                    .mapToObj(i -> i == line ? rows[i].substring(0,character) : rows[i])
                    .collect(Collectors.joining(System.lineSeparator()));
}

评论

1赞 amaidment 11/9/2023
+1 - 我喜欢;它当然更整洁,我已经基于此更新了我的 buildSubstring()(并给予了荣誉);但有 3 个区别,i) 我们可以使用 Stream.limit(line + 1) 只用我们感兴趣的行填充 String[];ii) 如果线路>否,则快速返回。线条;iii) 最后一行的 Math.min() 如果字符超出该行的长度。
0赞 DONGMO BERNARD GERAUD 11/9/2023 #3

我认为这个解决方案几乎适用于所有 Java 版本。

public static String indexSubstring(String text, int line, int character) {
    String result = "";
    try {
        String[] lines = text.split("\n");
        for (int i = 0; i < line; i++) {
            result += lines[i] + "\n";
        }
        result += lines[line].substring(0, character);
        return result;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return result;
}

我用 java 15 测试了它,它适用于所有形式的多行字符串 “”“你的多行字符串""";

0赞 tevemadar 11/9/2023 #4

如果您可以取回带有原始换行符 (*) 的子字符串,则可以遍历字符,并仅在最后执行一次实际调用:substring()

public static void main(String[] args) {
    String n = "hello\nworld\nhow\nare\nyou?";
    String r = "hello\rworld\rhow\rare\ryou?";
    String rn = "hello\r\nworld\r\nhow\r\nare\r\nyou?";
    System.out.println(substring(n, 0, 2));
    System.out.println(substring(r, 0, 2));
    System.out.println(substring(rn, 0, 2));
    System.out.println(substring(n, 1, 3));
    System.out.println(substring(r, 1, 3));
    System.out.println(substring(rn, 1, 3));
    System.out.println(substring(n, 3, 0));
    System.out.println(substring(r, 3, 0));
    System.out.println(substring(rn, 3, 0));
}

public static String substring(String text, int line, int character) {
    int pos = 0;
    char sep = 0;
    while (line > 0) {
        char c = text.charAt(pos++);
        if (c == '\n' || c == '\r') {
            if (sep == 0)
                sep = c;
            if (c == sep)
                line--;
        }
    }
    char c = text.charAt(pos);
    if (c != sep && (c == '\n' || c == '\r'))
        pos++;
    return text.substring(0, pos + character);
}

假设换行符在字符串中是一致的,因此遇到第一个实际的换行符意味着所有其他字符看起来都一样,并且另一个字符要么不使用,要么可以忽略(但在循环后仍然需要一些特殊处理)。

代码实际上在这里工作:https://ideone.com/AWLBuD,但是 (*) 适用,正如您所看到的,IdeOne 在大多数情况下都成功转换,但是如果生成一个末尾带有“原始”换行符的字符串,然后得到 -d(添加“本机”换行符),则会导致打印 2 或 1 个总换行符,具体取决于“原始”换行符是否与“本机”换行符匹配。我认为这也可能发生在实际的游戏机中。substring(x, 3, 0)println()

0赞 Reilas 11/11/2023 #5

"...如何获取第 n 行的多行字符串中的字符位置以及该行上的字符索引?
即用于 string.substring(0, x)。..."

使用正则表达式模式计算换行符分隔符。

下面是一个示例。

String substring(String text, int line, int character) {
    Pattern p = Pattern.compile("\\R");
    Matcher m = p.matcher(text);
    int o = 0;
    while (line-- > 0 && m.find()) o = m.end();
    return text.substring(0, o + character);
}