Visit the perfect-numbers exercise on Exercism to read the full instructions and download the exercise files.
Dig Deeper
Use if statements
if statement
using System;
using System.Linq;
public enum Classification
{
Perfect,
Abundant,
Deficient
}
public class PerfectNumbers
{
public static Classification Classify(int number)
{
if (number < 1)
throw new ArgumentOutOfRangeException(nameof(number));
var sumOfFactors = Enumerable.Range(1, number / 2)
.Where(factor => number % factor == 0)
.Sum();
if (sumOfFactors < number)
return Classification.Deficient;
if (sumOfFactors > number)
return Classification.Abundant;
return Classification.Perfect;
}
}
The first step is to check the number for validity:
if (number < 1)
throw new ArgumentOutOfRangeException(nameof(number));
The next step is to calculate the sum of the number’s factors.
A factor is a number that you can divide another number with and not end up with a remainder, which we can check using the modulo (%) operator: number % factor == 0.
We then need to decide which numbers could possibly be factors.
For the lower bound, we can use 1, as that is always the smallest factor.
For the upper bound, we can leverage the fact that the second lowest factor is 2, which means that any numbers greater than number / 2 cannot be factors.
We can use the Enumerable.Range() method to iterate over the possible factors, then use Where() to select only valid factors and then finally sum them via the Sum() method:
var sumOfFactors = Enumerable.Range(1, number / 2)
.Where(factor => number % factor == 0)
.Sum();
Then finally we can apply the logic to classify the perfect number via some if statements:
if (sumOfFactors < number)
return Classification.Deficient;
if (sumOfFactors > number)
return Classification.Abundant;
return Classification.Perfect;
Shortening
You could use the ternary operator to replace the if statements:
return sumOfFactors < number ? Classification.Deficient :
sumOfFactors > number ? Classification.Abundant :
Classification.Perfect;
Combining Where and Sum vs. using Sum only
Instead of using Where and then Sum we could actually implement this by using Sum only.
We can provide the Sum with a lambda that tells it to keep every number that is a factor and use a 0 in place of every number that isn’t, effectively disregarding it when summing:
var sum = Enumerable.Range(1, number - 1)
.Sum(n => number % n == 0 ? n : 0);
This also uses the aforementioned ternary operator.
Use switch expression
switch expression
using System;
using System.Collections.Generic;
using System.Linq;
public enum Classification
{
Perfect,
Abundant,
Deficient
}
public static class PerfectNumbers
{
public static Classification Classify(int number)
{
var sum = Enumerable.Range(1, number - 1)
.Sum(n => number % n == 0 ? n : 0);
return (sum < number, sum > number) switch
{
(true, _) => Classification.Deficient,
(_, true) => Classification.Abundant,
_ => Classification.Perfect,
};
}
}
This approach does the following:
- Takes a range of numbers starting from the number
1 up until (but not including) the number being classified.
- For each number, it keeps the number if it is a factor, otherwise replaces it with a zero.
- Sums all the numbers and classifies this sum according to given instructions.
Let’s go through this in more detail:
In the first step a range of numbers is generated using the Range() function, giving it a starting number (1) and a count of how many numbers we want.
Enumerable.Range(1, number - 1)
You can test whether a number is a factor by using the modulo (%) operator and checking whether there’s no remainder.
The line .Sum(n => number % n == 0 ? n : 0); keeps every factor and uses a 0 in place of every number that isn’t a factor and then using sum to sum these.
Finally, we need to classify the number, this is done using switch expression on a tuple with the two necessary checks (the third Perfect case being implicitly tested):
return (sum < number, sum > number) switch
{
(true, _) => Classification.Deficient,
(_, true) => Classification.Abundant,
_ => Classification.Perfect,
};
Range and upperbound considerations
We’re using a property of the Range() function that it returns an empty range when giving it a zero count; we can use other functions on an empty range (like sum in this case) without any problem.
This means that we do not first have to verify whether number is positive.
The downside of using range this way is that we ask for more numbers than we need (number - 1); if we wouldn’t have to take into account 0, we could divide any given number by 2 (since a factor of a number can only be - at most - half the number) and use that as the upperbound (count).
Sum vs. Where and Sum
The line .Sum(n => number % n == 0 ? n : 0); can also be written as .Where(n => number % n == 0).Sum();, using where to keep only the factors and then sum the result, but this is a little more verbose.
Source: Exercism csharp/perfect-numbers