22 minute read

개요

Go 1.21부터 추가된 slices 패키지는 제네릭 기반의 슬라이스 조작 함수들을 제공합니다.

주요 특징:

  • 제네릭 기반: 모든 타입의 슬라이스 지원
  • 표준 라이브러리: import “slices”
  • 검색: Binary search, Index, Contains
  • 정렬: Sort, SortFunc, Stable sort
  • 수정: Insert, Delete, Replace, Compact
  • 비교: Equal, Compare (사전순)
  • 유틸리티: Clone, Reverse, Grow, Clip, Chunk

기본 순회

1. All - 인덱스와 값

import "slices"

func main() {
    numbers := []int{10, 20, 30}
    
    // 인덱스와 값 모두 순회
    for i, v := range slices.All(numbers) {
        fmt.Printf("Index: %d, Value: %d\n", i, v)
    }
    // Index: 0, Value: 10
    // Index: 1, Value: 20
    // Index: 2, Value: 30
}

2. Values - 값만

func main() {
    words := []string{"hello", "world"}
    
    // 값만 순회 (인덱스 무시)
    for word := range slices.Values(words) {
        fmt.Println(word)
    }
    // hello
    // world
}

3. Backward - 역순

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    
    // 역순으로 순회
    for i, v := range slices.Backward(numbers) {
        fmt.Printf("%d: %d\n", i, v)
    }
    // 4: 5
    // 3: 4
    // 2: 3
    // 1: 2
    // 0: 1
}

4. Collect - Iterator를 슬라이스로

func main() {
    // Iterator 생성
    seq := slices.Values([]int{1, 2, 3, 4, 5})
    
    // 슬라이스로 변환
    result := slices.Collect(seq)
    fmt.Println(result)  // [1 2 3 4 5]
}

검색 함수

1. Contains - 포함 여부

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    
    fmt.Println(slices.Contains(numbers, 3))  // true
    fmt.Println(slices.Contains(numbers, 10)) // false
    
    words := []string{"apple", "banana", "cherry"}
    fmt.Println(slices.Contains(words, "banana")) // true
}

2. ContainsFunc - 조건 검색

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    
    // 짝수가 있는지
    hasEven := slices.ContainsFunc(numbers, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println(hasEven)  // true
    
    // 10보다 큰 수가 있는지
    hasLarge := slices.ContainsFunc(numbers, func(n int) bool {
        return n > 10
    })
    fmt.Println(hasLarge)  // false
}

3. Index - 위치 찾기

func main() {
    words := []string{"go", "rust", "python", "java"}
    
    fmt.Println(slices.Index(words, "python"))  // 2
    fmt.Println(slices.Index(words, "c++"))     // -1 (없음)
    
    // 첫 번째 일치만 반환
    numbers := []int{1, 2, 3, 2, 1}
    fmt.Println(slices.Index(numbers, 2))  // 1
}

4. IndexFunc - 조건으로 찾기

func main() {
    numbers := []int{1, 3, 5, 8, 9}
    
    // 첫 번째 짝수의 인덱스
    idx := slices.IndexFunc(numbers, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println(idx)  // 3 (값 8의 위치)
    
    // 10보다 큰 첫 번째 수
    idx = slices.IndexFunc(numbers, func(n int) bool {
        return n > 10
    })
    fmt.Println(idx)  // -1 (없음)
}

5. BinarySearch - 이진 검색

func main() {
    // 정렬된 슬라이스 필요
    numbers := []int{1, 3, 5, 7, 9, 11, 13}
    
    // 값, 존재 여부 반환
    idx, found := slices.BinarySearch(numbers, 7)
    fmt.Printf("Index: %d, Found: %t\n", idx, found)
    // Index: 3, Found: true
    
    // 없는 경우: 삽입할 위치 반환
    idx, found = slices.BinarySearch(numbers, 8)
    fmt.Printf("Index: %d, Found: %t\n", idx, found)
    // Index: 4, Found: false
}

6. BinarySearchFunc - 커스텀 비교

type Person struct {
    Name string
    Age  int
}

func main() {
    people := []Person{
        {"Alice", 25},
        {"Bob", 30},
        {"Carol", 35},
    }
    
    // 나이로 검색
    idx, found := slices.BinarySearchFunc(people, Person{Age: 30},
        func(a, b Person) int {
            return cmp.Compare(a.Age, b.Age)
        })
    
    if found {
        fmt.Printf("Found: %s\n", people[idx].Name)  // Found: Bob
    }
}

수정 함수

1. Insert - 삽입

func main() {
    numbers := []int{1, 2, 5, 6}
    
    // 인덱스 2에 3, 4 삽입
    result := slices.Insert(numbers, 2, 3, 4)
    fmt.Println(result)  // [1 2 3 4 5 6]
    
    // 끝에 추가
    result = slices.Insert(numbers, len(numbers), 7, 8)
    fmt.Println(result)  // [1 2 5 6 7 8]
}

2. Delete - 삭제

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6}
    
    // 인덱스 1~3 삭제 (3은 포함 안 됨)
    result := slices.Delete(numbers, 1, 3)
    fmt.Println(result)  // [1 4 5 6]
    
    // 단일 요소 삭제
    numbers = []int{1, 2, 3, 4, 5}
    result = slices.Delete(numbers, 2, 3)
    fmt.Println(result)  // [1 2 4 5]
}

3. DeleteFunc - 조건 삭제

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6}
    
    // 짝수 삭제
    result := slices.DeleteFunc(numbers, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println(result)  // [1 3 5]
    
    // 3보다 큰 수 삭제
    numbers = []int{1, 2, 3, 4, 5}
    result = slices.DeleteFunc(numbers, func(n int) bool {
        return n > 3
    })
    fmt.Println(result)  // [1 2 3]
}

4. Replace - 교체

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    
    // 인덱스 1~3을 10, 20으로 교체
    result := slices.Replace(numbers, 1, 3, 10, 20)
    fmt.Println(result)  // [1 10 20 4 5]
    
    // 더 많은 요소로 교체
    numbers = []int{1, 2, 3, 4, 5}
    result = slices.Replace(numbers, 1, 2, 10, 20, 30)
    fmt.Println(result)  // [1 10 20 30 3 4 5]
}

5. Compact - 중복 제거

func main() {
    // 정렬된 슬라이스에서 연속 중복 제거
    numbers := []int{1, 1, 2, 2, 2, 3, 4, 4, 5}
    result := slices.Compact(numbers)
    fmt.Println(result)  // [1 2 3 4 5]
    
    // 정렬 안 된 경우
    unsorted := []int{1, 2, 1, 3, 2}
    result = slices.Compact(unsorted)
    fmt.Println(result)  // [1 2 1 3 2] (연속만 제거)
    
    // 완전한 중복 제거: 정렬 후 Compact
    slices.Sort(unsorted)
    result = slices.Compact(unsorted)
    fmt.Println(result)  // [1 2 3]
}

6. CompactFunc - 커스텀 중복 제거

func main() {
    words := []string{"apple", "Apple", "APPLE", "banana", "Banana"}
    
    // 대소문자 무시 중복 제거
    result := slices.CompactFunc(words, func(a, b string) bool {
        return strings.EqualFold(a, b)
    })
    fmt.Println(result)  // [apple banana]
}

7. Concat - 연결

func main() {
    s1 := []int{1, 2, 3}
    s2 := []int{4, 5, 6}
    s3 := []int{7, 8, 9}
    
    // 여러 슬라이스 연결
    result := slices.Concat(s1, s2, s3)
    fmt.Println(result)  // [1 2 3 4 5 6 7 8 9]
    
    // 빈 슬라이스도 가능
    result = slices.Concat([]int{1}, []int{}, []int{2, 3})
    fmt.Println(result)  // [1 2 3]
}

정렬 함수

1. Sort - 오름차순

func main() {
    // 정수
    numbers := []int{3, 1, 4, 1, 5, 9, 2, 6}
    slices.Sort(numbers)
    fmt.Println(numbers)  // [1 1 2 3 4 5 6 9]
    
    // 문자열
    words := []string{"zebra", "apple", "mango", "banana"}
    slices.Sort(words)
    fmt.Println(words)  // [apple banana mango zebra]
    
    // 실수
    floats := []float64{3.14, 2.71, 1.41, 1.73}
    slices.Sort(floats)
    fmt.Println(floats)  // [1.41 1.73 2.71 3.14]
}

2. SortFunc - 커스텀 정렬

func main() {
    numbers := []int{3, 1, 4, 1, 5, 9, 2, 6}
    
    // 내림차순
    slices.SortFunc(numbers, func(a, b int) int {
        return cmp.Compare(b, a)  // 순서 반대
    })
    fmt.Println(numbers)  // [9 6 5 4 3 2 1 1]
    
    // 절대값 정렬
    nums := []int{-3, 1, -2, 4, -5}
    slices.SortFunc(nums, func(a, b int) int {
        return cmp.Compare(abs(a), abs(b))
    })
    fmt.Println(nums)  // [1 -2 -3 4 -5]
}

func abs(n int) int {
    if n < 0 {
        return -n
    }
    return n
}

3. SortStableFunc - 안정 정렬

type Student struct {
    Name  string
    Grade int
    Score float64
}

func main() {
    students := []Student{
        {"Alice", 90, 85.5},
        {"Bob", 85, 92.0},
        {"Carol", 90, 88.0},
        {"Dave", 85, 87.0},
    }
    
    // 성적순 정렬 (같으면 원래 순서 유지)
    slices.SortStableFunc(students, func(a, b Student) int {
        return cmp.Compare(a.Grade, b.Grade)
    })
    
    for _, s := range students {
        fmt.Printf("%s: %d\n", s.Name, s.Grade)
    }
    // Bob: 85
    // Dave: 85  (원래 순서 유지)
    // Alice: 90
    // Carol: 90 (원래 순서 유지)
}

4. IsSorted - 정렬 확인

func main() {
    fmt.Println(slices.IsSorted([]int{1, 2, 3, 4}))     // true
    fmt.Println(slices.IsSorted([]int{1, 3, 2, 4}))     // false
    
    fmt.Println(slices.IsSorted([]string{"a", "b", "c"})) // true
    fmt.Println(slices.IsSorted([]string{"a", "c", "b"})) // false
}

5. IsSortedFunc - 커스텀 정렬 확인

func main() {
    words := []string{"apple", "Banana", "cherry"}
    
    // 대소문자 구분 (기본)
    fmt.Println(slices.IsSorted(words))  // false
    
    // 대소문자 무시
    isSorted := slices.IsSortedFunc(words, func(a, b string) int {
        return strings.Compare(strings.ToLower(a), strings.ToLower(b))
    })
    fmt.Println(isSorted)  // true
}

6. Sorted - Iterator를 정렬된 슬라이스로

func main() {
    // Iterator 생성
    seq := slices.Values([]int{3, 1, 4, 1, 5})
    
    // 정렬된 슬라이스로 변환
    result := slices.Sorted(seq)
    fmt.Println(result)  // [1 1 3 4 5]
}

7. SortedFunc - Iterator 커스텀 정렬

func main() {
    seq := slices.Values([]string{"zebra", "apple", "mango"})
    
    // 내림차순 정렬
    result := slices.SortedFunc(seq, func(a, b string) int {
        return strings.Compare(b, a)
    })
    fmt.Println(result)  // [zebra mango apple]
}

비교 함수

1. Equal - 동등 비교

func main() {
    s1 := []int{1, 2, 3}
    s2 := []int{1, 2, 3}
    s3 := []int{1, 2, 4}
    
    fmt.Println(slices.Equal(s1, s2))  // true
    fmt.Println(slices.Equal(s1, s3))  // false
    
    // 길이가 다르면 false
    fmt.Println(slices.Equal(s1, []int{1, 2}))  // false
    
    // nil과 빈 슬라이스는 다름
    fmt.Println(slices.Equal([]int(nil), []int{}))  // false
}

2. EqualFunc - 커스텀 비교

func main() {
    s1 := []int{1, 2, 3}
    s2 := []string{"1", "2", "3"}
    
    // 타입이 달라도 비교 가능
    equal := slices.EqualFunc(s1, s2, func(i int, s string) bool {
        return strconv.Itoa(i) == s
    })
    fmt.Println(equal)  // true
    
    // 대소문자 무시 비교
    words1 := []string{"apple", "banana"}
    words2 := []string{"APPLE", "BANANA"}
    equal = slices.EqualFunc(words1, words2, strings.EqualFold)
    fmt.Println(equal)  // true
}

3. Compare - 사전순 비교

func main() {
    s1 := []int{1, 2, 3}
    s2 := []int{1, 2, 3}
    s3 := []int{1, 2, 4}
    s4 := []int{1, 2}
    
    // 같으면 0
    fmt.Println(slices.Compare(s1, s2))  // 0
    
    // s1 < s3이면 -1
    fmt.Println(slices.Compare(s1, s3))  // -1
    
    // s1 > s3이면 1
    fmt.Println(slices.Compare(s3, s1))  // 1
    
    // 길이가 짧으면 작음
    fmt.Println(slices.Compare(s4, s1))  // -1
}

4. CompareFunc - 커스텀 사전순 비교

func main() {
    s1 := []int{1, 2, 3}
    s2 := []string{"1", "2", "3"}
    
    result := slices.CompareFunc(s1, s2, func(i int, s string) int {
        return cmp.Compare(strconv.Itoa(i), s)
    })
    fmt.Println(result)  // 0 (같음)
}

집계 함수

1. Min - 최솟값

func main() {
    numbers := []int{3, 1, 4, 1, 5, 9, 2, 6}
    fmt.Println(slices.Min(numbers))  // 1
    
    words := []string{"zebra", "apple", "mango"}
    fmt.Println(slices.Min(words))  // apple
    
    floats := []float64{3.14, 2.71, 1.41}
    fmt.Println(slices.Min(floats))  // 1.41
}

2. MinFunc - 커스텀 최솟값

func main() {
    type Product struct {
        Name  string
        Price float64
    }
    
    products := []Product{
        {"A", 100.0},
        {"B", 50.0},
        {"C", 75.0},
    }
    
    // 가격 기준 최솟값
    cheapest := slices.MinFunc(products, func(a, b Product) int {
        return cmp.Compare(a.Price, b.Price)
    })
    fmt.Printf("Cheapest: %s ($%.2f)\n", cheapest.Name, cheapest.Price)
    // Cheapest: B ($50.00)
}

3. Max - 최댓값

func main() {
    numbers := []int{3, 1, 4, 1, 5, 9, 2, 6}
    fmt.Println(slices.Max(numbers))  // 9
    
    words := []string{"zebra", "apple", "mango"}
    fmt.Println(slices.Max(words))  // zebra
}

4. MaxFunc - 커스텀 최댓값

func main() {
    words := []string{"a", "bb", "ccc", "dd"}
    
    // 길이 기준 최댓값
    longest := slices.MaxFunc(words, func(a, b string) int {
        return cmp.Compare(len(a), len(b))
    })
    fmt.Println(longest)  // ccc
}

유틸리티 함수

1. Clone - 복사

func main() {
    original := []int{1, 2, 3, 4, 5}
    
    // 얕은 복사
    cloned := slices.Clone(original)
    fmt.Println(cloned)  // [1 2 3 4 5]
    
    // 원본 수정해도 복사본은 영향 없음
    original[0] = 100
    fmt.Println(original)  // [100 2 3 4 5]
    fmt.Println(cloned)    // [1 2 3 4 5]
    
    // 포인터 슬라이스는 주의
    val := 10
    ptrs := []*int{&val}
    clonedPtrs := slices.Clone(ptrs)
    
    *ptrs[0] = 20
    fmt.Println(*clonedPtrs[0])  // 20 (같은 객체 참조)
}

2. Reverse - 역순

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    
    // 제자리 역순
    slices.Reverse(numbers)
    fmt.Println(numbers)  // [5 4 3 2 1]
    
    words := []string{"hello", "world"}
    slices.Reverse(words)
    fmt.Println(words)  // [world hello]
}

3. Grow - 용량 증가

func main() {
    s := make([]int, 3, 5)
    fmt.Printf("len=%d cap=%d\n", len(s), cap(s))
    // len=3 cap=5
    
    // 최소 10개 추가할 용량 확보
    s = slices.Grow(s, 10)
    fmt.Printf("len=%d cap=%d\n", len(s), cap(s))
    // len=3 cap=15 (5 + 10)
}

4. Clip - 용량 정리

func main() {
    // 큰 슬라이스의 일부만 사용
    large := make([]int, 100)
    small := large[:5:100]
    
    fmt.Printf("len=%d cap=%d\n", len(small), cap(small))
    // len=5 cap=100
    
    // 용량을 길이에 맞춤
    small = slices.Clip(small)
    fmt.Printf("len=%d cap=%d\n", len(small), cap(small))
    // len=5 cap=5
}

5. Chunk - 청크 분할

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    // 3개씩 분할
    for chunk := range slices.Chunk(numbers, 3) {
        fmt.Println(chunk)
    }
    // [1 2 3]
    // [4 5 6]
    // [7 8 9]
    
    // 나누어떨어지지 않는 경우
    numbers = []int{1, 2, 3, 4, 5}
    for chunk := range slices.Chunk(numbers, 2) {
        fmt.Println(chunk)
    }
    // [1 2]
    // [3 4]
    // [5]
}

6. Repeat - 반복

func main() {
    pattern := []int{1, 2, 3}
    
    // 3번 반복
    result := slices.Repeat(pattern, 3)
    fmt.Println(result)  // [1 2 3 1 2 3 1 2 3]
    
    // 0번 반복
    result = slices.Repeat(pattern, 0)
    fmt.Println(result)  // []
}

실전 예제

1. 페이지네이션

type Paginator[T any] struct {
    items    []T
    pageSize int
}

func NewPaginator[T any](items []T, pageSize int) *Paginator[T] {
    return &Paginator[T]{
        items:    items,
        pageSize: pageSize,
    }
}

func (p *Paginator[T]) GetPage(pageNum int) []T {
    chunks := slices.Collect(slices.Chunk(p.items, p.pageSize))
    
    if pageNum < 0 || pageNum >= len(chunks) {
        return nil
    }
    
    return chunks[pageNum]
}

func (p *Paginator[T]) TotalPages() int {
    return (len(p.items) + p.pageSize - 1) / p.pageSize
}

func main() {
    items := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    p := NewPaginator(items, 3)
    
    fmt.Printf("Total pages: %d\n", p.TotalPages())  // 4
    
    for i := 0; i < p.TotalPages(); i++ {
        fmt.Printf("Page %d: %v\n", i+1, p.GetPage(i))
    }
    // Page 1: [1 2 3]
    // Page 2: [4 5 6]
    // Page 3: [7 8 9]
    // Page 4: [10]
}

2. 중복 제거 유틸리티

// 순서를 유지하면서 중복 제거
func UniquePreserveOrder[T comparable](slice []T) []T {
    seen := make(map[T]bool)
    result := make([]T, 0, len(slice))
    
    for _, item := range slice {
        if !seen[item] {
            seen[item] = true
            result = append(result, item)
        }
    }
    
    return result
}

// 정렬 후 중복 제거 (더 빠름)
func UniqueSorted[T cmp.Ordered](slice []T) []T {
    if len(slice) == 0 {
        return slice
    }
    
    clone := slices.Clone(slice)
    slices.Sort(clone)
    return slices.Compact(clone)
}

func main() {
    numbers := []int{3, 1, 4, 1, 5, 9, 2, 6, 5, 3}
    
    // 순서 유지
    unique1 := UniquePreserveOrder(numbers)
    fmt.Println(unique1)  // [3 1 4 5 9 2 6]
    
    // 정렬됨
    unique2 := UniqueSorted(numbers)
    fmt.Println(unique2)  // [1 2 3 4 5 6 9]
}

3. 필터링 유틸리티

func Filter[T any](slice []T, predicate func(T) bool) []T {
    result := make([]T, 0, len(slice))
    
    for _, item := range slice {
        if predicate(item) {
            result = append(result, item)
        }
    }
    
    return result
}

func Map[T, R any](slice []T, mapper func(T) R) []R {
    result := make([]R, len(slice))
    
    for i, item := range slice {
        result[i] = mapper(item)
    }
    
    return result
}

func Reduce[T, R any](slice []T, initial R, reducer func(R, T) R) R {
    result := initial
    
    for _, item := range slice {
        result = reducer(result, item)
    }
    
    return result
}

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    
    // 짝수만 필터링
    evens := Filter(numbers, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println(evens)  // [2 4 6 8 10]
    
    // 제곱 맵핑
    squared := Map(numbers, func(n int) int {
        return n * n
    })
    fmt.Println(squared)  // [1 4 9 16 25 36 49 64 81 100]
    
    // 합계
    sum := Reduce(numbers, 0, func(acc, n int) int {
        return acc + n
    })
    fmt.Println(sum)  // 55
}

4. 정렬된 병합

func MergeSorted[T cmp.Ordered](slices ...[]T) []T {
    // 모든 슬라이스 연결
    result := slices[0]
    for i := 1; i < len(slices); i++ {
        result = slices.Concat(result, slices[i])
    }
    
    // 정렬
    slices.Sort(result)
    return result
}

// 더 효율적인 K-way 병합
func KWayMerge[T cmp.Ordered](slices ...[]T) []T {
    // 각 슬라이스가 이미 정렬되어 있다고 가정
    totalLen := 0
    for _, s := range slices {
        totalLen += len(s)
    }
    
    result := make([]T, 0, totalLen)
    indices := make([]int, len(slices))
    
    for {
        minIdx := -1
        var minVal T
        
        // 각 슬라이스의 현재 요소 중 최솟값 찾기
        for i, s := range slices {
            if indices[i] < len(s) {
                if minIdx == -1 || s[indices[i]] < minVal {
                    minIdx = i
                    minVal = s[indices[i]]
                }
            }
        }
        
        if minIdx == -1 {
            break  // 모든 슬라이스 처리 완료
        }
        
        result = append(result, minVal)
        indices[minIdx]++
    }
    
    return result
}

func main() {
    s1 := []int{1, 4, 7}
    s2 := []int{2, 5, 8}
    s3 := []int{3, 6, 9}
    
    merged := KWayMerge(s1, s2, s3)
    fmt.Println(merged)  // [1 2 3 4 5 6 7 8 9]
}

5. 슬라이스 회전

func RotateLeft[T any](slice []T, n int) {
    if len(slice) == 0 {
        return
    }
    
    n = n % len(slice)
    if n < 0 {
        n += len(slice)
    }
    
    // [0:n]을 뒤로, [n:]을 앞으로
    slices.Reverse(slice[:n])
    slices.Reverse(slice[n:])
    slices.Reverse(slice)
}

func RotateRight[T any](slice []T, n int) {
    RotateLeft(slice, -n)
}

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    
    // 왼쪽으로 2칸 회전
    RotateLeft(numbers, 2)
    fmt.Println(numbers)  // [3 4 5 1 2]
    
    // 오른쪽으로 1칸 회전
    RotateRight(numbers, 1)
    fmt.Println(numbers)  // [2 3 4 5 1]
}

6. 윈도우 슬라이딩

func SlidingWindow[T any](slice []T, windowSize int, fn func([]T)) {
    if windowSize <= 0 || windowSize > len(slice) {
        return
    }
    
    for i := 0; i <= len(slice)-windowSize; i++ {
        window := slice[i : i+windowSize]
        fn(window)
    }
}

func MaxSlidingWindow[T cmp.Ordered](slice []T, windowSize int) []T {
    if windowSize <= 0 || windowSize > len(slice) {
        return nil
    }
    
    result := make([]T, 0, len(slice)-windowSize+1)
    
    SlidingWindow(slice, windowSize, func(window []T) {
        max := slices.Max(window)
        result = append(result, max)
    })
    
    return result
}

func main() {
    numbers := []int{1, 3, -1, -3, 5, 3, 6, 7}
    
    // 크기 3인 윈도우의 최댓값
    maxes := MaxSlidingWindow(numbers, 3)
    fmt.Println(maxes)  // [3 3 5 5 6 7]
    
    // 각 윈도우 출력
    SlidingWindow(numbers, 3, func(window []T) {
        fmt.Println(window)
    })
    // [1 3 -1]
    // [3 -1 -3]
    // [-1 -3 5]
    // [-3 5 3]
    // [5 3 6]
    // [3 6 7]
}

7. 멀티 정렬

type Employee struct {
    Name       string
    Department string
    Salary     int
    Age        int
}

func main() {
    employees := []Employee{
        {"Alice", "Engineering", 100000, 30},
        {"Bob", "Sales", 80000, 35},
        {"Carol", "Engineering", 100000, 25},
        {"Dave", "Sales", 80000, 28},
        {"Eve", "Engineering", 120000, 30},
    }
    
    // 부서별, 급여별 (내림차순), 나이별 정렬
    slices.SortStableFunc(employees, func(a, b Employee) int {
        // 부서 비교
        if c := strings.Compare(a.Department, b.Department); c != 0 {
            return c
        }
        
        // 급여 비교 (내림차순)
        if c := cmp.Compare(b.Salary, a.Salary); c != 0 {
            return c
        }
        
        // 나이 비교
        return cmp.Compare(a.Age, b.Age)
    })
    
    for _, e := range employees {
        fmt.Printf("%s - %s: $%d (age %d)\n",
            e.Department, e.Name, e.Salary, e.Age)
    }
    // Engineering - Eve: $120000 (age 30)
    // Engineering - Carol: $100000 (age 25)
    // Engineering - Alice: $100000 (age 30)
    // Sales - Dave: $80000 (age 28)
    // Sales - Bob: $80000 (age 35)
}

8. 캐시 LRU 구현

type LRUCache[K comparable, V any] struct {
    capacity int
    keys     []K
    values   map[K]V
    mu       sync.Mutex
}

func NewLRUCache[K comparable, V any](capacity int) *LRUCache[K, V] {
    return &LRUCache[K, V]{
        capacity: capacity,
        keys:     make([]K, 0, capacity),
        values:   make(map[K]V),
    }
}

func (c *LRUCache[K, V]) Get(key K) (V, bool) {
    c.mu.Lock()
    defer c.mu.Unlock()
    
    val, ok := c.values[key]
    if !ok {
        var zero V
        return zero, false
    }
    
    // 최근 사용으로 이동
    c.moveToFront(key)
    return val, true
}

func (c *LRUCache[K, V]) Put(key K, value V) {
    c.mu.Lock()
    defer c.mu.Unlock()
    
    if _, ok := c.values[key]; ok {
        // 기존 키 업데이트
        c.values[key] = value
        c.moveToFront(key)
        return
    }
    
    // 새 키 추가
    if len(c.keys) >= c.capacity {
        // 가장 오래된 항목 제거
        oldest := c.keys[0]
        delete(c.values, oldest)
        c.keys = slices.Delete(c.keys, 0, 1)
    }
    
    c.keys = append(c.keys, key)
    c.values[key] = value
}

func (c *LRUCache[K, V]) moveToFront(key K) {
    idx := slices.Index(c.keys, key)
    if idx == -1 {
        return
    }
    
    // 키 제거 후 끝에 추가
    c.keys = slices.Delete(c.keys, idx, idx+1)
    c.keys = append(c.keys, key)
}

func main() {
    cache := NewLRUCache[string, int](3)
    
    cache.Put("a", 1)
    cache.Put("b", 2)
    cache.Put("c", 3)
    
    fmt.Println(cache.Get("a"))  // 1 true
    
    cache.Put("d", 4)  // "b" 제거됨
    
    fmt.Println(cache.Get("b"))  // 0 false
    fmt.Println(cache.Get("c"))  // 3 true
    fmt.Println(cache.Get("d"))  // 4 true
}

일반적인 실수

1. Compact 오해

// ❌ 나쁜 예 (정렬 없이 모든 중복 제거 기대)
func main() {
    numbers := []int{1, 2, 1, 3, 2}
    result := slices.Compact(numbers)
    fmt.Println(result)  // [1 2 1 3 2] (연속 중복만 제거)
}

// ✅ 좋은 예 (정렬 후 Compact)
func main() {
    numbers := []int{1, 2, 1, 3, 2}
    slices.Sort(numbers)
    result := slices.Compact(numbers)
    fmt.Println(result)  // [1 2 3]
}

2. BinarySearch 전 정렬 누락

// ❌ 나쁜 예 (정렬 안 된 슬라이스)
func main() {
    numbers := []int{3, 1, 4, 1, 5}
    idx, found := slices.BinarySearch(numbers, 4)
    // 잘못된 결과
}

// ✅ 좋은 예 (정렬 후 검색)
func main() {
    numbers := []int{3, 1, 4, 1, 5}
    slices.Sort(numbers)
    idx, found := slices.BinarySearch(numbers, 4)
    if found {
        fmt.Printf("Found at %d\n", idx)
    }
}

3. Clone의 얕은 복사

// ❌ 나쁜 예 (포인터 슬라이스 깊은 복사 기대)
func main() {
    type Data struct {
        Value int
    }
    
    original := []*Data{{Value: 1}, {Value: 2}}
    cloned := slices.Clone(original)
    
    original[0].Value = 100
    fmt.Println(cloned[0].Value)  // 100 (같은 객체)
}

// ✅ 좋은 예 (수동 깊은 복사)
func main() {
    type Data struct {
        Value int
    }
    
    original := []*Data{{Value: 1}, {Value: 2}}
    cloned := make([]*Data, len(original))
    
    for i, d := range original {
        cloned[i] = &Data{Value: d.Value}
    }
    
    original[0].Value = 100
    fmt.Println(cloned[0].Value)  // 1
}

4. Delete 후 원본 슬라이스 사용

// ❌ 나쁜 예 (Delete는 새 슬라이스 반환)
func main() {
    numbers := []int{1, 2, 3, 4, 5}
    slices.Delete(numbers, 1, 3)  // 반환값 무시
    fmt.Println(numbers)  // [1 4 5 4 5] (잘못됨)
}

// ✅ 좋은 예 (반환값 사용)
func main() {
    numbers := []int{1, 2, 3, 4, 5}
    numbers = slices.Delete(numbers, 1, 3)
    fmt.Println(numbers)  // [1 4 5]
}

5. Insert 인덱스 범위 초과

// ❌ 나쁜 예 (범위 초과)
func main() {
    numbers := []int{1, 2, 3}
    // result := slices.Insert(numbers, 10, 4)  // 패닉
}

// ✅ 좋은 예 (범위 확인)
func SafeInsert[T any](slice []T, index int, values ...T) []T {
    if index < 0 {
        index = 0
    }
    if index > len(slice) {
        index = len(slice)
    }
    return slices.Insert(slice, index, values...)
}

6. Equal과 Compare 혼동

// ❌ 나쁜 예 (동등성에 Compare 사용)
func main() {
    s1 := []int{1, 2, 3}
    s2 := []int{1, 2, 3}
    
    if slices.Compare(s1, s2) {  // 컴파일 에러 (int != bool)
        fmt.Println("Equal")
    }
}

// ✅ 좋은 예 (Equal 사용)
func main() {
    s1 := []int{1, 2, 3}
    s2 := []int{1, 2, 3}
    
    if slices.Equal(s1, s2) {
        fmt.Println("Equal")
    }
    
    // Compare는 순서 비교
    result := slices.Compare(s1, s2)
    if result == 0 {
        fmt.Println("Equal")
    }
}

7. Grow 용량 오해

// ❌ 나쁜 예 (Grow는 절대 용량이 아님)
func main() {
    s := make([]int, 3, 5)
    s = slices.Grow(s, 10)  // 용량 10이 아니라 15
    fmt.Println(cap(s))  // 15 (기존 5 + 10)
}

// ✅ 좋은 예 (의도 명확히)
func main() {
    s := make([]int, 3, 5)
    
    // 최소 10개 더 추가할 공간 필요
    s = slices.Grow(s, 10)
    
    // 또는 절대 용량 설정
    targetCap := 20
    if cap(s) < targetCap {
        s = slices.Grow(s, targetCap-cap(s))
    }
}

베스트 프랙티스

1. 불변성 유지

// ✅ 원본 보존
func ProcessItems(items []int) []int {
    // Clone으로 원본 보호
    result := slices.Clone(items)
    slices.Sort(result)
    return slices.Compact(result)
}

func main() {
    original := []int{3, 1, 4, 1, 5}
    processed := ProcessItems(original)
    
    fmt.Println(original)   // [3 1 4 1 5] (변경 없음)
    fmt.Println(processed)  // [1 3 4 5]
}

2. 용량 사전 할당

// ✅ 필터링 시 용량 예측
func FilterEvens(numbers []int) []int {
    // 최악의 경우 대비
    result := make([]int, 0, len(numbers))
    
    for _, n := range numbers {
        if n%2 == 0 {
            result = append(result, n)
        }
    }
    
    // 메모리 절약
    return slices.Clip(result)
}

3. 정렬 상태 확인

// ✅ 정렬 여부 확인 후 처리
func Search[T cmp.Ordered](slice []T, target T) (int, bool) {
    if !slices.IsSorted(slice) {
        // Linear search
        return slices.Index(slice, target), slices.Contains(slice, target)
    }
    
    // Binary search
    return slices.BinarySearch(slice, target)
}

4. 제네릭 함수 작성

// ✅ 재사용 가능한 제네릭 유틸리티
func Partition[T any](slice []T, predicate func(T) bool) ([]T, []T) {
    truthy := make([]T, 0)
    falsy := make([]T, 0)
    
    for _, item := range slice {
        if predicate(item) {
            truthy = append(truthy, item)
        } else {
            falsy = append(falsy, item)
        }
    }
    
    return truthy, falsy
}

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6}
    evens, odds := Partition(numbers, func(n int) bool {
        return n%2 == 0
    })
    
    fmt.Println(evens)  // [2 4 6]
    fmt.Println(odds)   // [1 3 5]
}

5. 에러 처리

// ✅ 안전한 인덱스 접근
func SafeGet[T any](slice []T, index int) (T, error) {
    var zero T
    
    if index < 0 || index >= len(slice) {
        return zero, fmt.Errorf("index %d out of range [0:%d]", index, len(slice))
    }
    
    return slice[index], nil
}

func SafeDelete[T any](slice []T, start, end int) ([]T, error) {
    if start < 0 || end > len(slice) || start > end {
        return nil, fmt.Errorf("invalid range [%d:%d]", start, end)
    }
    
    return slices.Delete(slice, start, end), nil
}

6. 성능 최적화

// ✅ Contains 대신 맵 사용 (많은 검색)
func HasAnyBad(items []string, targets []string) bool {
    for _, target := range targets {
        if slices.Contains(items, target) {  // O(n*m)
            return true
        }
    }
    return false
}

func HasAnyGood(items []string, targets []string) bool {
    // 맵 생성 O(n)
    itemSet := make(map[string]bool)
    for _, item := range items {
        itemSet[item] = true
    }
    
    // 검색 O(m)
    for _, target := range targets {
        if itemSet[target] {
            return true
        }
    }
    return false
}

7. 함수형 체이닝

// ✅ 파이프라인 패턴
type Pipeline[T any] struct {
    data []T
}

func NewPipeline[T any](data []T) *Pipeline[T] {
    return &Pipeline[T]{data: slices.Clone(data)}
}

func (p *Pipeline[T]) Filter(pred func(T) bool) *Pipeline[T] {
    result := make([]T, 0)
    for _, item := range p.data {
        if pred(item) {
            result = append(result, item)
        }
    }
    p.data = result
    return p
}

func (p *Pipeline[T]) Sort(cmp func(a, b T) int) *Pipeline[T] {
    slices.SortFunc(p.data, cmp)
    return p
}

func (p *Pipeline[T]) Unique() *Pipeline[T] where T: comparable {
    p.data = UniqueSorted(p.data)
    return p
}

func (p *Pipeline[T]) Result() []T {
    return p.data
}

func main() {
    numbers := []int{5, 2, 8, 1, 9, 2, 5}
    
    result := NewPipeline(numbers).
        Filter(func(n int) bool { return n > 2 }).
        Sort(func(a, b int) int { return cmp.Compare(a, b) }).
        Result()
    
    fmt.Println(result)  // [5 5 8 9]
}

8. 문서화

// ✅ 동작과 복잡도 명시
// RemoveDuplicates removes all duplicate elements from the slice.
// The order of elements is preserved. Time complexity: O(n).
// Space complexity: O(n) for the seen map.
func RemoveDuplicates[T comparable](slice []T) []T {
    seen := make(map[T]bool, len(slice))
    result := make([]T, 0, len(slice))
    
    for _, item := range slice {
        if !seen[item] {
            seen[item] = true
            result = append(result, item)
        }
    }
    
    return result
}

// SortAndCompact sorts the slice and removes consecutive duplicates.
// Time complexity: O(n log n) for sorting + O(n) for compacting.
// The input slice is modified in place.
func SortAndCompact[T cmp.Ordered](slice []T) []T {
    slices.Sort(slice)
    return slices.Compact(slice)
}

정리

  • 기본: All, Values, Backward로 순회, Collect로 변환
  • 검색: Contains, Index, BinarySearch (정렬 필요)
  • 수정: Insert, Delete, Replace, Compact (중복), Concat
  • 정렬: Sort, SortFunc, SortStableFunc, IsSorted
  • 비교: Equal (동등), Compare (사전순)
  • 집계: Min, Max, MinFunc, MaxFunc
  • 유틸리티: Clone, Reverse, Grow, Clip, Chunk, Repeat
  • 실수: Compact 정렬, BinarySearch 정렬, Clone 얕은 복사, Delete 반환값, 인덱스 범위, Equal/Compare 혼동, Grow 용량
  • 베스트: 불변성, 용량 할당, 정렬 확인, 제네릭, 에러 처리, 성능, 체이닝, 문서화