C#: semantic networks with default reasoning

Inherit properties up an is-a hierarchy and let more specific nodes override defaults.

C#
using System;
using System.Collections.Generic;

class Node {
  public string Name;
  public Node Parent;
  public Dictionary<string, bool> Properties = new Dictionary<string, bool>();

  public Node(string name, Node parent) {
    Name = name;
    Parent = parent;
  }

  public bool? Lookup(string property) {
    for (Node n = this; n != null; n = n.Parent) {
      if (n.Properties.TryGetValue(property, out bool value)) {
        return value;
      }
    }
    return null;
  }
}

class Program {
  static void Main() {
    var animal = new Node("animal", null);
    animal.Properties["alive"] = true;
    var bird = new Node("bird", animal);
    bird.Properties["flies"] = true;
    bird.Properties["feathers"] = true;
    var penguin = new Node("penguin", bird);
    penguin.Properties["flies"] = false;
    var tweety = new Node("tweety", bird);
    var opus = new Node("opus", penguin);

    foreach (var (node, property) in new[] { (tweety, "flies"), (opus, "flies"), (opus, "feathers"), (opus, "swims") }) {
      Console.WriteLine(node.Name + " " + property + ": " + (node.Lookup(property)?.ToString() ?? "unknown"));
    }
  }
}
Output
tweety flies: True
opus flies: False
opus feathers: True
opus swims: unknown

Explanation

A semantic network represents knowledge as nodes joined by links such as is-a. Properties attach to nodes, and a node inherits properties from its ancestors. Looking up a property means walking up the is-a chain until some node defines it.

Inherited properties are defaults, not guarantees. Birds fly by default, but a penguin is a bird that does not. The lookup finds the most specific definition first, so the penguin's override wins. This is non-monotonic reasoning: learning that Opus is a penguin makes us withdraw the conclusion that Opus flies.

Explore the dry run
Guided walkthroughReady
1using System;
2using System.Collections.Generic;
3
4class Node {
5 public string Name;
6 public Node Parent;
7 public Dictionary<string, bool> Properties = new Dictionary<string, bool>();
8
9 public Node(string name, Node parent) {
10 Name = name;
11 Parent = parent;
12 }
13
14 public bool? Lookup(string property) {
15 for (Node n = this; n != null; n = n.Parent) {
16 if (n.Properties.TryGetValue(property, out bool value)) {
17 return value;
18 }
19 }
20 return null;
21 }
22}
23
24class Program {
25 static void Main() {
26 var animal = new Node("animal", null);
27 animal.Properties["alive"] = true;
28 var bird = new Node("bird", animal);
29 bird.Properties["flies"] = true;
30 bird.Properties["feathers"] = true;
31 var penguin = new Node("penguin", bird);
32 penguin.Properties["flies"] = false;
33 var tweety = new Node("tweety", bird);
34 var opus = new Node("opus", penguin);
35
36 foreach (var (node, property) in new[] { (tweety, "flies"), (opus, "flies"), (opus, "feathers"), (opus, "swims") }) {
37 Console.WriteLine(node.Name + " " + property + ": " + (node.Lookup(property)?.ToString() ?? "unknown"));
38 }
39 }
40}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Walk up the is-a chain from the most specific node.—
—
The penguin overrides the default inherited from bird.—
—
opus is a penguin, which is a bird, which is an animal.chain: opus → penguin → bird → animal
—
tweety inherits flies from bird; opus finds the penguin override first; feathers comes from bird; swims is defined nowhere.chain: opus → penguin → bird → animal
tweety flies: True
opus flies: False
opus feathers: True
opus swims: unknown

Cost and common mistakes

O(d) per lookup for depth d of the hierarchy.

  • A node with several parents (multiple inheritance) needs a rule for conflicts, as in the Nixon diamond.
  • Returning null for unknown keeps 'not known' separate from 'false'. Treating unknown as false is the closed-world assumption.

Try it yourself

What does tweety's lookup of 'alive' return?

Reveal answer and reasoning

True, inherited through bird from animal.

Further reading