65.9K
CodeProject 正在变化。 阅读更多。
Home

LINQ to Family Tree (Prolog 风格)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2013年7月31日

CPOL

13分钟阅读

viewsIcon

45158

downloadIcon

715

以 LINQ 的方式查询家谱。

引言

在这篇文章中,我想向您展示如何使用 LINQ 查询家谱。本文将深入探讨 LINQ 技术,以及它如何与动态编程相结合变得非常强大。

这次我将使用 Prolog 风格来实现我的目标,使其更简单、更易于理解。

背景 

家谱是以树状结构表示家庭关系的图表。家谱通常将最老的几代人放在顶部,而较新的几代人放在底部。

图中代表了家谱的一个样本(辛普森一家)。我知道这很有趣微笑 | <img src=,但这会让我们的想法简单明了。当然,文章的其余部分也会很有趣,因为我们很多人都知道辛普森一家。图中说明了家谱会是什么样子,我们如何组织家庭成员等等。

图中有三代人,第一代在树的顶部,第三代在底部。

这样的树描述了每个节点的亲属关系,例如,Bart 是 Homer 的儿子,Homer 是 Marge 的配偶。所以我们可以知道家庭中的每一种关系,包括父亲、母亲、兄弟、姐妹、儿子、女儿、祖父母等等。

这是树形结构的一个真实样本,已经在数据结构书籍中讲授过。

在接下来的几句话中,我想简要介绍一下 Prolog,它是一种通用的逻辑编程语言,广泛用于人工智能(AI)。Prolog 是许多函数式编程语言之一。这是什么意思?这意味着这种语言中的一切都是函数式的!这听起来可能很奇怪皱眉 | <img src=,别担心,我会尝试用一些细节来探讨一个例子。

Prolog 程序通过子句描述关系。子句可以是事实或规则。

male(homer).
 
father(homer, bart).

Prolog 中的事实只不过是带有逗号结尾的一些参数的函数。如果我们看上面的框,我们会注意到 Male、Female 和 Father 是事实。为了清楚起见,我们可以说 Homer 是 Male,Homer 是 Bart 的 Father。简而言之,它似乎是一种“Is A”关系。

brother(X,Y):- male(X), father(Z,X), father(Z,Y).

Prolog 中的规则是一种函数,它有一个定义实际函数的体。在上面的框中,我们有一个 Brother 规则,它说 X 是 Y 的 Brother 当且仅当 X 是 Male,并且我们找到 Z 既是 X 的 Father 也是 Y 的 Father。

我认为这很容易。现在您可以开始在 Prolog 中定义自己的事实和规则,它们是该语言的本质。剩下的就是求值,这是一种 Prolog 的查询引擎,因为所有事实和规则都已存储在知识数据库中。用户可以随意向 Prolog 查询引擎提问。然后引擎开始求值并回答这些问题。这里有几个查询。

?- male(homer).
Yes
 
?- father(bart, homer).
No
 
?- father(X, bart).
X=homer

第一个查询询问 Homer 是否是 Male。Prolog 将检查这个事实并返回结果,在本例中是 Yes;第二个查询也是一样。如果我们仔细查看最后一个查询,我们会注意到我们有一个 X,它是一个变量,所有以大写字母开头的名字都被认为是变量,所以这次 Prolog 将寻找 Bart 的 Father,Prolog 将响应 X=Homer。

我认为这是 Prolog 的快速之旅……让我们进入任务……

使用代码 

我们已经了解了 Prolog 以及它如何使用知识数据库和查询引擎进行推理。

'GEN 1:   Abraham        Mona       Clancy               Jackie
'            |             |           |                   |
'            +---o-----o---+           +---o-----o-----o---+
'                |     |                   |     |     |
'GEN 2:        Herb  Homer               Marge Patty  Selma
'                      |                   |            |
'                      +---o-----o-----o---+            o
'                          |     |     |                |
'GEN 3:                  Bart   Lisa Maggie            Ling

上面的图探讨了辛普森一家的家谱。它显示了每一代家庭的每个成员。在这里绘制它很好,因为其余的演示将使用这个家谱,并且更容易发现关系并遵循我们稍后将构建的规则。

现在我想用 Prolog 的风格构建我的 LINQ to Family Tree 提供程序。对我来说有趣的部分是查询引擎,我正在本文中探讨它,因为它是推理和评估结果的核心部分。

再次,根据我最新的技巧/窍门,我介绍了LINQ to CSV,我意识到 LINQ 和动态编程的结合使 LINQ 如此出色,并且对于 LINQ 动态对象更加强大。同样的方式,我一直在思考构建一个 LINQ to Family Tree。

Fact 类

Class Fact
    Inherits DynamicObject

    Public Property Name As String
    Public Property Arguments As New List(Of String)

    Public Overrides Function TryInvokeMember(binder As InvokeMemberBinder, _
               args() As Object, ByRef result As Object) As Boolean
        Select Case args.Length
            Case 1
                mFacts.Add(New Fact() With {.Name = binder.Name.ToProper(), _
                      .Arguments = New List(Of String) From {args(0)}})
            Case 2
                mFacts.Add(New Fact() With {.Name = binder.Name.ToProper(), _
                      .Arguments = New List(Of String) From {args(0), args(1)}})
        End Select
        Return True
    End Function
End Class

Fact 类是参与核心类 FamilyTree 的基本类之一。它定义了一个简单的事实,其中包含两个属性 Name,它保存事实的名称,如 Male、Female、Father、Brother 等,Arguments,它是一个字符串列表,保存所有与事实相关的参数。例如,Female(Amy)Parent(Neal,Ian)。参数包含 Female 事实的“Amy”作为参数,以及 Parent 事实的“Neal”和“Ian”两个参数。

我们会注意到 Fact 类继承自 DynamicObject,这使得该类能够包含运行时函数,这非常方便,因为它允许我们在运行时添加任意多的事实而不会出错。

Fact 类必须是动态的,因为如果我们看一下 Prolog 示例,我们会注意到我们可以写任何名称并使其成为一个事实,这在非动态世界中是很奇怪的事情!!问题是如何使这个类灵活并包含运行时函数?!!DynamicObject 来帮忙。

<Extension()>
Public Function ToProper(str As String) As String
    Return StrConv(str, VbStrConv.ProperCase)
End Function

ToProper 是一个扩展方法,它将任何字符串转换为适当的大小写,这很有用,也许用户写了一个事实 Female("Amy"),而查询是 Female("amy"),结果肯定会是 false,因为在比较字符串时名称是区分大小写的。为了避免这种情况,我引入了这个帮助函数。

Fact 类中的最后一项是事实的创建,它通过重写 TryInvokeMember 方法来完成,该方法允许我们在调用该类中的任何函数时编写一些代码。代码很简单,它将 Fact 类的一个新实例推送到 FamilyTree 类中声明的 mFacts 属性。它简单地保存家谱中的所有事实。我只想指出 binder.Name 返回“.”之后的实际名称。例如 fact.Father = ...

Rule 类

Class Rule
    Inherits DynamicObject

    Public Property Name As String
    Public Property Definition As Func(Of String, String, Boolean)

    Public Overrides Function TrySetMember(binder As SetMemberBinder, value As Object) As Boolean
        mRules.Add(New Rule() With {.Name = binder.Name, .Definition = value})
        Return True
    End Function
End Class

Rule 类定义了一个简单的规则,它以与 Fact 相同的方式参与 FamilyTree 类。它有两个属性:Name,保存规则的名称,以及 Definition,保存函数的实际主体。如我们所见,它是一个 Func 委托,指向一个接受两个字符串并返回布尔值的函数,因为所有规则都接受两个参数作为输入,并且它有一些逻辑来检查规则是否满足。

这次我们重写了 TrySetMember。实际上,我们想创建一个包含函数的运行时属性,所以它是设置成员而不是调用成员。当代码将 Rule 类的一个新实例推送到 FamilyTree 类中定义的 mRules 属性(用于保存树中的所有规则)时,也会发生同样的事情。

我想指出的是,value 是编程设置的实际值,应该跟在“=”之后,例如,r.Brother = ...,这是定义 Brother 规则的函数。

FamilyTree 类

FamilyTree 类是 LINQ to FamilyTree 的核心类,它充当 Prolog 的查询引擎。它有点长,所以我将尝试将其分成几部分,使其更易读、更易于理解。

Public Class FamilyTree
    Inherits DynamicObject

    Private Shared Property mFacts As New List(Of Fact)
    Private Shared Property mRules As New List(Of Rule)

再一次,这个类是一个动态对象,允许我们创建运行时函数来进行任意我们需要的查询。基本上,它包含两个重要属性 mFactsmRules,我们在之前的类中已经看到过它们,所以它们是 Private Shared,以便 FamilyTree 类及其内部的所有类(在本例中是 FactRule)都可以访问它们:

Public Sub New()
    mRules.Add(New Rule() With {.Name = "Parent", _
             .Definition = New Func(Of String, String, Boolean)(Function(x, y)
         Return mFacts.Where(Function(f) f.Name = "Parent" AndAlso _
               f.Arguments.First() = x AndAlso f.Arguments.Last() = y).Any()
     End Function)})
    mRules.Add(New Rule() With {.Name = "Married", _
              .Definition = New Func(Of String, String, Boolean)(Function(x, y)
          Return mFacts.Where(Function(f) f.Name = "Married" AndAlso _
               f.Arguments.First() = x AndAlso f.Arguments.Last() = y).Any()
      End Function)})
    mRules.Add(New Rule() With {.Name = "Male", _
              .Definition = New Func(Of String, String, Boolean)(Function(x, y)
           Return mFacts.Where(Function(f) f.Name = "Male" AndAlso f.Arguments.First() = x).Any()
       End Function)})
    mRules.Add(New Rule() With {.Name = "Female", _
            .Definition = New Func(Of String, String, Boolean)(Function(x, y)
         Return mFacts.Where(Function(f) f.Name = "Female" AndAlso f.Arguments.First() = x).Any()
     End Function)})
End Sub

我们在构造函数中添加了四个基本规则:ParentMarriedMaleFemale。让我描述其中两个。

  • Parent:名称为“Parent”,定义是一个简单的函数,接受 x、y 作为参数,并检查是否存在一个名称为“Parent”,以 x 作为第一个参数,以 y 作为最后一个参数的事实;如果存在,则返回 True,否则返回 False。
  • Male:名称为“Male”,定义是一个简单的函数,接受 x、y 作为参数,并检查是否存在一个名称为“Male”,以 x 作为第一个参数的事实;如果存在,则返回 True,否则返回 False。如果您注意到,我们不再关心最后一个参数,因为这个事实只有一个参数。
Public Sub Facts(action As Action(Of Object))
    action(New Fact())
End Sub
 
Public Sub Rules(action As Action(Of Object))
    action(New Rule())
End Sub

正如我们所见,我引入了添加事实和规则的新过程,但我使用了 Action(Of T) 而不是直接将实例推送到事实或规则列表中!不用担心,我这样做是为了能够一次性添加事实或规则,也就是说,作为一个实例的集合,而不是一个一个地添加。同时也使其看起来像我们在 Prolog 中看到的函数。

Public Overrides Function TryInvokeMember(binder As InvokeMemberBinder, _
            args() As Object, ByRef result As Object) As Boolean
    Dim mRule = mRules.Where(Function(r) r.Name = binder.Name).SingleOrDefault()
    If mRule IsNot Nothing Then
        Dim lst As New List(Of String)
        Dim lst1 As List(Of String)
        Dim lst2 As List(Of String)
        If args(0).GetType() Is GetType(String) Then
            lst1 = New List(Of String)
            lst1.Add(args(0).ToString().ToProper())
        Else
            lst1 = New List(Of String)(CType(args(0), List(Of String)).Select(Function(s) s.ToProper()))
        End If
        Select Case args.Length
            Case 1
                If binder.Name = "Male" OrElse binder.Name = "Female" Then
                    result = mRules.Where(Function(r) _
                                 r.Name = binder.Name).Single().Definition.Invoke(args(0), Nothing)
                    Exit Select
                End If
                For Each item1 In lst1
                    lst.AddRange(mFacts.Select(Function(f) f.Arguments.First()).Distinct().Where(_
                        Function(f) f <> item1 AndAlso mRule.Definition.Invoke(f, item1)))
                Next
                result = lst
            Case 2
                If args(1).GetType() Is GetType(String) Then
                    lst2 = New List(Of String)
                    lst2.Add(args(1).ToString().ToProper())
                Else
                    lst2 = New List(Of String)(CType(args(1), _
                              List(Of String)).Select(Function(s) s.ToProper()))
                End If
                For Each item1 In lst1
                    For Each item2 In lst2
                        If mRule.Definition.Invoke(item1, item2) Then
                            result = True
                            Exit Select
                        End If
                    Next
                Next
                result = False
        End Select
    Else
        Throw New ArgumentException(String.Format("The rule {0} is not exist", binder.Name))
    End If
    Return True
End Function 

在这里,我重写了 TryInvokeMember 方法来执行与 FamilyTree 对象关联的查询。我知道这看起来有点奇怪,但请等等,我将给您主要思路并解释它是如何工作的。

基本上,我们正在寻找一个规则,其名称在“.”之后。如果我们发出 familyTree.Brother,这意味着我们应该在 mRules 列表中查找 Brother 规则;如果它不存在,这意味着它尚未定义,所以我抛出一个 ArgumentException,否则我们有两种选择。

  1. 布尔查询:如果查询包含两个参数,则表示我们要检查给定的规则是否满足。查询可能包含一个参数,这是 Male 和 Female 规则的特殊情况。
  2. 列表查询:如果查询包含一个参数,则表示我们正在寻找满足给定规则的参数。

如果我们深入研究前面的代码,您会看到我引入了两个 List(Of String),因为有时一个或两个参数可能包含多个值。例如,Sister 规则定义如下:

sister(X,Y):- female(X), sibling(X,Y) 

X 可能有一个或多个兄弟姐妹,我认为使用 List(Of String) 而不是 String 来覆盖所有情况会更好。

再次,如果我们深入研究代码,您会注意到,每当我们尝试执行列表查询时,我们都会查看树中的所有事实,这些事实存储在 mFacts 属性中。然后我们返回所有满足给定规则的匹配项。

就是这样……让我们看看如何将辛普森一家表示在 FamilyTree 对象中,当然,我们应该创建一个 FamilyTree 类的实例,如下所示:

Dim family As Object = New FamilyTree() 

之后,让我们定义事实,这些事实显示在顶部的图表中。

'Facts
family.Facts(New Action(Of Object)(Sub(f)
   f.Male("Abraham")
   f.Male("Clancy")
   f.Male("Herb")
   f.Male("Homer")
   f.Male("Bart")
   f.Female("Mona")
   f.Female("Jackie")
   f.Female("Marge")
   f.Female("Patty")
   f.Female("Selma")
   f.Female("Lisa")
   f.Female("Maggie")
   f.Female("Ling")
   f.Married("Abraham", "Mona")
   f.Married("Clancy", "Jackie")
   f.Married("Homer", "Marge")
   f.Parent("Abraham", "Herb")
   f.Parent("Mona", "Herb")
   f.Parent("Abraham", "Homer")
   f.Parent("Mona", "Homer")
   f.Parent("Clancy", "Marge")
   f.Parent("Jackie", "Marge")
   f.Parent("Clancy", "Patty")
   f.Parent("Jackie", "Patty")
   f.Parent("Clancy", "Selma")
   f.Parent("Jackie", "Selma")
   f.Parent("Homer", "Bart")
   f.Parent("Marge", "Bart")
   f.Parent("Homer", "Lisa")
   f.Parent("Marge", "Lisa")
   f.Parent("Homer", "Maggie")
   f.Parent("Marge", "Maggie")
   f.Parent("Selma", "Ling")
End Sub)) 

代码很简单,我们创建一个新的 Action(Of Object) 并传递所有事实。它就像一个 Prolog 事实,只是它将参数作为神奇字符串传递,但更具可读性。如我之前提到的,我们有四个基本规则(MaleFemaleMarriedParent),它们在我的 LINQ to Family Tree 提供程序中是预定义的。这不是很酷吗?我希望……还有很多更酷的东西可以继续……

在定义了事实之后,就该定义规则或家庭关系了……

'Rules
family.Rules(New Action(Of Object)(Sub(r)
   r.Spouse = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
                   Return family.Married(x, y) OrElse family.Married(y, x)
               End Function)

   r.Husbund = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
            Return family.Male(x) AndAlso family.Spouse(x, y)
        End Function)

   r.Wife = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
             Return family.Female(x) AndAlso family.Spouse(x, y)
         End Function)

   r.Father = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
               Return family.Male(x) AndAlso family.Parent(x, y)
           End Function)

   r.Mother = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
           Return family.Female(x) AndAlso family.Parent(x, y)
       End Function)

   r.Sibling = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
        Return Equals(family.Father(x), family.Father(y)) AndAlso _
              Equals(family.Mother(x), family.Mother(y)) AndAlso x <> y
    End Function)

   r.Brother = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
                Return family.Male(x) AndAlso family.Sibling(x, y)
            End Function)

   r.Sister = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
                   Return family.Female(x) AndAlso family.Sibling(x, y)
               End Function)

   r.GrandParent = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
                    Return family.Parent(x, family.Parent(y))
                End Function)

   r.GrandFather = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
                    Return family.Male(x) AndAlso family.GrandParent(x, y)
                End Function)

   r.GrandMother = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
            Return family.Female(x) AndAlso family.GrandParent(x, y)
        End Function)

   r.GrandChild = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
                   Return family.GrandParent(y, x)
               End Function)

   r.GrandSon = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
             Return family.Male(x) AndAlso family.GrandChild(x, y)
         End Function)

   r.GrandDaughter = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
                      Return family.Female(x) AndAlso family.GrandChild(x, y)
                  End Function)

   r.Child = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
          Return family.Parent(y, x)
      End Function)

   r.Son = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
                    Return family.Male(x) AndAlso family.Child(x, y)
                End Function)

   r.Daughter = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
                 Return family.Female(x) AndAlso family.Child(x, y)
             End Function)

   r.Uncle = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
          Return family.Male(x) AndAlso (family.Sibling(x, family.Parent(y)) _
               OrElse family.Parent(family.Sibling(family.Spouse(x)), y))
      End Function)

   r.Aunt = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
         Return family.Female(x) AndAlso (family.Sibling(x, family.Parent(y)) _
                 OrElse family.Parent(family.Sibling(family.Spouse(x)), y))
     End Function)

   r.Cousin = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
           Return family.Uncle(family.Parent(x), y) OrElse family.Aunt(family.Parent(x), y)
       End Function)

   r.Nephew = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
           Return family.Male(x) AndAlso family.Sibling(family.Parent(x), y)
       End Function)

   r.Niece = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
              Return family.Female(x) AndAlso family.Sibling(family.Parent(x), y)
          End Function)

   r.GreatGrandParent = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
             Return family.Parent(x, family.GrandParent(y))
         End Function)

   r.GreatGrandFather = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
             Return family.Male(x) AndAlso family.GreatGrandParent(x, y)
         End Function)

   r.GreatGrandMother = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
         Return family.Female(x) AndAlso family.GreatGrandParent(x, y)
     End Function)

   r.GreatGrandChild = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
            Return family.Parent(family.GrandChild(y), x)
        End Function)

   r.GreatGrandSon = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
          Return family.Male(x) AndAlso family.GreatGrandChild(x, y)
      End Function)

   r.GreatGrandDaughter = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
               Return family.Female(x) AndAlso family.GreatGrandChild(x, y)
           End Function)

   r.ParentInLaw = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
            Return family.Parent(x, family.Spouse(y))
        End Function)

   r.FatherInLaw = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
            Return family.Male(x) AndAlso family.ParentInLaw(x, y)
        End Function)

   r.MotherInLaw = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
            Return family.Female(x) AndAlso family.ParentInLaw(x, y)
        End Function)

   r.SiblingInLaw = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
         Return family.Sibling(x, family.Spouse(y))
     End Function)

   r.BrotherInLaw = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
             Return family.Male(x) AndAlso family.SiblingInLaw(x, y)
         End Function)

   r.SisterInLaw = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
            Return family.Female(x) AndAlso family.SiblingInLaw(x, y)
        End Function)

   r.ChildInLaw = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
           Return family.Spouse(x, family.Child(y))
       End Function)

   r.SonInLaw = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
             Return family.Male(x) AndAlso family.ChildInLaw(x, y)
         End Function)

   r.DaughterInLaw = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
              Return family.Female(x) AndAlso family.ChildInLaw(x, y)
          End Function)
End Sub))

就像我在事实中所做的那样,我定义了几乎所有的规则,并将它们传递给一个新的 Action(Of Object) 实例,该实例将被传递到 Rules 过程。

让我现在解释其中三个。

1. Father

r.Father = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
   Return family.Male(x) AndAlso family.Parent(x, y)
End Function)

我定义了一个名为 Father 的动态成员,它持有一个接受两个字符串并返回布尔值的函数。换句话说,它持有一个规则定义。该函数非常简单:Father(x, y)。 x 是 y 的 Father,如果 x 是 male 并且 x 是 y 的 Parent。如果这两个条件都满足,那么 x 就是 y 的父亲。

2. Sibling

r.Sibling = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
 Return Equals(family.Father(x), family.Father(y)) AndAlso _
        Equals(family.Mother(x), family.Mother(y)) AndAlso x <> y
End Function) 
 我定义了一个名为 Sibling 的动态成员,它持有一个规则。Sibling(x, y)。 x 是 y 的 Sibling,如果 x 的 Father 与 y 的 Father 完全相同,并且他们的 Mother 也相同,并且 x 不等于 y。这里我使用了一个辅助函数 Equals,因为我们正在执行列表查询,并且我们知道 = 运算符不适合比较两个列表,所以我们使用了以下方法。
Public Function Equals(lst1 As List(Of String), lst2 As List(Of String)) As Boolean
   Return lst1.Intersect(lst2).Any()
End Function 

3. Uncle

r.Uncle = New Func(Of String, String, Boolean)(Function(x, y) As Boolean
   Return family.Male(x) AndAlso (family.Sibling(x, family.Parent(y)) _
          OrElse family.Parent(family.Sibling(family.Spouse(x)), y))
End Function)

我定义了一个名为 Uncle 的动态成员,它持有一个规则。Uncle(x, y)。 x 是 y 的 Uncle,如果 x 是 male 并且 y 的 Parent 是 x 的 Sibling。我们将一个函数作为参数传递给另一个函数,这就是 Prolog 等函数式编程语言的风格。

知识数据库

知识数据库是一种数据结构,它由两部分组成:

1. Facts 

一个线性数据结构,家谱中的所有事实都组织在其中。它简单地包含所有事实名称及其关联的参数。

2. Rules 

一个线性数据结构,家谱中的所有规则都组织在其中。它简单地包含所有规则名称和定义。

查询引擎是如何工作的?

查询引擎负责执行查询并从知识数据库评估结果。

1. 布尔查询

在此类查询中,引擎工作流程如下:

  • 初始化查询
  • 在 Facts 数据结构(在我们的代码中是 mFacts)中查找已发出且具有其参数的相同事实名称。
  • 如果在事实中找到匹配项,则返回 true。
  • 否则,在 Rules 数据结构中查找已发出且具有参数的相同规则名称。
  • 如果规则定义包含预定义事实的声明,则只需应用带有给定参数的规则定义,并返回满足 Facts 中规则定义的名字列表。
  • 如果规则定义包含另一个规则,我们应该深入研究该规则并执行与上一步相同的操作。

2. 列表查询 

在此类查询中,引擎工作流程如下:

  • 初始化查询
  • 在 Rules 数据结构(在我们的代码中是 mRules)中查找已发出且具有其参数的相同规则名称。
  • 如果找到规则定义,则返回该规则的定义。
  • 使用所有事实中的名称应用该规则以及给定的参数。
  • 返回匹配上一个规则的名称列表。

最后但同样重要的是,让我们发出一些查询并查看它们的结果。

'Queries
Console.WriteLine(family.Mother("Selma", "Ling"))
 
True
 
Console.WriteLine(family.Sister("Ling", "Lisa"))
 
False
 
Dim query = From s In CTypeDynamic(Of List(Of String))(family.Daughter("Homer"))
            Select s
 
For Each item In query
   Console.WriteLine(item)
Next For Each item In query
 
Lisa
Maggie

第一个查询返回 True,因为 Selma 是 Female 并且是 Ling 的 Parent,所以 Selma 是 Ling 的 Mother。而第二个查询返回 False,因为 Ling 不是 Lisa 的 Sibling。

上面的查询是布尔查询的样本,而最后一个查询是列表查询的样本,它使用 LINQ 枚举 Homer 的所有女儿,在我们的情况下是 LisaMaggie

最后我想向您展示一个使用 LINQ to Family Tree 的复杂查询。

Dim query = From gc In CTypeDynamic(Of List(Of String))(family.GrandFather("Bart"))
                    Let Children = family.Child(gc)
                    Where Children.Count > 1
                    Select Name = gc, Children
 
For Each item In query
    Console.WriteLine("{0} ({1}) childern", item.Name, item.Children.Count)
    For Each subitem In item.Children
        Console.WriteLine("  {0}", subitem)
    Next
    Console.WriteLine()
Next 
 
Abraham (2) children
  Herb
  Homer
 
Clancy (3) children
  Marge
  Patty
  Selma

在上面的查询中,我们正在寻找 Bart 的祖父 Abraham 和 Clancy,并检索他们的名字以及他们是否拥有多个孩子。当然,结果将显示 Abraham、Clancy 以及他们的孩子。

就是这样……希望您喜欢我的 LINQ to Family Tree 提供程序。

关注点

LINQ 是一项出色的技术,可用于查询任何底层数据源,并与动态编程结合以探索其强大功能。在这篇文章中,我解决了一些细微问题,例如参数的大小写处理、搜索过程优化和未定义规则的检测。

© . All rights reserved.