1. 概述

在这个快速教程中,我们将学习什么是阿姆斯特朗数,以及如何通过创建一个Java程序来检查和找到它们。

2. 问题介绍

首先,让我们理解什么是阿姆斯特朗数。

给定一个有 n 位的正整数 i ,如果将它的每一位数字的 n 次方之和等于 i ,那么 i 就是一个阿姆斯特朗数。 阿姆斯特朗数构成了OEIS序列A005188。例如:

  • 1:(pow(1,1) = 1) -> 1 是一个阿姆斯特朗数。
  • 123:(pow(1, 3) + pow(2, 3) + pow(3, 3) = 1 + 8 +27 = 36 \neq 123) -> 123 不是阿姆斯特朗数。
  • 1634:(pow(1, 4) + pow(6, 4) + pow(3, 4) + pow(4, 4) = 1 + 1296 + 81 + 256 = 1643) -> 1634 是一个阿姆斯特朗数。

因此,我们想要编写一个Java程序,方便地检查给定的数字是否为阿姆斯特朗数,并生成小于给定限制的OEIS序列A005188。为了简化,我们将使用单元测试断言来验证我们的方法是否按预期工作。

3. 解决问题的思路

现在我们了解了阿姆斯特朗数,接下来考虑如何解决这个问题。

首先,生成给定限制下的OEIS序列A005188可以转化为从0到给定的上限,找出所有的阿姆斯特朗数。 如果我们有一个判断整数是否为阿姆斯特朗数的方法,那么很容易从整数范围内筛选出非阿姆斯特朗数,得到所需的序列。

因此,主要问题是如何创建阿姆斯特朗数检查方法。一个直观的解决方案是两步法:

  • 步骤1 - 将给定的整数拆分成一个数字列表,例如:(12345 \to [1, 2, 3, 4, 5])
  • 步骤2 - 对列表中的每个数字,计算 (pow(digit, list.size())),然后求和,最后将结果与初始给定的整数进行比较

接下来,我们将这个想法转换成Java代码。

4. 创建阿姆斯特朗数方法

正如我们讨论过的,首先将给定的整数转换为数字列表:

static List<Integer> digitsInList(int n) {
    List<Integer> list = new ArrayList<>();
    while (n > 0) {
        list.add(n % 10);
        n = n / 10;
    }
    return list;
}

如上所示,我们在一个 while 循环中从 n 中提取一位数字,每次取 n % 10,然后通过 n = n / 10 缩小数字。

另一种方法是将数字转换为字符串,然后使用 split() 方法获取字符串形式的数字列表。然后,最后再将每个数字转换回整数。这里我们没有采用这种方法。

现在我们已经创建了检查方法,接下来进行步骤2:计算幂并求和:

static boolean isArmstrong(int n) {
    if (n < 0) {
        return false;
    }
    List<Integer> digitsList = digitsInList(n);
    int len = digitsList.size();
    int sum = digitsList.stream()
      .mapToInt(d -> (int) Math.pow(d, len))
      .sum();
    return n == sum;
}

isArmstrong() 检查方法中,我们使用Java 8流的 mapToInt() 方法将每个数字转换为幂运算后的结果,然后在列表中对结果进行求和。

最后,我们将求和的结果与初始整数进行比较,以确定该数字是否为阿姆斯特朗数。

值得一提的是,我们可以将 mapToInt()sum() 调用合并成一个 reduce() 调用

int sum = digits.stream()
  .reduce(0, (subtotal, digit) -> subtotal + (int) Math.pow(digit, len));

接下来,我们创建一个方法生成OEIS序列A005188,直到达到给定的上限:

static List<Integer> getA005188Sequence(int limit) {
    if (limit < 0) {
        throw new IllegalArgumentException("The limit cannot be a negative number.");
    }
    return IntStream.range(0, limit)
      .boxed()
      .filter(ArmstrongNumberUtil::isArmstrong)
      .collect(Collectors.toList());
}

如代码所示,我们再次使用流API来过滤阿姆斯特朗数并生成序列。

5. 测试

现在,让我们编写一些测试来验证我们的方法是否按预期工作。首先,我们从上面的测试数据开始:

static final Map<Integer, Boolean> ARMSTRONG_MAP = ImmutableMap.of(
  0, true,
  1, true,
  2, true,
  153, true,
  370, true,
  407, true,
  42, false,
  777, false,
  12345, false);

现在,我们将上述 Map 中的每个数字传递给我们的检查方法,看看返回的结果是否符合预期:

ARMSTRONG_MAP.forEach((number, result) -> assertEquals(result, ArmstrongNumberUtil.isArmstrong(number)));

运行测试后,它会通过。所以,检查方法正确地完成了任务。

下一步,让我们准备两个预期的序列,测试 getA005188Sequence() 是否按预期工作:

List<Integer> A005188_SEQ_1K = ImmutableList.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407);
List<Integer> A005188_SEQ_10K = ImmutableList.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407, 1634, 8208, 9474);

assertEquals(A005188_SEQ_1K, ArmstrongNumberUtil.getA005188Sequence(1000));
assertEquals(A005188_SEQ_10K, ArmstrongNumberUtil.getA005188Sequence(10000));

如果我们运行测试,它也会通过。

6. 总结

在这篇文章中,我们讨论了什么是阿姆斯特朗数,并创建了检查整数是否为阿姆斯特朗数的方法以及生成OEIS序列A005188直到给定上限的函数。如往常一样,这里展示的所有代码片段都在GitHub上可用。