Создать список смежности со связанными узлами, используя неизменяемые списки - PullRequest
0 голосов
/ 07 ноября 2018

Я пытаюсь создать список смежности со связанными узлами, который в настоящее время определен так:

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 # раньше (используя изменяемые списки), поэтому я могу упустить момент здесь. Конечно, я мог бы также использовать изменяемые списки, но я хотел попробовать использовать неизменяемые.

Ответы [ 3 ]

0 голосов
/ 08 ноября 2018

В 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: ref до Node 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 = [...];};}; ...];};}; ...];};};
          ...];};}];};}
0 голосов
/ 08 ноября 2018

Проблема с вашим подходом - тип 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
0 голосов
/ 07 ноября 2018

Я не думаю, что есть какой-то способ сделать то, что вы хотите с вашим точным представлением. Одна простая альтернатива - сделать множество соседей ленивым:

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

Ключ в том, что, делая список соседей ленивым, мы можем сначала создать набор всех узлов, которые нам нужны до заполнения набора соседей (сосед вычисление указывается одновременно с созданием узла, но не запускается до более поздней версии).

...