Multiple filtering in an ArrayList

2

I have a method that receives an Object of type List of a class of its own that is called Pallet. The class Pallet has a creation date, an article, an id, a warehouse ... etc.
The method

  

filterPalletWithDataretorna (List list, ListParametersFilter parameters);

returns another list with the pallets that have been filtered by the fields that I received in the object ListParametersFilter

public List<Pallet> filterPalletWithData(List<Pallet> list, ListParametersFilter parameters){
    //aqui va mi codigo de filtrado
         }

What I'm doing is:

  public List<Pallet> filterPalletWithData(List<Pallet> list, ListParametersFilter parameters) {

            List<Pallet> returnedPalets = new ArrayList<>();

            returnedPalets.clear();
            //por ppc
            if (parameters.getId() >= 0)
                for (Pallet p : list) {
                    if (p.getIdNum() == parameters.getId())
                       returnedPalets.add(p);
                }


            if (parameters.getOrderNum() >= 0)
                for (Pallet p : list) {
                    if (p.getNum() == parameters.getOrderNum()) {
                          returnedPalets.add(p)
                    }
                }

......
}

This is the class ListParametersFilter

 public class ListParametersFilter {

        private int id;
        private int orderNum;
        private long manufacturedArticle;
        private long plantation;
        private long crop;
        private long crew;
        private long customer;
        private long pallet;
        private long box;
        private String since;
        private String until;



        public ListParametersFilter(int id, int orderNum,
                                    long manufacturedArticle, long plantation,
                                    long crop, long crew, long customer, long pallet,
                                    long box, String since, String until) {
            this.id= id;
            this.orderNum = orderNum;
            this.manufacturedArticle = manufacturedArticle;
            this.plantation = plantation;
            this.crop = crop;
            this.crew = crew;
            this.customer = customer;
            this.pallet = pallet;
            this.box = box;
            this.since = since;
            this.until = until;
        }


        public ListParametersFilter(){}

    //Getters && setters

This is adding the pallets to a list that I then return, it only works if the filtering field is unique, for example by

  

id

but the problem comes when the filtering is multiple, that is, what happens if the user wants to filter by

  

store + id + article + date.

How can I implement that?

    
asked by JoCuTo 03.10.2017 в 15:29
source

1 answer

4

If you do not have Java 8

If you do not have java 8, you can create a filter class that takes the list, applies the filters you want and returns a copy of the filtered list.

I have implemented a functional interface (to which you can not pass a lambda) along with a generic class that will allow you to apply several filters sequentially:

The interface:

public interface FilterPredicate<T>
{
    boolean applyFilter(T t);
}

The filtering class:

public class ListFilter<T>
{
    private List<T> list;

    public ListFilter(List<T> list)
    {
        this.list = new ArrayList<>(list);
    }

    public void filter(FilterPredicate<T> filter)
    {
        Iterator<T> iterator = list.iterator();
        while (iterator.hasNext())
        {
            T t = iterator.next();
            if (!filter.applyFilter(t)) iterator.remove();
        }
    }

    public List<T> getList()
    {
        return this.list;
    }   
}

The trick is to overwrite the applyFilter method at the time you call filter , so you define the filtering method at the time of filtering. The filtering process goes like this:

// Una lista de Pallet de prueba
List<Pallet> palletList = new ArrayList<>();
palletList.add(new Pallet(1,5, "Toronto", "Primero"));
palletList.add(new Pallet(2, 45, "Madrid", "Segundo"));
palletList.add(new Pallet(3, 45, "Madrid", "Tercero"));
palletList.add(new Pallet(4, 46, "Madrid", "Cuarto"));

// Crear la clase filtradora de Pallet
ListFilter<Pallet> filterer = new ListFilter<>(palletList);

// Filtrar por todos los Pallet que tengan Madrid como warehouse
filterer.filter(new FilterPredicate<Pallet>()
{
    @Override
    public boolean applyFilter(Pallet pallet)
    {
        return pallet.getWarehouse().equals("Madrid");
    }
});

// De la lista resultante del primer filtro, filtrar los que tengan numero de orden = 45
filterer.filter(new FilterPredicate<Pallet>()
{
    @Override
    public boolean applyFilter(Pallet pallet)
    {
        return pallet.getNumOrder() == 45;
    }
});

// De la lista resultante del primer filtro, filtrar los que tengan id = 2
filterer.filter(new FilterPredicate<Pallet>()
{
    @Override
    public boolean applyFilter(Pallet pallet)
    {
        return pallet.getId() == 2;
    }
});

As you can see, the amount of code is much greater, although it works.

If you have Java 8

If you use Java 8 and want to add filters dynamically, it occurs to me that you can use the functional interface Predicate and its method and() to generate a list of filters dynamically.

As Predicate is a Functional Interface , it can be the target of a lambda expression, which in this case will be the filter you want to add.

First, I've changed your class ListParametersFilter to represent a dynamic list of filters:

public class ListParametersFilter {

    List<Predicate<Pallet>> filters = new ArrayList<>();

    public void addFilter(Predicate<Pallet> filter)
    {
        filters.add(filter);
    }

    public Predicate<Pallet> getFilter()
    {
        return filters.stream().reduce(Predicate::and).orElse(p -> true);
    }
}

The addFilter method allows you to place a lambda expression (a function) as a method parameter (because it accepts a functional interface), which is just what you need to add filters. An example of how to use it:

ListParametersFilter filters = new ListParametersFilter();
filters.addFilter(pallet -> pallet.getId() == 5);

The getMultipleFilter method is the one that gives you all the filters in one. The trick is that the and() of Predicate method allows you to add another Predicate through the logical operation && . ( Credits to Marko Topolnik for teaching how to concatenate% co_of% by reducing a stream, here your answer on SO ).

I created a small example with a class Predicate test to demonstrate the operation:

public void testFiltrado()
{
    List<Pallet> pallets = new ArrayList<>();
    pallets.add(new Pallet(4, "No cumple ningun filtros", 3));
    pallets.add(new Pallet(5, "Cumple el primer filtro", 4));
    pallets.add(new Pallet(5, "Cumple ambos filtros", 5));

    ListParametersFilter filters = new ListParametersFilter();
    filters.addFilter(pallet -> pallet.getId() == 5);
    System.out.println("Filtro: id = 5 aplicado");

    pallets.stream().filter(filters.getMultipleFilter()).forEach(p -> System.out.println(p.getAlmacen()));

    filters.addFilter(pallet -> pallet.getOrderNum() == 5);
    System.out.println("Filtro: orderNum = 5 aplicado");

    pallets.stream().filter(filters.getMultipleFilter()).forEach(p -> System.out.println(p.getAlmacen()));
}

And the exit:

Filtro: id = 5 aplicado
Cumple el primer filtro
Cumple ambos filtros
Filtro: orderNum = 5 aplicado
Cumple ambos filtros

To return a list, simply change the terminal operation Pallet by forEach :

return pallets.stream().filter(filters.getMultipleFilter()).collect(Collectors.toList());
    
answered by 03.10.2017 / 17:41
source