2015-12-30 15 views
7

Próbuję policzyć liczbę dopasowań wzorca regex za pomocą prostego rozwiązania opartego o lambdę/strumienie Javy 8. Na przykład dla tego wzoru/dopasowującego:Policz wyniki regex ze strumieniami

final Pattern pattern = Pattern.compile("\\d+"); 
final Matcher matcher = pattern.matcher("1,2,3,4"); 

Jest to metoda, która dzieli splitAsStream tekst na dany wzór zamiast dopasowywania wzoru. Mimo że jest to elegancki i zachowuje niezmienność, to nie zawsze jest poprawne:

// count is 4, correct 
final long count = pattern.splitAsStream("1,2,3,4").count(); 

// count is 0, wrong 
final long count = pattern.splitAsStream("1").count(); 

Próbowałem również (AB) przy użyciu IntStream. Problem polega na tym, że muszę zgadnąć, ile razy powinienem zadzwonić pod numer matcher.find() zamiast do momentu, w którym zwróci false.

final long count = IntStream 
     .iterate(0, i -> matcher.find() ? 1 : 0) 
     .limit(100) 
     .sum(); 

jestem zaznajomiony z tradycyjnego rozwiązania while (matcher.find()) count++; gdzie count jest zmienny. Czy istnieje prosty sposób, aby to zrobić za pomocą Lambda/strumieni w języku Java 8?

+1

Spróbuj spojrzeć na 'takeWhile': http://stackoverflow.com/a/20765715/1743880 – Tunaki

+3

Dzielenie = pasującym!. Właśnie dlatego otrzymujesz liczby nieparzyste. Powinieneś zanegować swój Wzór, aby pobrać liczby i uzyskać to, co chcesz. – Flown

+0

@Tunaki 'takeWhile' wygląda całkiem interesująco. Ale będzie on prawdopodobnie dostępny w Javie 9, a nie w Javie 8. –

Odpowiedz

4

Aby użyć Pattern::splitAsStream właściwie trzeba odwrócić wyrażenia regularnego.Oznacza to, że zamiast mieć \\d+ (który byłby podzielony na każdy numer), należy użyć \\D+. Daje ci to kiedykolwiek numer w twoim ciągu.

final Pattern pattern = Pattern.compile("\\D+"); 
// count is 4 
long count = pattern.splitAsStream("1,2,3,4").count(); 
// count is 1 
count = pattern.splitAsStream("1").count(); 
+0

To jest "* proste *" rozwiązanie, którego szukałem! Ale wolę negować wzór taki jak ten "" (?: \ \ D +) "', ponieważ jest to łatwiejsze/możliwe zanegowanie jakiegokolwiek innego wzoru, nie tylko liczby całkowite. –

+0

Nie zawsze jednak działa. Dla tego wejścia '' a 2 "' liczba to 2 zamiast 1 –

+4

Java 9 ma proste rozwiązanie 'Pattern.compile (" \\ d + "). Matcher (" 1,2,3,4 "). results(). count() '... – Holger

1

Krótko mówiąc, masz stream of String i String pattern: ile tych ciągów pasuje do tego wzoru?

final String myString = "1,2,3,4"; 
Long count = Arrays.stream(myString.split(",")) 
     .filter(str -> str.matches("\\d+")) 
     .count(); 

gdzie pierwsza linia może być inny sposób strumień List<String>().stream(), ...

jestem zły?

+0

Wymaga to 2 różnych wzorów regex. 1 dla separatora i 1 dla dopasowania danych. Chciałbym tego uniknąć. W przeciwnym razie daje prawidłowe wyniki. –

3

Niezwykle wymyślny język w javadoc z Pattern.splitAsStream jest prawdopodobnie odpowiedzialny za to.

Strumień zwrócone tym sposobem zawiera każdy podciąg sekwencji wejściowej, który jest zakończony innym podciągu, który pasuje do tego wzorca lub kończy się po zakończeniu sekwencji wejściowej.

Jeśli wydrukować wszystkie mecze 1,2,3,4 może być zaskoczony, aby zauważyć, że jest to rzeczywiście zwrotu przecinki, a nie liczb.

System.out.println("[" + pattern.splitAsStream("1,2,3,4") 
      .collect(Collectors.joining("!")) + "]"); 

wydruki [!,!,!,]. Dziwne jest to, dlaczego daje 4, a nie 3.

Oczywiście to również wyjaśnia, dlaczego "1" daje 0 ponieważ nie istnieją ciągi między liczb w ciąg.

Szybkie demo:

private void test(Pattern pattern, String s) { 
    System.out.println(s + "-[" + pattern.splitAsStream(s) 
      .collect(Collectors.joining("!")) + "]"); 
} 

public void test() { 
    final Pattern pattern = Pattern.compile("\\d+"); 
    test(pattern, "1,2,3,4"); 
    test(pattern, "a1b2c3d4e"); 
    test(pattern, "1"); 
} 

drukuje

1,2,3,4-[!,!,!,] 
a1b2c3d4e-[a!b!c!d!e] 
1-[] 
+0

Dzięki. Właściwie wiem, co robi 'splitAsStream' i dlaczego nie działa tak jak go używam. Nadal nie wiem, jak liczyć mecze. Niemniej jednak twoja odpowiedź jest dość pouczająca i dobrze napisana, dzięki czemu otrzymujesz +1. –

3

można rozszerzyć AbstractSpliterator aby rozwiązać ten problem:

static class SpliterMatcher extends AbstractSpliterator<Integer> { 
    private final Matcher m; 

    public SpliterMatcher(Matcher m) { 
     super(Long.MAX_VALUE, NONNULL | IMMUTABLE); 
     this.m = m; 
    } 

    @Override 
    public boolean tryAdvance(Consumer<? super Integer> action) { 
     boolean found = m.find(); 
     if (found) 
      action.accept(m.groupCount()); 
     return found; 
    } 
} 

final Pattern pattern = Pattern.compile("\\d+"); 

Matcher matcher = pattern.matcher("1"); 
long count = StreamSupport.stream(new SpliterMatcher(matcher), false).count(); 
System.out.println("Count: " + count); // 1 

matcher = pattern.matcher("1,2,3,4"); 
count = StreamSupport.stream(new SpliterMatcher(matcher), false).count(); 
System.out.println("Count: " + count); // 4 


matcher = pattern.matcher("foobar"); 
count = StreamSupport.stream(new SpliterMatcher(matcher), false).count(); 
System.out.println("Count: " + count); // 0 
+0

Po prostu próbowałem i to daje prawidłowe wyniki. Jest to również bardzo pouczające! Nie jestem pewien, czy kwalifikuje się to jako "* proste *" rozwiązanie! Wtedy myślę, że muszę tylko napisać 'SpliterMatcher' raz i ponownie użyć go z różnymi matami. –

+1

Nie ma nic złego w tworzeniu nowego spliteratora dla każdego strumienia - i tak zawsze dzieje się za kulisami. Jest to również prosty sposób implementacji nieistniejącego już strumienia i pod tym względem * jest * prosty, składa się z pojedynczej klasy zawierającej jedną konkretną metodę i pojedynczy obiekt delegowany. O ile prostsze może być? Ale kiedy przesyłasz strumień liczb całkowitych zamiast 'MatchResult's, wydajniej jest zaimplementować' Spliterator.OfInt' zamiast 'Spliterator ' i utworzyć 'IntStream'. Aby zapewnić możliwość ponownego użycia, powinien on zgłaszać 'ORDERED' ... – Holger

+0

I zalecam nadpisanie' forEachRemaining', jeśli jest możliwa prosta, bezpośrednia implementacja (tak jak ma to miejsce w tym przypadku). – Holger

0

Java 9

Można użyć Matcher#results() zdobyć wszystkich meczy:

Stream<MatchResult>       results()
Zwraca strumień wyników dla każdego podciągu z sekwencja wejściowa pasująca do wzorca. Wyniki dopasowania występują w tej samej kolejności, co pasujące podsekwencje w sekwencji wejściowej.

Java 8 i dolna

Innym prostym rozwiązaniem opartym na użyciu odwrotnej wzór:

String pattern = "\\D+"; 
System.out.println("1".replaceAll("^" + pattern + "|" + pattern + "$", "").split(pattern, 0).length); // => 1 

Tutaj, wszystkie nie-cyfry usuwa się na początku i na końcu łańcucha, a następnie ciąg jest dzielony przez sekwencje bez cyfr, bez zgłaszania żadnych pustych elementów końcowych białych znaków (od 0 jest przekazywany jako argument dla split).

Zobacz this demo:

String pattern = "\\D+"; 
System.out.println("1".replaceAll("^" + pattern + "|" + pattern + "$", "").split(pattern, 0).length); // => 1 
System.out.println("1,2,3".replaceAll("^" + pattern + "|" + pattern + "$", "").split(pattern, 0).length);// => 3 
System.out.println("hz 1".replaceAll("^" + pattern + "|" + pattern + "$", "").split(pattern, 0).length); // => 1 
System.out.println("1 hz".replaceAll("^" + pattern + "|" + pattern + "$", "").split(pattern, 0).length); // => 1 
System.out.println("xxx 1 223 zzz".replaceAll("^" + pattern + "|" + pattern + "$", "").split(pattern, 0).length);//=>2 
Powiązane problemy