2

I'm trying to create a node that has a string and an array of nodes but those other nodes are not yet created so how do I use them? I'm gonna need to access the edges of a particular node later in a 'for all edges in node' loop. It's a graph data structure. The graph is directed meaning a node A can have a connection with B and B doesn't have a connection with A.

type
  TNode = record
    name: String;
    edges: TNodeArray;
    procedure Init(const aName: String; const aEdges: TNodeArray);
  end;
  TNodeArray = array of TNode;

procedure  TNode.Init(const aName: String; const aEdges: TNodeArray);
begin
  name := aName;  edges := aEdges;
end;

function NewNode(const aName: String; const aEdges: TNodeArray): TNode;
begin
  Result.Init(aName, aEdges);
end;

procedure Main;
var
  n0, n1, n2, n3: TNode;
begin
  n0 := NewNode('new york', TNodeArray.Create(n1, n2));
  n1 := NewNode('london', TNodeArray.Create(n2));
  n2 := NewNode('moscu', TNodeArray.Create(n1, n3));
  n3 := NewNode('other city', nil);  // 'other city' has no connections
end;
13
  • The variables are all declared when you use them, but they are uninitialized, and that's not OK. You have to restructure your code to avoid that. Maybe you can create the individual nodes first and the links between them later? Commented Apr 23, 2020 at 11:15
  • That circular reference could prove troublesome ..... Commented Apr 23, 2020 at 11:16
  • You might need to create nodes first, and then add connections later Commented Apr 23, 2020 at 11:16
  • That's what I want. How would I add the nodes to the edges array without creating two of the same. I mean if two nodes have another in the list if I access and change one of them I want to modify both. Commented Apr 23, 2020 at 11:19
  • Oh, according to your recent edit, you use records (value types), and not classes (reference types). That's a problem. Commented Apr 23, 2020 at 11:20

1 Answer 1

1

In the comments, it turned out that this is a directed graph. If so, you could use something like this:

type
  TNode = class
  strict private
    FName: string;
    FOutgoingArcs: TList<TNode>;
    function GetOutgoingArc(Index: Integer): TNode;
    function GetOutgoingArcCount: Integer;
  public
    constructor Create(const AName: string);
    procedure AddOutgoingArc(ANode: TNode);
    procedure AddOutgoingArcs(const ANodes: array of TNode);
    property OutgoingArcs[Index: Integer]: TNode read GetOutgoingArc;
    property OutgoingArcCount: Integer read GetOutgoingArcCount;
    property Name: string read FName write FName;
    destructor Destroy; override;
  end;

implementation

{ TNode }

procedure TNode.AddOutgoingArc(ANode: TNode);
begin
  FOutgoingArcs.Add(ANode)
end;

procedure TNode.AddOutgoingArcs(const ANodes: array of TNode);
var
  Node: TNode;
begin
  for Node in ANodes do
    AddOutgoingArc(Node);
end;

constructor TNode.Create(const AName: string);
begin
  FName := AName;
  FOutgoingArcs := TList<TNode>.Create;
end;

destructor TNode.Destroy;
begin
  FOutgoingArcs.Free;
  inherited;
end;

function TNode.GetOutgoingArcCount: Integer;
begin
  Result := FOutgoingArcs.Count;
end;

function TNode.GetOutgoingArc(Index: Integer): TNode;
begin
  Result := FOutgoingArcs[Index];
end;

I also think it is good to keep track of all nodes in a single list, so I'd do

var
  Nodes: TObjectList<TNode>;

function CreateNode(const AName: string): TNode;
begin
  Result := TNode.Create(AName);
  Nodes.Add(Result);
end;

Now we can play (make sure to create Nodes first: Nodes := TObjectList<TNode>.Create(True{say})):

var
  NewYork,
  London,
  Paris,
  Moscow: TNode;
begin

  NewYork := CreateNode('New York');
  London := CreateNode('London');
  Paris := CreateNode('Paris');
  Moscow := CreateNode('Moscow');

  NewYork.AddOutgoingArc(London);
  London.AddOutgoingArcs([NewYork, Paris, Moscow]);
  Paris.AddOutgoingArcs([London, Moscow]);
  Moscow.AddOutgoingArc(NewYork);

But of course there are a thousand ways to design this. This is only one possible solution.

Update:

Notice that there is only a single object named "London", so if you change this "via Paris", it will be seen "via New York":

Paris.OutgoingArcs[0].Name := 'The Capital of the United Kingdom';

Now

NewYork.OutgoingArcs[0].Name

is also 'The Capital of the United Kingdom'.

Also notice that, with

Nodes := TObjectList<TNode>.Create(True)

the nodes will be owned by the Nodes object list (that's what True means), so they will be freed when Nodes is freed. So, for instance, if you use these nodes in your own class TTravelPlanner, you might want to create Nodes in TTravelPlanner.Create and do Nodes.Free in TTravelPlanner.Destroy.

Sign up to request clarification or add additional context in comments.

3 Comments

I'd like to see that changing the node from one list changes it in another. Can you add a get and a set like in this example: pastebin.com/RqxsRp8U Thank you.
@Arturo: There is only one New York node. If you change it "via London", you change this node, and you see it "via Moscow". Because there is only a single "New York" node, but many references to it. I don't know if that is what you mean. Maybe you want to be able to read/change the name? If so, you create one property with a "read" and a "write" part. Both can refer to the field directly; no need for setter/getter methods. See my updated A and the documentation for properties.
Changing the name was just to make sure I could change the node through another.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.