티스토리 뷰

  • 함수형 코드를 작성하기 위해서는, 함수형 언어인 스칼라나 클로저로의 전환이 필요한 것이 아니라 문제에 접근하는 방식의 전환이 필요하다.

 

명령형 처리와 함수형 처리

 

  • 명령형 처리
    • 전통적인 프로그래밍 방식
    • 문제를 명령형 루프내에서 수행
    • 예제 : 전형적인 회사 프로세스
      • 어떤 이름 목록에서 한 글자로 된 이름을 제외한 모든 이름을 대문자화해서 쉼표로 연결한 문자열을 구한다.
      • 한 글짜짜리 이름을 필터
      • 목록에 남아있는 이름을 대문자로 변형
      • 이 목록의 하나의 문자열로 변환
package com.promiseflower.functionalthinking.ch2;

import android.os.Build;

import java.util.List;
import java.util.stream.Collectors;

import androidx.annotation.RequiresApi;

public class TheCompanyProcess {
    public String cleanNames(List listOfNames) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < listOfNames.size(); i++) {
            if (listOfNames.get(i).length() > 1) {
                result.append(capitalizeString(listOfNames.get(i))).append(",");
            }
        }
        return result.substring(0, result.length() - 1).toString();
    }

    public String capitalizeString(String s) {
        return s.substring(0, 1).toUpperCase() + s.substring(1, s.length());
    }
}

 

  • 함수형 처리
    • 함수형 처리는 언어에서 제공하는 함수를 이용하여 서술형으로 문제를 처리
    • 필터, 변형, 변환등의 논리적인 분류를 저수준의 작업이라 칭하여 문제를 처리하는 방식보다는 세부적인 로직에 집중할 수 있다.
    • 개발자는 고계함수에 매개변수로 주어지는 함수를 이용하여 저수준의 작업을 커스터마이징할 수 있다.
    • 예제 : 전형적인 회사 프로세스
// 예제 : 전형적인 회사 프로세스에 대한 의사코드
listOfEmps
-> filter(x.length > 1)
-> transform(x.capitalize)
-> convert(x + "," + y)

// 예제 : 전형적인 회사 프로세스에 대한 코틀린으로 작성한 함수형 처리
class TheCompanyProcessWithKotlin {
    fun cleanNames(listOfNames: List): String {
        return listOfNames
            .filter { it.length > 1 }
            .map { it.capitalize() }
            .reduce { acc, s -> "$acc,$s" }
    }
}
// capitalize() : 최초문자가 대문자인 카피형을 반환

 

  • filter() 함수를 통해 이름목록에서 한 글자 이름을 필터해서 제거

    • 목록 내의 null이 있을 경우를 대비에서 filter() 함수에서 null 체크를 할 수 있다.
  • map() 함수를 통해 주어진 코드 블록에서 최초문자가 대문자인 카피형을 반환하는 capitalize()를 통해 값을 반환

  • reduce() 함수를 통해 각 요소를 주어진 코드 블록에 규칙에 따라 결합한다.

  • 함수형 사고로의 전환은 어떤 경우에 세부적인 구현에 뛰어들지 않고 이런 고수준 추상 개념을 적용할지를 배우는 것

    • 고수준의 추상적 사고로 얻는 이점
      • 문제의 공통점을 고려하여 다른 방식으로 분류하기를 권장
      • 런타임이 최적화를 잘할 수 있도록 해줌
        • 어떤 경우에는 결과가 변하지 않는 한, 작업 순서를 바꾸면 더 능률적이 된다.
      • 개발자가 엔진 세부사항에 깊이 파묻힐 경우 불가능한 해답을 가능하게 한다.
        • 자바 코드를 여러 스레드에 나누어 처리하게끔 할 때, 개발자가 저수준의 반복과정을 제어해야 하기 때문에, 스레드 관련 코드가 문제해결 코드로 섞일 수 밖에 없다.
        • 이러한 내용을 간단한 병렬처리로 대체할 수 있다.
// 예제 : 코틀린에서의 병렬 처리
    fun cleanNames(listOfNames: List): String {
        return listOfNames
            .parallelStream()
            .filter { it.length > 1 }
            .map { it.capitalize() }
            .reduce { acc, s -> "$acc,$s" }
            .toString()
    }
    
// 예제 : 자바8 버전에서의 분산 처리
    public String cleanNames(List listOfNames) {
        if (listOfNames == null) return "";
        return listOfNames
                .parallelStream()
                .filter(n -> n.length() > 1)
                .map(e -> capitalizeString(e))
                .collect(Collectors.joining(","))
                .toString()
    }

 

 

사례 연구 : 자연수의 분류

 

  • 자연수 분류법
    • 완전수 : 자신의 모든 양의 약수의 합
    • 양의 약수 : 곱해서 대상의 값이 나오는 두 수 중 자신을 제외한 수
    • 예) 6은 약수가 1,2,3,6 이고, 6 = 1+2+3이므로 완전수
      • 완전수 : 진약수의 합 = 수
      • 과잉수 : 진약수의 합 > 수
      • 부족수 : 진약수의 합 < 수

 

// 예제 : 자바를 사용한 자연수 분류
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class ImpNumberClassifierSimple {
    private int _number;
    private Map<Integer, Integer> _cache;

    public ImpNumberClassifierSimple(int targetNumber) {
        _number = targetNumber;
        _cache = new HashMap<>();
    }

    public boolean isFactor(int potential) {
        return _number % potential == 0;
    }

    public Set getFactors() {
        Set factors = new HashSet<>();
        factors.add(1);
        factors.add(_number);
        for (int i = 2; i < _number; i++) {
            if (isFactor(i)) {
                factors.add(i);
            }
        }
        return factors;
    }

    public int aliquotSum() {
        if (_cache.get(_number) == null) {
            int sum = 0;
            for (int i : getFactors()) {
                sum += i;
                _cache.put(_number, sum - _number);
            }
        }
        return _cache.get(_number);
    }

    public boolean isPerfact() {
        // 완전수
        return aliquotSum() == _number;
    }

    public boolean isAbundant() {
        // 과잉수
        return aliquotSum() > _number;
    }

    public boolean isDeficient() {
        // 부족수
        return aliquotSum() < _number;
    }

}

 

// 예제 : 자바8의 람다, 고계함수와 스트림을 통한 함수형 자연수 분류기 에제
import java.util.stream.IntStream;

import static java.util.stream.IntStream.range;

public class NumberClassifier {

    public static IntStream factorsOf(int number) {
        return range(1, number + 1).filter(potential -> number % potential == 0);
    }

    public static int aliquotSum(int number) {
        return factorsOf(number).sum() - number;
    }

    public static boolean isPerfect(int number) {
        // 완전수
        return aliquotSum(number) == number;
    }

    public static boolean isAbundant(int number) {
        // 과잉수
        return aliquotSum(number) > number;
    }

    public static boolean isDeficient(int number) {
        // 부족수
        return aliquotSum(number) < number;
    }
    
}

 

// 예제 : 코틀린을 사용한 자연수 분류기

object NumberClassifier {
    
    private fun factorsOf(number : Int) : List {
        return (1 .. number + 1).filter { potential -> number % potential == 0 }
    }

    fun aliquotSum(number: Int): Int {
        return factorsOf(number).sum() - number
    }

    fun isPerfect(number: Int): Boolean {
        // 완전수
        return aliquotSum(number) == number
    }

    fun isAbundant(number: Int): Boolean {
        // 과잉수
        return aliquotSum(number) > number
    }

    fun isDeficient(number: Int): Boolean {
        // 부족수
        return aliquotSum(number) < number
    }

}

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함