더북(TheBook)

리듀스 단계

reducer() 함수에서는 토큰과 토큰의 위치 정보를 매개변수로 받아 파일별로 토큰이 사용된 횟수를 세서 반환한다.

func reducer(token string, positions []scanner.Position) map[string]int {
    result := make(map[string]int)
    for _, p := range positions {
        result[p.Filename] += 1
    }
    return result
}

반환 값은 map[string]int 타입으로 전달되는데, 파일 이름이 키가 되고 토큰이 사용된 횟수는 값이 된다.

최종 계산 결과(각 토큰이 파일별로 사용된 횟수) 타입인 summary 구조체를 정의해 보자. 실제 계산 결과는 내부 필드 m에 저장된다. 리듀스 단계를 여러 고루틴에서 병행으로 처리하는 경우 m 값을 보호해주기 위해 뮤텍스 mu 필드를 지정한다.

type summary struct {
    // 키: token
    // 값: map[string]int
    //           키: file path
    //           값: token count
    m map[string]map[string]int
           
    // 공유 데이터 m을 보호하기 위한 뮤텍스
    mu sync.Mutex
}

fmt.Print() 같은 기본 출력 함수로 출력될 문자열을 지정하기 위해 summaryString() 메서드를 추가해 보자. 문자열 연산을 빠르게 처리하기 위해 bytes.Buffer를 사용한다.

func (s summary) String() string {
    var buffer bytes.Buffer
   
    for token, value := range s.m {
        buffer.WriteString(fmt.Sprintf("Token: %s\n", token))
        total := 0
        for path, cnt := range value {
            if path == "" {
                continue
            }
            total += cnt
            buffer.WriteString(fmt.Sprintf("%8d %s ", cnt, path))
            buffer.WriteString("\n")
        }
        buffer.WriteString(fmt.Sprintf("Total: %d\n\n", total))
    }
    return buffer.String()
}

다음 함수는 리듀서를 실행한다.

func runReduce(tokenPositions intermediate) summary {
    s := summary{m: make(map[string]map[string]int)}
    for token, positions := range tokenPositions {
        s.mu.Lock() // m 값을 변경하는 부분(임계 영역)을 뮤텍스로 잠금
        s.m[token] = reducer(token, positions)
        s.mu.Unlock() // m 값 변경 완료 후 뮤텍스 잠금 해제
    }
    return s
}

전체 코드는 reduce.go를 참고하기 바란다.

신간 소식 구독하기
뉴스레터에 가입하시고 이메일로 신간 소식을 받아 보세요.