C#: semantic networks with default reasoning
Inherit properties up an is-a hierarchy and let more specific nodes override defaults.
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"));
}
}
}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
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")); } }}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
| Step / line | What happens | State after step | Output 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.