Exercism - Perfect Numbers
This post shows you how to get Perfect Numbers exercise of Exercism.
Preparation
Before we click on our next exercise, let’s see what concepts of DART we need to consider

So we need to use the following concepts.
Enums
Enums define a set of named constants. They’re useful for representing a fixed set of related values, like classification types.
enum Classification { perfect, abundant, deficient }
void main() {
// Use enum values
Classification type = Classification.perfect;
print(type); // Classification.perfect
// Compare enum values
if (type == Classification.perfect) {
print('Perfect number!');
}
// Switch on enum
switch (type) {
case Classification.perfect:
print('Perfect');
break;
case Classification.abundant:
print('Abundant');
break;
case Classification.deficient:
print('Deficient');
break;
}
}
ArgumentError and Exception Handling
ArgumentError is thrown when a function receives an invalid argument. You can throw it to indicate that the input doesn’t meet the function’s requirements.
void main() {
int divide(int a, int b) {
if (b == 0) {
throw ArgumentError('Cannot divide by zero');
}
return a ~/ b;
}
int classify(int number) {
if (number <= 0) {
throw ArgumentError('Number must be positive');
}
// Classification logic...
return 0;
}
try {
classify(-5); // Throws ArgumentError
} catch (e) {
print(e); // ArgumentError: Number must be positive
}
}
Helper Methods
Helper methods (often private methods starting with _) encapsulate reusable logic, making code more organized and maintainable.
class PerfectNumbers {
// Public method
Classification classify(int number) {
final sum = _aliquotSum(number);
return sum == number ? Classification.perfect : Classification.deficient;
}
// Private helper method
int _aliquotSum(int n) {
// Calculate sum of factors
return 0;
}
// Another helper method
List<int> _factors(int n) {
// Find all factors
return [];
}
}
Finding Factors Efficiently
To find all factors of a number efficiently, you only need to check up to the square root of the number. For each divisor i found, you also get its pair n / i.
void main() {
List<int> findFactors(int n) {
final factors = <int>[1]; // 1 is always a factor
// Only check up to sqrt(n)
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
factors.add(i); // Add the divisor
if (i != n ~/ i) {
factors.add(n ~/ i); // Add its pair
}
}
}
return factors;
}
// Example: factors of 12
// i=2: 12 % 2 == 0, add 2 and 6
// i=3: 12 % 3 == 0, add 3 and 4
// i=4: 4*4 > 12, stop
// Result: [1, 2, 6, 3, 4]
print(findFactors(12)); // [1, 2, 6, 3, 4]
}
Modulo Operator
The modulo operator (%) returns the remainder of a division operation. It’s essential for checking divisibility.
void main() {
// Check if a number is divisible by another
int n = 12;
print(12 % 2); // 0 (12 is divisible by 2)
print(12 % 3); // 0 (12 is divisible by 3)
print(12 % 5); // 2 (12 is not divisible by 5)
// Check divisibility
if (n % i == 0) {
print('$i is a factor of $n');
}
}
Integer Division
The integer division operator (~/) divides two numbers and returns an integer result, truncating any decimal part.
void main() {
int n = 12;
// Get the pair factor
int i = 3;
int pair = n ~/ i;
print(pair); // 4 (12 / 3 = 4)
// Avoid adding duplicate factors
if (i != n ~/ i) {
print('Different factors');
}
// Example: for n=16, i=4
// 4 == 16 ~/ 4 (both are 4), so don't add duplicate
}
Reduce Method
The reduce() method combines all elements of a collection into a single value using a combining function. It’s perfect for summing a list of numbers.
void main() {
List<int> factors = [1, 2, 3, 4, 6];
// Sum all factors
int sum = factors.reduce((a, b) => a + b);
print(sum); // 16
// Product of all factors
int product = factors.reduce((a, b) => a * b);
print(product); // 144
// Find maximum
int max = factors.reduce((a, b) => a > b ? a : b);
print(max); // 6
}
Conditional (Ternary) Operator
The ternary operator (? :) provides a concise way to write if-else statements. It’s useful for simple conditional assignments.
void main() {
int sum = 6;
int number = 6;
// Simple ternary
String result = sum == number ? 'equal' : 'not equal';
print(result); // 'equal'
// Nested ternary
String classification = sum == number
? 'perfect'
: sum > number
? 'abundant'
: 'deficient';
print(classification); // 'perfect'
// With return
Classification classify(int sum, int number) {
return sum == number
? Classification.perfect
: sum > number
? Classification.abundant
: Classification.deficient;
}
}
Lists
Lists are ordered collections of items. You can add elements, iterate over them, and perform various operations.
void main() {
// Create empty list
List<int> factors = <int>[];
// Add elements
factors.add(1);
factors.add(2);
factors.add(3);
// List literal with type
List<int> numbers = <int>[1, 2, 3];
// Iterate
for (var factor in factors) {
print(factor);
}
}
Introduction
Determine if a number is perfect, abundant, or deficient based on Nicomachus’ (60 - 120 CE) classification scheme for positive integers.
The Greek mathematician Nicomachus devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of perfect, abundant, or deficient based on their aliquot sum. The aliquot sum is defined as the sum of the factors of a number not including the number itself. For example, the aliquot sum of 15 is 1 + 3 + 5 = 9.
Perfect
A number is perfect when it equals its aliquot sum. For example:
- 6 is a perfect number because 1 + 2 + 3 = 6
- 28 is a perfect number because 1 + 2 + 4 + 7 + 14 = 28
Abundant
A number is abundant when it is less than its aliquot sum. For example:
- 12 is an abundant number because 1 + 2 + 3 + 4 + 6 = 16
- 24 is an abundant number because 1 + 2 + 3 + 4 + 6 + 8 + 12 = 36
Deficient
A number is deficient when it is greater than its aliquot sum. For example:
- 8 is a deficient number because 1 + 2 + 4 = 7
- Prime numbers are deficient
Task
Implement a way to determine whether a given number is perfect. Depending on your language track, you may also need to implement a way to determine whether a given number is abundant or deficient.
What is an aliquot sum?
The aliquot sum of a positive integer is the sum of all proper divisors of the number (all divisors except the number itself). For example, the aliquot sum of 12 is 1 + 2 + 3 + 4 + 6 = 16. This concept is fundamental to number theory and was used by ancient mathematicians like Nicomachus to classify numbers.
— Number Theory
How can we classify numbers?
To classify a number:
- Find all factors: Find all divisors of the number (excluding the number itself)
- Calculate aliquot sum: Sum all the factors
- Compare:
- If sum == number → Perfect
- If sum > number → Abundant
- If sum < number → Deficient
- Handle edge cases: Throw an error for non-positive numbers
The key insight is efficiently finding factors by only checking up to the square root of the number, since for each divisor i, we also get its pair n / i.
For example, for number 12:
- Factors (excluding 12): 1, 2, 3, 4, 6
- Aliquot sum: 1 + 2 + 3 + 4 + 6 = 16
- 16 > 12 → Abundant
Solution
enum Classification { perfect, abundant, deficient }
class PerfectNumbers {
Classification classify(int number) {
if (number <= 0) throw ArgumentError();
final sum = _aliquotSum(number);
return sum == number
? Classification.perfect
: sum > number
? Classification.abundant
: Classification.deficient;
}
int _aliquotSum(int n) =>
n == 1 ? 0 : _factors(n).reduce((a, b) => a + b);
List<int> _factors(int n) {
final factors = <int>[1];
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
factors.add(i);
if (i != n ~/ i) factors.add(n ~/ i);
}
}
return factors;
}
}
Let’s break down the solution:
-
enum Classification- Defines the three possible classifications:perfect- Number equals its aliquot sumabundant- Number is less than its aliquot sumdeficient- Number is greater than its aliquot sum
-
List<int> _factors(int n)- Helper method that finds all factors of a number:- Initialize: Starts with
[1]since 1 is always a factor - Efficient loop: Only checks up to
sqrt(n)(usingi * i <= n) - Find divisors: For each
iwheren % i == 0,iis a divisor - Add pair: Also adds
n ~/ i(the pair factor), unless it’s a perfect square (avoiding duplicates) - Example: For n=12, finds 1, 2, 6, 3, 4 (excluding 12 itself)
- Initialize: Starts with
-
int _aliquotSum(int n)- Helper method that calculates the aliquot sum:- Edge case: Returns 0 for n=1 (1 has no proper divisors)
- Calculate sum: Uses
reduce()to sum all factors found by_factors(n) - Returns the sum of all proper divisors
-
Classification classify(int number)- Main method that classifies a number:- Validation: Throws
ArgumentErrorif number is not positive - Calculate sum: Gets the aliquot sum using
_aliquotSum(number) - Classify: Uses nested ternary operators to determine classification:
- If
sum == number→Classification.perfect - Else if
sum > number→Classification.abundant - Else →
Classification.deficient
- If
- Validation: Throws
The solution efficiently finds factors by only checking up to the square root, making it much faster than checking all numbers up to n. The helper methods keep the code organized and the main classify method clean and readable.
A video tutorial for this exercise is coming soon! In the meantime, check out my YouTube channel for more Dart and Flutter tutorials. 😉
Visit My YouTube Channel