View Source N Factorial
Mix.install([
{:guesswork, "~> 0.6"}
])
Recursive Queries
import Guesswork.Ast
alias Guesswork.Ast.And
alias Guesswork.Ast.Fact
alias Guesswork.Ast.Is
alias Guesswork.Ast.Stop
Looking for a factorial is a little different from some of the other queries
we have written.
Instead generating possible values with criteria, we'll use a recursive query
to define what a valid factorial is.
If you look below you can see some of the normal things you would see in a recursive
definition: deffact
is used to define a base case, and defrule
is used to define
the recursive step (where Fact
invoces the next step).
defmodule FactorialKB do
use Guesswork.KnowledgeBase.Collection
deffact("n_factorial", [0, 1])
defrule("n_factorial", [n, f]) do
Stop.new([n, n1], &Kernel.</2)
Stop.new([f, f1], &Kernel.</2)
Is.new(n, [n1, 1], &Kernel.+/2)
Is.new(f, [n, f1], &Kernel.*/2)
Fact.new("n_factorial", [n1, f1])
end
end
This can be used simillarly to other queries, like the pythagorean triple query, where
we can pull the first n
valid answer sets.
Guesswork.query(term(Fact.new("n_factorial", [n, f])), 10, knowledge_base: FactorialKB)
But because the query is defined in terms of n
and f
we can query what f
is
when n
is 5
.
Guesswork.query(term(Fact.new("n_factorial", [5, f])), 10, knowledge_base: FactorialKB)
And the system supports binding f
and searching for n
.
Guesswork.query(term(Fact.new("n_factorial", [n, 120])), 10, knowledge_base: FactorialKB)
Even when there are multiple valid values for n
, the query will return them all
(provided you ask for them).
Guesswork.query(term(Fact.new("n_factorial", [n, 1])), 10, knowledge_base: FactorialKB)
Stop Conditions
If you look above, you'll notice that even though we ask for 10
results
from Guesswork.query/3
, we only get the valid values, and then the query
terminates.
This is done using stop conditions, via the Stop
statement.
Stop conditions can also allow us to use the query to search for potentially invalid values.
Guesswork.query(term(Fact.new("n_factorial", [n, 121])), 10, knowledge_base: FactorialKB)
Or test if a relationship is valid between some n
and f
.
Guesswork.query(term(Fact.new("n_factorial", [4, 120])), 10, knowledge_base: FactorialKB)
Stop conditions will terminate the entire stream after they are triggered
(unlike Guesswork.Ast.Is
, or tests, which will just disqualify individual answer sets).
But when we didn't define anything for n
or f
, we never triggered the stop conditions,
the query just kept returning values.
This is because the stop condition needs to be defined in terms of some concrete value. You you can think of the recursive definition as building possible answer sets, first with just the base fact, then with one recursive, then two, and so on. This is why the query is resonably fast, despite the large numbers; it doesn't have to search all possible numbers, just instantiate and resolve the answer sets.
But when you define n
or f
you need to account for the possibility that there is no
possible valid answer set, and tell the query to stop looking.
In this case we just have the system stop once it fines a recursive step with values
greater than the values we are looking for, since the following anwser sets (which will
all have more recursive steps) will have increasingly larger values, and therefore
cannot be valid either.
In the unbound query, because n
and f
are always products of the answer set (and
not defined by us) the stop condition cannot possibly be true.