[Golang] Linked List 자료구조

Golang은 (고랭지 배추가 생각나..) 기본적으로 데이터 컨테이너로 배열(Array), 슬라이스(Slice), 맵(Map)을 지원합니다. 이 세개의 데이터 컨테이너는 별도의 라이브러리를 import하지 않아도 사용할 수 있는 goland의 기본 요소입니다. 여기에 추가적으로 Linked List 자료구조는 별도의 라이브러를 import 하여 사용할 수 있는데요. 간단히 아래의 예제 코드를 통해 다른 언어를 통해 링크드 리스트를 접해본 개발자라면 쉽게 이해할 수 있을 것입니다.

package main

import (
    "container/list"
    "fmt"
)

func main() {
    ll := list.New()

    ll.PushBack("A")
    ll.PushBack(100)
    ll.PushBack(true)
    ll.PushFront("B")
    ll.PushFront(200)

    for e := ll.Front(); e != nil; e = e.Next() {
        fmt.Printf("[%T] %v\n", e.Value, e.Value)
    }

    fmt.Println("-------------")

    for e := ll.Back(); e != nil; e = e.Prev() {
        fmt.Printf("[%T] %v\n", e.Value, e.Value)
    }
}

링크드리스트 객체는 list.New() 함수 호출을 통해 생성할 수 있습니다. 데이터를 뒷 부분부터 추가(코드 11번~13번)할 수도 있고, 시작 부분에 추가(코드 14번~15번)할 수도 있습니다. 그리고 코드17번~19번처럼 링크드리스트의 시작부터 끝부분까지 순회할 수 있습니다. 이와 반대 방향으로 순회하는 코드는 23번~25번입니다. 실행 결과는 아래와 같습니다.

[int] 200
[string] B
[string] A
[int] 100
[bool] true
-------------
[bool] true
[int] 100
[string] A
[string] B
[int] 200

golang의 링크드 리스트는 배열이나 슬라이스, 맵과는 다르게 지정된 타입의 값만을 추가할 수 없고 모든 타입에 대한 값을 추가한다는 점에 주의해야 합니다.

[Golang] 함수 실행 시간 측정

golang에는 defer라는 예약어가 있습니다. defer로 지정된 함수 호출은, defer를 호출한 함수의 종료시에 호출되도록 보장한다는 것입니다. 이를 응용해서 함수의 실행 시간을 측정할 수 있는 범용성 있는 함수를 다음 코드처럼 만들 수 있습니다.

func ElapsedTime(tag string, msg string) func() {
    if msg != "" {
        log.Printf("[%s] %s", tag, msg)
    }

    start := time.Now()
    return func() { log.Printf("[%s] Elipsed Time: %s", tag, time.Since(start)) }
}

ElapsedTime 함수는 defer 문과 함께 사용되어야 의미가 있는데요. 이 함수를 호출하면 함수를 반환하게 되는데, 이 반환된 함수가 바로 defer 문을 통해 최종적으로 호출될 함수입니다. 이 함수를 테스트하기 위해 아래처럼 코드를 작성해 보겠습니다.

package main

import (
    "log"
    "time"
)

func bigSlowOperation() int {
    defer ElapsedTime("bigSlowOperation", "start")()

    time.Sleep(10 * time.Second) // 10초 정도 소요되는 연산에 해당하는 코드라 가정

    return 0
}

func ElapsedTime(tag string, msg string) func() {
    // 생략
}

func main() {
    bigSlowOperation()
}

크고 느린 연산를 호출하는 함수인 bigSlowOperation 함수 내부를 보면 defer 문으로 ElapsedTime 함수를 호출하여 반환된 함수를 호출하고 있습니다. 이를 통해 bigSlowOperation 호출이 끝나면 bigSlowOperation의 실행에 소요된 시간을 표시하게 되는데요. 그 결과는 아래와 같습니다.

2016/10/01 22:28:07 [bigSlowOperation] start
2016/10/01 22:28:17 [bigSlowOperation] Elipsed Time: 10.0007115s

[Golang] 명령줄에 대한 플래그 얻기(Command-Line Flag)

Go에서 만들어진 실행 파일을 실행할 때, 명령줄에 입력한 플래그 값을 쉽게 얻기 위해 제공하는 flag 패키지 사용에 대한 코드를 정리해 봅니다. 아래의 코드는 플래그로써 word, numb, fork에 값을 지정하고자 하는 경우입니다. word는 문자열, numb는 정수형, fork는 블린형으로 지정합니다.

package main

import (
    "flag"
    "fmt"
)

func main() {
    wordPtr := flag.String("word", "foo", "a string")
    numPtr := flag.Int("numb", 42, "an int")
    boolPtr := flag.Bool("fork", false, "a bool")

    flag.Parse()

    fmt.Println("word: ", *wordPtr)
    fmt.Println("numb: ", *numPtr)
    fmt.Println("fork: ", *boolPtr)
    fmt.Println("tail: ", flag.Args())
}

위의 프로그램을 컴파일해 실행 파일로 cmd_flag.exe가 만들어졌다고 할 때, 앞서 언급한 각 플래그에 값을 지정하여 실행한 예는 아래와 같습니다.

d:\__Working__\tstGo\cmd_flags>cmd_flags.exe -word=Hello -numb=23 -fork=true
word:  Hello
numb:  23
fork:  true
tail:  []

d:\__Working__\tstGo\cmd_flags>cmd_flags.exe -word="Hello World" -numb=23 -fork=true
word:  Hello World
numb:  23
fork:  true
tail:  []

d:\__Working__\tstGo\cmd_flags>cmd_flags.exe -word="Hello World" -numb=23 -fork=true a1 a2 a3
word:  Hello World
numb:  23
fork:  true
tail:  [a1 a2 a3]

d:\__Working__\tstGo\cmd_flags>cmd_flags.exe -word="Hello World" -numb=a -fork=true
invalid value "a" for flag -numb: strconv.ParseInt: parsing "a": invalid syntax
Usage of cmd_flags.exe:
  -fork
        a bool
  -numb int
        an int (default 42)
  -word string
        a string (default "foo")

d:\__Working__\tstGo\cmd_flags>

[Golang] 클로져, 가변인자 예제코드

closures

상태값을 가지는 함수를 반환하는 함수에 대한 예입니다. (출처: https://gobyexample.com)

package main

import (
    "fmt"
)

func intSeq() func() int {
    i := 0

    return func() int {
        i += 1
        return i
    }
}

func main() {
    nextInt1 := intSeq()
    nextInt2 := intSeq()

    fmt.Println(nextInt1())
    fmt.Println(nextInt1())
    fmt.Println(nextInt1())
    fmt.Println(nextInt1())

    fmt.Println(nextInt2())
    fmt.Println(nextInt2())
    fmt.Println(nextInt2())
    fmt.Println(nextInt2())
}

결과는 아래와 같습니다.

1
2
3
4
1
2
3
4

가변인자(Variadic)

함수에 임의 개수의 인자를 넘길 수 있는 가변인자를 갖는 함수에 대한 예입니다. 특히 네번째 sum 함수의 호출시 인자를 슬라이스로 전달 가능합니다. (출처: https://gobyexample.com)

package main

import (
    "fmt"
)

func sum(nums ...int) {
    fmt.Print(nums, " ")
    total := 0

    for _, num := range nums {
        total += num
    }

    fmt.Println(total)
}

func main() {
    sum(1, 2)
    sum(1, 2, 3)
    sum(1, 2, 3, 4)

    nums := []int{1, 2, 3, 4}
    sum(nums...)
}

결과는 아래와 같습니다.

[1 2] 3
[1 2 3] 6
[1 2 3 4] 10
[1 2 3 4] 10