0

I just got into Google Guava and it seems like a powerful tool and I see how you can use Predicates and filter by a specific property. How you can also chain predicates in FluentIterable My question is what's the best way to filter for a single property.

For example, if I have a collection of Cars. How do I filter the Cars.getPaintColor() to give me cars that are in Black, Red, and Yellow? Creating 3 separate predicates and using FluentIterable seems clumsy. Especially in my use, I could want possibly 10+ filters on the same property and I wouldn't want to create 10 Predicates.

Thanks you!

        List<String> colorList = (List<String>)filterCriteria.get("Color");
        List<String> makeList = (List<String>)filterCriteria.get("Make");
        List<String> rimSizeList = (List<String>)filterCriteria.get("RimSize");

        Predicate<String> predColor = Predicates.in(ImmutableSet.copyOf(colorList));
        Predicate<CarObj> predDirection2 = Predicates.compose(predColor ,[????] );

        Predicate<String> predMakeList  = Predicates.in(ImmutableSet.copyOf(makeList));
        Predicate<CarObj> predMakeList2 = Predicates.compose(predMakeList, [????] );

        Predicate<String> predRimSize = Predicates.in(ImmutableSet.copyOf(rimSizeList));
        Predicate<CarObj> predRimSize2 = Predicates.compose(predRimSize, [????] );

        Collection<CarObj> filtered = FluentIterable.from(mAllCars)
                .filter(predDirection2)
                .filter(predMakeList2)
                .filter(predRimSize2)
                .toList();

Since I am using an List, I used copyOf instead of of when creating ImmutableSet.

I am not sure what to put in the second parameter of the compose. I am guessing it is something like this... in the CarObj class.

static Predicate<CarObj> byColor= new Predicate<CarObj>() {
    public boolean apply(CarObj input) {

        // What do I put here?
    }
};
Alan
  • 9,331
  • 14
  • 52
  • 97
  • Do you want to filter on one value of the property at a time (but with different possible values) or do you want to partition all your Cars based on the value the property? You either need a parameterized `Predicate` or just the extracting `Function` (as mentioned by ColinD) and `Multimaps.index()`. – Frank Pavageau Jan 09 '15 at 09:04
  • @FrankPavageau I want to filter on one value of the property with difference possible values, but I also want to filter on other properties that have different possible values. – Alan Jan 09 '15 at 14:52

2 Answers2

3

So, to check if a paint color is one of black, read or yellow, you'd want to create a Predicate that checks if a set contains that color:

Predicate<PaintColor> p = Predicates.in(ImmutableSet.of(
    PaintColor.RED, PaintColor.BLACK, PaintColor.YELLOW));

You could then compose that with a Function<Car, PaintColor> that returns the paint color property of your class:

Predicate<Car> p2 = Predicates.compose(p, Car.GET_PAINT_COLOR_FUNCTION);

Edit:

By Car.GET_PAINT_COLOR_FUNCTION I just mean something like this:

public static final Function<Car, PaintColor> GET_PAINT_COLOR_FUNCTION =
    new Function<Car, PaintColor>() {
      @Override public PaintColor apply(Car car) {
        return car.getPaintColor();
      }
    });

As I said in the comments, you can adapt that to your actual types as needed. For example, make it a Function<CarObj, String> instead.

ColinD
  • 108,630
  • 30
  • 201
  • 202
  • @Alan: From there you just pass the predicate to `filter`. – ColinD Jan 09 '15 at 17:00
  • What if the colors is not an enum? – Alan Jan 12 '15 at 21:42
  • @Alan: It doesn't really matter how you create the `Collection` that you pass to `Predicates.in`. – ColinD Jan 12 '15 at 21:43
  • but I see you have `Predicate`. Does that mean I need to create a class with wiht a single String holding the paint color? And getter/setter for it? – Alan Jan 12 '15 at 21:48
  • Replace `PaintColor` with whatever type you happen to be representing the paint color with. If that's `String`, make it a `Predicate`. I used `PaintColor` because I thought it made it more clear for my example. – ColinD Jan 12 '15 at 22:02
  • thank you so much for the quick responses. I believe I am getting close to how to use it. I updated my question to show you what I have so far. I am not sure what you mean in your answer of `Car.GET_PAINT_COLOR_FUNCTION` since `CarObj.getCarColor()` doesn't resolve. I am not sure what to put there. Thank you! – Alan Jan 12 '15 at 22:37
  • Actually figured it out. Thank you! – Alan Jan 13 '15 at 15:02
2

The alternative to composing your extracting Function<Car, PaintColor> with a Predicates.in() as suggested by ColinD is to write your parameterized Predicate<Car>:

public class CarPaintColorPredicate implements Predicate<Car> {
    private final PaintColor paintColor;

    public CarPaintColorPredicate(PaintColor paintColor) {
        this.paintColor = paintColor;
    }

    @Override
    public boolean apply(@Nullable Car input) {
        return input != null && input.getPaintColor() == paintColor;
    }
}

which you can then use directly:

FluentIterable.from(cars)
        .filter(new CarPaintColorPredicate(PaintColor.RED))
        .toList();

or combine for multiple colors:

FluentIterable.from(cars)
        .filter(Predicates.or(
            new CarPaintColorPredicate(PaintColor.RED),
            new CarPaintColorPredicate(PaintColor.BLACK)))
        .toList();

or even combine with other types of predicates:

FluentIterable.from(cars)
        .filter(new CarPaintColorPredicate(PaintColor.RED))
        .filter(new CarMakePredicate("Ferrari"))
        .toList();

To be complete, the version with the Function<Car, PaintColor> is as follows:

public enum CarPaintColorFunction implements Function<Car, PaintColor> {
    INSTANCE;

    @Override
    public PaintColor apply(@Nullable Car input) {
        return input == null ? null : input.getPaintColor();
    }
}

The Function simply returns the value of the property, which is then compared to the collection (hopefully a Set) of accepted values through the Predicate composition:

FluentIterable.from(cars)
        .filter(Predicates.compose(
            Predicates.in(Sets.immutableEnumSet(PaintColor.RED, PaintColor.BLACK)),
            CarPaintColorFunction.INSTANCE))
        .toList();

All that's really explained in the Functional Explained page in the Guava Wiki.

Frank Pavageau
  • 11,477
  • 1
  • 43
  • 53