본문 바로가기

Go 언어 공부

[GO 마스터하기] 04-합성 타입 사용법

구조체

type aStructure struct {
    person string 
    height int
    weight int 
}

구조체 리터럴은 다음과 같이 정의할 수 있다.

두번째 방법은 모든 필드를 초기화 하지 않아도 된다.

p1 := aStructure{"fmt", 12, -2}
p1 := aStructure{weight:12, height: -2}
package main

import "fmt"

func main() {

    type XYZ struct {
        X int
        Y int
        Z int
    }
    var s1 XYZ
    fmt.Println(s1.Y, s1.Z)

    p1 := XYZ{23, 12, -2}
    p2 := XYZ{Z: 12, Y: 13}
    fmt.Println(p1)
    fmt.Println(p2)

    pSlice := [4]XYZ{}
    pSlice[2] = p1
    pSlice[0] = p2
    fmt.Println(pSlice)
    p2 = XYZ{1,2,3}
    fmt.Println(pSlice)
}
$ go run foo.go
0 0
{23 12 -2}
{0 13 12}
[{0 13 12} {0 0 0} {23 12 -2} {0 0 0}]
[{0 13 12} {0 0 0} {23 12 -2} {0 0 0}]

pSlice라는 구조체 배열을 생성하였다. 구조체 배열에 구조체를 할당하면 그 내용이 복사되기 때문에 구조체 배열에 담긴 오브젝트가 변하더라도 원래 구조체의 값은 아무런 영향을 받지 않은 것을 확인할 수 있다.

  • 구조체의 타입 동일성을 판단할 때는 구조체 타입을 정의할 때 나열한 필드의 순서가 중요하다. 다시 말해 서로 같은 필드로 구성된 구조체 두 개가 있을 때, 그 안에 필드가 나열된 순서가 일치하지 않으면 go는 두개가 서로 다른 객체라고 판단한다.

구조체에 대한 포인터

package main

type myStructure struct {
    Name string
    Surname string
    Height int32
}

func createStruct(n, s string, h int32) *myStructure {
    if h > 300 {
        h = 0 
    }
    return &myStructure{n, s, h}
}

createStruct()에서 적용한 기법을 활용하면 여러 가지 장점이 있다. 가령 입력한 값이 정확하고 유효한지 확인할 수 있다. 구조체 변수에 대해 초기화한 곳에서 처리하기 때문에 구조체 생성 시 문제가 발생할 때 어디서 일어났는지, 무엇 때문에 발생했는지 명확히 알 수 있다.

func retStructure(n, s string, h int32) myStructure {
    if h > 300 {
        h = 0
    }
    return myStructure{n, s, h}
}

위 기능은 포인터를 넘겨주냐 마느냐만 다를 뿐 기능은 같다ㅏ.

new 키워드 사용법

Go는 new 키워드를 이용해 새로운 오브젝트를 할당하는 기능을 지원한다. 반드시 기억해야할 중요한 사항이 있다. new가 리턴하는 값은 할당된 오브젝트에 대한 메모리 주소라는 점이다. 한 마디로 new는 포인터를 리턴한다. 또한 new구문으로 nil에 대한 포인터를 가진 슬라이스를 하나 생성할 수 있다.

p5 := new(aStructure)
sp := new([]aStructure)
  • new와 make의 가장 큰 차이점은 make로 생성한 변수는 정상적으로 초기화된 반면, new로 생성된 변수는 할당된 메모리 공간에 단지 0만 채운다는 점이다. 또한 make는 맵, 채널, 슬라이스에만 적용할 수 있으며 메모리 주소를 리턴하지 않는다. 다시 말해 make는 포인터를 리턴하지 않는다.

튜플

튜플은 크기가 유한한 순서 리스트이다. Go에서 튜플을 직접 지원하지 않는다.

package main

func retThree(x int) (int, int, int) {
    return 2 * x, x * x, -x
}

retThree함수는 세 개의 정수 값을 가진 튜플을 리턴한다.

func main() {
    fmt.Println(retThree(10))
    n1, n2, n3 := retThree(20)
    fmt.Println(n1, n2, n3)

    n1, n2 = n2, n1 
}

만약 튜플 리턴 값 중 관심 없는 값이 있으면 언더스코어를 적어주면 된다. 또한 swap 작업을 손 쉽게 처리할 수 있다.

정규 표현식과 패턴 매칭

패턴 매칭은 go에서 굉장히 핵심적인 역할을 담당하는 기법으로, 스트링에서 일정한 문자 집합을 찾기 위한 용도로 사용한다. 정규 표현식과 문법에 기반한 특수한 검색 패턴으로 표현한다.

정규 표현식에 관련된 몇 가지 이론

컴파일러는 코드에 나온 정규 표현식마다 finite automaton라 부르는 보다 일반화되 형태의 transition diagram을 기반으로 만든 인식기로 컴파일한다. finite automaton는 determiinistic일 수도 있고 아닐수 도있다. 여기서 아닌 것은 어떤 상태에서 주어진 입력값에 대해 전이될 수 있는 상태가 여러개 있다는 말이다. 또한 인식기란 스트링 x를 입력으로 받아서 그 x가 주어진 언어에 맞는 문장인지 판별하는 프로그램을 말한다.

문법이란 형식 언어에서 문장을 생성하는 데 적용되는 일련의 규칙이다. 이러한 생성 규칙은 언어에서 사용하는 알파벳을 이용하여 그 언어의 구문에 맞게 생성할 수 있는 유효한 스트링을 표현한다. 문법은 스트링의 의미나 문맥을 표현하지 않고, 오로지 스트링의 형태만 표현할 뿐이다. 여기서 중요한 것은 정규 표현식에서 가장 핵심적인 부분이 바로 문법이다.

간단한 예제

한 줄의 텍스트에서 특정한 열을 선택하는 방법을 살펴보자. 두 개의 커맨드라인 인수를 받는 프로그램을 살펴 보자. 첫 번째 인수는 원하는 열 번호고, 두 번째 인수는 처리할 텍스트 파일에 대한 경로다

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    arguments := os.Args
    if len(arguments) < 2 {
        fmt.Println("No args")
        os.Exit(1)
    }

    temp, err := strconv.Atoi(arguments[1])
    if err != nil {
        fmt.Println("Col val is not an integer:", temp)
        return
    }
    column := temp
    if column < 0 {
        fmt.Println("invalid col num")
        os.Exit(1)
    }

    for _, filename := range arguments[2:] {
        fmt.Println("\t\t", filename)
        f, err := os.Open(filename)
        if err != nil {
            fmt.Printf("err opening file %s\n", err)
            continue
        }
        defer f.Close()

텍스트 파일이 실제로 있는지 검사한다. 파일을 여는 작업은 os.Open 함수로 처리한다.

        r := bufio.NewReader(f)
        for {
            line, err := r.ReadString('\n')
            if err == io.EOF {
                break
            } else if err != nil {
                fmt.Printf("err reading file %s",err)
            }

bufio.ReadString()함수는 매개 변수로 지정한 내용을 처음 발견할 때까지 파일을 읽는다. 그래서 위 예제에서는 개행을 만날때까지 파일을 읽는다.

            data := strings.Fields(line)
            if len(data) >= column {
                fmt.Println(data[column-1])
            }
        }
    }
}

위 프로그램은 텍스트를 줄 단위로 쪼개서 원하는 열을 선택하는 것이다. 각

심화 예제

package main

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

func main() {
    arguments := os.Args
    if len(arguments) < 2 {
        fmt.Println("No args")
        os.Exit(1)
    }

    filename := arguments[1]
    f, err := os.Open(filename) 
    if err != nil {
        fmt.Printf("err opening file %s", err) 
        os.Exit(1)
    }
    defer f.Close()

    notAMatch := 0 
    r := bufio.NewReader(f) 
    for {
        line, err := r.ReadString('\n')
        if err == io.EOF {
            break
        } else if err != nil {
            fmt.Printf("error reading file %s", err) 
        }

파일을 한 줄씩 읽기 위해 입력된 파일을 열었다. 입력 파일이 프로그램에서 정의한 두 개의 정규 표현식 모두에 매칭되지 않으면 그 파일에 담긴 줄의 수를 notAMatch 변수에 저장한다.

r1 := regexp.MustCompile(`.*\[(\d\d\/\w+/\d\d\d\d:\d\d:\d\d:\d\d.*)\] .*`)
if r1.MatchString(line) {
    match := r1.FindStringSubmatch(line)
    d1, err := time.Parse("02/Jan/2006:15:04:05 -0700", match[1])
    if err == nil {
        newFormat := d1.Format(time.Stamp)
        fmt.Print(strings.Replace(line, match[1], newFormat, 1))
    } else {
        notAMatch++
    }
    continue
}

regexp.MustCompile() 함수는 regexp.Compile()함수와 비슷하지만, 표현식을 파싱할 수 없으면 뻗어버린다.(패닉). 정규 표현식을 소괄호로 감싸면 매칭된 내용을 나중에 사용할 수 있다. 여기는 FindStringSubmatch을 사용해 매칭된 값만 가져온다.

        r2 := regexp.MustCompile(`.*\[(\w+\-\d\d-\d\d:\d\d:\d\d:\d\d.*)\] .*`)
        if r2.MatchString(line) {
            match := r2.FindStringSubmatch(line)
            d1, err := time.Parse("Jan-02-06:15:04:05 -0700", match[1])
            if err == nil {
                newFormat := d1.Format(time.Stamp)
                fmt.Print(strings.Replace(line, match[1], newFormat, 1))
            } else {
                notAMatch++
            }
            continue
        }
    }
    fmt.Println(notAMatch, "lines did not match!")
}

IPv4 주소 매칭하기

Ipv4 주소, 즉 IP주소는 네 부분의 8비트 바이너리 숫자로 구성된다. 각 부분마다 0부터 255사이의 값을 가진다.

Ip주소를 매칭하는 방법을 살펴보자.

package main

import (
    "bufio"
    "fmt"
    "io"
    "net"
    "os"
    "path/filepath"
    "regexp"
)

func findIP(input string) string {
    partIP := "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
    grammar := partIP + "\\." + partIP + "\\." + partIP + "\\." + partIP
    matchMe := regexp.MustCompile(grammar)
    return matchMe.FindString(input)
}

여기서 IPv4 주소를 쉽게 찾도록 정규 표현식 하나를 정의했다.

PartIP에 정의한 정규 표현식은 IP주소의 네 부분을 매칭하는 데 사용된다. 올바른 IPv4 주소라면 각 부분에 나온 숫자가 다음과 같아야한다. 첫 번째 경우는 25로 시작하고 0,1,2,3,4,5 중 하나로 끝나는 것이다. 8비트 바이너리 숫자에서 가장 큰 값은 이렇게 구성이 된다. 두번 째 경우에는 2 뒤에 0,1,2,3,4가 나오고 0,1,2,3,4,5,6,7,8,9로 끝나는 것이다. ... 생략

grammar 변수는 우리가 찾으려는 숫자가 네 부분으로 구성된다고 표현한다. 각 부분은 반드시 partIP에 매칭되어야 한다.

func main() {
    arguments := os.Args
    if len(arguments) < 2 {
        fmt.Printf("usage: %s logFile\n", filepath.Base(arguments[0]))
        os.Exit(1)
    }

    for _, filename := range arguments[1:] {
        f, err := os.Open(filename)
        if err != nil {
            fmt.Printf("error opening file %s\n", err)
            os.Exit(-1)
        }
        defer f.Close()

        r := bufio.NewReader(f)
        for {
            line, err := r.ReadString('\n')
            if err == io.EOF {
                break
            } else if err != nil {
                fmt.Printf("error reading file %s", err)
                break
            }

bufio.readString()을 이용하여 입력 파일을 한 줄씩 읽는다.

            ip := findIP(line)
            trial := net.ParseIP(ip)
            if trial.To4() == nil {
                continue
            } else {
                fmt.Println(ip)
            }
        }
    }
}

입력된 텍스 파일의 각 줄마다 findIP() 함수를 호출한다. net.ParseIP()함수는 유요한 IPv4 주소를 가지고 있는지 다시 확인한다.

스트링

엄밀히 말해 스트링은 합성 타입은 아니지만, Go 함수 중에서 스트링을 지원하는 것들이 굉장히 많다. Go에서 스트링은 포인터가 아닌 값이다. 또한 기본적으로 UTF-8 스트링을 지원하기 때문에 유니코드 문자를 출력하기 위해 다른 기법은 필요하지 않다. 하지만 문자, 룬, 바이트 사이에 차이가 존재하고, 스트링과 스트링 리터럴 사이에도 다른점이 있다.

Go 언어의 스트링은 읽기 전용인 바이트 슬라이스로서 어떠한 타입의 바이트도 담을 수 있고 길이에도 제한이 없다.

스트링 리터럴을 새로 정의하는 방법은 다음과 같다

const sLiteral = "hihi"

스트링 변수를 정의하는 방법은 다음과 같다

s2 := "하이"

스트링 변수나 스트링 리터럴의 길이는 len()함수로 알아낼 수 있다.

package main

import "fmt"

func main() {
    const sLiteral = "\x99\x42\x32\x55\x50\x35\x23\x50\x29\x9c"
    fmt.Println(sLiteral)
    fmt.Printf("x : %x\n", sLiteral)
    fmt.Printf("sLiteral length : %d\n", len(sLiteral))

\xAB 형태의 기호는 각각 sLiteral을 구성하는 문자 하나를 표현한다. 따라서 len(sLiteral)을 호출하면 sLiteral을 구성하는 문자의 개수를 리턴한다. fmt.Printf()에 %x라 적으면 \xAB라는 기호에서 AB에 해당하는 부분을 리턴한다.

    for i := 0; i < len(sLiteral); i++ {
            fmt.Printf("%x ", sLiteral[i])
    }
    fmt.Println() 

    fmt.Printf("q : %q\n", sLiteral)
    fmt.Printf("+q : %+q\n", sLiteral)
    fmt.Printf(" x : % x\n", sLiteral)

    fmt.Printf("s : As a string : %s", sLiteral)

이 코드를 보면 스트링 리터럴을 마치 슬라이스인 것처럼 접근한다. %q로 적으면 go 문법에 따라 해석하지 않은(esacpae)한 원래 모양대로 출력한다. %+q를 사용시 아스키 문자만 출력한다

% x를 지정하면 출력된 바이트 사이에 공백을 추가한다.

    s2 := "€£³"
    for x, y := range s2 {
        fmt.Printf("%#U starts at byte position %d\n", y, x)
    }
    fmt.Printf("s2 length : %d\n", len(s2))

세 개의 유니코드 문자로 구성된 s2를 정의한다. %#U를 지정하면 U+0058포맷으로 출력한다. 유니코드 문자가 담긴 스트링에 range로 각 유니코드 문자를 하나씩 처리할 수 있다.

len(s2)를 실행한 결과는 좀 놀라운데, s2 변수에 유니코드 문자가 있기 때문에 바이트 단위로 표현한 s2의 크기는 실제로 들어 있는 문자의 개수보다 크다.

    /const s3 = "ab12AB"
    fmt.Println("s3: ", s3) 
    fmt.Printf("x : % x\n", s3) 

    fmt.Printf("s3 length : %d\n", len(s3)) 

    for i := 0; i < len(s3); i++ {
        fmt.Printf("%x ", s3[i])
    }
    fmt.Println() 
}

룬이란

룬은 int32에 해당하는 go 언어에서 정식으로 제공하는 타입으로 유니코드 포인트를 표현하는데 주로 사용한다. 유니코드 포인트란 하나의 유니코드 문자를 표현하는 숫자 값을 의미한다.

  • 스트링은 룬의 묶음으로 볼 수 있다

룬 리터럴은 작은 따옴표로 묶은 문자 하나를 의미한다. 룬 리터럴을 룬 상수라고 볼 수도 있다. 내부 처리 방식을 보면, 룬 리터럴 하나는 유니코드 코드 포인트 하나에 대응된다.

package main

import "fmt"

func main() {
    const r1 = '€'
    fmt.Println("(int32) r1 :", r1)
    fmt.Printf("(HEX) r1: %x\n", r1)
    fmt.Printf("(string) r1: %s\n", r1)
    fmt.Printf("(as a character) r1 : %c\n", r1)
    fmt.Println("A string is a collection of runes:", []byte("Mihalis"))
    aString := []byte("Mihalis")
    for x, y := range aString {
        fmt.Println(x, y)
        fmt.Printf("Char: %c\n", aString[x])
    }
    fmt.Printf("%s\n", aString)
}

먼저 r1이라는 이름의 룬 리터럴을 정의한다.

바이트 슬라이스는 일종의 룬 묶음이다. 이러한 슬라이스를 fmt.Println으로 출력하면 원하는 형태로 리턴하지 않을 수 있다. 룬을 문자로 변환하려면 fmt.Printf문에서 %c를 지정해야 한다. 그리고 바이트 슬라이스를 스트링으로 출력하려면 fmt.Printf에 %s를 지정해야한다.

$ go run foo.go 
(int32) r1 : 8364
(HEX) r1: 20ac
(string) r1: %!s(int32=8364)
(as a character) r1 : €
A string is a collection of runes: [77 105 104 97 108 105 115]
0 77
Char: M
1 105
Char: i
2 104
Char: h
3 97
Char: a
4 108
Char: l
5 105
Char: i
6 115
Char: s
Mihalis

unicode 패키지

unicode 패키지는 여러 가지 유용한 함수를 제공한다. 그 중 하나로 unicode.IsPrint()라는 함수가 있는데, 이를 이용하면 스트링을 구성하는 룬 중에서 출력할 수 있는 부분을 알아낼 수 있다.

package main

import (
    "fmt"
    "unicode"
)

func main() {
    const sL = "\x99\x00ab\x50\x00\x23\x50\x29\x9c"
    for i := 0; i < len(sL); i++ {
        if unicode.IsPrint(rune(sL[i])) {
            fmt.Printf("%c\n", sL[i])
        } else {
            fmt.Printf("Cant")
        }
    }
}

지저분한 작업은 unicode.IsPrint() 함수가 대신 처리해줄 수 있다.

strings 패키지

strings 패키지를 이용하면 utf-8 스트링을 조작할 수 있다.

package main

import (
    "fmt"
    s "strings"
    "unicode"
)

var f = fmt.Printf
  • 이런식으로 패키지에 대한 alias를 지정할 수 있다.
func main() {
    upper := s.ToUpper("Hello there!")
    f("To Upper : %s\n", upper)
    f("To Lower: %s\n", s.ToLower("HELL"))
    f("%s\n", s.Title("tHis wiLL be A title!"))

    f("EqualFold : %v\n", s.EqualFold("Mihalis", "MIHAlis"))
    f("EqualFold : %v\n", s.EqualFold("Mihalis", "MIHAli"))

strings.EqualFold() 함수를 이용하면 서로 다른 문자로 구성된 스트링이 서로 같은지 알아낼 수 있다.

    f("Prefix: %v\n", s.HasPrefix("Mihalis", "Mi"))
    f("Prefix: %v\n", s.HasPrefix("Mihalis", "mi"))
    f("Suffix: %v\n", s.HasSuffix("Mihalis", "is"))
    f("Suffix: %v\n", s.HasSuffix("Mihalis", "IS"))

    f("Index: %v\n", s.Index("Mihalis", "ha"))
    f("Index: %v\n", s.Index("Mihalis", "Ha"))
    f("Count: %v\n", s.Count("Mihalis", "i"))
    f("Count: %v\n", s.Count("Mihalis", "I"))
    f("Repeat: %s\n", s.Repeat("ab", 5))

    f("TrimSpace: %s\n", s.TrimSpace(" \tThis is a line. \n"))
    f("TrimLeft: %s", s.TrimLeft(" \tThis is a\t line. \n", "\n\t "))
    f("TrimRight: %s\n", s.TrimRight(" \tThis is a\t line. \n", "\n\t "))

strings.Count()는 첫 번째 매개변수로 전달된 스트링에서 두번째 매개변수가 나타난 회수를 중첩되지 않게 센다.

    f("Compare: %v\n", s.Compare("Mihalis", "MIHALIS"))
    f("Compare: %v\n", s.Compare("Mihalis", "Mihalis"))
    f("Compare: %v\n", s.Compare("MIHALIS", "MIHalis"))

    f("Fields: %v\n", s.Fields("This is a string!"))
    f("Fields: %v\n", s.Fields("Thisis\na\tstring!"))

    f("%s\n", s.Split("abcd efg", ""))

compare함수는 두개 의 스트링을 사전 항목 나열 순으로 비교한다. 따라서 두 같이 같으면 0을 아니면 -1이나 1을 리턴한다.

마지막으로 strings.Fields() 함수는 스트링 매개변수를 공백 문자를 기준으로 쪼갠다.

    f("%s\n", s.Replace("abcd efg", "", "_", -1))
    f("%s\n", s.Replace("abcd efg", "", "_", 4))
    f("%s\n", s.Replace("abcd efg", "", "_", 2))

    lines := []string{"Line 1", "Line 2", "Line 3"}
    f("Join: %s\n", s.Join(lines, "+++"))

    f("SplitAfter: %s\n", s.SplitAfter("123++432++", "++"))

    trimFunction := func(c rune) bool {
        return !unicode.IsLetter(c)
    }
    f("TrimFunc: %s\n", s.TrimFunc("123 abc ABC \t .", trimFunction))
}

strings.Replace()는 네 개의 매개변수를 받는다.

  • 처리하려는 원본 스트링
  • 기존 스트링에서 검색해서 세 번째 매개변수로 대체할 스트링
  • 마지막으로는 교체할 최대 횟수, 음수 시에 무제한

trim함수는 스트링을 구성하는 룬에서 원하는 부분을 걸러낼 수 있으며, strings.TrimFunc()함수의 두번째 인수로 사용할 수 있다.

strings.SplitAfter는 첫번째 매개변수로 지정한 스트링을 여러 서브스트링으로 나누며, 두번째 매개변수로 지정한 스트링 나온 바로 뒤에 자른다

$ go run foo.go 
To Upper: HELLO THERE!
To Lower: hello there
THis WiLL Be A Title!
EqualFold: true
EqualFold: false
Prefix: true
Prefix: false
Suffix: true
Suffix: false
Index: 2
Index: -1
Count: 2
Count: 0
Repeat: ababababab
TrimSpace: This is a line.
TrimLeft: This is a      line. 
TrimRight:      This is a        line.
Compare: 1
Compare: 0
Compare: -1
Fields: [This is a string!]
Fields: [Thisis a string!]
[a b c d   e f g]
_a_b_c_d_ _e_f_g_
_a_b_c_d efg
_a_bcd efg
Join: Line 1+++Line 2+++Line 3
SplitAfter: [123++ 432++ ]
TrimFunc: abc ABC

switch 문

switch의 case조건에서 정규 표현식을 사용한다.

switch asString {
case "1":
    fmt.Println("One!")
case "0":
    fmt.Println("Zero!")
default:
    fmt.Println("Do not care")
}

switch문을 작성할 때, 앞에 나온 case 조건에 걸리지 않은 나머지 경우에 대한 case 문을 항상 작성하는 것이 바람직하다.

package main

import (
    "fmt"
    "os"
    "regexp"
    "strconv"
)

func main() {

    arguments := os.Args
    if len(arguments) < 2 {
        fmt.Println("usage: switch number.")
        os.Exit(1)
    }

    number, err := strconv.Atoi(arguments[1])
    if err != nil {
        fmt.Println("This value is not an intger:", number)
    } else {
        switch {
        case number < 0:
            fmt.Println("Less than zero")
        case number > 0:
            fmt.Println("Bigger than zero")
        default:
            fmt.Println("Zero!")
        }
    }
    asString := arguments[1]
    switch asString {
    case "5":
        fmt.Println("Five")
    case "0":
        fmt.Println("Zero")
    default:
        fmt.Println("Do not care")
    }
    var negative = regexp.MustCompile(`-`)
    var floatingPoint = regexp.MustCompile(`\d?\.\d`)
    var email = regexp.MustCompile(`^[^@]+@[^@.]+\.[^@.]+`)

    switch {
    case negative.MatchString(asString):
        fmt.Println("Negative number")
    case floatingPoint.MatchString(asString):
        fmt.Println("Floating point!")
    case email.MatchString(asString):
        fmt.Println("It is an email!")
        fallthrough
    default:
        fmt.Println("Something else!")
    }
    var aType error = nil
    switch aType.(type) {
    case nil:
        fmt.Println("It is nil interface!")
    default:
        fmt.Println("Not nil interface!")
    }
}

switch 블록에 regexp.MatchString() 함수를 이용하며 세 정규 표현식을 모두 사용하고 있다. 또한 타입에 따라 분기하도록 switch문을 작성할 수 도 있다.

$ go run foo.go 5
Bigger than zero
Five
Something else!
It is nil interface!

Go 언어로 키/값 스토어 만들기

키 값 스토어의 핵심 아이디어는 꽤 단순하다. 질의에 빠르게 응답하면 된다.

  1. 새로운 원소 추가하기
  2. 스토어에 들어 있던 기존 원소를 키로 지정하여 삭제하기
  3. 특정한 키에 대한 값을 스토어에서 검색하기
  4. 기존 키를 변경하기
package main 

type myElement struct {
    Name string 
    Surname string 
    Id string 
}

var DATA = make(map[string]myElement)

키-값 스토어는 Go에서 제공하는 기본 맵으로 구성한다.


func ADD(k string, n myElement) bool {
    if k == "" {
        return false
    }    
    if LOOKUP(k) == nil {
        DATA[k] = n 
        return true 
    }
    return false 
}

func DELETE(k string) bool {
    if LOOKUP(k) == nil {
        delete(DATA, k)
        return true 
    }
    return false
}

기존에 키를 추가하면 기존 키-값을 변경하는 것이 아닌 에러 메시지를 출력한다.

func LOOKUP(k string) *myElement {
    _, ok := DATA[k]
    if ok {
        n := DATA[k]
        return &n
    } else {
        return nil
    }
}

func CHANGE(k string, n myElement) bool {
    DATA[k] = n
    return true
}

func PRINT() {
    for k, d := range DATA {
        fmt.Printf("key: %s value: %v\n", k, d) 
    }
}

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        text := scanner.Text()
        text = strings.TrimSpace(text)
        tokens := strings.Fields(text)

        switch len(tokens) {
        case 0:
            continue
        case 1:
            tokens = append(tokens, "")
            tokens = append(tokens, "")
            tokens = append(tokens, "")
            tokens = append(tokens, "")
        case 2:
            tokens = append(tokens, "")
            tokens = append(tokens, "")
            tokens = append(tokens, "")
        case 3:
            tokens = append(tokens, "")
            tokens = append(tokens, "")
        case 4:
            tokens = append(tokens, "")
        }

        switch tokens[0] {
        case "PRINT":
            PRINT()
        case "STOP":
            return
        case "DELETE":
            if !DELETE(tokens[1]) {
                fmt.Println("Delete operation failed!")
            }
        case "ADD":
            n := myElement{tokens[2], tokens[3], tokens[4]}
            if !ADD(tokens[1], n) {
                fmt.Println("Add operation failed!")
            }
        case "LOOKUP":
            n := LOOKUP(tokens[1])
            if n != nil {
                fmt.Printf("%v\n", *n)
            }
        case "CHANGE":
            n := myElement{tokens[2], tokens[3], tokens[4]}
            if !CHANGE(tokens[1], n) {
                fmt.Println("Update operation failed!")
            }
        default:
            fmt.Println("Unknown command – please try again!")
        }
    }
}