Stream mit Sonderbehandlung des ersten Elements

looparda

Top Contributor
Ich suche nach einem eleganten Weg einen Stream zu durchlaufen mit Sonderbehandlung des ersten Elements. Ich habe ein paar Ansätze und Libraries gefunden und ausprobiert, aber bin noch nicht so zufrieden, da alle über den Index gehen und ich das gern weghaben möchte. Vielleicht fällt jemandem noch etwas besseres ein oder jemand kennt eine passende Lib.

[CODE lang="java" title="StreamFirstTest"]
import static org.assertj.core.api.Assertions.*;

import java.util.List;

import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Test;

class StreamFirstTest {

private final List<String> input = Lists.newArrayList("a", "b", "c");
private final List<String> expected = Lists.newArrayList("first", "b", "c");

@Test
public void test_mapThingsWithSpecialCaseFirstItem() {
final List<String> actual = StreamFirst.mapThingsWithSpecialCaseFirstItem(input);

assertThat(actual).isEqualTo(expected);
}

@Test
public void test_mapThingsWithSpecialCaseFirstItem_Index() {
final List<String> actual = StreamFirst.mapThingsWithSpecialCaseFirstItem_Index(input);

assertThat(actual).isEqualTo(expected);
}

@Test
public void test_mapThingsWithSpecialCaseFirstItem_StreamEx_EntryStream() {
final List<String> actual = StreamFirst.mapThingsWithSpecialCaseFirstItem_StreamEx_EntryStream(input);

assertThat(actual).isEqualTo(expected);
}

@Test
public void test_mapThingsWithSpecialCaseFirstItem_Guava() {
final List<String> actual = StreamFirst.mapThingsWithSpecialCaseFirstItem_Guava_mapWithIndex(input);

assertThat(actual).isEqualTo(expected);
}
}
[/CODE]
[CODE lang="java" title="StreamFirst"]import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import javax.util.streamex.EntryStream;

import com.google.common.collect.Streams;

public class StreamFirst {

private static String mapping(String item, boolean isFirstItem) {
if (isFirstItem) {
return "first";
} else {
return item;
}
}

public static List<String> mapThingsWithSpecialCaseFirstItem(List<String> list) {
return list
.stream()
.map(item -> mapping(item, list.indexOf(item) == 0))
.collect(Collectors.toList());
}

public static List<String> mapThingsWithSpecialCaseFirstItem_Index(List<String> list) {
return IntStream.range(0, list.size())
.mapToObj(i -> mapping(list.get(i), i == 0))
.collect(Collectors.toList());
}

public static List<String> mapThingsWithSpecialCaseFirstItem_StreamEx_EntryStream(List<String> list) {
return EntryStream.of(list)
.mapKeyValue((index, item) -> mapping(item, index == 0))
.toList();
}

@SuppressWarnings("UnstableApiUsage")
public static List<String> mapThingsWithSpecialCaseFirstItem_Guava_mapWithIndex(List<String> list) {
return Streams.mapWithIndex(list.stream(), (item, index) -> mapping(item, index == 0))
.collect(Collectors.toList());
}

}[/CODE]
 

httpdigest

Top Contributor
Hmmm.. da fällt mir sonst nur noch skip() ein:
Java:
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Stream.*;
...
public static List<String> mapThingsWithSpecialCaseFirstItem_Skip(List<String> list) {
  return list.isEmpty()
       ? emptyList()
       : concat(of("first"), list.stream().skip(1)).collect(toList());
}
 

looparda

Top Contributor
Ich habe die mapping Funktion zum besseren Verständnis sehr vereinfacht dargestellt und tatsächlich geht es auch nicht um Strings. Ich benötige ein
  1. Mapping und
  2. im Mapping den Zugriff auf das aktuelle Element und ob es das erste ist.
 

httpdigest

Top Contributor
Benötigt die Mapping-Funktion denn auch im Falle des ersten Elementes das Element selbst (weil sie bei dir aktuell im Falle des ersten Elementes ja eine Konstante zurückliefert)?

Falls nicht, dann hier eine ganz generische Version:
Java:
public static <T, R> List<R> mapThingsWithSpecialCaseFirstItem_Skip(
    List<T> list,
    BiFunction<T, Boolean, R> mapFunction) {
  return list.isEmpty()
       ? emptyList()
       : concat(of(mapFunction.apply(null, true)),
                list.stream()
                    .skip(1)
                    .map(e -> mapFunction.apply(e, false)))
        .collect(toList());
}
 
Zuletzt bearbeitet:

httpdigest

Top Contributor
Ja, leider schon. Die Konstante kommt auch durch die Vereinfachung des Problems.
:D

Als nächstes sagst du wahrscheinlich: "Das erste" ist auch eine Vereinfachung des Problems. In Wirklichkeit muss ein Element eine kompliziertere Prädikatsfunktion erfüllen, um als (aktuell vereinfacht) "das erste" zu gelten. :)

Und dann: Es gibt nicht nur ein "das erste" Element (bzw. ein prädikatserfüllendes Element), sondern mehrere. :D
 

looparda

Top Contributor
Basierend auf deinem Ansatz müsste sowas gehen. Aber ist wiederum nicht schön... unter anderem wegen Stream im Stream.
Java:
    public static <T, R> List<R> mapThingsWithSpecialCaseFirstItem_Skip(
            List<T> list,
            BiFunction<T, Boolean, R> mapFunction) {
        return list.isEmpty()
                ? emptyList()
                : concat(list.stream().limit(1).map(e -> mapFunction.apply(e, true)),
                list.stream().skip(1).map(e -> mapFunction.apply(e, false)))
                .collect(Collectors.toList());
    }
 

looparda

Top Contributor
Vielleicht trifft es das Beispiel besser:
Java:
import static org.assertj.core.api.Assertions.*;

import java.util.List;

import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Test;

class StreamFirstTest {

    private final List<OrderItem> input = Lists.newArrayList(new OrderItem(2), new OrderItem(3), new OrderItem(4));
    private final List<String> expected = Lists.newArrayList(
            "first item in order with discount:1.8",
            "regular price:3.0",
            "regular price:4.0");

    @Test
    public void test_mapThingsWithSpecialCaseFirstItem() {
        final List<String> actual = StreamFirst2.mapThingsWithSpecialCaseFirstItem(input);

        assertThat(actual).isEqualTo(expected);
    }

}

import java.util.List;
import java.util.stream.Collectors;

public class StreamFirst2 {

    static class OrderItem {
        double price;
        OrderItem(double price) {
            this.price = price;
        }
    }

    public static String firstItemDiscountMapper(OrderItem item, boolean isFirstItem) {
        if (isFirstItem) {
            return "first item in order with discount:" + item.price*0.9;
        } else {
            return "regular price:" + item.price;
        }
    }

    public static List<String> mapThingsWithSpecialCaseFirstItem(List<OrderItem> list) {
        return list
                .stream()
                .map(item -> firstItemDiscountMapper(item, list.indexOf(item) == 0))
                .collect(Collectors.toList());
    }

}
 

LimDul

Top Contributor
Nimm doch einen Zustandsbehafteten Mapper:

Java:
public class SpecialOrderMapper() {
private boolean first = true;
public String mapToString(OrderItem item) {
        if (first) {
            first = false;
            return "first item in order with discount:" + item.price*0.9;
        } else {
            return "regular price:" + item.price;
        }
    }
}

public class BusinessCode {
    public List<String> mapThingsWithSpecialCaseFirstItem(List<OrderItem> list) {
       SpecialOrderMapper mapper = new SpecialOrderMapper();
        return list
                .stream()
                .map(specialMapper::mapToString)
                .collect(Collectors.toList());
    }
}
Ungetestet :)
 

Flown

Administrator
Mitarbeiter
Ich frage mich bei solchen Lösungen immer, ist das dann einfach parallelisierbar (bzw. funktioniert das beim parallelen Ausführen auch noch).

Daher was markiert das erste Element in einer Liste: der Index 0.

Daher mein Ansatz:
Java:
var whatever = IntStream.range(0, list.size()).mapToObj(i -> mapper.apply(list.get(i), i == 0)).collect(Collectors.toList());
 

looparda

Top Contributor

Flown

Administrator
Mitarbeiter
Das entspricht dem mapThingsWithSpecialCaseFirstItem_Index Ansatz. Und ja, ich denke der Ansatz hat Vorteile, aber sieht trotzdem nicht elegant aus und drückt sofort ersichtlich aus: "Für das erste Element so, für den Rest so".
Oh shit ich hab nicht alles gelesen sorry.

Streams sind nicht wirklich geeignet um Zustände und Positionen zu halten oder zu verarbeiten. Solange hier kein Stream.withIndices oder so exisitiert musst du mit sowas wohl leben müssen, ohne jetzt irgendetwas neu zu stricken oder StreamEx zu verwenden.
 
Das scheint mir die einzige saubere Lösung zu sein:
Java:
    public static List<String> mapThingsWithSpecialCaseFirstItem(List<Double> orders) {
        Iterator<Double> iterator = orders.iterator();
        List<String> list = new LinkedList<>();
        if (iterator.hasNext())
            list.add(String.valueOf(iterator.next() * 0.9));
        iterator.forEachRemaining(o -> list.add(String.valueOf(o * 0.95)));
        return list;
    }

    public static void main(String[] args) {
        System.out.println(mapThingsWithSpecialCaseFirstItem(Arrays.asList(10.0, 10.0, 10.0)));
    }
 

CSHW89

Bekanntes Mitglied
Über Spliterator kann man immer ziemlich viel machen. Ich hab mal was kleines gebaut:
Java:
public class StreamEx {
   
    public static void main(String[] args) {
        List<Integer> lst = new ArrayList<>();
        for(int i = 0; i < 100; ++i) {
            lst.add(i);
        }
        List<String> res = specialFirstMapping(lst.stream(),
                    a -> "first",
                    b -> b.toString())
                .parallel()
                .collect(Collectors.toList());
        res.sort(Comparator.comparingInt(s -> {
            int i = 0;
            try {
                i = Integer.parseInt(s);
            }
            catch (Exception e) {
            }
            return i;
        }));
        System.out.println(res);
    }
   
   
    public static <T, R> Stream<R> specialFirstMapping(
            Stream<T> stream,
            Function<T, R> firstMapping,
            Function<T, R> restMapping) {
        return StreamSupport.stream(new SpecialFirstMappingSpliterator<>(
                stream.spliterator(), firstMapping, restMapping), stream.isParallel());
    }
   
   
    private static class SpecialFirstMappingSpliterator<T, R> implements Spliterator<R> {
       
        private final Spliterator<T> base;
        private final Function<T, R> firstMapping;
        private final Function<T, R> restMapping;
        private R firstObj = null;
        private boolean first = true;
       
        private SpecialFirstMappingSpliterator(Spliterator<T> base,
                Function<T, R> firstMapping,
                Function<T, R> restMapping) {
            this.base = base;
            this.firstMapping = firstMapping;
            this.restMapping  = restMapping;
        }
       
        @Override
        public boolean tryAdvance(Consumer<? super R> action) {
            if (firstObj != null) {
                action.accept(firstObj);
                firstObj = null;
                return true;
            }
            if (first) {
                first = false;
                return base.tryAdvance(t -> action.accept(firstMapping.apply(t)));
            }
            return base.tryAdvance(t -> action.accept(restMapping.apply(t)));
        }
       
        @Override
        public Spliterator<R> trySplit() {
            if (first) {
                base.tryAdvance(t -> { firstObj = firstMapping.apply(t); });
                Objects.requireNonNull(firstObj);
                first = false;
            }
            Spliterator<T> other = base.trySplit();
            if (other != null) {
                SpecialFirstMappingSpliterator<T,R> res = new SpecialFirstMappingSpliterator<T,R>(other, firstMapping, restMapping);
                res.first = false;
                return res;
            }
            return null;
        }
       
        @Override
        public long estimateSize() {
            return base.estimateSize();
        }
       
        @Override
        public int characteristics() {
            return base.characteristics();
        }
       
    }
   
}
Wie aber schon hier vermutet, wird es mit einer parallelen Verarbeitung schwierig. Ich habs über trySplit versucht. Allerdings garantiert die Methode nicht, welche Seite (links oder rechts) sie weiter bearbeitet und welche sie zurückgibt. Somit kann ich nur garantieren, dass EIN Element durch "firstMapping" behandelt wird, aber nicht welches.
Edit: Die parallele Verarbeitung klappt nun auch. Wenn man es nicht braucht, lässt man "trySplit" einfach "null" returnen und entfernt alles mit "firstObj".
 
Zuletzt bearbeitet:
Ähnliche Java Themen
  Titel Forum Antworten Datum
KonradN SonarLint: Resources should be closed bei Stream<T>? Allgemeine Java-Themen 6
S Umstellung von File auf Path - Probleme mit Stream Allgemeine Java-Themen 5
A verschachtelte for-Schleife in einen Stream umwandeln? Allgemeine Java-Themen 4
A Wie schreibe ich eine For-Schleife in ein Stream API um? Allgemeine Java-Themen 12
R Java Stream: Ist es möglich, einen stream zusammenzufassen Allgemeine Java-Themen 6
S Mittelwert anhand eines Stream berechnen Allgemeine Java-Themen 5
H Stream in ArrayList umwandeln Allgemeine Java-Themen 2
M stream.Collectors Fehlermeldung Allgemeine Java-Themen 1
C TCP Server und BufferedReader Leerstring im Stream? Allgemeine Java-Themen 5
G Neues Objekt aus List<JsonObject> mit Stream Allgemeine Java-Themen 4
Y Liste in Stream Packen Allgemeine Java-Themen 1
N Java stream filtern. Allgemeine Java-Themen 19
H Collector Generics Problem (incl. Stream & Lambda) Allgemeine Java-Themen 4
N javax.xml.stream.XMLStreamException: ParseError at [row,col]:[1,1] Allgemeine Java-Themen 3
N [stream-api] Parameter pro Typ zählen Allgemeine Java-Themen 1
J Stream-basierter Cache Allgemeine Java-Themen 4
D IP-Cam live stream speichern Allgemeine Java-Themen 9
B BufferedWriter in InputStream oder Zeichen-Stream in Byte-Stream Allgemeine Java-Themen 5
D Klassen Zeit aus NMEA-Stream in SimpleDateFormat Allgemeine Java-Themen 17
J Stream-Murks! Allgemeine Java-Themen 18
T Problem mit gzip Stream und Ende der Datei Allgemeine Java-Themen 2
JAVATUX Java Programm mit ATI Stream Unterstützung Allgemeine Java-Themen 3
X Audio Internet-Stream Allgemeine Java-Themen 2
B Stream Verständnisproblem Allgemeine Java-Themen 2
P Mime Type aus Stream lesen Allgemeine Java-Themen 5
C Bit Stream Klasse Allgemeine Java-Themen 9
dayaftereh Serializable und Object In/Out Stream Allgemeine Java-Themen 2
M PCL Stream lesen und schreiben Allgemeine Java-Themen 6
musiKk Stream zum Lesen von Dateien mit seek und peek Allgemeine Java-Themen 2
J Problem mit Scanner-Stream Allgemeine Java-Themen 2
O Stream unvollständig Allgemeine Java-Themen 3
0 Stream/Datei an VLC übergeben und abspielen Allgemeine Java-Themen 10
B getImage() vom Stream oder File Allgemeine Java-Themen 3
N Werte Von C++ nach Java über den Stream möglich? Allgemeine Java-Themen 8
S Stream ReadLine() Allgemeine Frage Allgemeine Java-Themen 5
S Stream ohne Referenz kopieren ? Allgemeine Java-Themen 4
D FileInputStream bzw. BufferedInput Stream Puffern Allgemeine Java-Themen 6
G Live-Stream einer WebCam importieren Allgemeine Java-Themen 3
T Mehrere Dateien byteweise durch stream schieben Allgemeine Java-Themen 9
T Stream encodierrn und decodieren mit JSpeex Allgemeine Java-Themen 2
R Stream Byte für Byte durchgehen Allgemeine Java-Themen 5
O Input stream geht net Allgemeine Java-Themen 2
N Speichern von binären Dateien (Zip-Archiv) per Stream? Allgemeine Java-Themen 6
T Bit-Stream Allgemeine Java-Themen 12
R Stream für alle Dateiarten Allgemeine Java-Themen 9
thE_29 API für transport stream Allgemeine Java-Themen 2
M Output Stream / Protokoll does not support output Allgemeine Java-Themen 2
D GetResourceAsStream - Stream closed exception Allgemeine Java-Themen 5
G Umwandlung in Stream Allgemeine Java-Themen 5
J Output Stream Allgemeine Java-Themen 4
M Enums zum ersten mal Allgemeine Java-Themen 15
A Mit dem letzten bis zum ersten Wert aus Array rechnen Allgemeine Java-Themen 15
S Start des zweiten Threads erst nach Beenden des ersten Threads Allgemeine Java-Themen 13
R Welche waren eure ersten Projekte? Allgemeine Java-Themen 10
B ZIP - Problem mit dem ersten Ordner Allgemeine Java-Themen 2
K ersten programmstart erkennen Allgemeine Java-Themen 7
D Datei beim ersten Programmstart anlegen? Allgemeine Java-Themen 3
thE_29 Vor dem ersten Posten bitte lesen! Allgemeine Java-Themen 0
G Zweites Java-Programm im ersten aufrufen?!? Allgemeine Java-Themen 15

Ähnliche Java Themen

Neue Themen


Oben