-
Notifications
You must be signed in to change notification settings - Fork 0
/
slices_transform.go
129 lines (109 loc) · 4.24 KB
/
slices_transform.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package generics
import (
"context"
"fmt"
"sort"
)
// SliceMapperExpression is a type that represents a generic mapper
type SliceMapperExpression[T any, V any] func(index int, input T) V
// SliceMapperExpressionWithContext is a type used to represent a context aware generic mapper
type SliceMapperExpressionWithContext[T any, V any] func(ctx context.Context, index int, v T) (V, error)
// Group rolls up items into groups based on a mapper function that provides a key per item
func Group[T any, K comparable](items []T, keyMapper SliceMapperExpression[T, K]) map[K][]T {
output := make(map[K][]T)
for i, value := range items {
key := keyMapper(i, value)
output[key] = append(output[key], value)
}
return output
}
type compactionSurvivor[T any] struct {
lastPosition int
value T
}
// Compact takes a slice and compacts it, by reducing to only the last occurrence of a given key. This
// is akin to Kafka topic compaction, and used for scenarios where you have a slice of mixed updates
// but want to take only the final update for a given predicate. The result order is determined by the
// final position(s) of the surviving elements relative to each other.
func Compact[T any, K comparable](input []T, keyMapper SliceMapperExpression[T, K]) []T {
// Create a set of last updates per item, tracking their index
survivors := make(map[K]compactionSurvivor[T])
for i, v := range input {
key := keyMapper(i, v)
survivors[key] = compactionSurvivor[T]{
lastPosition: i,
value: v,
}
}
// Flatten to a map
kvp := ToKeyValues(survivors)
sort.Slice(kvp, func(i, j int) bool {
return kvp[i].Value.lastPosition < kvp[j].Value.lastPosition
})
result := make([]T, len(kvp))
for i, v := range kvp {
result[i] = v.Value.value
}
return result
}
// GroupWithContext rolls up items into groups based on a mapper function that provides a key per item
func GroupWithContext[T any, K comparable](ctx context.Context, items []T, keyMapper SliceMapperExpressionWithContext[T, K]) (map[K][]T, error) {
output := make(map[K][]T)
for i, value := range items {
key, err := keyMapper(ctx, i, value)
if err != nil {
return nil, fmt.Errorf("error mapping index %d: %w", i, err)
}
output[key] = append(output[key], value)
}
return output, nil
}
// Map converts values in a slice from one type to another
func Map[T any, V any](items []T, mapper SliceMapperExpression[T, V]) []V {
output := make([]V, len(items))
for i, value := range items {
output[i] = mapper(i, value)
}
return output
}
// MapWithContext executes a mapper over the members of a slice using the specified context
func MapWithContext[T any, V any](ctx context.Context, items []T, mapper SliceMapperExpressionWithContext[T, V]) ([]V, error) {
output := make([]V, len(items))
for i, value := range items {
mapped, err := mapper(ctx, i, value)
if err != nil {
return nil, fmt.Errorf("error mapping item %d: %w", i, err)
}
output[i] = mapped
}
return output, nil
}
// ToMap converts a slice of items into a dictionary using mappers for the key and value pairs. If
// multiple items yield the same key, the last key in the set will be the one kept.
func ToMap[T any, K comparable, V any](items []T, keyMapper SliceMapperExpression[T, K], valueMapper SliceMapperExpression[T, V]) map[K]V {
output := make(map[K]V, len(items))
for i, item := range items {
key := keyMapper(i, item)
value := valueMapper(i, item)
output[key] = value
}
return output
}
// ToMapWithContext converts a slice of items into a dictionary using mappers for the key and value pairs. If
// multiple items yield the same key, the last key in the set will be the one kept. If any mapper fails, the
// operation as a whole fails.
func ToMapWithContext[T any, K comparable, V any](ctx context.Context, items []T, keyMapper SliceMapperExpressionWithContext[T, K], valueMapper SliceMapperExpressionWithContext[T, V]) (map[K]V, error) {
output := make(map[K]V, len(items))
for i, item := range items {
key, err := keyMapper(ctx, i, item)
if err != nil {
return nil, fmt.Errorf("error mapping key for index %d: %w", i, err)
}
value, err := valueMapper(ctx, i, item)
if err != nil {
return nil, fmt.Errorf("error mapping value for index %d: %w", i, err)
}
output[key] = value
}
return output, nil
}