具有流的正整数的素因子分解

时间:2016-03-13 05:00:54

标签: java java-8 java-stream prime-factoring

我目前正在尝试将Java 8的Stream API合并到我的日常Java工具箱中。我正在尝试使用Streams来查找正整数的素因子,然后将每个因子存储在数组(或ArrayList)中,并将它们的多重性存储在并行数组中。或者,我正在尝试创建一个说明... FactorWithMultiplicity对象的流,或者甚至是Map,其中因子为关键,多重性为值。如果因子按升序排序,如果它甚至可以处理非常大的数字(例如,我敢说Long.MAX_VALUE),那就太好了。

目前,我的代码看起来像这样,但是,由于我是Streams的初学者,我确信有更快或更适合的方式来完成此任务。请使用Streams创建您的解决方案,但如果您知道某些非Stream解决方案更快,请随时指向该代码。

int num = getPositiveInt();
ArrayList<Integer> factors = new ArrayList<>();
ArrayList<Integer> multiplicities = new ArrayList<>();
boolean isPrime = IntStream.rangeClosed(2, num / 2)
    .reduce(num, (int temp, int factor) -> {
        int count = 0;
        while (temp % factor == 0) {
            temp /= factor;
            count++;
        }
        if (count > 0) {
            factors.add(factor);
            multiplicities.add(count);
        }
        return temp;
    }) > 1;

5 个答案:

答案 0 :(得分:4)

如果您特别需要基于流的解决方案,则可以使用递归方式对数字进行因子分析:

IntStream factors(int num) {
    return IntStream.range(2, num)
        .filter(x -> num % x == 0)
        .mapToObj(x -> IntStream.concat(IntStream.of(x), factors(num / x)))
        .findFirst()
        .orElse(IntStream.of(num));
}

然后您可以使用以下代码制作两个列表:

Map<Integer, Integer> f2m = factors(2, num).boxed()
        .collect(toMap(f -> f, f -> 1, Integer::sum));  // or groupingBy with summingInt(f->1), whichever you prefer

List<Integer> factors = new ArrayList<>(f2m.keySet());
List<Integer> multiplicities = factors.stream().map(f2m::get).collect(toList());

如果您希望从中获得更多性能,可以将最后找到的因素传递给factors方法并使用该方法而不是2

如果您想要考虑多头,那么这里有一些性能改进的版本:

static LongStream factors(long lastFactor, long num) {
    return LongStream.rangeClosed(lastFactor, (long) Math.sqrt(num))
            .filter(x -> num % x == 0)
            .mapToObj(x -> LongStream.concat(LongStream.of(x), factors(x, num / x)))
            .findFirst()
            .orElse(LongStream.of(num));
}

如果您希望结果按排序顺序排列,可以使用

SortedMap<Long, Integer> f2m = factors(2, num).boxed()
         .collect(toMap(f -> f, f -> 1, Integer::sum, TreeMap::new));

或者,保持Map不变,然后使用

List<Long> factors = f2m.keySet().stream().sorted().collect(toList());

答案 1 :(得分:1)

如果您想反复调用factorsOf,那么另一种变体很有用。 (我在某个地方偷了筛子的基本想法,修好了。)

这里的想法是使用素数作为流,过滤因子并确定它们的多样性,以创建建立结果的FactorTimes对象。

public class PrimeFactors  {
  private final int limit = 1_000_000;
  private BitSet sieve = new BitSet( limit+1 );
  public PrimeFactors(){
    sieve.set( 2, limit );
    long count = sieve.stream()
    .peek( x -> { if( (long)x*x < limit )
                    for( int i = x*x; i <= limit; i += x )
                      sieve.clear( i );
                })
    .count();
  }
  public FactorTimes[] factorsOf( int num ){
    FactorTimes[] fts = sieve.stream()
    .limit( num/2 )
    .filter( x -> num % x == 0 ) 
    .mapToObj( x -> { int n = 1;
                      int k = num/x;
                      while( k % x == 0 ){ k /= x; n++; }
                      return new  FactorTimes( x, n );
                    } )
    .toArray( FactorTimes[]::new );
    return fts;
  }
  public static void main( String[] args ){
    PrimeFactors pf = new PrimeFactors();
    for( FactorTimes ft: pf.factorsOf( 4504500 ) ){
      System.out.println( ft );
    }
  }
}

class FactorTimes {
  private int factor, multiplicity;
  public FactorTimes(int f, int m) {
    factor = f; multiplicity = m;
  }
  public int getFactor() { return factor; }
  public int getMultiplicity() { return multiplicity; }
  public String toString(){
    return multiplicity > 1 ? factor + "(" + multiplicity + ")"
                            : Integer.toString( factor ); }
}

答案 2 :(得分:1)

要生成素数因子,您需要跟踪几种状态。因此Streams不适合这项任务。

您可以做的是提供自己的Spliterator来创建IntStream。现在您可以生成数组或分组操作:

public static IntStream primeFactors(int n) {
  int characteristics = Spliterator.ORDERED | Spliterator.SORTED | Spliterator.IMMUTABLE | Spliterator.NONNULL;
  Spliterator.OfInt spliterator = new Spliterators.AbstractIntSpliterator(Long.MAX_VALUE, characteristics) {
    int val = n;
    int div = 2;

    @Override
    public boolean tryAdvance(IntConsumer action) {
      while (div <= val) {
        if (val % div == 0) {
          action.accept(div);
          val /= div;
          return true;
        }
        div += div == 2 ? 1 : 2;
      }
      return false;
    }

    @Override
    public Comparator<? super Integer> getComparator() {
      return null;
    }
  };
  return StreamSupport.intStream(spliterator, false);
}

并打电话给这样的话:

int n = 40500;
System.out.println(Arrays.toString(primeFactors(n).toArray()));
System.out.println(primeFactors(n).boxed().collect(
    Collectors.groupingBy(Function.identity(), Collectors.summingInt(i -> 1)))
);

你应该得到你想要的结果:

[2, 2, 3, 3, 3, 3, 5, 5, 5]
{2=2, 3=4, 5=3}

答案 3 :(得分:0)

当分解整数时,最有利可图的优化是尝试除数直到数字的平方根(包括,例如:尝试分解49)。此外,在检查2之后,您可以在事后检查只有奇数。

int num = getPositiveInt();
ArrayList<Integer> factors = new ArrayList<>();
ArrayList<Integer> multiplicities = new ArrayList<>();
int factor = 2;
int f_delta = 1;  // to increase by +1 only once (2 to 3) 
while ((factor*factor)<=num) {
  int count = 0;
  while (num % factor == 0) {
    num /= factor;
    count++;
  }  
  if (count > 0) {
    factors.add(factor);
    multiplicities.add(count);
  }
  factor += f_delta;
  f_delta = 2;
}

答案 4 :(得分:0)

在彻底调查之后,我发现与我在问题中发布的内容相比,这是速度的极大提升。唯一更快的是 @Misha 在更改factors功能后使用.rangeClosed(prevFactor, Math.sqrt(num))发布的更快的内容。但是, this other solution 是最快的解决方案...期间......但不使用Streams。

public static Map<Long, Integer> factorize(long num) { //NOW USING LONG
    Map<Long, Integer> factors = new LinkedHashMap<>(); //NOW USING MAP
    long lastRemainder = LongStream.rangeClosed(2, (long) Math.sqrt(num)) //NOW USING SQRT
            .filter(x -> (x== 2||x%2>0)&&(x==3||x%3>0)&&(x==5||x%5>0)) //ADDED THIS
            .reduce(num, (temp, factor) -> {
                if (factor <= temp / factor) { //ADDED THIS
                    int count = 0;
                    while (temp % factor == 0) {
                        temp /= factor;
                        count++;
                    }
                    if (count > 0)
                        factors.put(factor, count);
                }
                return temp;
            });
    if (lastRemainder != num && lastRemainder > 1) //ADDED THIS
        factors.put(lastRemainder, 1);
    return factors;
}