[The Go Programming Language] 1장 튜토리얼 - 1.3 중복 줄 찾기

in kr-dev •  6 years ago 

modolee_logo
안녕하세요. 개발자 모도리입니다.
The Go Programming Language 라는 책으로 Go를 공부하고 있으며, 해당 책의 내용을 요약 정리해서 올리려고 합니다. 저는 번역본을 구매해서 공부하고 있습니다.

지난 게시물

튜토리얼을 너무 자세히 분석하고 있는 것 같아서, 이제부터는 뒤에서 다루지 않는 내용들만 집고 넘어가겠습니다.


1장 튜토리얼

1.3 중복 줄 찾기

  • 대부분의 파일 복사, 인쇄, 검색, 정렬, 카운트 등을 수행하는 프로그램은 구조가 유사합니다.
  • 입력을 순회하고, 각 원소를 계산하며, 그때 그때 또는 마지막에 결과를 생성합니다.
  • 유닉스 uniq 명령과 비슷한 프로그램을 작성해 봅니다.

중복 줄 찾기 구현1

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    counts := make(map[string]int)
    input := bufio.NewScanner(os.Stdin)
    for input.Scan() {
        counts[input.Text()]++
    }
    // NOTE: input.Err()에서의 잠재적 오류는 무시합니다.
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

예제코드 [ch1/dup1.go]

실행결과
$ go run ch1/dup1.go
아래 값을 직접 입력해야 됩니다.
hello
hello
hello
hi
hi
bye
bye
seeya
control + d (윈도우는 control + z) 로 입력 종료

3 hello
2 hi
2 bye

D 또는 Z가 출력될 수 있는데... 이건 입력 종료 때 입력 된 키가 겹쳐서 보이는 것입니다.

  • if문 (line 17)
    • 조건 절 주위에는 for문과 마찬가지로 괄호를 사용하지 않습니다.
  • map 데이터 타입 (line 10, 13)
    • key, value 형태로 데이터를 저장합니다.
    • 예제코드 [ch1/dup1.go]에서는 keystring, valueint 입니다.
    • 저장, 추출, 맵 안에 있는 특정 원소의 유무 검사를 상수 시간에 수행합니다.
    • 각 줄을 읽을 때 마다 읽은 줄(string)을 map의 key로 사용하고 key에 해당하는 값을 증가 시킵니다.
    counts[input.Text()]++
    
    // 위와 동일한 역할을 수행한다.
    line := input.Text()
    counts[line] = counts[line] + 1
    
    • range의 범위로 map을 사용하였을 경우 index, index의 원소 값이 아니라, key, value 쌍을 반환한다.
    • 4.3절에서 자세히 다룹니다.
  • bufio 패키지 (line 11, 13)
    • 입력과 출력을 효율적이고 편리하게 도와주는 패키지입니다.
    • Scanner 타입은 입력을 읽고 줄이나 단어 단위로 나눌 때 사용합니다.
    • input := bufio.NewScanner(os.Stdin) 표준 입력을 읽습니다.
    • input.Scan()을 호출 할 때 마다 다음 줄을 읽고 맨 끝의 개행문자를 제거합니다.
    • input.Text()를 호출하여 결과를 얻을 수 있습니다.
  • fmt.Printf 함수 (line 18)
    • C나 그 외의 언어의 printf와 마찬가지로 포매팅한 결과를 출력합니다.

    fmt.Printf 포매팅 옵션
    %d : 10진 정수
    %x, %o, %b : 16진, 8진, 2진 정수
    %f, %g, %e : 부동소수점 수
    %t : Boolean (true or false)
    %c : Rune 문자 (유니코드 문자열)
    %s : 문자열
    %q : 따옴표로 묶인 문자열 "abc" 또는 Rune 'c'
    %v : 원래 형태의 값
    %T : 값의 타입
    %% : % 기호 (연산자 아님)

중복 줄 찾기 구현2

  • 표준 입력을 읽거나 파일명의 목록을 받아 각각 os.Open으로 열고 처리합니다.
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    counts := make(map[string]int)
    files := os.Args[1:]
    if len(files) == 0 {
        countLines(os.Stdin, counts)
    } else {
        for _, arg := range files {
            f, err := os.Open(arg)
            if err != nil {
                fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
                continue
            }
            countLines(f, counts)
            f.Close()
        }
    }
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

func countLines(f *os.File, counts map[string]int) {
    input := bufio.NewScanner(f)
    for input.Scan() {
        counts[input.Text()]++
    }
    // NOTE: input.Err()에서의 잠재적 오류는 무시합니다.
}

예제코드 [ch1/dup2.go]

hello
hi
hi
hello
hello
bye
bye
seeya

입력 파일 [ch1/input/words]

실행결과
$ go run ch1/dup2.go ch1/input/words
3 hello
2 hi
2 bye
$ go run ch1/dup2.go ch1/input/no_file
dup2: open ch1/input/no_file: no such file or directory

  • 파일 열기, 닫기 (line 16, 22)
    • os.Open함수는 두 값을 반환합니다.
      1. 열린 파일(*os.File) : 다음에 Scanner에서 읽을 때 사용합니다.
      2. 내장 된 error 타입의 값 : 값이 nil과 같으면 파일이 성공적으로 열린 것입니다.
    • 파일이 성공적으로 열리지 않은 경우에는 Fprintf를 이용해서 에러 메세지를 출력합니다.
    • 파일을 다 읽고 끝에 도달했다면, Close를 이용해서 파일을 닫고 할당 된 모든 리소스를 해제합니다.

중복 줄 찾기 구현3

  • 입력 데이터 전체를 메모리로 읽어 들인 다음 한 번에 모든 줄을 분리하고 줄 단위로 처리합니다.
package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "strings"
)

func main() {
    counts := make(map[string]int)
    for _, filename := range os.Args[1:] {
        data, err := ioutil.ReadFile(filename)
        if err != nil {
            fmt.Fprintf(os.Stderr, "dup3: %v\n", err)
            continue
        }
        for _, line := range strings.Split(string(data), "\n") {
            counts[line]++
        }
    }
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

예제코드 [ch1/dup3.go]

  • 파일의 모든 내용 읽기 (line 13)
    • ioutil.ReadFile 함수는 파일 이름을 받아서 파일 내용을 byte의 데이터와 에러 타입 쌍을 반환합니다.
    • string 으로 사용하기 위해 형 변환을 해줘야 합니다.
  • 문자열 분리 (line 18)
    • strings.Split 함수에 분리를 원하는 문자열과 구분자를 넘기면 구분자로 구분된 문자열 슬라이스를 반환합니다.
Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order: