cli-utils/pkg/object/objmetadata_set.go

212 lines
5.5 KiB
Go

// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//
package object
import (
"hash/fnv"
"sort"
"strconv"
)
// ObjMetadataSet is an ordered list of ObjMetadata that acts like an unordered
// set for comparison purposes.
type ObjMetadataSet []ObjMetadata
// UnstructuredSetEquals returns true if the slice of objects in setA equals
// the slice of objects in setB.
func ObjMetadataSetEquals(setA []ObjMetadata, setB []ObjMetadata) bool {
return ObjMetadataSet(setA).Equal(ObjMetadataSet(setB))
}
// ObjMetadataSetFromMap constructs a set from a map
func ObjMetadataSetFromMap(mapA map[ObjMetadata]struct{}) ObjMetadataSet {
setA := make(ObjMetadataSet, 0, len(mapA))
for f := range mapA {
setA = append(setA, f)
}
return setA
}
// Equal returns true if the two sets contain equivalent objects. Duplicates are
// ignored.
// This function satisfies the cmp.Equal interface from github.com/google/go-cmp
func (setA ObjMetadataSet) Equal(setB ObjMetadataSet) bool {
mapA := make(map[ObjMetadata]struct{}, len(setA))
for _, a := range setA {
mapA[a] = struct{}{}
}
mapB := make(map[ObjMetadata]struct{}, len(setB))
for _, b := range setB {
mapB[b] = struct{}{}
}
if len(mapA) != len(mapB) {
return false
}
for b := range mapB {
if _, exists := mapA[b]; !exists {
return false
}
}
return true
}
// Contains checks if the provided ObjMetadata exists in the set.
func (setA ObjMetadataSet) Contains(id ObjMetadata) bool {
for _, om := range setA {
if om == id {
return true
}
}
return false
}
// Remove the object from the set and return the updated set.
func (setA ObjMetadataSet) Remove(obj ObjMetadata) ObjMetadataSet {
for i, a := range setA {
if a == obj {
setA[len(setA)-1], setA[i] = setA[i], setA[len(setA)-1]
return setA[:len(setA)-1]
}
}
return setA
}
// Intersection returns the set of unique objects in both set A and set B.
func (setA ObjMetadataSet) Intersection(setB ObjMetadataSet) ObjMetadataSet {
var maxlen int
if len(setA) > len(setB) {
maxlen = len(setA)
} else {
maxlen = len(setB)
}
mapI := make(map[ObjMetadata]struct{}, maxlen)
mapB := setB.ToMap()
for _, a := range setA {
if _, ok := mapB[a]; ok {
mapI[a] = struct{}{}
}
}
intersection := make(ObjMetadataSet, 0, len(mapI))
// Iterate over setA & setB to retain input order and have stable output
for _, id := range setA {
if _, ok := mapI[id]; ok {
intersection = append(intersection, id)
delete(mapI, id)
}
}
for _, id := range setB {
if _, ok := mapI[id]; ok {
intersection = append(intersection, id)
delete(mapI, id)
}
}
return intersection
}
// Union returns the set of unique objects from the merging of set A and set B.
func (setA ObjMetadataSet) Union(setB ObjMetadataSet) ObjMetadataSet {
m := make(map[ObjMetadata]struct{}, len(setA)+len(setB))
for _, a := range setA {
m[a] = struct{}{}
}
for _, b := range setB {
m[b] = struct{}{}
}
union := make(ObjMetadataSet, 0, len(m))
// Iterate over setA & setB to retain input order and have stable output
for _, id := range setA {
if _, ok := m[id]; ok {
union = append(union, id)
delete(m, id)
}
}
for _, id := range setB {
if _, ok := m[id]; ok {
union = append(union, id)
delete(m, id)
}
}
return union
}
// Diff returns the set of objects that exist in set A, but not in set B (A - B).
func (setA ObjMetadataSet) Diff(setB ObjMetadataSet) ObjMetadataSet {
// Create a map of the elements of A
m := make(map[ObjMetadata]struct{}, len(setA))
for _, a := range setA {
m[a] = struct{}{}
}
// Remove from A each element of B
for _, b := range setB {
delete(m, b) // OK to delete even if b not in m
}
// Create/return slice from the map of remaining items
diff := make(ObjMetadataSet, 0, len(m))
// Iterate over setA to retain input order and have stable output
for _, id := range setA {
if _, ok := m[id]; ok {
diff = append(diff, id)
delete(m, id)
}
}
return diff
}
// Unique returns the set with duplicates removed.
// Order may or may not remain consistent.
func (setA ObjMetadataSet) Unique() ObjMetadataSet {
return ObjMetadataSetFromMap(setA.ToMap())
}
// Hash the objects in the set by serializing, sorting, concatonating, and
// hashing the result with the 32-bit FNV-1a algorithm.
func (setA ObjMetadataSet) Hash() string {
objStrs := make([]string, 0, len(setA))
for _, obj := range setA {
objStrs = append(objStrs, obj.String())
}
sort.Strings(objStrs)
h := fnv.New32a()
for _, obj := range objStrs {
// Hash32.Write never returns an error
// https://pkg.go.dev/hash#pkg-types
_, _ = h.Write([]byte(obj))
}
return strconv.FormatUint(uint64(h.Sum32()), 16)
}
// ToMap returns the set as a map, with objMeta keys and empty struct values.
func (setA ObjMetadataSet) ToMap() map[ObjMetadata]struct{} {
m := make(map[ObjMetadata]struct{}, len(setA))
for _, objMeta := range setA {
m[objMeta] = struct{}{}
}
return m
}
// ToStringMap returns the set as a serializable map, with objMeta keys and
// empty string values.
func (setA ObjMetadataSet) ToStringMap() map[string]string {
stringMap := make(map[string]string, len(setA))
for _, objMeta := range setA {
stringMap[objMeta.String()] = ""
}
return stringMap
}
// FromStringMap returns a set from a serializable map, with objMeta keys and
// empty string values. Errors if parsing fails.
func FromStringMap(in map[string]string) (ObjMetadataSet, error) {
var set ObjMetadataSet
for s := range in {
objMeta, err := ParseObjMetadata(s)
if err != nil {
return nil, err
}
set = append(set, objMeta)
}
return set, nil
}