Home > Enterprise >  List of list , with predicat filter on parent list and another predicat filter on son list
List of list , with predicat filter on parent list and another predicat filter on son list

Time:11-27

I am looking to filter a list according to a predicate and also to filter her child list

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;  

class Hotel {
    private final String city;
    private final int numberOfStart;
    private final List<Room> rooms = new ArrayList<>();

    public String getCity(){return city;}
    public int  getNumberOfStart(){return numberOfStart;}
    public List<Room> getRooms(){return rooms;}
    public Hotel(String city, int numberOfStart) {
        this.city = city;
        this.numberOfStart = numberOfStart;
    }

    public void creatRooms(String roomNumber ,int numberOfbed,Double price)   {
        Room room = new Room(roomNumber,numberOfbed,price);
        this.rooms.add(room);
    }

    @Override
    public String toString() {
        return "Hotel{\n\t"  
                "city='"   city   '\''  
                ", numberOfStart="   numberOfStart  
                ", \n\trooms="   rooms  
                "}\n\n";
    }
}

class Room {
    private final double price;
    private final int numberOfBed;
    private final String roomNumber;

    Room (String roomNumber,int numberOfBed, Double price){
        this.price=price;
        this.roomNumber=roomNumber;
        this.numberOfBed=numberOfBed;

    }

    public double getPrice() {return price;}
    public int getNumberOfBed(){return numberOfBed;}

    @Override
    public String toString() {
        return "\n\t\tRoom{"  
                "price="   price  '\''  
                ", numberOfBed="   numberOfBed  
                ", roomNumber='"   roomNumber  
                '}';
    }
}

public class Main  {

    /**
     * @param hotelList List search on a list of hotels.
     * @param city relates to the location of the hotel, if empty "", then the predicate will be true, and ignore the city parameter.
     * @param start concerns the quality of the hotel, if set to 0 then the predicate will be true, and ignore the start parameter.
     * @param priceMax
     * @param nbBed concerns the amount beds, ignored if set to 0
     * @return
     */
    public static List<Hotel> searchHotelRoom(List<Hotel> hotelList, String city, int start, Double priceMax, int nbBed) {

        //condition about city location and price on hotel list
        Predicate<Hotel> byCity = !city.isEmpty()? hotel -> hotel.getCity().equalsIgnoreCase(city) : hotel -> true;
        Predicate<Hotel> byStart =!(start==0)? hotel -> hotel.getNumberOfStart() == start:hotel -> true;

        //condition on room list
        Predicate<Room> byNbBed =!(nbBed==0)? room  -> (room.getNumberOfBed()== nbBed) :room -> false;
        Predicate<Room> byPrice = room ->  room.getPrice()<=priceMax;

        return hotelList.stream()
                .filter(byStart)
                .filter(byCity)
                .filter(room -> room.getRooms().stream().anyMatch(byPrice))
                .filter(room -> room.getRooms().stream().anyMatch(byNbBed))
                .collect(Collectors.toList());
    }

    public static void main(String[] args) {

        List<Hotel> hotelList = new ArrayList<>();

        //Dummy hotel data
        Hotel hotelA = new Hotel("Paris",4);
        hotelA.creatRooms("p12", 2, 150.);
        hotelA.creatRooms("p17", 1, 200.);
        hotelA.creatRooms("p15", 3, 50.);
        hotelList.add(hotelA);

        Hotel hotelB = new Hotel("Montpellier",4);
        hotelB.creatRooms("b12", 2, 20.);
        hotelB.creatRooms("b17", 1, 200.);
        hotelB.creatRooms("b15", 1, 40.);
        hotelB.creatRooms("b15", 1, 1.);
        hotelList.add(hotelB);

        Hotel hotelC = new Hotel("Toulouse",4);
        hotelC.creatRooms("c12", 21, 200.);
        hotelC.creatRooms("c17", 11, 100.);
        hotelC.creatRooms("c15", 21, 50.);
        hotelC.creatRooms("c16", 30, 25.);
        hotelList.add(hotelC);

        //System.out.println("Hotels List\n");
        //hotelList.forEach(System.out::println);

        List<Hotel> result= searchHotelRoom(hotelList,"",0,200.,2);
        System.out.println("Result of search");
        result.forEach(System.out::println);
    }

}

The search function does not work as i would like there is some inconsistency for example for

List<Hotel> result= searchHotelRoom(hotelList,"paris",0,200.,1);

i have this result

Result of search
Hotel{
    city='Paris', numberOfStart=4, 
    rooms=[
        Room{price=150.0', numberOfBed=2, roomNumber='p12}, 
        Room{price=200.0', numberOfBed=1, roomNumber='p17}, 
        Room{price=50.0', numberOfBed=3, roomNumber='p15}]}

but i want something like

Result of search
Hotel{
    city='Paris', numberOfStart=4, 
    rooms=[
            Room{price=200.0', numberOfBed=1, roomNumber='p17}}

and it seems that I have no and logic between the filters

List<Hotel> result= searchHotelRoom(hotelList,"paris",0,200.,2);

must return nohing , but i have a result

And on many hotel

List<Hotel> result= searchHotelRoom(hotelList,"",0,200.,1);

I have

Result of search
Hotel{
    city='Paris', numberOfStart=4, 
    rooms=[
        Room{price=150.0', numberOfBed=2, roomNumber='p12}, 
        Room{price=200.0', numberOfBed=1, roomNumber='p17}, 
        Room{price=50.0', numberOfBed=3, roomNumber='p15}]}


Hotel{
    city='Montpellier', numberOfStart=4, 
    rooms=[
        
        Room{price=200.0', numberOfBed=1, roomNumber='b17}, 

but i looking for something like

Result of search
Hotel{
    city='Paris', numberOfStart=4, 
    rooms=[
        Room{price=200.0', numberOfBed=1, roomNumber='p17} 
}


Hotel{
    city='Montpellier', numberOfStart=4, 
    rooms=[
    
        Room{price=200.0', numberOfBed=1, roomNumber='b17} 
}

in search method anyMatch return a boolean but i want list of room,
so i have trie somme stuff on my searh methode like , but doesn't work

 .filter(room -> room.getRooms().stream().filter(byPrice))

Does anyone have a clue to help me please?

CodePudding user response:

There's a logical flaw in the searchHotelRoom() method.

I guess return type should be a List<Room> (not a list of Hotel). Think about it this way: when need to find a place to stay, you are not going to book the whole hotel, you need a room instead. There's no need for searchHotelRoom() generate new Hotel instances based on the rooms that match the criteria, we need the rooms itself.

You might want to retain the information to which Hotel a certain Room belongs, this problem can be (and should be) addressed on the class-design level. For that, you can introduce a property to the Room class. For instance: String hotelName, or alternatively a room could hold a reference to the instance of Hotel, but in such case you need to be careful while implementing methods like toString() and hashCode() because you might end up with infinite recursion.

So, to get a List of Rooms that match the given criteria, you need to apply a hotel-related filter and then transform a stream of hotels Stream<Hotel> into a stream of rooms Stream<Room>. For that, you can use flatMap() operation (or Java 16 mapMulti()), which is meant to perform one-to-many transformation and expects a function producing a Stream as an argument.

Then a room-related filter, and collect the result.

Note that instead of applying filter() twice you can chain the predicates using Precate.and() which is an equivalent of the logical AND &&.

public static List<Room> searchHotelRoom(List<Hotel> hotelList, String city, int start, Double priceMax, int nbBed) {
    
    //condition about city location and price on hotel list
    Predicate<Hotel> byCity = !city.isEmpty()? hotel -> hotel.getCity().equalsIgnoreCase(city) : hotel -> true;
    Predicate<Hotel> byStart = !(start == 0)? hotel -> hotel.getNumberOfStart() == start:hotel -> true;
    
    //condition on room list
    Predicate<Room> byNbBed = !(nbBed == 0)? room  -> (room.getNumberOfBed() == nbBed) :room -> false;
    Predicate<Room> byPrice = room ->  room.getPrice() <= priceMax;
    
    return hotelList.stream()                        // Stream<Hotel>
        .filter(byStart.and(byCity))
        .flatMap(hotel -> hotel.getRooms().stream()) // Stream<Room>
        .filter(byPrice.and(byNbBed))
        .toList(); // for Java 16 or collect(Collectors.toList())
}

CodePudding user response:

You need to use a map (or flatmap) as well as a filter to do something like this. The rough form will be something like:

hotelsList.stream()
    .filter(hotelFilter)
    .flatMap(hotel -> hotel.rooms.stream()
        .filter(roomFilter))
    .collect(whatever)

In general, map operations take an A<B> and a B -> C and give you a A<C>. Flatmaps take an A<B> and a B -> A<C> and give you an A<C> (for instance, combining lists, but also performing an operation on an Optional that might itself return None even if the original value existed)

CodePudding user response:

Due to the fact that other answers have already suggested how the problem can be solved by modifying the Room class (or just returning a List of Rooms), I'd like to suggest a solution that doesn't modify it, whilst maintaining the relationship between a Room and a Hotel.

For the method Main#searchHotelRoom, you could use Collectors.toMap and return Map<Hotel, List<Room>> which denotes a map of Hotels against a List of Rooms that met the search criteria for that Hotel.

Here is an example of how you could achieve this:

return hotelList.stream()
                .filter(byStart)
                .filter(byCity)
                .collect(
                    Collectors.toMap(Functions.identity(),
                        hotel -> hotel.getRooms()
                                      .stream()
                                      .filter(byPrice)
                                      .filter(byNbBed)
                                      .collect(Collectors.toList())
                    )
                );
  • Related