k8s.io/apimachinery/pkg/util/sets の使い道
sets が便利なので使いましょう!
Set は slice のような概念ですが、内部的には map なので要素の重複は許されておらず、そのため要素の追加や削除、存在確認などを短い時間で行えます。
集合の和集合(Union)、差集合(Difference)、交差(Intersection)、対称差(Symmetric Difference)など数学的な集合演算が組み込みで提供されています。
もちろん Insert や Delete 、Equal 、 Clear 、 Clone 、 Len などの一般的な操作をするための関数は用意されています。
また UnsortedList の method があるため最終的な結果を slice にして返却することが可能です。
今回は sets 紹介していきたいと思います。
型的には以下のように定義されています。
type Empty struct{}
type Set[T comparable] map[T]Empty
New
func New[T comparable](items ...T) Set[T]
説明不要ですね、新たな Set を返す関数です。
注意点としては sets.NewString()
などの型を指定して作成する関数は Deprecated になっているため、型を指定して全て sets.New()
で作成してください。
s := sets.NewString()
ではなく
s := sets.New[string]()
のようにして作成してください。
Union
func (s1 Set[T]) Union(s2 Set[T]) Set[T]
s1 と s2 の和集合(全て)を返す関数です。
Difference
func (s1 Set[T]) Difference(s2 Set[T]) Set[T]
s1 と s2 の差集合(s1 だけに含まれ s2 には含まれないもの)を返す関数です。
Intersection
func (s1 Set[T]) Intersection(s2 Set[T]) Set[T]
s1 と s2 の共通部分(s1 にも s2 にも含まれるもの)を返す関数です。
SymmetricDifference
func (s1 Set[T]) SymmetricDifference(s2 Set[T]) Set[T]
Example
関数の説明は Doc を読めば良いと思うのでここからは使用例を紹介していこうと思います。
存在しているかを判定する
例えば Has
を用いて map のように指定する値が含まれているか確認する処理を書き直せます。
disallowedEnvs := map[string]struct{}{
"RESERVED": {},
}
var pod corev1.Pod
_ := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(hoge), &pod)
for i, container := range pod.Spec.Containers {
for k, env := range container.Env {
if _, exists := disallowedEnvs[env.Name]; exists {
var envList []string
for env := range disallowedEnvs {
envList = append(envList, env)
}
return field.Invalid(field.NewPath("spec", "containers").Index(i).Child("env").Index(k).Child("name"), env.Name, fmt.Sprintf("must not be same as %v", envList))
}
}
}
disallowedEnvs := sets.New("RESERVED")
var pod corev1.Pod
_ := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(hoge), &pod)
for i, container := range pod.Spec.Containers {
for k, env := range container.Env {
if disallowedEnvs.Has(env.Name) {
return field.Invalid(field.NewPath("spec", "containers").Index(i).Child("env").Index(k).Child("name"), env.Name, fmt.Sprintf("must not be same as %v", disallowedEnvs.UnsortedList()))
}
}
}
二重ループを回避する
Go で for の二重ループを回避するために map に入れ直すケースはよくあると思います。
sets を使用すると make(map[string]struct{})
のような謎の map を書かずに簡潔に書くことができます。
list1 := []string{"a", "b", "c"}
list2 := []string{"b", "c", "d"}
var common []string
for _, item1 := range list1 {
for _, item2 := range list2 {
if item1 == item2 {
common = append(common, item1)
break
}
}
}
// common = ["b", "c"]
list1 := []string{"a", "b", "c"}
list2 := []string{"b", "c", "d"}
set := make(map[string]struct{})
for _, item := range list1 {
set[item] = struct{}{}
}
var common []string
for _, item := range list2 {
if _, exists := set[item]; exists {
common = append(common, item)
}
}
// common = ["b", "c"]
list1 := []string{"a", "b", "c"}
list2 := []string{"b", "c", "d"}
s := sets.New(list1...)
commons := sets.New[string]()
for _, item := range list2 {
if s.Has(item) {
common.Insert(item)
}
}
common := commons.UnsortedList()
// common = ["b", "c"]
list1 := []string{"a", "b", "c"}
list2 := []string{"b", "c", "d"}
common := sets.New(list1...).Intersection(sets.New(list2...)).UnsortedList()
// common = ["b", "c"]
まとめ
全ての関数の例を示すことはできていませんが sets
を使用することで、slice の操作時により意図が明確になり、コードの可読性が向上することができます。
サービス側で許可している(していない)値がありユーザーからの値を確認してチェックする場合や、複数の配列を操作する必要がある場合などに使用すると力を感じられると思います。
ぜひ sets
を使用してみてください!
Discussion