
안녕하세요. 개발자 모도리입니다.
**[The Go Programming Language](https://www.gopl.io/)** 라는 책으로 Go를 공부하고 있으며, 해당 책의 내용을 요약 정리해서 올리려고 합니다. 저는 [번역본](http://www.aladin.co.kr/shop/wproduct.aspx?ItemId=76703559)을 구매해서 공부하고 있습니다.
### 지난 게시물
* [[Go] Mac에서 Atom으로 Go 개발 환경 구축하기](https://steemit.com/kr-dev/@modolee/go-mac-atom-go)
* [[The Go Programming Language] 1장 튜토리얼 - 1.1 Hello, World](https://steemit.com/kr-dev/@modolee/the-go-programming-language-1-hello-world)
* [[The Go Programming Language] 1장 튜토리얼 - 1.2 커맨드라인 인수](https://steemit.com/kr-dev/@modolee/the-go-programming-language-1-1-2)
튜토리얼을 너무 자세히 분석하고 있는 것 같아서, 이제부터는 뒤에서 다루지 않는 내용들만 집고 넘어가겠습니다.
---
# 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]*에서는 **key**는 `string`, **value**는 `int` 입니다.
* 저장, 추출, 맵 안에 있는 특정 원소의 유무 검사를 상수 시간에 수행합니다.
* 각 줄을 읽을 때 마다 읽은 줄(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` 함수에 분리를 원하는 문자열과 구분자를 넘기면 구분자로 구분된 문자열 슬라이스를 반환합니다.