스트림 (2)

💻 Programming/Java

[Java] 스트림을 이용한 소수 구하기

요즘 이펙티브 자바 Third 에디션 책을 동네 도서관에서 대여해서 읽고있습니다.

스트림 병렬화에 대한 글을 읽다가 소수구하는 로직을 스트림으로 작성한 부분을 보게되었습니다.

소수구하기 로직은 이직준비 할 때나 다시 들여다볼만한거라 오래전에 for-loop 로 구현해본 기억만 있다보니 신선하게 느껴졌네요.

아직도 많은 블로그들에서 for-loop를 이용한 방법들만 많이 소개하고 있기도 하고해서 포스팅 주제로 삼아봤습니다. 

개인적인 기록도 할겸...

책을 읽고 새로 알게된 부분은 스트림에 대한 부분도 있지만 이미 BigInteger 클래스에 isProbablePrime 이란 메서드가 있었다는 겁니다..

 

아래는 특정 숫자 n 까지 소수가 몇 개인지를 출력하고, 그 목록도 출력하는 것을 테스트한 코드입니다.

public static void main(String[] args) {
    int n = (int) Math.pow(10, 8);
    // n 까지의 수 중에 소수의 개수 출력하기
    long cnt = LongStream.rangeClosed(2, n)
    	    .parallel()
            .mapToObj(BigInteger::valueOf)
            .filter(i -> i.isProbablePrime(50))
            .count();
    System.out.println(cnt);

    // n 까지의 수 중에 소수 목록 출력하기
    System.out.println(LongStream.rangeClosed(2, n)
            .mapToObj(BigInteger::valueOf)
            .filter(i -> i.isProbablePrime(50))
            .collect(Collectors.toList()));
}

LongStream을 이용하여 숫자의 범위를 정하고 mapToObj 를 이용하여 BigInteger로 변형한뒤 BigInteger.isProbablePrime(int certainty) 메서드를 필터로 전달하여 소수를 구하고 있습니다.

BigInteger.isProbablePrime(int certainty) 메서드는 소수를 구하기위해 내부적으로 밀러-라빈(Miller-Rabin) 테스트와 루카스-레머(Lucas-Lehmer) 테스트를 사용하고 있고요. 여기서 사용된 밀러라빈 테스트는 DSA(Digital Signiture Algorithm) 스펙 (NIST FIPS 186-2)을 기반으로 했다고 합니다. 이 스펙은 파일로 첨부하니 관심있으신 분들은 다운받아 보셔도 될 것 같네요. 

 

fips186-2.pdf
0.35MB

 

참고로 .parallel() 은 스트림에서 사용할 때 매우 주의를 요하는 기능입니다. 책에서는 이렇게 얘기합니다. Stream.iterate 를 데이터 소스로 이용하거나 중간 연산으로 limit 을 사용하는 스트림 파이프라인에서는 parallel을 이용한 성능개선을 기대할 수 없으며 오히려 안좋아질 수도 있다고 말이죠. 실제로 운영환경에서 저것 때문에 이슈가 발생한 적도 있었습니다. 그냥 무한루프에 빠진것처럼 쓰레드 하나가 먹통이 되어버리더군요. 스트림에서 병렬연산에 적합한 적은 reduce, min, max, count, sum 등의 연산이며, collect처럼 가변축소를 수행하는 메서드는 병렬연산에 적합하지 않다고 합니다. 참고하시기 바랍니다.

자바 스트림을 이용한 유용한 변환 방법

해당 게시글은 java의 stream을 이용하여 데이터를 쉽게 가공할 수 있는 방법을 정리 해놓은 문서로

유용한 케이스를 발견할 때마다 계속 업데이트할 예정입니다.

 

 

1. String배열을 int배열로 변환하기

int[] intArray = Stream.of(stringArray).mapToInt(Integer::parseInt).toArray();

2. Integer배열을 String배열로 변환하기

String[] stringArray = Stream.of(arr).map(String::valueOf).toArray(String[]::new);

3. String배열을 String리스트로 변환하기

String[] stringArray = new String[]{"!", "@", "#"};
List<String> stringList = Stream.of(stringArray).collect(Collectors.toList());

4. String리스트를 String배열로 변환하기

List<String> stringList = new ArrayList<>(Arrays.asList("!","@","#"));
String[] stringArray = stringList.toArray(new String[0]);

5. stream().map()을 이용한 객체(object) 변환하기

// 변환 전 클래스
@Getter
class Soldier {
  String name;
  int age;

  public Soldier(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

// 변환 대상 클래스
@Getter
class King {
  String name;
  int age;
  public King(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

// 샘플 Soldier stream 생성
Stream<Soldier> s = Stream.of(new Soldier("A", 18), new Soldier("B", 22), new Soldier("C", 19));

// map()을 이용하여 변환하기
List<King> k = s.map(soldier -> new King(soldier.getName(), soldier.getAge()))
		.collect(Collectors.toList());

 

6. filter()를 이용하여 필터링하기

// 변환 전 클래스
@Getter
class Soldier {
  String name;
  int age;

  public Soldier(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

// 변환 대상 클래스
@Getter
class King {
  String name;
  int age;
  public King(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

// 샘플 Soldier stream 생성
Stream<Soldier> s = Stream.of(new Soldier("Arthur", 18), new Soldier("Belisarius", 22), new Soldier("Caesar", 19));

// filter()를 이용하여 Arthur만 골라서 map()을 이용하여 King으로 변환하기
List<King> k = s.filter(soldier -> "Arthur".equals(soldier.getName()))
    .map(soldier -> new King(soldier.getName(), soldier.getAge()))
    .collect(Collectors.toList());

 

7. min() / max() 를 이용하여 comparator 기준 최소 / 최대 값을 갖는 객체 추출하기

    @Getter
    @Setter
    class Datapoint {
        private Double maximum;
    }
    
    public void sortStream() {
        List<Datapoint> datapoints = new ArrayList<>();
        int max = 10;
        datapoints.add(new Datapoint().withMaximum(Math.random() * max));
        datapoints.add(new Datapoint().withMaximum(Math.random() * max));
        datapoints.add(new Datapoint().withMaximum(Math.random() * max));
        log.debug("data points: {}", datapoints);

        // 객체 목록 중 특정 필드(maximum)값을 기준으로 최대값을 갖고 있는 객체 추출 (stream.max() 이용)
        Datapoint datapointMax = datapoints.stream()
                .max(Comparator.comparing(Datapoint::getMaximum))
                .orElse(new Datapoint().withMaximum(0D));
        log.debug("data point MAX: {}", datapointMax);

        // 객체 목록 중 특정 필드(maximum)값을 기준으로 최소값을 갖고 있는 객체 추출 (stream.min() 이용)
        Datapoint datapointMin = datapoints.stream()
                .min(Comparator.comparing(Datapoint::getMaximum))
                .orElse(new Datapoint().withMaximum(0D));
        log.debug("data point MIN: {}", datapointMin);
    }
    
    
    
    // 실행 로그
    data points: [{Maximum: 5.471390124016474,}, {Maximum: 2.7559360370945916,}, {Maximum: 0.5778257233234019,}]
    data point MAX: {Maximum: 5.471390124016474,}
    data point MIN: {Maximum: 0.5778257233234019,}