Mit Java 8+ Streams Zeilen nummern zu Zeilen hinzufügen

Zongatel

Mitglied
Hallo allesamt,
irgendwie ist es ein fürchterlich banales Streben. Imperativ hätte ich überhaupt keine Probleme damit:
Gegeben wäre z.B. ein Array aus Strings denen ich gerne jeweils vorne eine iterierte Nummer geben möchte um sie z.B. hinterherausgeben oder sowas

Also die Tutorials sind ja ganz nett, man kann sich Zahlen in einem endlos-Stream geben lassen man kann sogar Zeilen eines Files lesen, wow.
Aber wie mache ich das mit Zahlen die ich mir generieren lasse um die Strings oder Zeilen zu nummerieren?
Ich dachte an sowas wie Intstream.generate und map etc, aber wie kombiniere ich dass denn dann mittels Stream?
Hat jemand da eine Idee oder Beispiel?
Wie gesagt hinerher soll sowas wie
1. meine erste Zeile
2. meine zweite Zeile
3. ....
rauskommen

PS: ich googelte auch schon nach dem Stichwort Java8 Streams merge aber die Autoren diesen Themas verstehen unter merge lediglich ein aneinanderhängen von Streams, sowas verstehe ich tatsächlich nicht unter merge, sondern sowas wie:
Stream1 : 1,2,3,4,5,6,7
Stream2: a,b,c,d,e,f,g
Merge 1a,2b,3c ...
 
Zuletzt bearbeitet:
Das ist etwas kompliziert, weil Streams in Java dafür nicht vorgesehen sind, wie du auch schon richtig angemerkt hast.

 
Gegeben wäre z.B. ein Array aus Strings denen ich gerne jeweils vorne eine iterierte Nummer geben möchte um sie z.B. hinterherausgeben oder sowas
Java:
static class SequenceMapper<T> {
    AtomicInteger current;
    BiFunction<T, Integer, T> mapper;

    public SequenceMapper(int start, BiFunction<T, Integer, T> mapper) {
        current = new AtomicInteger(start);
        this.mapper = mapper;
    }

    public T map(T value) {
        return mapper.apply(value, current.getAndIncrement());
    }
}

Java:
SequenceMapper<String> mapper = new SequenceMapper<>(1000, (s,v) -> String.format("%d: %s", v, s));
Stream.of("A","B","C","D","E","F").map(mapper::map).forEach(System.out::println);
 
Kaum abgeschickt, fällt mir auf, dann man statt des Integers einfach einen Supplier hätte verwenden können...

Java:
static class SequenceMapper<T,V> {
    Supplier<V> supplier;
    BiFunction<T, V, T> mapper;

    public SequenceMapper(Supplier<V> supplier, BiFunction<T, V, T> mapper) {
        this.supplier = supplier;
        this.mapper = mapper;
    }

    public T map(T value) {
        return mapper.apply(value, supplier.get());
    }
}
 
Es gibt da einige Möglichkeiten (der Supplier gefällt mir z.B. sehr gut). Es läuft - egal wie du's machst - darauf raus, dass dein Indexwert nicht einfach in einer Variable stehen kann, sondern durch eine Methode oder was auch immer an die Lambdafunktion übergeben wird.
 
Das Stichwort wäre zip, statt merge. Gibt es aber ohne Libs in Java so nicht direkt
Zip ist semantisch nicht exakt äquivalent zu interleave; zip "verschmelzt" Elemente

Wie es auch sei, Streams sind in Java nicht dafür vorgesehen; der Workaround mit AtomicInteger funktioniert zwar, ist aber ein Antipattern

Oder anders: Das mit Streams ist unsinnig, lass es, oder verwende einen imperativen Ansatz.
 
Danke für diese interessante Lösung.
Ich selbst hatte dann auch mal über eine Lösung nachgedacht, von der ich allerdings nicht weiß, ob sie so im Sinne der "Funktionalen Programmierung ist.


[CODE lang="java" title="Mit einer simplen Counter-Klasse"]




static private void lineNumberTest(String ... lines)
{
Counter lineNumber = new Counter();

Stream<String> lineStream = Stream.of(lines);

lineStream.map(line -> ""+lineNumber.getNextNum() + ':' +line)
.forEach(System.out:😛rintln);
}
public static void main(String[] args)
{
randomTest();
biFunctionTest();
System.out.println("-----------");
lineNumberTest("Balala ","blubsbla","flucka flüh","simsalabim");
}

static private class Counter
{
int i = 1;

public int getNextNum()
{
return i++;
}
}

[/CODE]

Wäre das so aus Eurer Sicht eine gute Art, oder gibt es bei dieser Lösung einen Drawback/Pferdefuß?
 
Zip ist semantisch nicht exakt äquivalent zu interleave; zip "verschmelzt" Elemente

Wie es auch sei, Streams sind in Java nicht dafür vorgesehen; der Workaround mit AtomicInteger funktioniert zwar, ist aber ein Antipattern

Oder anders: Das mit Streams ist unsinnig, lass es, oder verwende einen imperativen Ansatz.
Um die Philosophie besser zu verstehen, was wäre beim AtomicInteger der Pferdefuß?
 
Grundsätzlich kann man das machen - sinnvoll ist es, wie schon geschrieben, nicht wirklich.

Man verteilt die Logik auf mehrere Funktionen (sogar Klassen!), sie ist gegenüber der direkten, imperativen Lösung schwerer lesbar/schwerer verständlich. Deine ist z.B. nicht Thread-Safe, je nach Nutzung.

Es ist schlicht erhöhte Komplexität ohne Nutzen. Eine Aufteilung in mehrere Funktionen / Klassen macht man ja im Sinne eines Seperation of Concerns, weil die unterschiedliche Dinge machen. Hier macht man die Aufteilung aufgrund einer Restriktion der Stream-Logik und nicht weil das hochzählen einer Variable etwas ist, was man auslagern sollte.
 
Grundsätzlich kann man das machen - sinnvoll ist es, wie schon geschrieben, nicht wirklich.

Man verteilt die Logik auf mehrere Funktionen (sogar Klassen!), sie ist gegenüber der direkten, imperativen Lösung schwerer lesbar/schwerer verständlich. Deine ist z.B. nicht Thread-Safe, je nach Nutzung.

Es ist schlicht erhöhte Komplexität ohne Nutzen. Eine Aufteilung in mehrere Funktionen / Klassen macht man ja im Sinne eines Seperation of Concerns, weil die unterschiedliche Dinge machen. Hier macht man die Aufteilung aufgrund einer Restriktion der Stream-Logik und nicht weil das hochzählen einer Variable etwas ist, was man auslagern sollte.
Ja ich finde auch, dass es nicht wirklich verständlicher wird, und viel weniger Zeilen Code als eine imperative Lösung ist es dann auch nicht zumindest in diesem Beispiel. Aber warum muss man da bei so naheliegenden Aufgaben mit Streams solche Verränkungen machen oder gibt es am Ende doch eine bessere Möglichkeit mit Streams, die mir nur auf Grund mangelnder Erfahrung nicht einfällt?
 
Streams sind für solche Dinge schlicht nicht gemacht.

Streams sind Datenflüsse, dass heißt ich schaue mir jedes "Paket" was vorbeikommt an und entscheide lokal was ich damit mache. Einen Zustand während der Verarbeitung gibt es in der Form nicht und passt schlicht auch nichts in Konzept. Im Best-Case kann ein .stream() auch durch .parallelStream() ersetzen ohne Nachteile. Eine Zeilen-Nummerierung kommt aber eben nicht ohne Zustand (letzte vergebene Zeilennummer) aus und deswegen muss man diese Verrenkungen machen.

Eine Schraube reindrehen ist auch etwas total simples, trotzdem nimmt niemand einen Hammer dafür 🙂
 
Aber damit wäre ohne diese einen oder anderern Verrenkungen ein Stream ja eigentlich nicht turingvollständig, oder?
Die Aussage verstehe ich in der Form nicht. Du müsstest jetzt erstmal definieren, was ein Stream ist. Und Verrenkungen sind bei einer Turing-Vollständigkeit normal - versuch mal dein Problem auf einer 1-Band Turing Maschine mit 0en und 1en als einzige Zeichen zu lösen - da musst du dich ziemlich verrenken.
 
versuch mal dein Problem auf einer 1-Band Turing Maschine mit 0en und 1en als einzige Zeichen zu lösen - da musst du dich ziemlich verrenken.
du musst deine turing maschine nicht so definieren... du kannst auch einfach sagen ich darf 7er speichern auf einem band.. nur ich speicher da halt eine unter turing maschine... die maschine muss gewisse sachen erfüllen um turing komplett zu sein

was ein stream damit zu tun hat weis ich leider auch nicht da es da um die sprache geht die man benutzt
 
Vielleicht suchst du auch so etwas:

Java:
import java.util.Arrays;
import java.util.Iterator;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class TwoStreams {
    public static void print1(String[] lines) {
        for (int i = 0; i < lines.length; i++) {
            System.out.println(i + 1 + ": " + lines[i]);
        }
    }

    public static void print2(String[] lines) {
        Stream<String> stream = Arrays.stream(lines);
        Iterator<String> iterator = stream.iterator();
        IntStream.rangeClosed(1, lines.length).mapToObj(i -> i + ": " + iterator.next()).forEach(System.out::println);
    }

    public static void main(String[] args) {
        String[] lines = {
                "one", "two", "three", "four"
        };
        print1(lines);
        print2(lines);
    }
}

obwohl das genau genommen gemogelt ist, weil print2 ein String-Array hat und von Arrays die Länge bekannt ist...
 

Zurück
Oben