Functional/Stream programming for the graph problem “Reconstruct Itinerary”

[亡魂溺海] 提交于 2021-02-05 07:36:17

问题


I am trying to solve the reconstruct itinerary problem (https://leetcode.com/problems/reconstruct-itinerary/) in Scala using functional approach. Java solution works but Scala doesn't. One reason I found out was the hashmap is being updated and every iteration has the latest hashmap (even when popping from recursion) which is weird.

Here is the solution in Java:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;

public class Solution1 {

private void dfg(Map<String, PriorityQueue<String>> adj, LinkedList<String> result, String vertex){
  PriorityQueue<String> pq = adj.get(vertex);
  while (pq!=null && !pq.isEmpty()){
    System.out.println("Before :"+adj.get(vertex));
    String v = pq.poll();
    System.out.println("After :"+ adj.get(vertex));
    dfg(adj,result,v);
  }
  result.addFirst(vertex);
}

public List<String> findItinerary(List<List<String>> tickets){
  Map<String,PriorityQueue<String>> adj = new HashMap<>();
  for(List<String> ticket:  tickets){
    adj.putIfAbsent(ticket.get(0),new PriorityQueue<>());
    adj.get(ticket.get(0)).add(ticket.get(1));
  }
  LinkedList<String> result = new LinkedList<>();
  dfg(adj,result,"JFK");
  //not reverse yet
  return result;
}

public static void main(String[] args){

  List<List<String>> tickets = new ArrayList<>();
  List t1= new ArrayList();
  t1.add("JFK");
  t1.add("SFO");
  tickets.add(t1);
  List t2= new ArrayList();
  t2.add("JFK");
  t2.add("ATL");
  tickets.add(t2);
  List t3= new ArrayList();
  t3.add("SFO");
  t3.add("ATL");
  tickets.add(t3);
  List t4= new ArrayList();
  t4.add("ATL");
  t4.add("JFK");
  tickets.add(t4);
  List t5= new ArrayList();
  t5.add("ATL");
  t5.add("SFO");
  tickets.add(t5);


  System.out.println();
  Solution1 s1 = new Solution1();
  List<String> finalRes  = s1.findItinerary(tickets);

  for(String model : finalRes) {
    System.out.print(model + " ");
  }
}

}

Here is my solution in Scala which is not working:

package graph

class Itinerary {

}

case class Step(g: Map[String,List[String]],sort: List[String]=List())
object Solution {
  def main(arr: Array[String]) = {
    val tickets = List(List("JFK","SFO"),List("JFK","ATL"),List("SFO","ATL"),List("ATL","JFK"),List("ATL","SFO"))
    println(findItinerary(tickets))
  }
  def findItinerary(tickets: List[List[String]]): List[String] = {
    val g = tickets.foldLeft(Map[String,List[String]]())((m,t)=>{
      val key=t(0)
      val value= t(1)
      m + (key->(m.getOrElse(key,Nil) :+ value).sorted)
    })
    println(g)
    // g.keys.foldLeft(Step())((s,n)=> dfs(n,g,s)).sort.toList
    dfs("JFK",Step(g)).sort.toList
  }

  def dfs(vertex: String,step: Step): Step = {
    println("Input vertex " + vertex)
    println("Input map "+ step.g)
    val updatedStep= step.g.getOrElse(vertex,Nil).foldLeft(step) ((s,n)=>{
      //println("Processing "+n+" of vertex "+vertex)
      //delete link
      val newG = step.g + (vertex->step.g.getOrElse(vertex,Nil).filter(v=>v!=n))
      // println(newG)
      dfs(n,step.copy(g=newG))
    })
    println("adding vertex to result "+vertex)
    updatedStep.copy(sort = updatedStep.sort:+vertex)
  }
}

回答1:


Scala is sometimes approached as a "better" Java, but that's really very limiting. If you can get into the FP mindset, and study the Standard Library, you'll find that it's a whole new world.

def findItinerary(tickets: List[List[String]]): List[String] = {
  def loop(from : String
          ,jump : Map[String,List[String]]
          ,acc  : List[String]) : List[String] = jump.get(from) match {
    case None => if (jump.isEmpty) from::acc else Nil
    case Some(next::Nil) => loop(next, jump - from, from::acc)
    case Some(nLst) =>
      nLst.view.map{ next =>
        loop(next, jump+(from->(nLst diff next::Nil)), from::acc)
      }.find(_.lengthIs > 0).getOrElse(Nil)
  }
  loop("JFK"
      ,tickets.groupMap(_(0))(_(1)).map(kv => kv._1 -> kv._2.sorted)
      ,Nil).reverse
}



回答2:


I am going to be honest that I didn't look through your code to see where the problem was. But, I got caught by the problem and decided to give it a go; here is the code:
(hope my code helps you)

type Airport = String // Refined 3 upper case letters.

final case class AirlineTiket(from: Airport, to: Airport)

object ReconstructItinerary {
  // I am using cats NonEmptyList to improve type safety, but you can easily remove it from the code.
  private final case class State(
      currentAirport: Airport,
      availableDestinations: Map[Airport, NonEmptyList[Airport]],
      solution: List[Airport]
  )
  
  def apply(tickets: List[AirlineTiket])(start: Airport): Option[List[Airport]] = {
    @annotation.tailrec
    def loop(currentState: State, checkpoints: List[State]): Option[List[Airport]] = {
      if (currentState.availableDestinations.isEmpty) {
        // We used all the tickets, so we can return this solution.
        Some((currentState.currentAirport :: currentState.solution).reverse)
      } else {
        val State(currentAirport, availableDestinations, solution) = currentState
        
        availableDestinations.get(currentAirport) match {
          case None =>
            // We got into nowhere, lets see if we can return to a previous state...
            checkpoints match {
              case checkpoint :: remaining =>
                // If we can return from there
                loop(currentState = checkpoint, checkpoints = remaining)
              
              case Nil =>
                // If we can't, then we can say that there is no solution.
                None
            }
          
          case Some(NonEmptyList(destination, Nil)) =>
            // If from the current airport we can only travel to one destination, we will just follow that.
            loop(
              currentState = State(
                currentAirport = destination,
                availableDestinations - currentAirport,
                currentAirport :: solution
              ),
              checkpoints
            )
          
          case Some(NonEmptyList(destination, destinations @ head :: tail)) =>
           // If we can travel to more than one destination, we are going to try all in order.
           val newCheckpoints = destinations.map { altDestination =>
             val newDestinations = NonEmptyList(head = destination, tail = destinations.filterNot(_ == altDestination))
             State(
               currentAirport = altDestination,
               availableDestinations.updated(key = currentAirport, value = newDestinations),
               currentAirport :: solution
             )
           }
           
           loop(
             currentState = State(
               currentAirport = destination,
               availableDestinations.updated(key = currentAirport, value = NonEmptyList(head, tail)),
               currentAirport :: solution
             ),
             newCheckpoints ::: checkpoints
           )
        }
      }
    }
    
    val availableDestinations = tickets.groupByNel(_.from).view.mapValues(_.map(_.to).sorted).toMap 
    loop(
      currentState = State(
        currentAirport = start,
        availableDestinations,
        solution = List.empty
      ),
      checkpoints = List.empty
    )
  }
}

You can see the code running here.



来源:https://stackoverflow.com/questions/64776981/functional-stream-programming-for-the-graph-problem-reconstruct-itinerary

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!