Skip to content

Commit

Permalink
feat(lists): add counter type with utility methods
Browse files Browse the repository at this point in the history
  • Loading branch information
lvlcn-t committed Aug 18, 2024
1 parent fbd1f3d commit 7c6a489
Show file tree
Hide file tree
Showing 2 changed files with 367 additions and 0 deletions.
81 changes: 81 additions & 0 deletions lists/operations.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package lists

import (
"fmt"
"math/rand/v2"
)

Expand Down Expand Up @@ -44,6 +45,86 @@ func Count[T any](slice []T, f Predicate[T]) int {
return count
}

// Counter represents a map with the count of each element.
type Counter[T comparable] map[T]int

func (c Counter[T]) String() string {
return fmt.Sprintf("%v", map[T]int(c))
}

// CountBy returns a map with the count of each element in the slice.
func CountBy[T comparable](slice []T) Counter[T] {
counts := make(Counter[T], len(slice))
for _, item := range slice {
counts[item]++
}
return counts
}

// Get returns the count of the element in the counter.
func (c Counter[T]) Get(key T) int {
return c[key]
}

// MostCommon returns the most common element(s) in the counter.
func (c Counter[T]) MostCommon() []T {
result := []T{}
maximum := 0
for k, v := range c {
if v > maximum {
result = []T{k}
maximum = v
continue
}
if v == maximum {
result = append(result, k)
}
}
return result
}

// LeastCommon returns the least common element(s) in the counter.
func (c Counter[T]) LeastCommon() []T {
result := []T{}
minimum := 0
for k, v := range c {
if minimum == 0 || v < minimum {
result = []T{k}
minimum = v
continue
}
if v == minimum {
result = append(result, k)
}
}
return result
}

// Elements returns a slice with the elements in the counter.
func (c Counter[T]) Elements() []T {
result := []T{}
for k := range c {
result = append(result, k)
}
return result
}

// Total returns the total count of all elements in the counter.
func (c Counter[T]) Total() int {
total := 0
for _, v := range c {
total += v
}
return total
}

// Clear removes all elements from the counter.
func (c Counter[T]) Clear() {
for k := range c {
delete(c, k)
}
}

// Distinct returns a new slice containing only the unique elements of the original slice.
func Distinct[T comparable](slice []T) []T {
seen := make(map[T]struct{})
Expand Down
286 changes: 286 additions & 0 deletions lists/operations_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package lists

import (
"fmt"
"reflect"
"slices"
"testing"
)

Expand Down Expand Up @@ -777,3 +779,287 @@ func TestNoneMatch(t *testing.T) { //nolint:dupl // generic functions cannot be
})
}
}

func TestCountBy(t *testing.T) {
tests := []struct {
name string
slice []int
want map[int]int
}{
{
name: "success",
slice: []int{1, 2, 3, 4},
want: map[int]int{
1: 1,
2: 1,
3: 1,
4: 1,
},
},
{
name: "empty slice",
slice: []int{},
want: map[int]int{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := CountBy(tt.slice)
if got == nil {
t.Errorf("CountBy() = nil, want %v", tt.want)
}

for k, v := range tt.want {
if got[k] != v {
t.Errorf("CountBy() = %v, want %v", got, tt.want)
}
}
})
}
}

func TestCounter_Get(t *testing.T) {
tests := []struct {
name string
c Counter[int]
key int
want int
}{
{
name: "success",
c: Counter[int]{
1: 2,
2: 3,
},
key: 2,
want: 3,
},
{
name: "not found",
c: Counter[int]{
1: 2,
2: 3,
},
key: 3,
want: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.c.Get(tt.key)
if got != tt.want {
t.Errorf("Counter.Get() = %v, want %v", got, tt.want)
}
})
}
}

func TestCounter_MostCommon(t *testing.T) {
tests := []struct {
name string
c Counter[int]
want []int
}{
{
name: "success",
c: Counter[int]{
1: 2,
2: 3,
3: 3,
},
want: []int{2, 3},
},
{
name: "tie",
c: Counter[int]{
1: 2,
2: 2,
3: 2,
},
want: []int{1, 2, 3},
},
{
name: "empty",
c: Counter[int]{},
want: []int{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.c.MostCommon()
slices.Sort(got)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Counter.MostCommon() = %v, want %v", got, tt.want)
}
})
}
}

func TestCounter_LeastCommon(t *testing.T) {
tests := []struct {
name string
c Counter[int]
want []int
}{
{
name: "success",
c: Counter[int]{
1: 2,
2: 3,
3: 3,
},
want: []int{1},
},
{
name: "tie",
c: Counter[int]{
1: 2,
2: 2,
3: 2,
},
want: []int{1, 2, 3},
},
{
name: "empty",
c: Counter[int]{},
want: []int{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.c.LeastCommon()
slices.Sort(got)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Counter.LeastCommon() = %v, want %v", got, tt.want)
}
})
}
}

func TestCounter_Elements(t *testing.T) {
tests := []struct {
name string
c Counter[int]
want []int
}{
{
name: "success",
c: Counter[int]{
1: 2,
2: 3,
3: 3,
},
want: []int{1, 2, 3},
},
{
name: "empty",
c: Counter[int]{},
want: []int{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.c.Elements()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Counter.Elements() = %v, want %v", got, tt.want)
}
})
}
}

func TestCounter_Total(t *testing.T) {
tests := []struct {
name string
c Counter[int]
want int
}{
{
name: "success",
c: Counter[int]{
1: 2,
2: 3,
3: 3,
},
want: 8,
},
{
name: "empty",
c: Counter[int]{},
want: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.c.Total()
if got != tt.want {
t.Errorf("Counter.Total() = %v, want %v", got, tt.want)
}
})
}
}

func TestCounter_Clear(t *testing.T) {
tests := []struct {
name string
c Counter[int]
want Counter[int]
}{
{
name: "success",
c: Counter[int]{
1: 2,
2: 3,
3: 3,
},
want: Counter[int]{},
},
{
name: "empty",
c: Counter[int]{},
want: Counter[int]{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.c.Clear()
if !reflect.DeepEqual(tt.c, tt.want) {
t.Errorf("Counter.Clear() = %v, want %v", tt.c, tt.want)
}
})
}
}

func TestCounter_String(t *testing.T) {
tests := []struct {
name string
c Counter[int]
want string
}{
{
name: "success",
c: Counter[int]{
1: 2,
2: 3,
3: 3,
},
want: fmt.Sprintf("%v", map[int]int{
1: 2,
2: 3,
3: 3,
}),
},
{
name: "empty",
c: Counter[int]{},
want: fmt.Sprintf("%v", map[int]int{}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.c.String()
if got != tt.want {
t.Errorf("Counter.String() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 7c6a489

Please sign in to comment.