I'm doing an exercise and where I need to create a simple dating agency matching system. Currently each person have one hobby, hobbies are categories by active and inactive and if the two hobbies match and the two persons have opposite sex, the dating agency thinks these to persons are a match.
So now I need the system to be able to allow for multiple hobbies but I cant seem to get my head around this.
Here's what I have:
(deftemplate MAIN::person
(slot name)
(slot age)
(multislot hobby)
(slot town)
(slot sex))
(person (name "Jane")
(age 25)
(hobby fashion gardening)
(town montrose)
(sex female))
(deffacts hobby_types "Hobby categories"
(hobby_type gardening active)
(hobby_type swinging active)
(hobby_type reading inactive)
(hobby_type fashion inactive)
)
(defrule compatible_hobbies
(opposite_sex ?name1 ?name2)
(person (name ?name1) (hobby ?hobby1))
(person (name ?name2) (hobby ?hobby2))
(hobby_type ?hobby1 ?type)
(hobby_type ?hobby2 ?type)
=>
(assert (hobbies_match ?name1 ?name2))
)
So first up, do I declare the multislot field correctly? And how do I adjust my compatible_hobbies rule to match against either of the hobbies that a person might have?
The multislot hobby in person is declared correctly. But your rule won't match the hobbies correctly and would match the same fact to the same fact showing that Jane is compatible with Jane. Actually if the rule matched facts it would generate the Cartesian product of the person facts (or a large number of non-desired matches).
I modified the rule to:
(defrule compatible_hobbies
(person (name ?n1) (hobby $? ?h1 $?))
(person (name ?n2) (hobby $? ?h1 $?))
(hobby_type ?h1 ?x)
(test (neq ?n1 ?n2))
=>
(assert (hobbies_compatible ?n1 ?n2))
)
So if you assert two person facts like
(person (name "Jane") (hobby blah1 gardening blah2)) and (person (name "Jim") (hobby blah3 gardening blah4)) then the rule will fire and the fact (hobbies_compatible Jim Jane) will be asserted (as well as the fact (hobbies_compatible "Jim" "Jane")).
The wildcard $? variable matches a set of symbols preceding and following the desired matching hobby. Your opposite sex fact should probably also be a test instead of a fact.
Related
I am using CLIPS 6.3 to develop a simple production management algorithm.
Currently I have a deftemplate for product orders and another one for product details.
(deftemplate orderSheets
(slot orderID (type INTEGER))
(slot orderDate (type INTEGER))
(slot product (type STRING))
(slot quantity (type INTEGER))
(slot deliverDate (type INTEGER))
)
(deftemplate productInfoSheets
(slot productID (type STRING))
(slot productStock (type INTEGER))
(slot prodTime (type INTEGER))
(slot maximumProduction (type INTEGER))
)
My doubt is how can I create a rule that relates the product order quantity (available in orderSheets) vs the available stock (available in productInfoSheets.
Thank you for your support,
Joan
Here's an example rule comparing order quantity to available stock:
(defrule not-enough
(orderSheets (quantity ?quantity))
(productInfoSheets (productStock ?stock))
(test (> ?quantity ?stock))
=>)
I decided to learn how to use query expressions in F# and found the official Microsoft documentation. The caveats don't seem spelled out so I'm sure I've hit a problem with a simple fix but don't know why I'm getting the error it shows.
My thought was to write a query that did both a join and grouping in the same go. For example, using their sample 'MyDatabase' I thought I would try to find the number of classes that each student is enrolled in. After writing the following query, the compiler flags no warning, but when I go to run it it gives an error.
My query expression:
query {
for student in db.Student do
join course in db.CourseSelection
on (student.StudentID = course.StudentID)
groupBy student into group
select (group.Key, group.Count())
}
|> Seq.iter (fun (student, classCount) -> printfn "Student %s has %i classes" student.Name classCount)
The error:
System.InvalidOperationException: Could not format node 'New' for execution as SQL.
at System.Data.Linq.SqlClient.SqlFormatter.Visitor.VisitNew(SqlNew sox)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
> at System.Data.Linq.SqlClient.SqlFormatter.Visitor.VisitAlias(SqlAlias alias)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlFormatter.Visitor.VisitSelect(SqlSelect ss)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlFormatter.Visitor.VisitScalarSubSelect(SqlSubSelect ss)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlFormatter.Visitor.VisitRow(SqlRow row)
at System.Data.Linq.SqlClient.SqlFormatter.Visitor.VisitSelect(SqlSelect ss)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlFormatter.Visitor.Format(SqlNode node, Boolean isDebug)
at System.Data.Linq.SqlClient.SqlFormatter.Format(SqlNode node)
at System.Data.Linq.SqlClient.SqlProvider.BuildQuery(ResultShape resultShape, Type resultType, SqlNode node, ReadOnlyCollection`1 parentParameters, SqlNodeAnnotations annotations)
at System.Data.Linq.SqlClient.SqlProvider.BuildQuery(Expression query, SqlNodeAnnotations annotations)
at System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
at System.Data.Linq.DataQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at Microsoft.FSharp.Collections.SeqModule.Iterate[T](FSharpFunc`2 action, IEnumerable`1 source)
at <StartupCode$FSI_0008>.$FSI_0008.main#() in C:\Users\JDKS\Library\query expressions.fsx:line 129
Stopped due to error
I even thought I could get out of this issue by using a subquery:
query {
for student in db.Student do
join course in db.CourseSelection
on (student.StudentID = course.StudentID)
let count = query {
for s in student.Name do
select course.CourseID
count
}
select (student.Name, count)
}
|> Seq.iter (fun (student, classCount) -> printfn "Student %s has %i classes" student classCount)
but that gave an even larger error:
> System.NotSupportedException: Sequence operators not supported for type 'System.String'.
at System.Data.Linq.SqlClient.SqlBinder.Visitor.ConvertToFetchedSequence(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitAlias(SqlAlias a)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlVisitor.VisitSource(SqlSource source)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSelect(SqlSelect select)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitAlias(SqlAlias a)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlVisitor.VisitSource(SqlSource source)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSelect(SqlSelect select)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitAlias(SqlAlias a)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlVisitor.VisitSource(SqlSource source)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSelect(SqlSelect select)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlVisitor.VisitSequence(SqlSelect sel)
at System.Data.Linq.SqlClient.SqlVisitor.VisitScalarSubSelect(SqlSubSelect ss)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSubSelect(SqlSubSelect ss)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitExpression(SqlExpression expr)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.FetchExpression(SqlExpression expr)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitNew(SqlNew sox)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitExpression(SqlExpression expr)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSelect(SqlSelect select)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitAlias(SqlAlias a)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlVisitor.VisitSource(SqlSource source)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSelect(SqlSelect select)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitIncludeScope(SqlIncludeScope scope)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Bind(SqlNode node)
at System.Data.Linq.SqlClient.SqlProvider.BuildQuery(ResultShape resultShape, Type resultType, SqlNode node, ReadOnlyCollection`1 parentParameters, SqlNodeAnnotations annotations)
at System.Data.Linq.SqlClient.SqlProvider.BuildQuery(Expression query, SqlNodeAnnotations annotations)
at System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
at System.Data.Linq.DataQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at Microsoft.FSharp.Collections.SeqModule.Iterate[T](FSharpFunc`2 action, IEnumerable`1 source)
at <StartupCode$FSI_0015>.$FSI_0015.main#() in C:\Users\JDKS\Library\query expressions.fsx:line 144
Stopped due to error
My guess is that I don't truly understand what's going on under the hood enough to work out the error or lead myself to a solution. Is it possible to query for what I want here? Is there a workaround?
When I tried running it locally I got this exception
System.Exception: Grouping over multiple tables is not supported yet
I am not sure why you would be getting the less pretty version of that error. But if you pull the GroupBy out of the query, things seem to work fine.
query {
for student in db.Student do
join course in db.CourseSelection
on (student.StudentID = course.StudentID)
select (student.Name, course.Id)
}
|> Seq.countBy snd
|> Seq.iter (fun (student, classCount) -> printfn "Student %s has %i classes" student classCount)
I believe that the reason why your first query is not working is because of the groupBy student into group.
I think what you actually want to write is groupBy student.StudentID into group.
I found an answer from a similar question:
query {
for student in db.Student do
join course in db.CourseSelection
on (student.StudentID = course.StudentID)
let count = query {
for c in db.CourseSelection do
where (c.StudentID = student.StudentID)
select c
count
}
select (student.Name, count)
distinct
}
|> Seq.iter (fun (student, classCount) -> printfn "Student %s has %i classes" student classCount)
The underlying paradigm of these query expressions still eludes me.
EDIT:
I found that you can join implicitly without needing to use the join operator
query {
for student in db.Student do
let count = query {
for course in student.CourseSelection do
count
}
select (student.Name, count)
}
|> Seq.iter (fun (name, count) -> printfn "%s has %i courses" name count)
I have an object with a multislot of INSTANCE.
I have problems in getting instance's slot with one call.
Example:
(defclass AUTOMA (is-a USER)
(slot uuid))
(defclass TUTOMA (is-a USER)
(multislot list
(type INSTANCE)))
(make-instance A1 of AUTOMA
(uuid a1))
(make-instance A2 of AUTOMA
(uuid a2))
(make-instance T1 of TUTOMA
(list [a1] [a2]))
I want to retrive the first object uuid of the multislot list.
1) Try with "first$":
CLIPS> (first$ (send [T1] get-list))
([a1])
CLIPS> (send (first$ (send [T1] get-list)) get-uuid)
[MSGFUN1] No applicable primary message-handlers found for get-uuid.
FALSE
2) Try with "implode$":
CLIPS> (implode$ (first$ (send [T1] get-list)))
"[a1]"
CLIPS> (send (implode$ (first$ (send [T1] get-list))) get-uuid)
[MSGFUN1] No applicable primary message-handlers found for get-uuid.
FALSE
It seems that both ([a1]) and "[a1]" are not good for the (send XXX get-uuid) command.
Any suggestions, please?
Thank you
Nic
First$ returns a multifield value and implode$ returns a string. You need to use an instance name. Use nth$ to retrieve a field from within a multifield. You also need to keep the case used for your instance names consistent:
CLIPS>
(defclass AUTOMA
(is-a USER)
(slot uuid))
CLIPS>
(defclass TUTOMA
(is-a USER)
(multislot list (type INSTANCE)))
CLIPS> (make-instance A1 of AUTOMA (uuid a1))
[A1]
CLIPS> (make-instance A2 of AUTOMA (uuid a2))
[A2]
CLIPS> (make-instance T1 of TUTOMA (list [A1] [A2]))
[T1]
CLIPS> (send (nth$ 1 (send [T1] get-list)) get-uuid)
a1
CLIPS>
I wrote this rule for an expert system :
(defrule wild chicory
(attribute (name habitat) (value sea montain grassland unknown))
=>
(assert (plant "Cichorium_Intybus"))
)
However I don't want the habitat's value to match all of the values that I have given, but to only match at least one of values.
I'm wondering how I should do this. I could do it so:
(defrule wild chicory
(or (attribute (name habitat) (value sea))
(attribute (name habitat) (value mountain))
(attribute (name habitat) (value grassland))
(attribute (name habitat) (value unknow))
)
=>
(assert (plant "Cichorium_Intybus"))
)
But I would like to know if there is a better solution. Thanks
If value is a single field slot, do it this way:
(defrule wild chicory
(attribute (name habitat) (value sea | mountain | grassland | unknown))
=>
(assert (plant "Cichorium_Intybus")))
If value is a multi field slot, do it this way:
(defrule wild chicory
(attribute (name habitat) (value $? sea | mountain | grassland | unknown $?))
=>
(assert (plant "Cichorium_Intybus")))
I am currently working with SPARQL (and TopBraidComposer). I have a query which only brings back matching literals, and then filters out the literals based on not wanting certain categories.
Currently, this query is taking a long time to run, and I think it is my FILTER which is causing the delay. I was wondering if someone would have a better and faster way of filtering out (NOT returning) rows which contain a set of key words (ex. cat1, cat2, cat3).
As of now, I am using;
SELECT ?category
WHERE {
?s1 ?p ?category .
?s2 ?p ?category .
FILTER (str(?category) != "Cat1") .
FILTER (str(?category) != "Cat2") .
FILTER (str(?category) != "Cat3") .
FILTER (str(?category) != "Cat4") .
FILTER (str(?category) != "Cat6") .
FILTER (str(?category) != "Cat8") .
}
It's not clear how much you've trimmed down your example, but the code you presented is doing more work than it needs to.
SELECT ?category
WHERE {
?s1 ?p ?category .
?s2 ?p ?category .
FILTER (str(?category) != "Cat1") .
FILTER (str(?category) != "Cat2") .
FILTER (str(?category) != "Cat3") .
FILTER (str(?category) != "Cat4") .
FILTER (str(?category) != "Cat6") .
FILTER (str(?category) != "Cat8") .
}
Suppose your data has
:a :p "Cat0" .
:b :p "Cat0" .
Then the bindings for ?s1, ?s2, ?p? and ?category can be
?s1 ?s2 ?p ?category
--------------------
:a :a :p "Cat0"
:a :b :p "Cat0"
:b :b :p "Cat0"
:b :a :p "Cat0"
That's four ways to select "Cat0". You said that you want literals, but right now you're hitting every kind of ?category and applying str to it multiple times. You might do this instead:
SELECT DISTINCT ?category
WHERE {
?s ?p ?category .
FILTER( isLiteral(?category) &&
!(str(?category) in ("Cat1", "Cat2", "Cat3",
"Cat4", "Cat6", "Cat8")) )
}