Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c29edbd
#5 - initialize tests for map collections
maciej-sz Feb 23, 2025
e984026
#5 - map functionality wip
maciej-sz Feb 23, 2025
85334a0
#5 - map functionality wip
maciej-sz Feb 24, 2025
da5dfcf
#5 - map functionality wip
maciej-sz Feb 26, 2025
d6e7e64
#5 - map functionality wip
maciej-sz Feb 27, 2025
efced07
#5 - finished comfyMap impl
maciej-sz Feb 28, 2025
f36da30
#5 - introduced internal interfaces
maciej-sz Feb 28, 2025
295445e
#5 - introduced extractors for underlying structures in tests
maciej-sz Mar 2, 2025
9993d50
#5 - reorganize fold/reduce to only use underlying slices
maciej-sz Mar 4, 2025
841e830
#5 - initial mapcmp is ready; introduced BasePairs interface; extract…
maciej-sz Mar 5, 2025
d0a92ed
#5 - all `map` cases covered also for `mapcmp`
maciej-sz Mar 7, 2025
aca7ec5
#7 - remove `ToSlice` and `ToMap` test coupling for map and cmpmap
maciej-sz Mar 7, 2025
869476e
#5 - non-mutable cmp tests done
maciej-sz Mar 7, 2025
b88a9f4
#5 - Complete cmp tests, add mutable sort, and refactor test builders
maciej-sz Mar 9, 2025
f808c31
#10 - added conditional valuesCounter tests to cmpMap
maciej-sz Mar 9, 2025
e75d25b
#9, #10 - Implement value counting and use nil slices for empty colle…
maciej-sz Mar 9, 2025
a801d76
#7, #5 - remove test coupling; some fixes; full coverage of methods
maciej-sz Mar 9, 2025
08cdbe4
#5 - fix linter errors
maciej-sz Mar 10, 2025
6cc8677
#5 - cr fixes
maciej-sz Mar 10, 2025
22ca276
#5 - valuesCounter optimize; add tests
maciej-sz Mar 10, 2025
cc006e7
#5 - valuesCounter test update
maciej-sz Mar 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .idea/collections.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 24 additions & 2 deletions README.adoc
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
= Comfy Gopher
= What is Comfy Gopher?

*A Set of general-purpose Tools, Utilities, and Data Structures for Comfortable Development*
*Comfy Gopher is a Set of general-purpose Tools, Utilities, and Data Structures for Comfortable Development*

These tools prioritize speed and ease of development over strict efficiency or full compliance with Go philosophy.
They accelerate development and enhance the experience, by reducing the cognitive load,
making them well suited for rapid prototyping.

== Comfy Gopher - Collections

=== Goals

1. Provide convenient abstraction for collection data structures
1. Reduce repetition in day-to-day collections operations
1. Address the missing ordered map data structure
1. Provide API for in-place modifications of collections
1. Reduce strain of juggling between empty slice pointers `[]V(nil) vs []V{}`

=== No-goals

This is a set of elements that the Collections package are NOT trying to achieve:

1. Thread-safety
+
You must implement your own thread-safety.

1. Superb efficiency
+
Although care is taken to ensure that the data structures used are efficient, exceptional efficiency is not the main goal here.

== Alternatives

There is a very nice library https://github.com/charbz/gophers[github.com/charbz/gophers].
Expand Down
112 changes: 100 additions & 12 deletions base_cases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import (
"testing"
)

type baseIntArgs = testArgs[Base[int], int]
type baseTestCase = testCase[Base[int], int]
type baseCollIntBuilder = testCollectionBuilder[Base[int], int]
type baseIntArgs = testArgs[baseInternal[int], int]
type baseTestCase = testCase[baseInternal[int], int]
type baseCollIntBuilder = testCollectionBuilder[baseInternal[int]]

type baseIntPairArgs = testArgs[Base[Pair[int, int]], Pair[int, int]]
type baseIntPairTestCase = testCase[Base[Pair[int, int]], Pair[int, int]]
type baseCollIntPairBuilder = testPairCollectionBuilder[Base[Pair[int, int]]]
type baseIntPairArgs = testArgs[baseInternal[Pair[int, int]], Pair[int, int]]
type baseIntPairTestCase = testCase[baseInternal[Pair[int, int]], Pair[int, int]]
type baseCollIntPairBuilder = testPairCollectionBuilder[baseInternal[Pair[int, int]]]

func getContainsCases(builder baseCollIntBuilder) []baseTestCase {
return []baseTestCase{
Expand Down Expand Up @@ -598,6 +598,50 @@ func testFind(t *testing.T, builder baseCollIntBuilder) {
}
}

func getFindCasesWithDupes(builder baseCollIntPairBuilder) []*baseIntPairTestCase {
return []*baseIntPairTestCase{
{
name: "Find() on six-item collection, first one",
coll: builder.SixWithDuplicates(),
args: baseIntPairArgs{
predicate: func(i int, p Pair[int, int]) bool { return true },
defaultValue: nil,
},
want1: NewPair(1, 111),
},
{
name: "Find() on six-item collection, second one",
coll: builder.SixWithDuplicates(),
args: baseIntPairArgs{
predicate: func(i int, p Pair[int, int]) bool { return p.Val() == 222 },
defaultValue: nil,
},
want1: NewPair(2, 222),
},
{
name: "Find() on six-item collection, not found",
coll: builder.SixWithDuplicates(),
args: baseIntPairArgs{
predicate: func(i int, p Pair[int, int]) bool { return p.Val() == 999 },
defaultValue: nil,
},
want1: nil,
},
}
}

func testFindWithDupes(t *testing.T, builder baseCollIntPairBuilder) {
cases := getFindCasesWithDupes(builder)
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
got := tt.coll.Find(tt.args.predicate, tt.args.defaultValue)
if !reflect.DeepEqual(got, tt.want1) {
t.Errorf("Find() = %v, want1 %v", got, tt.want1)
}
})
}
}

func getFindLastCases(builder baseCollIntBuilder) []*baseTestCase {
return []*baseTestCase{
{
Expand Down Expand Up @@ -659,6 +703,50 @@ func getFindLastCases(builder baseCollIntBuilder) []*baseTestCase {
}
}

func getFindLastCasesWithDupes(builder baseCollIntPairBuilder) []*baseIntPairTestCase {
return []*baseIntPairTestCase{
{
name: "FindLast() on six-item collection, first one",
coll: builder.SixWithDuplicates(),
args: baseIntPairArgs{
predicate: func(i int, p Pair[int, int]) bool { return true },
defaultValue: nil,
},
want1: NewPair(6, 333),
},
{
name: "FindLast() on six-item collection, second one",
coll: builder.SixWithDuplicates(),
args: baseIntPairArgs{
predicate: func(i int, p Pair[int, int]) bool { return p.Val() == 222 },
defaultValue: nil,
},
want1: NewPair(5, 222),
},
{
name: "FindLast() on six-item collection, not found",
coll: builder.SixWithDuplicates(),
args: baseIntPairArgs{
predicate: func(i int, p Pair[int, int]) bool { return p.Val() == 999 },
defaultValue: nil,
},
want1: nil,
},
}
}

func testFindLastWithDupes(t *testing.T, builder baseCollIntPairBuilder) {
cases := getFindLastCasesWithDupes(builder)
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
got := tt.coll.FindLast(tt.args.predicate, tt.args.defaultValue)
if !reflect.DeepEqual(got, tt.want1) {
t.Errorf("FindLast() = %v, want1 %v", got, tt.want1)
}
})
}
}

func testFindLast(t *testing.T, builder baseCollIntBuilder) {
cases := getFindLastCases(builder)
for _, tt := range cases {
Expand Down Expand Up @@ -1005,7 +1093,7 @@ func getSearchPairCases(builder baseCollIntPairBuilder) []*baseIntPairTestCase {
name: "Search() pair on six-item collection, found first occurrence",
coll: builder.SixWithDuplicates(),
args: baseIntPairArgs{predicate: func(i int, v Pair[int, int]) bool {
return v.Value() == 111
return v.Val() == 111
}},
want1: NewPair(1, 111),
want2: true,
Expand All @@ -1014,7 +1102,7 @@ func getSearchPairCases(builder baseCollIntPairBuilder) []*baseIntPairTestCase {
name: "Search() pair on six-item collection, found first occurrence",
coll: builder.SixWithDuplicates(),
args: baseIntPairArgs{predicate: func(i int, v Pair[int, int]) bool {
return v.Value() == 222
return v.Val() == 222
}},
want1: NewPair(2, 222),
want2: true,
Expand All @@ -1023,7 +1111,7 @@ func getSearchPairCases(builder baseCollIntPairBuilder) []*baseIntPairTestCase {
name: "Search() pair on six-item collection, found first occurrence",
coll: builder.SixWithDuplicates(),
args: baseIntPairArgs{predicate: func(i int, v Pair[int, int]) bool {
return v.Value() == 333
return v.Val() == 333
}},
want1: NewPair(3, 333),
want2: true,
Expand Down Expand Up @@ -1199,7 +1287,7 @@ func getSearchRevPairCases(builder baseCollIntPairBuilder) []*baseIntPairTestCas
name: "SearchRev() on six-item collection, found first occurrence",
coll: builder.SixWithDuplicates(),
args: baseIntPairArgs{predicate: func(i int, v Pair[int, int]) bool {
return v.Value() == 111
return v.Val() == 111
}},
want1: NewPair(4, 111),
want2: true,
Expand All @@ -1208,7 +1296,7 @@ func getSearchRevPairCases(builder baseCollIntPairBuilder) []*baseIntPairTestCas
name: "SearchRev() on six-item collection, found first occurrence",
coll: builder.SixWithDuplicates(),
args: baseIntPairArgs{predicate: func(i int, v Pair[int, int]) bool {
return v.Value() == 222
return v.Val() == 222
}},
want1: NewPair(5, 222),
want2: true,
Expand All @@ -1217,7 +1305,7 @@ func getSearchRevPairCases(builder baseCollIntPairBuilder) []*baseIntPairTestCas
name: "SearchRev() on six-item collection, found first occurrence",
coll: builder.SixWithDuplicates(),
args: baseIntPairArgs{predicate: func(i int, v Pair[int, int]) bool {
return v.Value() == 333
return v.Val() == 333
}},
want1: NewPair(6, 333),
want2: true,
Expand Down
33 changes: 33 additions & 0 deletions cmp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package coll

import "cmp"

type valuesCounter[V cmp.Ordered] struct {
counter map[V]int
}

func newValuesCounter[V cmp.Ordered]() *valuesCounter[V] {
return &valuesCounter[V]{
counter: make(map[V]int),
}
}

func (c *valuesCounter[V]) Count(v V) int {
return c.counter[v]
}

func (c *valuesCounter[V]) Increment(v V) {
c.counter[v]++
}

func (c *valuesCounter[V]) Decrement(v V) {
count, exists := c.counter[v]
if !exists {
return
}
if count == 1 {
delete(c.counter, v)
} else {
c.counter[v]--
}
}
Comment on lines +23 to +33
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Add test coverage for the non-existent key case in Decrement.

The implementation correctly checks for key existence before attempting to decrement, which addresses the issue raised in the previous review. However, static analysis indicates that lines 33-34 (the early return when a key doesn't exist) aren't covered by tests.

Please add test cases that verify the behavior when attempting to decrement a non-existent key to ensure complete test coverage.


🏁 Script executed:

#!/bin/bash
# Check if there are any existing tests that might cover this code path
rg -A 5 -B 5 "Decrement" --glob "**/*_test.go"

Length of output: 46


Action Required: Add Test Case for Non-Existent Key in Decrement

The Decrement method in cmp.go (lines 30–40) handles the non-existent key case by returning early when the key isn't found. However, our repository lacks tests covering this branch (as indicated by the absence of test output for references to Decrement in *_test.go files). Please add a unit test that calls Decrement on a key not present in the counter to verify that it exits gracefully without modifying the counter.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 33-34: cmp.go#L33-L34
Added lines #L33 - L34 were not covered by tests

Loading
Loading