[Go] slices package
개요
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 용량
- 베스트: 불변성, 용량 할당, 정렬 확인, 제네릭, 에러 처리, 성능, 체이닝, 문서화