Amaz
Amaz

Reputation: 43

How to filter map and return list of values

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

Answers (3)

Pritesh Ladhe
Pritesh Ladhe

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

IlyaMuravjov
IlyaMuravjov

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

Cardinal System
Cardinal System

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:

  1. Find flights which match either the given origin or or destination
  2. Check for single flights which match BOTH the given origin AND destination
  3. 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

Related Questions