람다 표현식과 함수형 프로그래밍

2025. 3. 28. 10:09Programming Languages/C++

9.5 람다 표현식과 함수형 프로그래밍

9.5.1 람다 표현식 고급 기능

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <chrono>
#include <numeric>

class Timer {
private:
    std::chrono::high_resolution_clock::time_point start;
    std::string name;
    
public:
    Timer(const std::string& n) : name(n) {
        start = std::chrono::high_resolution_clock::now();
    }
    
    ~Timer() {
        auto end = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
        std::cout << name << " 실행 시간: " << duration.count() << " 마이크로초" << std::endl;
    }
};

int main() {
    // 기본 람다 표현식
    auto add = [](int a, int b) {
        return a + b;
    };
    
    std::cout << "3 + 4 = " << add(3, 4) << std::endl;
    
    // 변수 캡처 (Capturing Variables)
    int multiplier = 5;
    
    // 값으로 캡처
    auto multiplyByValue = [multiplier](int value) {
        return value * multiplier;
    };
    
    // 참조로 캡처
    auto multiplyByReference = [&multiplier](int value) {
        multiplier++;  // 외부 변수 수정 가능
        return value * multiplier;
    };
    
    std::cout << "5 * 3 = " << multiplyByValue(3) << std::endl;
    std::cout << "6 * 3 = " << multiplyByReference(3) << std::endl;  // multiplier는 이제 6
    std::cout << "multiplier = " << multiplier << std::endl;  // 7 출력
    
    // 모든 변수 캡처
    auto captureAll = [=](int value) {  // 모든 변수를 값으로 캡처
        return value * multiplier;
    };
    
    auto captureAllRef = [&](int value) {  // 모든 변수를 참조로 캡처
        multiplier++;
        return value * multiplier;
    };
    
    // 혼합 캡처
    auto mixedCapture = [=, &multiplier](int value) {  // multiplier만 참조로, 나머지는 값으로
        multiplier++;
        return value * multiplier;
    };
    
    // 초기 캡처 (C++14)
    auto counter = [count = 0]() mutable {
        return ++count;
    };
    
    std::cout << "카운터: " << counter() << std::endl;  // 1
    std::cout << "카운터: " << counter() << std::endl;  // 2
    
    // 제네릭 람다 (C++14)
    auto genericAdd = [](auto a, auto b) {
        return a + b;
    };
    
    std::cout << "정수 합: " << genericAdd(5, 3) << std::endl;
    std::cout << "실수 합: " << genericAdd(3.14, 2.71) << std::endl;
    std::cout << "문자열 합: " << genericAdd(std::string("Hello, "), "World!") << std::endl;
    
    // 성능 비교 - 람다 vs 함수 객체 vs 일반 함수
    const int ITERATIONS = 10000000;
    std::vector<int> numbers(ITERATIONS);
    std::iota(numbers.begin(), numbers.end(), 0);  // 0부터 시작하는 연속 값 채우기
    
    // 일반 함수
    auto isEvenFunc = [](int n) { return n % 2 == 0; };
    
    {
        Timer t("람다 표현식");
        int count = std::count_if(numbers.begin(), numbers.end(), isEvenFunc);
        std::cout << "짝수 개수: " << count << std::endl;
    }
    
    // 함수 객체
    struct IsEven {
        bool operator()(int n) const {
            return n % 2 == 0;
        }
    };
    
    {
        Timer t("함수 객체");
        int count = std::count_if(numbers.begin(), numbers.end(), IsEven());
        std::cout << "짝수 개수: " << count << std::endl;
    }
    
    // 함수 포인터
    bool isEven(int n) {
        return n % 2 == 0;
    }
    
    {
        Timer t("함수 포인터");
        int count = std::count_if(numbers.begin(), numbers.end(), isEven);
        std::cout << "짝수 개수: " << count << std::endl;
    }
    
    // 람다 표현식을 std::function에 저장
    std::function<int(int, int)> operation;
    
    char op;
    std::cout << "연산자 입력 (+, -, *, /): ";
    std::cin >> op;
    
    switch (op) {
        case '+': 
            operation = [](int a, int b) { return a + b; };
            break;
        case '-': 
            operation = [](int a, int b) { return a - b; };
            break;
        case '*': 
            operation = [](int a, int b) { return a * b; };
            break;
        case '/': 
            operation = [](int a, int b) { return b != 0 ? a / b : 0; };
            break;
        default:
            operation = [](int a, int b) { return a + b; };
    }
    
    std::cout << "10 " << op << " 5 = " << operation(10, 5) << std::endl;
    
    return 0;
}

9.5.2 함수형 프로그래밍 패턴

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <numeric>
#include <string>

// 함수형 프로그래밍 유틸리티
namespace fp {
    // 컨테이너의 모든 요소에 함수 적용 (map)
    template<typename Container, typename Function>
    auto map(const Container& container, Function func) {
        using ResultType = decltype(func(*container.begin()));
        std::vector<ResultType> result;
        result.reserve(container.size());
        
        std::transform(container.begin(), container.end(), 
                      std::back_inserter(result), func);
        
        return result;
    }
    
    // 조건을 만족하는 요소만 필터링 (filter)
    template<typename Container, typename Predicate>
    Container filter(const Container& container, Predicate pred) {
        Container result;
        
        std::copy_if(container.begin(), container.end(), 
                    std::back_inserter(result), pred);
        
        return result;
    }
    
    // 컨테이너 요소를 단일 값으로 축소 (reduce)
    template<typename Container, typename Function, typename T>
    T reduce(const Container& container, Function func, T initialValue) {
        return std::accumulate(container.begin(), container.end(), 
                              initialValue, func);
    }
    
    // 두 함수를 합성하여 새 함수 생성 (compose)
    template<typename F, typename G>
    auto compose(F f, G g) {
        return [=](auto x) {
            return f(g(x));
        };
    }
    
    // 부분 적용 함수 (curry)
    template<typename Function, typename FirstArg>
    auto curry(Function func, FirstArg first) {
        return [=](auto... args) {
            return func(first, args...);
        };
    }
}

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 1. 맵(Map) - 각 요소 변환
    auto squared = fp::map(numbers, [](int x) { return x * x; });
    
    std::cout << "제곱 결과: ";
    for (int num : squared) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    
    // 2. 필터(Filter) - 조건에 맞는 요소 선택
    auto evens = fp::filter(numbers, [](int x) { return x % 2 == 0; });
    
    std::cout << "짝수만: ";
    for (int num : evens) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    
    // 3. 리듀스(Reduce) - 단일 값으로 축소
    int sum = fp::reduce(numbers, [](int acc, int x) { return acc + x; }, 0);
    std::cout << "합계: " << sum << std::endl;
    
    // 맵-필터-리듀스 조합 (짝수만 제곱한 후 합계 계산)
    int result = fp::reduce(
        fp::map(
            fp::filter(numbers, [](int x) { return x % 2 == 0; }),
            [](int x) { return x * x; }
        ),
        [](int acc, int x) { return acc + x; },
        0
    );
    
    std::cout << "짝수 제곱의 합: " << result << std::endl;
    
    // 4. 함수 합성(Composition)
    auto double_it = [](int x) { return x * 2; };
    auto increment = [](int x) { return x + 1; };
    
    // double_it을 적용한 후 increment 적용
    auto double_then_increment = fp::compose(increment, double_it);
    
    // increment를 적용한 후 double_it 적용
    auto increment_then_double = fp::compose(double_it, increment);
    
    std::cout << "double_then_increment(5): " << double_then_increment(5) << std::endl;  // 11
    std::cout << "increment_then_double(5): " << increment_then_double(5) << std::endl;  // 12
    
    // 5. 커리(Curry)와 부분 적용
    auto add = [](int a, int b) { return a + b; };
    auto add5 = fp::curry(add, 5);  // 첫 번째 인자를 5로 고정
    
    std::cout << "add5(10): " << add5(10) << std::endl;  // 15
    
    // 커리된 함수로 맵 수행
    auto add5ToAll = fp::map(numbers, add5);
    
    std::cout << "모든 요소에 5 더하기: ";
    for (int num : add5ToAll) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    
    // 함수형 스타일로 데이터 처리 예제
    std::vector<std::string> names = {"Alice", "Bob", "Charlie", "Dave", "Eve", "Frank"};
    
    // 이름이 5글자 이상인 사람만 선택하여 대문자로 변환 후 이름 길이의 합계 계산
    int totalLength = fp::reduce(
        fp::map(
            fp::filter(names, [](const std::string& name) { return name.length() >= 5; }),
            [](const std::string& name) {
                std::string upper = name;
                std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
                return upper;
            }
        ),
        [](int acc, const std::string& name) { return acc + name.length(); },
        0
    );
    
    std::cout << "5글자 이상 이름의 총 길이: " << totalLength << std::endl;
    
    return 0;
}

9.6 정규 표현식(Regex) (C++11)

#include 
#include 
#include 
#include 

void printMatches(const std::string& text, const std::regex& regex) {
    std::smatch matches;
    std::string searchText = text;
    
    std::cout << "「" << text << "」에서 찾은 결과:" << std::endl;
    
    while (std::regex_search(searchText, matches, regex)) {
        std::cout << "  - " << matches[0] << std::endl;
        searchText = matches.suffix();
    }
    std::cout << std::endl;
}

int main() {
    // 기본 패턴 매칭
    std::string text = "The quick brown fox jumps over the lazy dog";
    std::regex wordRegex("\\b\\w{5}\\b");  // 정확히 5글자인 단어 찾기
    
    printMatches(text, wordRegex);
    
    // 이메일 주소 검증
    std::vector emails = {
        "user@example.com",
        "invalid.email",
        "another.user@gmail.com",
        "not_an_email",
        "name.surname@company.co.uk"
    };
    
    std::regex emailRegex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
    
    std::cout << "이메일 주소 유효성 검사:" << std::endl;
    for (const auto& email : emails) {
        bool isValid = std::regex_match(email, emailRegex);
        std::cout << email << ": " << (isValid ? "유효" : "유효하지 않음") << std::endl;
    }
    std::cout << std::endl;
    
    // 전화번호 형식 변환
    std::string phoneNumbers = "연락처: 010-1234-5678, 02-987-6543, 01087654321";
    std::regex phoneRegex("(\\d{2,3})[-]?(\\d{3,4})[-]?(\\d{4})");
    
    // 형식 변환 (일관된 형식으로)
    std::string formattedNumbers = std::regex_replace(
        phoneNumbers,
        phoneRegex,
        "$1-$2-$3"
    );
    
    std::cout << "원본 전화번호: " << phoneNumbers << std::endl;
    std::cout << "형식 변환 후: " << formattedNumbers << std::endl << std::endl;
    
    // 캡처 그룹 사용
    std::string htmlText = "
        안녕하세요
        이것은 HTML 텍스트입니다.
    ";
    std::regex tagRegex("<([a-z]+)>(.*?)");
    
    std::cout << "HTML 태그 분석:" << std::endl;
    
    std::sregex_iterator it(htmlText.begin(), htmlText.end(), tagRegex);
    std::sregex_iterator end;
    
    while (it != end) {
        std::smatch match = *it;
        std::cout << "태그: " << match[1] << ", 내용: " << match[2] << std::endl;
        ++it;
    }
    std::cout << std::endl;
    
    // HTML 태그 제거
    std::string plainText = std::regex_replace(htmlText, std::regex("<[^>]*>"), "");
    std::cout << "태그 제거 후 텍스트: " << plainText << std::endl << std::endl;
    
    // URL 파싱
    std::string url = "https://www.example.com:8080/path/to/resource?param1=value1¶m2=value2";
    std::regex urlRegex("(https?)://([^:/]+)(?::(\\d+))?(/[^?#]*)?(?:\\?([^#]*))?");
    
    std::smatch urlMatch;
    if (std::regex_match(url, urlMatch, urlRegex)) {
        std::cout << "URL 분석 결과:" << std::endl;
        std::cout << "  프로토콜: " << urlMatch[1] << std::endl;
        std::cout << "  호스트: " << urlMatch[2] << std::endl;
        std::cout << "  포트: " << (urlMatch[3].matched ? urlMatch[3] : "기본값") << std::endl;
        std::cout << "  경로: " << (urlMatch[4].matched ? urlMatch[4] : "/") << std::endl;
        std::cout << "  쿼리: " << (urlMatch[5].matched ? urlMatch[5] : "없음") << std::endl;
    }
    
    return 0;
}
 

 

'Programming Languages > C++' 카테고리의 다른 글

다중 스레딩과 동시성(C++11)  (0) 2025.03.28
스마트 포인터(Smart Pointers) (C++11)  (0) 2025.03.28
파일 입출력  (0) 2025.03.28
고급 주제  (0) 2025.03.28
챕터8. 실습 문제  (0) 2025.03.28