Start learning free

LESSON 249 OF 677 · HELPER FUNCTIONS

Perfect Numbers in Scala: A sumOfDivisors Helper

Practice Read the full explanation

You get an array of integers nums. Return the perfect numbers among them, in the same order.

A number is perfect when it equals the sum of its divisors smaller than itself. For example, 6 is perfect, because 1 + 2 + 3 = 6. The number 1 is not perfect. It has no smaller divisors, so its sum is 0.

If a perfect number appears twice, it is kept twice. If no number is perfect, the result is empty.

The work is split between two functions. The first one is a helper. It gets one number and returns the sum of its divisors smaller than itself. The second one is the function that is graded. It keeps every number that equals the sum the helper returns for it.

 

Example 1:

Input: nums = [6, 8, 28, 12]
Output: [6, 28]
Explanation: 6 = 1 + 2 + 3.
28 = 1 + 2 + 4 + 7 + 14.
For 8, the sum is 1 + 2 + 4 = 7.
For 12, the sum is 1 + 2 + 3 + 4 + 6 = 16.

Example 2:

Input: nums = [1, 5, 10]
Output: []
Explanation: The sum for 1 is 0.
The sum for 5 is 1.
The sum for 10 is 1 + 2 + 5 = 8.

Example 3:

Input: nums = [496, 6, 6]
Output: [496, 6, 6]
Explanation: 496 is perfect too.
6 appears twice, so it is kept twice.

 

Constraints:

  • nums has 1 to 20 numbers.
  • Every number is between 1 and 10,000.
STEP 1 OF 8
    Ctrl + Enter to check

    SCALA · HELPER FUNCTIONS

    Perfect Numbers in Scala, explained

    Problem overview

    The method perfectNumbers gets an Array[Int] called nums and returns the perfect numbers among them, in the same order, as an ArrayBuffer[Int]. A number is perfect when it equals the sum of its divisors that are smaller than itself. For 6 those divisors are 1, 2 and 3, and 1 + 2 + 3 = 6. The number 1 has no smaller divisors, so its sum is 0 and it is not perfect. A perfect number that appears twice is kept twice, and if there are none the result is empty. There are 1 to 20 numbers, each between 1 and 10,000.

    For [6, 8, 28, 12] the answer is [6, 28]. For [1, 5, 10] it is empty, and for [496, 6, 6] it is [496, 6, 6].

    Approach

    The work is split between two methods. The helper sumOfDivisors takes one number n and returns the sum of its divisors smaller than n. perfectNumbers calls it on every number and keeps the ones where the helper's answer equals the number. The helper tries every candidate d from 1 up to n - 1 and adds those that divide n with no remainder. With numbers up to 10,000 that is at most about ten thousand checks per number, which is fast.

    Step by step

    The helper

    1. Create total with var, starting at 0.
    2. Loop with for (d <- 1 until n). The range 1 until n covers 1 to n - 1, so n itself is left out, as the rule asks. For n = 1 the range is empty and the sum stays 0.
    3. If n % d == 0, then d divides n evenly, so add it to total.
    4. After the loop, return total.

    The main method

    1. Create an empty ArrayBuffer[Int] called perfect.
    2. Loop with for (num <- nums).
    3. If sumOfDivisors(num) == num, append num with +=.
    4. Return perfect.

    The solution in Scala

    class Solution {
        def sumOfDivisors(n: Int): Int = {
            var total = 0
            for (d <- 1 until n) {
                if (n % d == 0) {
                    total += d
                }
            }
            return total
        }
    
        def perfectNumbers(nums: Array[Int]): ArrayBuffer[Int] = {
            val perfect = ArrayBuffer[Int]()
            for (num <- nums) {
                if (sumOfDivisors(num) == num) {
                    perfect += num
                }
            }
            return perfect
        }
    }
    

    The helper has its own loop, and the main method only calls it, once per number. A perfect number that appears twice is visited twice, so it is kept twice. 1 until n excludes its upper end, while 1 to n would include it and add n itself to the sum.

    Why this works

    Every number from 2 up has at least one divisor smaller than itself, namely 1, so most sums are at least 1. A sum can come out smaller than the number (a number with few divisors, like 5, has the sum 1), equal to it (a perfect number) or bigger than it (a number with many divisors, like 12 or 10,000). The test sumOfDivisors(num) == num accepts only the middle case.

    In the main method the helper's answer is compared with num using ==, which compares numbers by value. The helper is called once per number, so for the second example it is called three times, with 1, 5 and 10, and none of the three answers (0, 1 and 8) equals its number. That is why the result stays empty.

    Testing

    The helper returns these sums for 1, 5, 6, 8, 10, 12, 28 and 496 in order: 0, 1, 6, 7, 8, 16, 28 and 496. So 6, 28 and 496 equal their sums, and the others do not. The three examples return 6 28, an empty buffer and 496 6 6 (the tests print lists as values separated by spaces).

    A number can be bigger than its sum, as 8 is (7), or smaller, as 12 is (16). Only an exact match counts as perfect.

    Another way

    A divisor of n that is smaller than n is never bigger than n / 2, so the helper can stop there:

    def sumOfDivisors(n: Int): Int = {
        var total = 0
        for (d <- 1 to n / 2) {
            if (n % d == 0) {
                total += d
            }
        }
        return total
    }
    

    It returns the same sums as the lesson's version for every number from 1 to 10,000, with about half the checks. For 1 the range 1 to 0 is empty, so the sum is still 0. The lesson's version is the simpler one to read, and for at most 20 numbers up to 10,000 either is fast.

    The main loop can also be written with filter: nums.filter(n => sumOfDivisors(n) == n) keeps 6 28 on the first example, but it returns an Array, not the ArrayBuffer the method must return.

    Edge cases and common mistakes

    • Including n itself. With 1 to n, every sum grows by exactly n. Then 6 and 28 no longer match, and 1 wrongly does: on [6, 8, 28, 12, 1] the result is 1 instead of 6 28.
    • The number 1. Its range is empty, the sum is 0, and 1 is correctly not perfect.
    • Using / instead of %. The test is a remainder of 0, written n % d == 0.
    • Removing duplicates. The statement keeps a repeated perfect number every time it appears, so the main method must not skip numbers it has seen.
    • Comparing with the wrong value. Compare the helper's answer with num, not with total or a fixed value like 6.
    Keep going with Scala500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free