Reputation: 43
I am trying to create a function that filters a map that holds a String as a key and a list of Flight objects as values and returns a list of strings. The map represents flight paths and the task is to find the shortest path from the origin to the destination. The flight class has two fields, origin and destination. So if I send Vienna, New York, I should get a list with fligth1 and flight2 in it. The funcition takes in two parameters (String origin, String destination)
The key in the map represents a city, while the values are locations from where flights arrive, like so:
Map<String, List<Flight>> paths = new HashMap<>();
List<Flight> flightsToBerlin = new ArrayList<>();
List<Flight> flightsToVienna = new ArrayList<>();
Flight flight1 = new Flight("Vienna", "Berlin");
Flight flight2 = new Flight("Berlin", "New York");
flightsToBerlin.add(flight1);
flightsToVienna.add(flight2);
paths.put("Vienna", flightsToVienna);
paths.put("Berlin", flightsToBerlin);
The trick is that the requirement is for it to be done in one line. And this is the part that drives me crazy. I have tried using streams but I get kind of confused after filtering the map and finding the destination, like so:
public List<Flight> findPath(String origin, String destination) {
return (List<Flight>) this.paths.entrySet().stream()
.filter(x -> x.getKey().equals(destination))..
}
How do I proceed from here?
Upvotes: 4
Views: 724
Reputation: 81
Try the code below:
return (List<Flight>) paths.entrySet()
.stream()
.filter(x -> x.getKey().equals(destination))
.map(Map.Entry::getValue)
.flatMap(List::stream)
.collect(Collectors.toList());
Upvotes: 1
Reputation: 2492
You can do it like this:
return Stream.of(
paths.values()
.stream()
.flatMap(Collection::stream)
.collect(Collectors.groupingBy(Flight::getStartingLocation))
).flatMap(flights ->
Stream.of(
new HashMap<>(Map.of(origin, new Flight(origin, origin)))
).peek(back ->
Stream.iterate(
List.of(origin),
list -> list.stream().flatMap(
now -> flights.getOrDefault(now, Collections.emptyList()).stream()
.filter(flight -> back.putIfAbsent(flight.getDestination(), flight) == null)
.map(Flight::getDestination)
).collect(Collectors.toList())
).filter(list -> list.contains(destination)).findFirst()
).map(back ->
Stream.iterate(
new Flight(destination, null),
now -> back.get(now.getStartingLocation())
)
.skip(1)
.takeWhile(flight -> !flight.getDestination().equals(origin))
.collect(Collectors.toList())
)
)
.map(ArrayList::new)
.peek(Collections::reverse)
.findFirst().get();
Upvotes: 4
Reputation: 3422
What you are requesting is nearly impossible. However, I was able to whip up a one-liner which mostly works. Its main limitation is that it can only find two flights to get you to your destination. In other words:
Berlin -> Vienna | WORKS
New York -> Berlin -> Vienna | WORKS
Boston -> New York -> Berlin -> Vienna | DOESN'T WORK
I usually take the time to explain what I did in my answer and why, but this is such a messy, complicated, inefficient abomination of the the Java language that I doubt I could explain it well enough. Here's my best attempt at explaining what is happening, but not why it is happening:
If there are no flights which match both the given origin and destination, compare every flight which matches either the origin or destination and see if any align with each other AND the given parameters at the same time. Example:
(A1) (A2)
findPath(Boston, Tokyo)
(B1) (B2) B1 aligns with A1
Flight[Boston, Istanbul] C2 aligns with A2
C1 aligns with B2
(C1) (C2)
Flight[Istanbul, Tokyo]
Before you implement the code, you will need the following imports:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
I am also assuming that your Flight
class looks something like this:
public class Flight {
private String origin;
private String dest;
public Flight(String origin, String dest) {
this.origin = origin;
this.dest = dest;
}
public String getOrigin() {
return origin;
}
public String getDestination() {
return dest;
}
}
Alrighty, here's your one-liner:
@SuppressWarnings("unchecked")
public List<Flight> findPath(String origin, String destination) {
return new ArrayList<Flight>((Collection<Flight>) this.paths.values().stream().map(l -> l.stream().filter(f -> f.getDestination().equalsIgnoreCase(destination)|| f.getOrigin().equalsIgnoreCase(origin)).collect(Collectors.toList())).map(t -> new Object[] { t.stream().filter(f -> f.getDestination().equalsIgnoreCase(destination)&& f.getOrigin().equalsIgnoreCase(origin)).findAny(), t }).map(t -> ((Optional<Flight>) t[0]).isPresent() ? ((Optional<Flight>) t[0]).get() : t[1]).reduce((t, u) -> t instanceof Flight ? new HashSet<Flight>(Arrays.asList((Flight) t)): t instanceof HashSet ? t: u instanceof Flight ? new HashSet<Flight>(Arrays.asList((Flight) u)): u instanceof HashSet ? u: Stream.concat(((List<Flight>) t).stream().filter(f1 -> (f1.getDestination().equalsIgnoreCase(destination)&& ((List<Flight>) u).stream().anyMatch(f2 -> f1.getOrigin().equalsIgnoreCase(f2.getDestination())))|| (f1.getOrigin().equalsIgnoreCase(origin)&& ((List<Flight>) u).stream().anyMatch(f2 -> f1.getDestination().equalsIgnoreCase(f2.getOrigin())))),((List<Flight>) u).stream().filter(f1 -> (f1.getDestination().equalsIgnoreCase(destination)&& ((List<Flight>) t).stream().anyMatch(f2 -> f1.getOrigin().equalsIgnoreCase(f2.getDestination())))|| (f1.getOrigin().equalsIgnoreCase(origin)&& ((List<Flight>) t).stream().anyMatch(f2 -> f1.getDestination().equalsIgnoreCase(f2.getOrigin()))))).collect(Collectors.toList())).get());
}
Upvotes: 1