ASP-12 | Counting Things
ASP's #count directive enables working with quantities and cardinality constraints. The directive provides a natural way to reason about counting in the real world.Photo Credit: Rob Grzywinski
Counting
Imagine you need to know how many students are in a class. You have facts about who the students are, but how do you get a count? Let's start with the simplest case:student(alice ; bob ; charlie) .
total(N) ← N = #count{ S : student(S) } .
This reads naturally as "N is the count of distinct students". The result shows us there are 3 students:The #count directive counts unique values that match a pattern. In this case, it counts distinct values of S that appear in student(S).
Multiple Arguments
What if we need to count students enrolled in different classes? We can count while keeping track of which class we're counting:class(math ; history) .
enrolled(alice, math) . enrolled(bob, math) .
enrolled(alice, history) . enrolled(bob, history) . enrolled(charlie, history) .
students_per_class(C,N) ← N = #count{ S : enrolled(S,C) }, class(C) .
The result shows us each class's enrollment: {| students_per_class/2 |
|---|
| history | 3 |
| math | 2 |
}
Counting with Constraints
The real power of #count emerges when we combine it with constraints. Say we want to ensure no class exceeds a maximum size:⊥ ← #count{ S : enrolled(S,C) } > 2, class(C) .
This constraint reads naturally as "it cannot be that any class has more than 2 students" and results in because there are three students in the history class. We also could have used (3) and write:⊥ ← students_per_class(C,N), N > 2 .
This shows two different ways to express the same constraint — one directly using #count and one using our previously defined count predicate. Both approaches are valid and choosing between them often comes down to readability and whether you need the intermediate count for other purposes.
Counting Specific Patterns
Sometimes we want to count only items matching certain criteria. For example, counting students who take both math and history:dual_enrolled(N) ← N = #count{ S : enrolled(S, math), enrolled(S, history) } .
{| enrolled/2 |
|---|
| alice | history |
| alice | math |
| bob | history |
| bob | math |
| charlie | history |
} The count only includes students appearing in both classes.
Advanced Counting Techniques
Counting with Multiple Conditions
Let's extend our example to show how we can count with multiple conditions:class(math ; history ; science) .
level(beginner ; advanced) .
enrolled(alice, math, advanced) .
enrolled(bob, math, beginner) .
enrolled(charlie, history, advanced) .
enrolled(alice, history, advanced) .
advanced_students(N) ← N = #count{ S,C : enrolled(S, C, advanced) } .
This shows how to count enrollments in advanced classes across all subjects. Note that if a student is in multiple advanced classes, they're counted multiple times.{| class/1 |
|---|
| history |
| math |
| science |
| enrolled/3 |
|---|
| alice | history | advanced |
| alice | math | advanced |
| bob | math | beginner |
| charlie | history | advanced |
} Unique vs Non-Unique Counting
Sometimes we want to count unique occurrences versus total occurrences. Compare these two counts:unique_advanced(N) ← N = #count{ S : enrolled(S, _, advanced) } .
total_advanced(N) ← N = #count{ S,C : enrolled(S, C, advanced) } .
The first rule counts each student only once even if they're in multiple advanced classes, while the second rule counts each advanced enrollment separately.Using Count in Choice Rules
#count can be particularly powerful when combined with choice rules:{ assign(S,C) : enrolled(S,C) } .
⊥ ← #count{ S : assign(S,C) } > 2, class(C) .
This, when combined with (2), generates all possible assignment combinations while ensuring no class has more than 2 students.{ }{}{}{}{}{| assign/2 |
|---|
| alice | math |
| bob | history |
}{| assign/2 |
|---|
| bob | history |
| bob | math |
}{| assign/2 |
|---|
| alice | math |
| bob | history |
| bob | math |
}{}{| assign/2 |
|---|
| bob | math |
| charlie | history |
}{| assign/2 |
|---|
| alice | math |
| charlie | history |
}{| assign/2 |
|---|
| alice | math |
| bob | math |
| charlie | history |
}{| assign/2 |
|---|
| bob | history |
| charlie | history |
}{| assign/2 |
|---|
| alice | math |
| bob | history |
| charlie | history |
}{| assign/2 |
|---|
| bob | history |
| bob | math |
| charlie | history |
}{| assign/2 |
|---|
| alice | math |
| bob | history |
| bob | math |
| charlie | history |
}{}{| assign/2 |
|---|
| alice | history |
| bob | math |
}{| assign/2 |
|---|
| alice | history |
| alice | math |
}{| assign/2 |
|---|
| alice | history |
| alice | math |
| bob | math |
}{| assign/2 |
|---|
| alice | history |
| charlie | history |
}{| assign/2 |
|---|
| alice | history |
| alice | math |
| charlie | history |
}{| assign/2 |
|---|
| alice | history |
| bob | math |
| charlie | history |
}{| assign/2 |
|---|
| alice | history |
| alice | math |
| bob | math |
| charlie | history |
}{| assign/2 |
|---|
| alice | history |
| bob | history |
}{| assign/2 |
|---|
| alice | history |
| bob | history |
| bob | math |
}{| assign/2 |
|---|
| alice | history |
| alice | math |
| bob | history |
}{| assign/2 |
|---|
| alice | history |
| alice | math |
| bob | history |
| bob | math |
}
Example: Server Task Allocation
In this example, we want to ensure that our servers aren't overloaded by counting the tasks assigned to each server. We'll enforce a constraint that flags any server with more than two tasks as overloaded:server(s1 ; s2 ; s3) .
task(t1 ; t2 ; t3 ; t4) .
assigned(t1, s1) . assigned(t2, s1) . assigned(t4, s1) .
assigned(t3, s2) . assigned(t4, s2) .
overloaded(S) ← #count{ T : assigned(T, S) } > 2, server(S) .
{| assigned/2 |
|---|
| t1 | s1 |
| t2 | s1 |
| t3 | s2 |
| t4 | s1 |
| t4 | s2 |
} This pattern is common in resource allocation, scheduling conflicts, and configuration management where counting constraints enforce real-world limitations.
Counting vs Cardinality Constraints
While both count things, #count and cardinality constraints serve different purposes:Cardinality constraints generate possibilities, while #count verifies properties about existing atoms.
Takeaways
The #count directive is one of ASP's most practical features, providing an intuitive way to work with quantities and enforce numerical constraints. It excels when you need to:- Count unique or total occurrences of patterns
- Enforce capacity limits or thresholds
- Validate resource allocations
- Track group sizes or membership
While cardinality constraints help generate possibilities, #count helps verify properties about those possibilities. Together, they form a powerful toolkit for expressing real-world constraints that involve quantities. The natural way #count expresses these constraints — "count the number of students in each class" or "ensure no server has more than two tasks" — mirrors how we think about these problems in everyday language, making ASP programs both readable and maintainable.