using System.Linq;
// Comparateur structurel d'atomes (sinon string[] compare par reference =>HashSet bug).
public class AtomEq : IEqualityComparer<(string,string[])> {
public bool Equals((string,string[]) x, (string,string[]) y) =>
x.Item1 == y.Item1 && x.Item2.SequenceEqual(y.Item2);
public int GetHashCode((string,string[]) x) {
int h = x.Item1.GetHashCode();
foreach (var a in x.Item2) h = HashCode.Combine(h, a);
return h;
}
}
// Une clause Horn : tete + corps (conjonction de litteraux).
public record Clause((string,string[]) Head, List<(string,string[])> Body);
bool IsVar(string t) => t.Length > 0 && char.IsUpper(t[0]);
(string,string[]) Subst((string,string[]) atom, Dictionary<string,string> theta) =>
(atom.Item1, atom.Item2.Select(a => theta.TryGetValue(a, out var v) ? v : a).ToArray());
// Etend theta pour que pattern devienne fact ; null si impossible.
Dictionary<string,string> MatchAtom((string,string[]) pattern, (string,string[]) fact,
Dictionary<string,string> theta) {
if (pattern.Item1 != fact.Item1 || pattern.Item2.Length != fact.Item2.Length) return null;
var th = new Dictionary<string,string>(theta);
for (int i = 0; i < pattern.Item2.Length; i++) {
string p = pattern.Item2[i], f = fact.Item2[i];
if (IsVar(p)) { if (th.TryGetValue(p, out var v) && v != f) return null; th[p] = f; }
else if (p != f) return null;
}
return th;
}
bool BodySatisfiable(List<(string,string[])> body, List<(string,string[])> facts,
Dictionary<string,string> theta, int k = 0) {
if (k == body.Count) return true;
foreach (var fact in facts) {
var th = MatchAtom(body[k], fact, theta);
if (th != null && BodySatisfiable(body, facts, th, k + 1)) return true;
}
return false;
}
bool Covers(Clause clause, (string,string[]) example, List<(string,string[])> facts) {
var th = MatchAtom(clause.Head, example, new Dictionary<string,string>());
return th != null && BodySatisfiable(clause.Body, facts, th);
}
string Fmt((string,string[]) a) => $"{a.Item1}({string.Join(", ", a.Item2)})";
string ShowClause(Clause c) =>
Fmt(c.Head) + (c.Body.Count > 0 ? " :- " + string.Join(", ", c.Body.Select(Fmt)) : "") + ".";
// Domaine : arbre genealogique
var FACTS = new List<(string,string[])> {
("parent", new[]{"tom","bob"}), ("parent", new[]{"tom","liz"}),
("parent", new[]{"bob","ann"}), ("parent", new[]{"bob","pat"}),
("parent", new[]{"liz","sue"}), ("parent", new[]{"pat","jim"}),
("parent", new[]{"sue","eve"}),
("male", new[]{"tom"}), ("male", new[]{"bob"}), ("male", new[]{"jim"}),
("female", new[]{"liz"}), ("female", new[]{"ann"}), ("female", new[]{"pat"}),
("female", new[]{"sue"}), ("female", new[]{"eve"}),
};
var POS = new List<(string,string[])> {
("grandfather", new[]{"tom","ann"}), ("grandfather", new[]{"tom","pat"}),
("grandfather", new[]{"tom","sue"}), ("grandfather", new[]{"bob","jim"}) };
var NEG = new List<(string,string[])> {
("grandfather", new[]{"liz","eve"}), ("grandfather", new[]{"liz","sue"}),
("grandfather", new[]{"bob","ann"}), ("grandfather", new[]{"pat","jim"}),
("grandfather", new[]{"tom","bob"}), ("grandfather", new[]{"ann","jim"}) };
// Cible (a retrouver par l'algorithme)
var target = new Clause(
("grandfather", new[]{"X","Y"}),
new List<(string,string[])>{
("male", new[]{"X"}), ("parent", new[]{"X","Z"}), ("parent", new[]{"Z","Y"})});
Console.WriteLine($"Cible : {ShowClause(target)}\n");
Console.WriteLine($"{"exemple",24} | attendu | couvert");
Console.WriteLine(new string('-', 48));
foreach (var e in POS.Concat(NEG)) {
string exp = POS.Contains(e, new AtomEq()) ? "+" : "-";
string couv = Covers(target, e, FACTS) ? "oui" : "non";
Console.WriteLine($"{Fmt(e),24} | {exp} | {couv}");
}