正規表現マッチのストリーム処理を考察

Java8 では、正規表現で分割 split したストリームは取得できるけど、
  java.util.regex.Pattern の splitAsStream(CharSequence input) ⇒ Stream<String>
マッチした文字列のストリームは標準では提供されていない。

自然に Matcher find() 実行ループをメソッドにすれば、

public static void results(String regex, String input, Consumer<String> c){
   Matcher m = Pattern.compile(regex).matcher(input);
   while(m.find()){
      c.accept(m.group());
   }
}

Consumer<String> にマッチした文字列を順番に処理するラムダは書ける。

Stream 生成するには、↑をリストに一旦格納するよりも、、
Java8 では、java.util.regex.Matcher から、spliterator を生成するなどの方法ぐらいしか
良い方法はない。

public static Stream<String> matchToStream(Pattern pattern, CharSequence target){
   Matcher m = pattern.matcher(target);
   return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<String>(){
      @Override
      public boolean hasNext(){
         return m.find();
      }
      @Override
      public String next(){
         return m.group();
      }
   }, Spliterator.ORDERED), false);
}

Java9 以上なら、java.util.regex.Matcher に、
 String replaceAll(Function<MatchResult, String> replacer) や、
 Stream<MatchResult>を取得する results()
があって、
Function で置換する為のストリームがあって便利だが、
MatchResult で、start() end() group() は参照できても
何番目のマッチなのか?までは、AtomicInteger など外にカウンタを置かないと
認識できない。

Java8 でもStream<MatchResult>を取得するものを
yipuran-core の中に用意はした。。。
 org.yipuran.regex.MatcherStream#findMatches(Pattern pattern, CharSequence input)
けど、何番目のマッチなのか?までは、相変わらずこのままでは取得できない。
Stream<MatchResult>を取得するだけなのだから当然である。
Stream<MatchResult>から collect(Collector.toList()) でリストにすれば順番が確定はするが、
その後で、再び Stream の処理をするのはナンセンスである。

BiConsumer でカウンタ付きの Consumer

public void resultMatch(String regex, String target, BiConsumer<MatchResult, Integer> b){
   AtomicInteger i = new AtomicInteger(0);
   Pattern.compile(regex).matcher(target).results().forEach(m->b.accept(m, i.getAndIncrement()));
}

なんか、メソッドを作るほどの事でもない。。。

でも、何番目にマッチするかを認識しながら、置換を行う場合、、、

public static String replace(String regex, String string, BiFunction<String, Integer, String> f){
   Matcher m = Pattern.compile(regex).matcher(string);
   AtomicInteger i = new AtomicInteger(0);
   AtomicInteger x = new AtomicInteger(0);
   return StreamSupport.stream(   Spliterators.spliteratorUnknownSize(new Iterator<String>(){
      @Override
      public boolean hasNext(){
         return m.find();
      }
      @Override
      public String next(){
         return string.substring(i.getAndSet(m.end()), m.start()) + f.apply(m.group(), x.getAndIncrement());
      }
   }, Spliterator.ORDERED), false)
   .collect(Collectors.joining()) + string.substring(i.get());
}

あるいは、yipuran-core の MatcherStream.findMatches を使用して、、

public static String replace(String regex, String string, BiFunction<MatchResult, Integer, String> f) {
   AtomicInteger i = new AtomicInteger(0);
   AtomicInteger x = new AtomicInteger(0);
   return Stream.concat( MatcherStream.findMatches(Pattern.compile(regex), string)
   .collect(()->new ArrayList<String>(), (r, t)->{
      r.add( string.substring(i.getAndSet(t.end()), t.start()) + f.apply(t, x.getAndIncrement())  );
   },(r, u)->{}).stream(), Stream.of(string.substring(i.get())))
   .collect(Collectors.joining());
}

これだと、Stream.concat で Stream を2つ作って結合であまり良くないかも。