Java Stream来写算法01——自幂数(水仙花数)

16 篇文章 0 订阅

总目录

自幂数,就是一个长度为 n n n的自然数,等于自身各个位上数字的 n n n次幂的和。
例如十进制中: 153 = 1 3 + 5 3 + 3 3 153=1^3+5^3+3^3 153=13+53+33,153是3位数,每一位数的3次幂的和,还是等于其自身,对于3位的自幂,还有一个特殊的名字“水仙花数”(Narcissistic Number)。
求自幂数的算法非常简单,只要遍历区间内所有数字,算出每一位上数字的“数字长度”的幂之和,与原数比较,只要相等即为自幂数。
如果用以往普通Java循环来写,for、while满屏横飞,各种代码结构完全混杂在一起,而用Stream来写,就可以代码写得即简单,又直白,初始逻辑与判断逻辑完成分离,结构的美感就体现出来了。

思考
1.由于位数不同,导致算法不同,所以简化一下,只查找指定位数的自幂数,即指定 n n n的值,如果让 n = 3 n=3 n=3,就是只查找100到999之间的自幂数。接下来的说法都以3位数来举例
2.需要将这个3位数中每一位的数取出来,取出后计算其3次幂的值
3.取每一位数的方法很多,这里使用与100相除的商来取百位,余数再与10相除,商为十位,余数再与1相除,商为个位数的方法
4.使用的方法中要尽量少用for、while语句

步骤1-初始化准备代码

计算3次幂的方法。

private int getPower(int initVal, int len) {
    return IntStream.iterate(initVal, v1 -> v1).limit(len).reduce(1, (a, b) -> a * b);
}
  • 这个代码可以用Math.power函数来实现,这里是个小炫技,求轻打
  • 参数len表示有多少位,initVal表示生成数列中包含的值。
  • getPower(5, 3)为例,IntStream.iterate(initVal, v1->v1)就会生成一个无限流,里面的内容全部都是5。
  • limit(len),是让这个无限流,只会有3个5,其他的全抛弃。
  • reduce(1, (a, b) -> a * b)是把这个流收敛,可以理解成把分散的流变成一个值,这里使用的收敛方法,让流中每个元素相乘在一起,最后就是形成 5 ∗ 5 ∗ 5 5*5*5 555这样的效果,即最后方法得到的是125,5的3次幂的值。
  • 灵活使用这个方法getPower(10, 2),就可以得到100,是生成百位数时,要用到的第一个除数。
  • 当得到了100,就可以得到3位数中最大值999,以及最小值100:
int firstDivide = getPower(10, len - 1);
int max = firstDivide * 10 -1;
int min = firstDivide;

步骤2-核心逻辑判断代码

非常遗憾地告诉各位,在这部分代码中,不得不使用的for循环,我也不想啊,可是现实就是让你低头啊,如果列位有更好解决方案,万望不吝赐教。

private IntPredicate isSelfPower(int len, int firstDivide) {
    return e -> {
        int dividend = e;// 被除数
        int divisor = firstDivide;// 除数
        int quotient;// 商
        int remainder;// 余数
        int sum = 0;
        for (int i = 0; i < len; i++) {
            quotient = dividend / divisor;
            remainder = dividend % divisor;
            sum += getPower(quotient, len);
            dividend = remainder;
            divisor /= 10;
        }
        return sum == e;
    };
}
  • IntPredicate是一个函数式接口,返回真或假,而如何判断是应该返回真,还是返回假,全在return语句后面的实现代码中。
  • 实现代码中循环语句的作用举例来说就是:先解析出百位的数,然后对这个数求其3次幂的值,并加到sum变量中,然后再解析出十位的数,再求3次幂,并加到sum变量中……这样的循环。
  • 解析百位数是除以100,解析十位是除以10,解析个位是除以1(这是比较无用的,但可以不用单独处理个位,保证代码的一致性)
  • 最后就是比较这个和sum与这个数本身是不是一样,如果是true,则说明是自幂,反之则不是

步骤3-用一行代码实现查找自幂数

IntStream.iterate(min, v -> v + 1).limit(max - min).parallel()
	.filter(isSelfPower(len, firstDivide)).forEach(System.out::println);
  • 这里面生成的数列的lambda表达式是v -> v+1,这个会生成1,2,3,4,5……这样递增的数列。
  • 使用parallel(),可以并行查找,如果把位数设的大一点,应该会有性能提升,但提升了多少,没有测试过,懒啊。

总结

把代码合并起来,就可以计算指定位数的自幂数了,如果设成8位数,可真是要算很长时间的。

之后的优化

  • 核心逻辑判断代码是用一个方法返回函数式接口的实例,其实可以用属性来代替
  • 在判断逻辑代码中使用了很多变量,也可以大幅度优化,修改后代码如下:
@FunctionalInterface
public interface ThreeParamPredicate<E> {
    boolean test(E e1, E e2, E e3);
}
private ThreeParamPredicate<Integer> threeParamPredicate = (len, divisor, dividend) -> {
    int sum = 0;
    for (int i = 0; i < len; i++) {
        int quotient = dividend / divisor;
        int remainder = dividend % divisor;
        sum += Double.valueOf(Math.pow(quotient, len)).intValue();
        dividend = remainder;
        divisor /= 10;
    }
    return sum == dividend;
}
  • 举例说明一下这个代码的作用:
  • 由于代码需要传入三个参数,而函数式接口中没有提供,所以自己写了一个接收三个入参的函数式接口
  • 比如判断153是不是自幂数,首先要把153折成1、5、3三个数
  • 循环第1次就是先拆出1这个数来,然后求1的3次幂,加到sum变量中
  • 循环第2次拆出5来,求出5的3次幂,再加到sum变量中
  • 循环第3次拆出3来,求出3的3次幂,再加到sum变量中
  • 最后比较sum变量的值与153的比较,相等则说明是自幂数
  • 但是这些代码,没有按照希望写出不带for/while语句的代码,所以再次修改如下
private IntPredicate isSelfPower1 = v -> {
    String str = String.valueOf(v);
    int len = str.length();
    int sum = Arrays.stream(str.split(""))
            .mapToInt(e -> Double.valueOf(Math.pow(Integer.valueOf(e), len)).intValue()).sum();
    return v == sum;
}
  • 这个代码意思是,先数字转换成字符串,求出其长度,再通过字符串,把每一个字符拆解出来,再转成数字,求幂值,最后求和
  • 通过这个代码优化,可以看出之前写的代码,把数字长度做为参数传进来,还把除数也传入,是完全没必要的
  • 这里面完全没有for/while语句了,很好啊!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值