2015-05-13 8 views
13

Ich habe eine Textdatei, die URLs und E-Mails enthält. Ich muss alle aus der Datei extrahieren. Jede URL und E-Mail kann mehr als einmal gefunden werden, aber das Ergebnis sollte keine Duplikate enthalten. Ich kann alle URLs extrahieren mit dem folgenden Code:Split java.util.stream.Stream

Files.lines(filePath). 
    .map(urlPattern::matcher) 
    .filter(Matcher::find) 
    .map(Matcher::group) 
    .distinct(); 

ich alle E-Mails extrahieren kann mit dem folgenden Code:

Files.lines(filePath). 
    .map(emailPattern::matcher) 
    .filter(Matcher::find) 
    .map(Matcher::group) 
    .distinct(); 

Kann ich extrahieren alle URLs und E-Mails, den Strom von Files.lines(filePath) zurück Lesen nur ein Zeit? So etwas wie das Aufteilen von Streams von Zeilen in einen Stream von URLs und E-Mail-Streams.

+0

'Stream- filestream = Files.lines (Paths.get ("Test")); \t \t fileStream.//email Spiel \t \t fileStream.//url match' Die einzige Lösung, die mir in den Sinn kommt, wenn Ihr Problem ist, dass Sie nicht – Loki

+2

2-Streams erstellen möchte ich denke, das Speichern der Linien in 'List' und zweimaliges Traversieren gilt nicht als Lösung, richtig? –

+6

Loki, du kannst den gleichen Stream nicht zweimal durchlaufen. –

Antwort

10

Sie können partitioningBy Sammler verwenden, obwohl es immer noch nicht sehr elegante Lösung ist.

Map<Boolean, List<String>> map = Files.lines(filePath) 
     .filter(str -> urlPattern.matcher(str).matches() || 
         emailPattern.matcher(str).matches()) 
     .distinct() 
     .collect(Collectors.partitioningBy(str -> urlPattern.matcher(str).matches())); 
List<String> urls = map.get(true); 
List<String> emails = map.get(false); 

Wenn Sie regexp zweimal nicht anwenden möchten, können Sie es das Zwischenpaar Objekt machen (zum Beispiel SimpleEntry):

public static String classify(String str) { 
    return urlPattern.matcher(str).matches() ? "url" : 
     emailPattern.matcher(str).matches() ? "email" : null; 
} 

Map<String, Set<String>> map = Files.lines(filePath) 
     .map(str -> new AbstractMap.SimpleEntry<>(classify(str), str)) 
     .filter(e -> e.getKey() != null) 
     .collect(Collectors.groupingBy(e -> e.getKey(), 
      Collectors.mapping(e -> e.getValue(), Collectors.toSet()))); 

würde meine freie StreamEx Bibliothek den letzten Schritt verwenden sei kürzer:

+0

Ich bearbeitete meine Antwort, fügte hinzu, 'matcher.group (1)' zu nennen, um URL oder E-Mail aus einer Zeichenfolge zu extrahieren. Wäre toll, wenn du das deinem Code hinzufügst, dass es korrekt wird. –

+1

Die Frage verwendete '.distinct()' nach dem Filtern, was nahelegt, dass das Sammeln zu 'Set' anstatt zu 'List's passender ist. Im Allgemeinen ist die 'classify'-Methode eine gute Idee, die es einfacher macht, vorhandene 'Collector's zu verwenden, als einen benutzerdefinierten' Collector' zu implementieren (wie ich es tat) – Holger

+1

@ york.beta: es macht keinen Sinn, 'group (1) 'solange Sie' matches' verwenden, da dies bedeutet, dass der gesamte 'String' übereinstimmt. Es wäre anders, wenn du 'find' benutzt hättest, aber das wäre eine ganz andere Frage, da es die Möglichkeit beinhaltet, beide Muster in derselben Zeile zu finden ... – Holger

1

Da Sie einen Stream nicht wiederverwenden können, wäre die einzige Option "tun Sie es manuell", denke ich.

File.lines(filePath).forEach(s -> /** match and sort into two lists */); 

Wenn es eine andere Lösung dafür gibt, würde ich mich freuen, darüber zu erfahren!

+0

Ja, ich dachte darüber nach, ich bin neugierig, gibt es eine andere Lösung, so beantwortet diese Frage. –

0

Die allgemeine Frage sollte sein: Warum möchten Sie nur einmal streamen?

Das Extrahieren der URLs und das Extrahieren der E-Mails sind verschiedene Operationen und sollten daher in ihren eigenen Streaming-Operationen behandelt werden. Selbst wenn die zugrunde liegende Stream-Quelle Hunderttausende von Datensätzen enthält, kann die Zeit für die Iteration im Vergleich zu den Mapping- und Filteroperationen vernachlässigt werden.

Die einzige Sache, die Sie als mögliches Leistungsproblem betrachten sollten, ist der IO-Betrieb. Die sauberste Lösung ist daher die Datei nur einmal zu lesen und dann zweimal auf eine resultierende Sammlung streamen:

List<String> allLines = Files.readAllLines(filePath); 
allLines.stream() ... // here do the URLs 
allLines.stream() ... // here do the emails 

Natürlich erfordert dies eine Erinnerung.

+0

Manchmal kann es sinnvoll sein, dies in einem Durchgang zu tun. Zum Beispiel enthält die Eingabedatei Millionen von Zeilen, von denen nur ein kleines Bit die Regexps erfüllt. –

+0

Extrahieren von URLs und E-Mails ist nur ein Beispiel, ich möchte eine andere Daten aus ein paar riesigen Dateien extrahieren. Es ist also keine Lösung, sie zu lesen oder ein paar Mal zu lesen. –

4

Sie können die passende führen innerhalb eines Collector:

Map<String,Set<String>> map=Files.lines(filePath) 
    .collect(HashMap::new, 
     (hm,line)-> { 
      Matcher m=emailPattern.matcher(line); 
      if(m.matches()) 
       hm.computeIfAbsent("mail", x->new HashSet<>()).add(line); 
      else if(m.usePattern(urlPattern).matches()) 
       hm.computeIfAbsent("url", x->new HashSet<>()).add(line); 
     }, 
     (m1,m2)-> m2.forEach((k,v)->m1.merge(k, v, 
            (s1,s2)->{s1.addAll(s2); return s1;})) 
    ); 
Set<String> mail=map.get("mail"), url=map.get("url"); 

Beachten Sie, dass diese leicht angepasst werden kann, mehrere Spiele innerhalb einer Zeile zu finden:

Map<String,Set<String>> map=Files.lines(filePath) 
    .collect(HashMap::new, 
     (hm,line)-> { 
      Matcher m=emailPattern.matcher(line); 
      while(m.find()) 
       hm.computeIfAbsent("mail", x->new HashSet<>()).add(m.group()); 
      m.usePattern(urlPattern).reset(); 
      while(m.find()) 
       hm.computeIfAbsent("url", x->new HashSet<>()).add(m.group()); 
     }, 
     (m1,m2)-> m2.forEach((k,v)->m1.merge(k, v, 
            (s1,s2)->{s1.addAll(s2); return s1;})) 
    );