Skip to content

Instantly share code, notes, and snippets.

@deep-diver
Last active May 21, 2021 00:47
Show Gist options
  • Save deep-diver/4f9ae525ae5469c0d9b9b845246ac395 to your computer and use it in GitHub Desktop.
Save deep-diver/4f9ae525ae5469c0d9b9b845246ac395 to your computer and use it in GitHub Desktop.
단어 검색 프로그램
package main
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
)
type LineInfo struct {
lineNo int
line string
}
type FindInfo struct {
filename string
lines []LineInfo
}
func main() {
if len(os.Args) < 3 {
fmt.Println("2개 이상의 실행 인수가 필요합니다.")
return
}
word := os.Args[1]
files := os.Args[2:]
findInfos := []FindInfo{}
for _, path := range files {
/*
... 의 의미
`variadic` 이라고 함. https://golang.org/ref/spec#Passing_arguments_to_..._parameters
```go
func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", "Anna", "Eileen")
```
> within Greeting, who will have the value nil in the first call, and []string{"Joe", "Anna", "Eileen"} in the second.
```go
s := []string{"James", "Jasmine"}
Greeting("goodbye:", s...)
```
> within Greeting, who will have the same value as s with the same underlying array.
이 중 두 번째 케이스가, 만든 예제에 적용되는 사항임.
정확히는 `append` 가 첫 번째 케이스로서 정의되었고, 예제 코드에스는 두 번째 케이스로서 `append` 를 사용하는 부분임.
파이썬에서 `*` 와 유사함.
*/
findInfos = append(findInfos, FindWordInAllFiles(word, path)...)
}
for _, findInfo := range findInfos {
fmt.Println(findInfo.filename)
fmt.Println("---------------------")
for _, lineInfo := range findInfo.lines {
fmt.Println("\t", lineInfo.lineNo, "\t", lineInfo.line)
}
fmt.Println("---------------------")
fmt.Println()
}
}
func GetFileList(path string) ([]string, error) {
return filepath.Glob(path)
}
func FindWordInAllFiles(word, path string) []FindInfo {
findInfos := []FindInfo{}
filelist, err := GetFileList(path)
if err != nil {
fmt.Println("파일을 찾을 수 없습니다. err:", err)
return findInfos
}
for _, filename := range filelist {
findInfos = append(findInfos, FindWordInFiles(word, filename))
}
return findInfos
}
func FindWordInFiles(word, filename string) FindInfo {
findInfo := FindInfo{filename, []LineInfo{}}
file, err := os.Open(filename)
if err != nil {
fmt.Println("파일을 찾을 수 없습니다. ", filename)
return findInfo
}
defer file.Close()
lineNo := 1
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
// func Contains(s, substr string) bool
if strings.Contains(line, word) {
findInfo.lines = append(findInfo.lines, LineInfo{lineNo, line})
}
lineNo++
}
return findInfo
}
func FindWordInAllFiles(word, path string) []FindInfo {
  findInfos := []FindInfo{}

- filelist, err := GetFileList(path)
+ filelist, err := filepath.Glob(path)
  ....
  
+  ch := make(chan FindInfo)
+  cnt := len(filelist)
+  recvCnt := 0

  for _, filename := range filelist {
-  findInfos = append(findInfos, FindWordInFiles(word, filename))
+  go FindWordInFile(word, filename, ch)
  }

+ for findInfo := range ch {
+   findInfos = append(findInfos, findInfo)
+   recvCnt++
+   if recvCnt == cnt {
+     break
+   }
+ }
+ return findInfos
+}

- func FindWordInFiles(word, filename string) FindInfo {
+ func FindWordInFile(word, filename string, ch chan FindInfo) {
  findInfo := FindInfo{filename, []LineInfo{}}
  file, err := os.Open(filename)
  ...
 
- return findInfo
+ ch <- findInfo
}

찾아볼 것, 궁금한 것 목록

대충 쥐뿔도 모르는 상태에서 어떻게 돌아가는지 생각해보기.

  • chan FindInfo 라는 놈을 만듬(make)
  • filelist 에 들어있는 파일 개수 만큼 FindWordInFile 함수로 가서(go) 실행하라고 명령
  • FindWordInFile 이 어쩌구 저쩌구 실행된 끝에 원하는 결괏값을 ch 에 넣어줌
  • 넣어진 결괏값을 끄집어내서 findInfos 에 병합
  1. GetFileList(path) 를 filepath.Glob(path) 로 대체. 근데 GetFileList(path) 구현은 정확히 filepath.Glob(path) 한 줄이라서 동일함. 오타 같기도

  2. ch chan FindInfo 에서 chan FindInfo 는 하나의 키워드처럼 쓰임 (구조체마다 다르겠지만, 쨋든 한 덩어리). 그냥 chan 으로 받을 수 없나? C에서 void 같은 역할을 하는놈은 없나?

  • 현재까지 찾아본 바로는 못 찾았음
  1. ch <- findInfo 라고 적으면 느낌적으로 대입하는거 같은데, 또 호출된 곳으로 돌아가보면 range ch 를 하고 있음. 배열같기도 하고... chan 의 개념을 잠깐 들여다볼 필요가 있을듯
  • 고루틴(goroutine)은 가벼운 쓰레드쯤 되는듯. 채널은 고루틴들을 연결해주는 파이프쯤 되는듯. main 고루틴 에서 도는 FindWordInAllFiles 함수와 go 키워드로 만든 별도의 FindWordInFiles 함수에 대한 고루틴들 사이를 연결하는 용도로 채널을 도입
  • v, ok := <- ch 해도 값을 얻을 수 있음 for { v, ok := <- ch if ok == false { ... }

대충 이딴식으로도 만들 수 있을듯. 다만 range 를 쓰면, 어떤일이 일어나는지 보자. receives values from the channel repeatedly until it is closed. (https://tour.golang.org/concurrency/4)

채널이 닫히기 전까지 계속 반복적으로 값을 끄집어낸다고 한다. 그렇다면 값이 없는데 채널은 열려있다면? 대기타나???

  • 한 가지 흥미로운 사실은 채널이 역할을 다 했을때 반드시 닫아(close) 해줄 필요는 없다는것. 더 이상 값이 없음을 반드시 통지해야만 한다면, 그 때는 close 로 채널을 닫아준다. 그 외에는 돈케어
  1. for findInfo := range ch 라고 범위 동안 반복해라 해놓고, if recvCnt == cnt 로 탈출시킴. 그냥 for { ... if recvCnt == cnt { ... } ... } 해도 되는것 아님? 왜 range ch 가 필요한가? 이 방식으로만 ch 에 담긴 findInfo 를 끄집어내야하나?
  • 대충 보아하니 range 를 쓰는 이유는, 채널은 일종의 큐 같이 값 목록을 가질 수 있는 구조인듯. 그리고 range 를 통한 이터레이션은 큐에 값이 존재할 때만 발동하는듯.
  • 큐니 뭐니 하기때문에, 사이즈 지정도 가능함 예. queue := make(chan string, 2)

https://gobyexample.com/range-over-channels

package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
if len(os.Args) < 3 {
fmt.Println("2개 이상의 실행 인수가 필요합니다. ex) ex26.1 word filepath")
return
}
word := os.Args[1]
files := os.Args[2:]
PrintAllFiles(files)
}
func PrintAllFiles(files []string) {
// range 키워드의 역할
// - 배열 인덱스와, 배열 아이템을 반환
// - for path := range files 처럼 첫 번째를 무시한, 축약 버전도 사용 가능
for _, path := range files {
filelist, err := GetFileList(path)
if err != nil {
fmt.Println("파일을 찾을 수 없습니다. err:", err)
return
}
fmt.Println("찾으려는 파일 리스트")
for _, name := range filelist {
fmt.Println(name)
}
}
}
func GetFileList(path string) ([]string, error) {
/*
filepath.Glob 함수의 역할: 주어진 패턴 문자열에 부합하는 모든 파일 목록을 반환함
> Glob returns the names of all files matching pattern or nil if there is no matching file. The syntax of patterns is the same as in Match.
Match 와 같은 패턴 문법으로 패턴 지정이 가능하다고 함 (아래 참조, https://golang.org/pkg/path/filepath/#Match)
pattern:
{ term }
term:
'*' matches any sequence of non-Separator characters
'?' matches any single non-Separator character
'[' [ '^' ] { character-range } ']'
character class (must be non-empty)
c matches character c (c != '*', '?', '\\', '[')
'\\' c matches character c
character-range:
c matches character c (c != '\\', '-', ']')
'\\' c matches character c
lo '-' hi matches character c for lo <= c <= hi
> Glob ignores file system errors such as I/O errors reading directories. The only possible returned error is ErrBadPattern, when pattern is malformed.
발생 가능한 에러는 ErrBadPattern 뿐임. 패턴 문자열이 패턴 문법을 어길 경우 발생. I/O 관련 에러는 잡아낼 수 없음.
*/
return filepath.Glob(path)
}
package main
import (
"fmt"
"os"
"bufio"
)
func main() {
PrintFile("hamlet.txt")
}
func PrintFile(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("파일을 찾을 수 없습니다. ", filename)
return
}
defer file.Close()
/*
Scan() 함수의 정의는 아래와 같음.
- func (s *Scanner) Scan() bool
이번에 알게된 것인데 Scanner 라는 구조체에 속한 메서드 라는 의미쯤 되는듯. ( * 이 붙은 이유도 찾아봐야함)
- 특정 구조체의 메서드를 만든다 라는 말에서,
특정 구조체 <- 대상을 Receiver 라고 부르는듯.
그래서 여기에 * 를 붙이면 Pointer Receiver 라고 표현됨. (https://tour.golang.org/methods/4)
* 이 붙고 안 붙고의 주요 차이는, Receiver 구조체가 가진 멤버 변수 값이 변하거나, 변할 수 없거나를 결정함. 일종의 immutable?
쨋든, Scan() 메서드는 bool 을 반환함. 근데 배열이 아님, 그냥 단일 bool 값임. 공식문서 설명에 따르면...
It returns false when the scan stops, either by reaching the end of the input or an error.
따라서 계속 읽을 수 있는 한, 계속해서 참 값을 반환한다는 뜻임.
* 특히 Scan() 은 읽을것이 있다를 판단하는 메서드고
* 실제 콘텐츠를 읽어오는 메서드는 Text() 라고 별도로 존재함
Text() 로 내용을 읽으면, 1. 오프셋이 이동하고, 2. Scan() 은 그 위치에서 다시 읽을 수 있는지 판단하고를 반복하는듯.
*/
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment