Map first element of stream differently than rest
Asked Answered
O

7

12

Is there a way in Java's Stream API to map first element of stream differently than other?

Equivalent of this code:

List<Bar> barList = new ArrayList<>();

for (int i=0; i<fooList.size(); i++) {
    Foo foo = fooList.get(i);
    Foo modifiedFoo = foo.getModifiedFoo();
    if (i == 0) {
        barList.add(new Bar(modifiedFoo, false));
    }else {
        barList.add(new Bar(modifiedFoo, true));
    }
}

Stream<Bar> = barList.stream();

Note: I already have a stream setup and I would want some operation after first mapping

fooList.stream()
        .map(Foo::getModifiedFoo)
        .(May be Some operation here to get different new Bar for first modifiedFoo)
        .map(modifiedFoo -> new Bar(modifiedFoo, true));
Overrule answered 23/3, 2018 at 18:13 Comment(0)
M
17

I would get the first element, create a Stream out of it and apply the needed mappings. Then, I'd take the rest of the list, create a stream out of it and apply the different mappings. Then concat the streams. Something like this:

Stream<Bar> first = Stream.of(fooList.get(0))
    .map(Foo::getModifiedFoo)
    .map(modifiedFoo -> new Bar(modifiedFoo, false));

Stream<Bar> others = fooList.subList(1, fooList.size()).stream()
    .map(Foo::getModifiedFoo)
    .map(modifiedFoo -> new Bar(modifiedFoo, true));

Stream<Bar> bars = Stream.concat(first, others).flatMap(s -> s);

Another approach:

Stream<Bar> bars = IntStream.range(0, fooList.size())
    .mapToObj(i -> new Bar(fooList.get(i).getModifiedFoo(), i > 0));

This way is succinct and does the job pretty well.

Martynne answered 23/3, 2018 at 19:43 Comment(2)
I like the last approach, it's clever, compact and uncluttered. nice!Curitiba
Sadly only works for a List. I have stream to start with.Jerkwater
J
4

Use an IntStream to iterate over the indices, then mapToObj to create an object for that index, and finally collect into a list:

List<Bar> barList = IntStream.range(0, fooList.size())
                             .mapToObj(i -> (i == 0 ? new Bar (fooList.get(i), false) : 
                                                      new Bar(fooList.get(i),true)))
                             .collect(Collectors.toList());

What would be more readable though, is doing the first item handling outside the loop, and using IntStream starting with 1.

Here is a demo using simple lists.

Jenine answered 23/3, 2018 at 18:27 Comment(0)
P
2

Although I think the accepted answer is better, here is an alternate approach.

    int[] counter = {-1};

    Stream<Bar> barListStream = fooList.stream().map(foo -> {
          counter[0]++;
          return new Bar(mfoo.getModifiedFoo(), counter[0]>0);
       }).collect(Collectors.toList()).stream();
Palmer answered 19/4, 2018 at 23:12 Comment(0)
M
1

I can propose two ways but I find your way straighter.

With IntStream such as :

List<Bar> barList = new ArrayList<>();
IntStream.range(0, fooList.size())
         .forEach(i->{
                if (i == 0) {
                    barList.add(new Bar(foo, false));
                }else {
                    barList.add(new Bar(foo, true));
                }
         }
);

It is not a real functional approach (forEach() use and no Collector) because it maintains the current index of the List.

As alternative, you could use a more functional approach but I don't find it straighter either :

List<Bar> barList = IntStream.range(0, fooList.size())
                             .mapToObj(i->{
                                    Foo foo = fooList.get(i);
                                    if (i == 0) {
                                        return new Bar(foo, false);
                                    }
                                    return new Bar(foo, true));                                     
                             })
                             .collect(Collectors.toList());
Microgram answered 23/3, 2018 at 18:27 Comment(2)
problem is that I already have a map before required operation in question. fooList.stream().map(FooUtility::getModifiedFoo)._Some operation here for first modifiedFoo to get new Bar_.map(modifiedFoo -> new Bar(modifiedFoo, true));Overrule
Can be simplified to: .mapToObj(i -> new Bar (fooList.get(i), i != 0))Container
R
1

Use an AtomicBoolean initially set to true to determine when is the first item.

    final AtomicBoolean first = new AtomicBoolean(true);
    System.out.println("** Print all numbers 1..10");
    IntStream.range(1, 11).forEach(number -> {
        System.out.print((first.get() ? "" : ",") + number);
        first.set(false);
    });
    System.out.println();
Rafaelof answered 28/12, 2022 at 22:28 Comment(0)
K
0

You can have an object to hold a flag e.g. AtomicBoolean or AtomicInteger - that you can reset on first or nth iteration (you would need something like AtomicInteger or some Integer holder to reset on nth iteration) e.g. following code using HashMap as the holder class - will print first line of the stream differently than the other lines:

    Map<String, Boolean> firstTime = new HashMap<>(Map.of("firstTime", true)); // to make the map modifiable
    try (Stream<String> lines = Files.lines(Paths.get(filename), Charset.defaultCharset())) {
        lines.forEachOrdered(line -> System.out.println(firstTime.remove("firstTime") != null ? ("firstTime: " + line) : line));
    }
Kingpin answered 13/10, 2021 at 23:41 Comment(0)
R
0

Maybe something like this:

Stream<Bar> barStream = barList.stream();

List<Bar> bars = barStream.skip(1)
                   .map(modifiedFoo -> new Bar(modifiedFoo, false))
                   .collect(Collectors.toList());

barStream.findFirst()
     .map(modifiedFoo -> new Bar(modifiedFoo, true))
     .ifPresent(bars::add);
Retentivity answered 17/8, 2023 at 9:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.