Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reuse application of filter & map on a Stream?

I have a set of domain objects that inherit from a shared type (i.e. GroupRecord extends Record, RequestRecord extends Record). The subtypes have specific properties (i.e. GroupRecord::getCumulativeTime, RequestRecord::getResponseTime).

Further, I have a list of records with mixed subtypes as a result of parsing a logfile.

List<Record> records = parseLog(...);

In order to compute statistics on the log records, I want to apply math functions only on a subset of the records that matches a specific subtype, i.e. only on GroupRecords. Therefore I want to have a filtered stream of specific subtypes. I know that I can apply a filter and map to a subtype using

records.stream()
       .filter(GroupRecord.class::isInstance)
       .map(GroupRecord.class::cast)
       .collect(...

Apply this filter&cast on the stream multiple times (especially when doing it for the same subtype multiple times for different computations) is not only cumbersome but produces lots of duplication.

My current approach is to use a TypeFilter

class TypeFilter<T>{

    private final Class<T> type;

    public TypeFilter(final Class<T> type) {
        this.type = type;
    }

    public Stream<T> filter(Stream<?> inStream) {
        return inStream.filter(type::isInstance).map(type::cast);
    }
}

To be applied to a stream:

TypeFilter<GroupRecord> groupFilter = new TypeFilter(GroupRecord.class); 

SomeStatsResult stats1 = groupFilter.filter(records.stream())
                                      .collect(...)
SomeStatsResult stats2 = groupFilter.filter(records.stream())
                                      .collect(...)

It works, but I find this approach a bit much for such a simple task. Therefore I wonder, is there a better or what is the best way for making this behavior reusable using streams and functions in a concise and readable way?

like image 447
Gerald Mücke Avatar asked May 29 '17 12:05

Gerald Mücke


People also ask

Can you reuse sterile filters?

In general, one can consider re-use of sterilizing-grade filters as applying to filtration of multiple batches of product or other process fluid. This approach is consistent with applications of re-use of air, gas and vent sterilizing filters in multiple batches or campaigns.

Can you reuse micron filters?

No, do not reuse micron bags. While some pressers clean their bags with ethanol for a second use, they will not be as accurate and will be prone to blowouts, wherein your materials spills out of the micron bag.

Can you reuse syringe filters?

We advise against reusing them, since fine particulates that are too small to be visible to the naked eye may cause cross-contamination and compromise your results.

What is the importance of filters?

Many people are shocked to learn that there are thousands of pollutants found within their homes. A clean air filter can trap many of these pollutants, and other measures can be taken to further improve indoor air quality. Breathing dirty air puts you at risk for asthma, allergies and other respiratory ailments.


1 Answers

It depends on what do you find "more concise and readable". I myself would argue that the way you already implemented is fine as it is.

However, there is indeed a way to do this in a way that is slightly shorter from the point of where you use it, by using Stream.flatMap:

static <E, T> Function<E, Stream<T>> onlyTypes(Class<T> cls) {
  return el -> cls.isInstance(el) ? Stream.of((T) el) : Stream.empty();
}

What it would do is it will convert each original stream element to either a Stream of one element if the element has expected type, or to an empty Stream if it does not.

And the use is:

records.stream()
  .flatMap(onlyTypes(GroupRecord.class))
  .forEach(...);

There are obvious tradeoffs in this approach:

  • You do lose the "filter" word from your pipeline definition. That may be more confusing that the original, so maybe a better name than onlyTypes is needed.
  • Stream objects are relatively heavyweight, and creating so much of them may result in performance degradation. But you should not trust my word here and profile both variants under heavy load.

Edit:

Since the question asks about reusing filter and map in slightly more general terms, I feel like this answer can also discuss a little more abstraction. So, to reuse filter and map in general terms, you need the following:

static <E, R> Function<E, Stream<R>> filterAndMap(Predicate<? super E> filter, Function<? super E, R> mapper) {
   return e -> filter.test(e) ? Stream.of(mapper.apply(e)) : Stream.empty();
}

And original onlyTypes implementation now becomes:

static <E, R> Function<E, Stream<R>> onlyTypes(Class<T> cls) {
  return filterAndMap(cls::isInstance, cls::cast);
}

But then, there is yet again a tradeoff: resulting flat mapper function will now hold captured two objects (predicate and mapper) instead of single Class object in above implementation. It may also be a case of over-abstracting, but that one depends on where and why you would need that code.

like image 136
M. Prokhorov Avatar answered Sep 17 '22 18:09

M. Prokhorov