Skip to content

Commit

Permalink
chore: add FilterInPlace method to maps and update module
Browse files Browse the repository at this point in the history
- run rekres
- Deprecate `maps.Clone` (implemented in runtime since 1.21), `maps.Clear` (implemented as builtin function since 1.21) and `maps.Copy` functions.
- Fix typos.
- Add `FilterInPlace` method to key-value containers.
- Add `Len` method to key-value containers.
- Use `clear` builtin in `ConcurrentMap.Clear`.
- Use `slices` package instead of `sort` for sorting in tests.

Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
  • Loading branch information
DmitriyMV committed Sep 17, 2023
1 parent 36a3ae3 commit efca710
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 48 deletions.
77 changes: 77 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
#
# Generated on 2023-09-17T20:55:00Z by kres latest.

name: default
"on":
push:
branches:
- main
tags:
- v*
pull_request: {}
env:
CI_ARGS: --cache-from=type=registry,ref=registry.dev.siderolabs.io/${GITHUB_REPOSITORY}:buildcache --cache-to=type=registry,ref=registry.dev.siderolabs.io/${GITHUB_REPOSITORY}:buildcache,mode=max
jobs:
default:
permissions:
contents: write
packages: write
runs-on:
- self-hosted
- X64
if: ${{ !startsWith(github.head_ref, 'renovate/') || !startsWith(github.head_ref, 'renovate/') }}
steps:
- name: checkout
uses: actions/checkout@v3
- name: Unshallow
run: |
git fetch --prune --unshallow
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
config-inline: |
[worker.oci]
gc = true
gckeepstorage = 100000 # 100 GiB
[[worker.oci.gcpolicy]]
keepBytes = 32212254720 # 30 GiB
keepDuration = 604800
filters = [ "type==source.local", "type==exec.cachemount", "type==source.git.checkout"]
[[worker.oci.gcpolicy]]
all = true
keepBytes = 107374182400 # 100 GiB
- name: base
run: |
make base
- name: unit-tests
run: |
make unit-tests
- name: unit-tests-race
run: |
make unit-tests-race
- name: coverage
run: |
make coverage
- name: lint
run: |
make lint
- name: Generate Checksums
if: startsWith(github.ref, 'refs/tags/')
run: |
sha256sum _out/* > _out/sha256sum.txt
sha512sum _out/* > _out/sha512sum.txt
- name: release-notes
if: startsWith(github.ref, 'refs/tags/')
run: |
make release-notes
- name: Release
if: startsWith(github.ref, 'refs/tags/')
uses: crazy-max/ghaction-github-release@v1
with:
body_path: _out/RELEASE_NOTES.md
draft: "true"
files: |-
_out/*
_out/sha*.txt
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
#
# Generated on 2023-08-21T14:49:21Z by kres latest.
# Generated on 2023-09-17T20:55:00Z by kres latest.

# common variables

Expand All @@ -14,9 +14,9 @@ WITH_RACE ?= false
REGISTRY ?= ghcr.io
USERNAME ?= siderolabs
REGISTRY_AND_USERNAME ?= $(REGISTRY)/$(USERNAME)
PROTOBUF_GO_VERSION ?= 1.28.1
PROTOBUF_GO_VERSION ?= 1.31.0
GRPC_GO_VERSION ?= 1.3.0
GRPC_GATEWAY_VERSION ?= 2.16.2
GRPC_GATEWAY_VERSION ?= 2.17.1
VTPROTOBUF_VERSION ?= 0.4.0
DEEPCOPY_VERSION ?= v0.5.5
GOLANGCILINT_VERSION ?= v1.54.2
Expand Down
55 changes: 55 additions & 0 deletions containers/lazymap.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ func (m *LazyBiMap[K, V]) ForEach(f func(K, V)) {
m.biMap.ForEach(f)
}

// FilterInPlace calls the given function for each key-value pair and returns a new map with the filtered values.
func (m *LazyBiMap[K, V]) FilterInPlace(f func(K, V) bool) {
m.biMap.FilterInPlace(f)
}

// Len returns the number of key-value pairs.
func (m *LazyBiMap[K, V]) Len() int {
return m.biMap.Len()
}

// BiMap (or “bidirectional map”) is a special kind of map that maintains
// an inverse view of the map while ensuring that no duplicate values are present
// and a value can always be used safely to get the key back.
Expand Down Expand Up @@ -143,6 +153,29 @@ func (m *BiMap[K, V]) ForEach(f func(K, V)) {
}
}

// FilterInPlace calls the given function for each key-value pair and returns a new map with the filtered values.
func (m *BiMap[K, V]) FilterInPlace(f func(K, V) bool) {
if m.k2v == nil {
return
}

for k, v := range m.k2v {
if !f(k, v) {
delete(m.k2v, k)
delete(m.v2k, v)
}
}
}

// Len returns the number of key-value pairs.
func (m *BiMap[K, V]) Len() int {
if m.k2v == nil {
return 0
}

return len(m.k2v)
}

// LazyMap is like usual map but creates values on demand.
type LazyMap[K comparable, V comparable] struct {
Creator func(K) (V, error)
Expand Down Expand Up @@ -197,3 +230,25 @@ func (m *LazyMap[K, V]) ForEach(f func(K, V)) {
f(k, v)
}
}

// FilterInPlace calls the given function for each key-value pair and returns a new map with the filtered values.
func (m *LazyMap[K, V]) FilterInPlace(f func(K, V) bool) {
if m.dataMap == nil {
return
}

for k, v := range m.dataMap {
if !f(k, v) {
delete(m.dataMap, k)
}
}
}

// Len returns the number of key-value pairs.
func (m *LazyMap[K, V]) Len() int {
if m.dataMap == nil {
return 0
}

return len(m.dataMap)
}
44 changes: 39 additions & 5 deletions containers/lazymap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package containers_test

import (
"fmt"
"sort"
"slices"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -133,12 +133,29 @@ func TestLazyBiMap(t *testing.T) {
values = append(values, v)
})

sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
sort.Slice(values, func(i, j int) bool { return values[i] < values[j] })
slices.Sort(keys)
slices.Sort(values)

assert.Equal(t, []int{1, 2}, keys)
assert.Equal(t, []int{100, 200}, values)
})

t.Run("should filter entries", func(t *testing.T) {
m.FilterInPlace(func(k int, v int) bool {
return k == 1
})

var keys []int
var values []int

m.ForEach(func(k int, v int) {
keys = append(keys, k)
values = append(values, v)
})

assert.Equal(t, []int{1}, keys)
assert.Equal(t, []int{100}, values)
})
}

func TestLazyMap(t *testing.T) {
Expand Down Expand Up @@ -231,10 +248,27 @@ func TestLazyMap(t *testing.T) {
values = append(values, v)
})

sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
sort.Slice(values, func(i, j int) bool { return values[i] < values[j] })
slices.Sort(keys)
slices.Sort(values)

assert.Equal(t, []int{4, 5}, keys)
assert.Equal(t, []int{400, 500}, values)
})

t.Run("should filter entries", func(t *testing.T) {
m.FilterInPlace(func(k int, v int) bool {
return k == 4
})

var keys []int
var values []int

m.ForEach(func(k int, v int) {
keys = append(keys, k)
values = append(values, v)
})

assert.Equal(t, []int{4}, keys)
assert.Equal(t, []int{400}, values)
})
}
20 changes: 17 additions & 3 deletions containers/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,22 @@ func (m *ConcurrentMap[K, V]) ForEach(f func(K, V)) {
}
}

// FilterInPlace calls the given function for each key-value pair and removes the key-value pair if the function returns false.
func (m *ConcurrentMap[K, V]) FilterInPlace(f func(K, V) bool) {
m.mx.Lock()
defer m.mx.Unlock()

if m.m == nil {
return
}

for k, v := range m.m {
if !f(k, v) {
delete(m.m, k)
}
}
}

// Len returns the number of elements in the map.
func (m *ConcurrentMap[K, V]) Len() int {
m.mx.Lock()
Expand All @@ -127,9 +143,7 @@ func (m *ConcurrentMap[K, V]) Clear() {
m.mx.Lock()
defer m.mx.Unlock()

for k := range m.m {
delete(m.m, k)
}
clear(m.m)
}

// Reset resets the underlying map.
Expand Down
15 changes: 15 additions & 0 deletions containers/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,21 @@ func TestConcurrentMap(t *testing.T) {

require.Equal(t, 0, m.Len())
})

t.Run("filter map", func(t *testing.T) {
t.Parallel()

m := containers.ConcurrentMap[int, int]{}
m.Set(1, 1)
m.Set(2, 2)
m.Set(3, 3)

m.FilterInPlace(func(key int, val int) bool {
return key == 1 || val == 3
})

require.Equal(t, 2, m.Len())
})
}

func TestConcurrentMap_GetOrCall(t *testing.T) {
Expand Down
42 changes: 15 additions & 27 deletions maps/maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
// Package maps contains the generic functions for maps.
package maps

// NOTE(DmitriyMV): I tried to implement this generic functions to be as perfomant as possible.
// However, I couldn't find a way to do it, since Go (1.18 at the time of writing) cannot inline closures if (generic)
import "maps"

// NOTE(DmitriyMV): I tried to implement this generic functions to be as performant as possible.
// However, I couldn't find any way to do it, since Go (1.18 at the time of writing) cannot inline closures if (generic)
// function, which accepts the closure, was not inlined itself.
// And inlining budget of 80 is quite small, since most of it is going towards closure call.
// Somewhat relevant issue: https://github.com/golang/go/issues/41988
Expand Down Expand Up @@ -142,7 +144,7 @@ func Intersect[K comparable](maps ...map[K]struct{}) []K {

// Filter returns a map containing all the elements of m that satisfy fn.
func Filter[M ~map[K]V, K comparable, V any](m M, fn func(K, V) bool) M {
// NOTE(DmitriyMV): We use type parameter M here to return exactly the same tyoe as the input map.
// NOTE(DmitriyMV): We use type parameter M here to return exactly the same type as the input map.
if len(m) == 0 {
return nil
}
Expand Down Expand Up @@ -170,7 +172,7 @@ func FilterInPlace[M ~map[K]V, K comparable, V any](m M, fn func(K, V) bool) M {
return m
}

// NOTE(DmitriyMV): We use type parameter M here to return exactly the same tyoe as the input map.
// NOTE(DmitriyMV): We use type parameter M here to return exactly the same type of map as the input map.
for k, v := range m {
if !fn(k, v) {
delete(m, k)
Expand All @@ -182,34 +184,20 @@ func FilterInPlace[M ~map[K]V, K comparable, V any](m M, fn func(K, V) bool) M {
}

// Clear removes all entries from m, leaving it empty.
func Clear[M ~map[K]V, K comparable, V any](m M) {
for k := range m {
delete(m, k)
}
}
//
// Deprecated: Use built-in clear function instead.
func Clear[M ~map[K]V, K comparable, V any](m M) { clear(m) }

// Clone returns a copy of m. This is a shallow clone:
// the new keys and values are set using ordinary assignment.
func Clone[M ~map[K]V, K comparable, V any](m M) M {
// Preserve nil in case it matters.
if m == nil {
return nil
}

r := make(M, len(m))
for k, v := range m {
r[k] = v
}

return r
}
//
// Deprecated: Use [maps.Clone] function instead.
func Clone[M ~map[K]V, K comparable, V any](m M) M { return maps.Clone(m) }

// Copy copies all key/value pairs in src adding them to dst.
// When a key in src is already present in dst,
// the value in dst will be overwritten by the value associated
// with the key in src.
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
for k, v := range src {
dst[k] = v
}
}
//
// Deprecated: Use [maps.Copy] instead.
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) { maps.Copy(dst, src) }
Loading

0 comments on commit efca710

Please sign in to comment.