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 Room
s 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 Room
s), 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 Hotel
s against a List
of Room
s 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())
)
);