【问题标题】:Create adjacency list with linked nodes using immutable lists使用不可变列表创建具有链接节点的邻接列表
【发布时间】:2018-11-07 19:29:19
【问题描述】:

我正在尝试使用当前定义如下的链接节点创建邻接列表:

type Node =
    {
        Name: string
        Neighbors: Node list
    }

type AdjacencyList(nodes: Node list) =

    interface IHasNodes with
        /// Gets the number of nodes in the adjacency list.
        member this.NodeCount = nodes.Length

        /// Gets a list of all nodes in the adjacency list.
        member this.Nodes = nodes

我要创建列表的输入是格式中的字符串序列

node_name neighbor_name_1 ... neighbor_name_n

所以,基本上这应该是一个简单的任务,但我想不出一种方法来更新节点而不在一个节点时遇到一个循环,例如A,与B有一条边,B与A有一条边。在这种情况下,我必须在创建其节点对象时更新B的邻居,然后将节点A中的邻居引用更新为节点B,这反过来又离开了我再次更新节点 B 等等。

module List =

    /// <summary>
    /// Replaces the first item in a list that matches the given predicate.
    /// </summary>
    /// <param name="predicate">The predicate for the item to replace.</param>
    /// <param name="item">The replacement item.</param>
    /// <param name="list">The list in which to replace the item.</param>
    /// <returns>A new list with the first item that matches <paramref name="predicate"/> replaced by <paramref name="item"/>.</returns>
    let replace predicate item list =
        let rec replaceItem remainingItems resultList =
            match remainingItems with
            | []         ->  resultList |> List.rev
            | head::tail ->
                match predicate(head) with
                | false -> replaceItem tail (head::resultList)
                | true  -> (item::resultList |> List.rev) @ tail

        replaceItem list []

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module AdjacencyList =

    let create (rows: seq<string>) =
        let mutable preBuiltNodes: Node list = []
        let rowsToEnumerate = rows |> Seq.where (fun str -> not (System.String.IsNullOrWhiteSpace(str)) || not (str.StartsWith("//")))
        let neighborsForNodes = Dictionary<string, string array>()

        // Build the base nodes and get the neighbors of each node.
        for row in rowsToEnumerate do
            let rowData = row.Split(' ')

            neighborsForNodes.Add(rowData.[0], rowData |> Array.skip 1)
            preBuiltNodes <- { Name = rowData.[0]; Neighbors = [] } :: preBuiltNodes

        // Build the final nodes from the pre-built nodes.
        let rec buildAdjacencyList remainingNodes (builtNodes: Node list) =
            match remainingNodes with
            | []         -> builtNodes
            | head::tail ->
                let neighbors = preBuiltNodes |> List.where (fun node -> neighborsForNodes.[head.Name] |> Array.exists (fun name -> name = node.Name))
                let newNode = { head with Neighbors = neighbors };

                // Update nodes referencing an old version of the new node.
                let mutable newBuiltNodes: Node list = []

                for i = 0 to (builtNodes.Length - 1) do
                    if builtNodes.[i].Neighbors |> List.exists (fun node -> node.Name = head.Name) then
                        let updatedNode = { builtNodes.[i] with Neighbors = builtNodes.[i].Neighbors |> List.replace (fun n -> n.Name = newNode.Name) newNode }
                        newBuiltNodes <- updatedNode :: newBuiltNodes

                        // Cycle here when updating newNode
                        // if it has an edge to the node at builtNodes.[i]

                    else
                        newBuiltNodes <- builtNodes.[i] :: newBuiltNodes

                preBuiltNodes <- preBuiltNodes |> List.replace (fun n -> n.Name = newNode.Name) newNode
                buildAdjacencyList tail (newNode::newBuiltNodes)

        buildAdjacencyList preBuiltNodes [] |> AdjacencyList

我之前在 C# 中实现了该算法(使用可变列表),所以我可能在这里遗漏了一点。当然我也可以使用可变列表,但我想尝试使用不可变列表。

【问题讨论】:

    标签: f# adjacency-list


    【解决方案1】:

    我不认为有任何方法可以用你的确切表示来做你想做的事。一个简单的替代方法是使邻居集变得惰性:

    type node<'a> = {
        id : 'a
        neighbors : Lazy<node<'a> list>
    }
    
    let convert (m:Map<'a, 'a list>) =
        let rec nodes = [
            for KeyValue(k,vs) in m -> 
                { id = k; 
                  neighbors = lazy 
                                vs 
                                |> List.map (fun id -> 
                                                nodes 
                                                |> List.find (fun n -> n.id = id))}]
        nodes
    
    convert (Map [1,[2;3]; 2,[3]; 3,[1]])
    

    关键是通过使邻居列表变得惰性,我们可以首先创建我们关心的所有节点的集合填充邻居集合(邻居计算 在创建节点的同时指定,但直到稍后才运行)。

    【讨论】:

    • 感谢您的意见。即使它像魅力一样工作,我也可能会使用其他答案中提到的用于定义邻居的字符串列表或地图,因为我将使用的最终图表有新的要求。
    【解决方案2】:

    在 C# 中,您将包含对相邻节点的引用。但是根据您的定义,您有一个 Neighbor 节点,这使它成为一项不可能完成的任务。 除了@kvb 的解决方案,还有其他选择:

    选项 1: 使用 string list

    对我来说,让列表引用节点名称而不是节点本身会更有意义:

    type Node =
        {
            Name     : string
            Neighbors: string list
        }
    

    首先是一些辅助函数:

    let addRelation relations (node1, node2)  = 
        relations 
        |> Set.add (node1, node2)
        |> Set.add (node2, node1)
    
    let allRelations nodes =
        nodes |> List.fold addRelation Set.empty
    

    这将是创建它的方式:

    let getNodes nodes = 
        let rels = allRelations nodes
        rels
        |> Seq.groupBy fst
        |> Seq.map (fun (name, neighbors) -> 
            { Name      = name
              Neighbors = neighbors |> Seq.map snd |> Seq.toList 
            } 
        )
        |> Seq.toList
    

    基本上你给它一个包含这对名字的列表:

    ["1", "2" 
     "3", "4"
     "1", "3"
     "4", "5" ]
    |> getNodes 
    |> Seq.iter (printfn "%A")
    

    产生:

    {Name = "1";
     Neighbors = ["2"; "3"];}
    {Name = "2";
     Neighbors = ["1"];}
    {Name = "3";
     Neighbors = ["1"; "4"];}
    {Name = "4";
     Neighbors = ["3"; "5"];}
    {Name = "5";
     Neighbors = ["4"];}
    

    选项 2: refNode list

    您可以参考相邻节点列表:

    type Node =
        {
            Name     : string
            Neighbors: Node list ref
        }
    

    这样您可以先创建节点,然后将它们添加到列表中:

    let getNodes nodePairs = 
        let rels        = allRelations nodePairs
        let nodes       = rels |> Seq.map (fun (name, _) -> name, { Name = name ; Neighbors = ref [] }) |> Map
        let getNode  nm = nodes |> Map.find nm
        rels
        |> Seq.groupBy fst
        |> Seq.iter (fun (name, neighbors) ->
            (getNode name).Neighbors := neighbors |> Seq.map (snd >> getNode) |> Seq.toList
        )
        nodes |> Map.toList |> List.map snd
    

    像这样测试它:

    ["1", "2" 
     "3", "4"
     "1", "3"
     "4", "5" ]
    |> getNodes 
    |> Seq.iter (printfn "%A")
    

    输出:

    {Name = "1";
     Neighbors =
      {contents =
        [{Name = "2";
          Neighbors = {contents = [...];};};
         {Name = "3";
          Neighbors =
           {contents =
             [...;
              {Name = "4";
               Neighbors = {contents = [...; {Name = "5";
                                              Neighbors = {contents = [...];};}];};}];};}];};}
    {Name = "2";
     Neighbors =
      {contents =
        [{Name = "1";
          Neighbors =
           {contents =
             [...;
              {Name = "3";
               Neighbors =
                {contents =
                  [...;
                   {Name = "4";
                    Neighbors =
                     {contents = [...; {Name = "5";
                                        Neighbors = {contents = [...];};}];};}];};}];};}];};}
    {Name = "3";
     Neighbors =
      {contents =
        [{Name = "1";
          Neighbors = {contents = [{Name = "2";
                                    Neighbors = {contents = [...];};}; ...];};};
         {Name = "4";
          Neighbors = {contents = [...; {Name = "5";
                                         Neighbors = {contents = [...];};}];};}];};}
    {Name = "4";
     Neighbors =
      {contents =
        [{Name = "3";
          Neighbors =
           {contents =
             [{Name = "1";
               Neighbors = {contents = [{Name = "2";
                                         Neighbors = {contents = [...];};}; ...];};};
              ...];};}; {Name = "5";
                         Neighbors = {contents = [...];};}];};}
    {Name = "5";
     Neighbors =
      {contents =
        [{Name = "4";
          Neighbors =
           {contents =
             [{Name = "3";
               Neighbors =
                {contents =
                  [{Name = "1";
                    Neighbors =
                     {contents = [{Name = "2";
                                   Neighbors = {contents = [...];};}; ...];};}; ...];};};
              ...];};}];};}
    

    【讨论】:

      【解决方案3】:

      您的方法的问题是Node 类型。我提出了一种不同的方法,如下所示:

      type Node = Node of string
      
      type Graph = Graph of Map<Node, Set<Node>>
      
      let emptyGraph = Graph Map.empty
      
      let addEdge nodeA nodeB g =
          let update mainNode adjNode map =
              let updatedAdjs =
                  match map |> Map.tryFind mainNode with
                  | Some adjs ->
                      adjs |> Set.add adjNode
                  | None ->
                      Set.singleton adjNode
              map
              |> Map.add mainNode updatedAdjs
      
          let (Graph map) = g
          map
          |> update nodeA nodeB
          |> update nodeB nodeA
          |> Graph
      
      let addIsolatedNode node g =
          let (Graph map) = g
          match map |> Map.tryFind node with
          | Some _ ->
              g
          | None ->
              map
              |> Map.add node Set.empty
              |> Graph
      
      let getAdjs node g =
          let (Graph map) = g
          map
          |> Map.tryFind node
      
      // TEST
      let myGraph =
          emptyGraph
          |> addEdge (Node "A") (Node "B")
          |> addEdge (Node "A") (Node "C")
          |> addEdge (Node "A") (Node "D")
          |> addEdge (Node "B") (Node "C")
          |> addEdge (Node "C") (Node "D")
          |> addIsolatedNode (Node "E")
      myGraph |> getAdjs (Node "A") // result: Some (set [Node "B"; Node "C"; Node "D"])
      myGraph |> getAdjs (Node "E") // result: Some (set [])
      myGraph |> getAdjs (Node "F") // result: None
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-07-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-04-25
        • 1970-01-01
        • 2017-11-12
        • 1970-01-01
        相关资源
        最近更新 更多