Home > other >  Output a pair of objects with stream
Output a pair of objects with stream

Time:01-12

I have two classes Foo and Bar, where Foo has a list of Bars in its attributes. Something like this:

public class Bar {
   int id;
   ...

   public boolean isGood() {...}
}
public class Foo {
   int id;
   List<Bar> bars;
   ...
    public boolean hasBadBar() {
        boolean hasOnlyGoodBars = bars.stream().allMatch(Bar::isGood);
        return !hasOnlyGoodBars;
    }
}

Now imagine I have a list of Foos called foos and want to go through foos and report Foos that have Bars that are bad (bar.isGood() is false). I want to report the pair of (String version of Foo id, String version of Bar id) of such incidents. Any ideas on how to do it in line with stream()?

I can report ids of such Foos (or such Bars) separately but I don't know how to do a pair of them.

CodePudding user response:

This should do the trick:

List<Pair<Foo,Bar>> pairs = foos.stream().
            flatMap(
                    //take all the non-good Bars and create a Pair together with its Foo
                    foo -> foo.bars.stream().
                           filter(Predicate.not(Bar::isGood)).
                           map(bar -> new Pair(foo, bar))
            ).
            collect(Collectors.toList());

Where Pair is some class Pair<A,B>

CodePudding user response:

since you need to return a list of lists you may want to use flatMap to collect everything

List<Pair> fooBarPair = foos.stream().flatMap( foo -> {
            List<Bar> badBars = foo.getBadBars();
            return badBars.stream().map( bar -> new Pair( foo.toString(), bar.toString() ) );
        } ).toList();
public class Test {
    @AllArgsConstructor
    @ToString(exclude = "isGood")
    static class Bar {
        int id;
        boolean isGood;

        public boolean isGood() {return isGood;}
        public boolean isBad() {return !isGood;}
    }


    @AllArgsConstructor
    @ToString(exclude = "bars")
    @Getter
    public class Foo {
        int id;
        List<Bar> bars;
        public boolean hasBadBar() {
            return bars.stream().filter(Bar::isBad).toList().size() != bars.size();
        }
        public List<Bar> getBadBars() {
            return bars.stream().filter( Bar::isBad ).collect( Collectors.toList());
        }
    }

    @AllArgsConstructor
    @ToString
    static class Pair {
        String foo;
        String bar;
    }

    @org.junit.jupiter.api.Test
    void test() {
        List<Foo> foos = new ArrayList<>();
        List<Bar> barsFoo0 = new ArrayList<>();
        barsFoo0.add( new Bar( 1, true ) );
        barsFoo0.add( new Bar( 2, true ) );
        barsFoo0.add( new Bar( 3, false ) );

        List<Bar> barsFoo1 = new ArrayList<>();
        barsFoo1.add( new Bar( 1, false ) );
        barsFoo1.add( new Bar( 2, false ) );
        barsFoo1.add( new Bar( 3, true ) );

        foos.add( new Foo( 0, barsFoo0 ) );
        foos.add( new Foo( 1, barsFoo1 ) );

        List<Pair> fooBarPair = foos.stream().flatMap( foo -> {
            List<Bar> badBars = foo.getBadBars();
            return badBars.stream().map( bar -> new Pair( foo.toString(), bar.toString() ) );
        } ).toList();

        System.out.println(fooBarPair);

    }



}

CodePudding user response:

Assuming that 1) you only cared about the first bad bar, and 2) you added an Optional firstBadBar() method to Foo, this code would work to collect the ids into a map:

Map<String,String> result = foos.stream()
        .filter( foo -> foo.firstBadBar().isPresent() )
        .collect( Collectors.toMap( foo -> String.valueOf( foo.id ), foo -> String.valueOf( foo.firstBadBar().get().id ) ) );

CodePudding user response:

The following should work if no collecting required:

    foos.stream()
        .filter(f ->f.hasBadBars())
        .forEach(f -> {
            System.out.printf("Foo %d has bad bars:%n", f.getId());
            f.getBars().stream()
                .filter(b -> !b.getIsGood())
                .forEach(bad ->System.out.printf("\tId of bad Bar: %d%n", bad.getId()));
        });

CodePudding user response:

You can create a Map<Foo, List<Bar>> (mapping a Foo to a List<Bar> containing all the bad Bars associated with the Foo).

First, you have a List<Foo> foos.

Then, you can use stream():

Map<Foo, List<Bar>> result = foos.stream()
    .filter(x -> x.hasBadBar())
    .map(x -> {
         List<Bar> badBars = new ArrayList<>(x.bars);
         badBars.removeIf(bar -> bar.isGood());
         return new Pair<>(x, badBars);
    })
    .collect(Collectors.toMap(Pair::getKey, Pair::getValue));

The filter method is used only to keep the Foo objects that have bad Bars, then the map method is used to create Pair<Foo, List<Bar>> objects for each Foo object, with the original Foo object as the key, and a list of bad Bars as the value. Finally, the collect method is used to collect the elements of the stream into a Map.

Then, you can use the Map to print a Foo and all the corresponding bad Bars

  • Related