使用不可变列表创建具有链接节点的邻接列表

时间:2018-11-07 19:29:20

标签: f# adjacency-list

我正在尝试使用当前定义为这样的链接节点创建邻接表:

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的节点对象时更新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#中实现了该算法,所以在这里我可能会遗漏一点。当然,我也可以使用可变列表,但是我想尝试使用不可变列表。

3 个答案:

答案 0 :(得分:2)

我认为没有任何方法可以用您的确切表示做您想要的事情。一个简单的替代方法是使一组邻居变得懒惰:

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]])

关键是通过使邻居列表变懒,我们可以先创建我们关心的所有节点的集合,然后再填充邻居集合(邻居 computation 是在创建节点的同时指定的,但要等到以后再运行。

答案 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 = [...];};}; ...];};}; ...];};};
          ...];};}];};}

答案 2 :(得分:2)

您的方法存在的问题是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