Perhaps consider the graph as a design abstraction rather than as an implementation guide. In 1996, i faced the same challenge and this is what i came up with;

My "graph" consisted of

concepts and

connections, but the connections did not have names since it turned out that the relations implied by connections needed also to be

concepts. It was thus unlabelled. That being the case, the "graph" collapsed into a set of properties per concept. Eventually, the meaning of the concept itself became defined by these properties and the concept itself just a label.

So, each concept is a

set;

**Code:**

lemon = {(color yellow) ...}

banana = {(color yellow) ...}

apple = {(color red) ...}

For inverses, eg. "what things are yellow", it was necessary to make a general rule; (R Y) in X => (Y X) in R.

then we get;

**Code:**

color = {(red apple), (yellow banana), (yellow lemon) ...}

Quickly yielding the results, banana & lemon.

It seems there are no collisions by storing the inverse properties in the

very same data structures as the forward properties - since which are

really forward and which

really backward? They don't clash.

A corollary of (R Y) in X => (Y X) in R, must be (X Y) in R => (X R) in Y !

Writing this out, we get;

**Code:**

red = {(apple color) ...}

yellow = {(banana color), (lemon color) ... }

I used to call these three projections;

forward,

backward and

sideways.

**Code:**

lemon = {(colour yellow)...} "the color of lemon includes yellow"

colour = {(yellow lemon)...} "yellow colored things includes lemon".

yellow = {(lemon colour)...} "lemons are yellow because of color".

Implementationwise, each concept was a standard tree/set or map.