cleanup usage of orderedmap

add tests
add groupby
add groupbyordered
This commit is contained in:
maddalax 2024-10-29 05:48:13 -05:00
parent d44cd0b2ed
commit cb012a4d82
12 changed files with 231 additions and 212 deletions

View file

@ -1,82 +0,0 @@
package astgen
// OrderedMap is a generic data structure that maintains the order of keys.
type OrderedMap[K comparable, V any] struct {
keys []K
values map[K]V
}
// Entries returns the key-value pairs in the order they were added.
func (om *OrderedMap[K, V]) Entries() []struct {
Key K
Value V
} {
entries := make([]struct {
Key K
Value V
}, len(om.keys))
for i, key := range om.keys {
entries[i] = struct {
Key K
Value V
}{
Key: key,
Value: om.values[key],
}
}
return entries
}
// NewOrderedMap creates a new OrderedMap.
func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
return &OrderedMap[K, V]{
keys: []K{},
values: make(map[K]V),
}
}
// Set adds or updates a key-value pair in the OrderedMap.
func (om *OrderedMap[K, V]) Set(key K, value V) {
// Check if the key already exists
if _, exists := om.values[key]; !exists {
om.keys = append(om.keys, key) // Append key to the keys slice if it's a new key
}
om.values[key] = value
}
// Get retrieves a value by key.
func (om *OrderedMap[K, V]) Get(key K) (V, bool) {
value, exists := om.values[key]
return value, exists
}
// Keys returns the keys in the order they were added.
func (om *OrderedMap[K, V]) Keys() []K {
return om.keys
}
// Values returns the values in the order of their keys.
func (om *OrderedMap[K, V]) Values() []V {
values := make([]V, len(om.keys))
for i, key := range om.keys {
values[i] = om.values[key]
}
return values
}
// Delete removes a key-value pair from the OrderedMap.
func (om *OrderedMap[K, V]) Delete(key K) {
if _, exists := om.values[key]; exists {
// Remove the key from the map
delete(om.values, key)
// Remove the key from the keys slice
for i, k := range om.keys {
if k == key {
om.keys = append(om.keys[:i], om.keys[i+1:]...)
break
}
}
}
}

View file

@ -7,16 +7,3 @@ import (
func PanicF(format string, args ...interface{}) {
panic(fmt.Sprintf(format, args...))
}
func Unique[T any](slice []T, key func(item T) string) []T {
var result []T
seen := make(map[string]bool)
for _, v := range slice {
k := key(v)
if _, ok := seen[k]; !ok {
seen[k] = true
result = append(result, v)
}
}
return result
}

View file

@ -1,18 +1,24 @@
package datastructures
// OrderedMap is a generic data structure that maintains the order of keys.
type OrderedMap[K comparable, V any] struct {
keys []K
values map[K]V
}
package orderedmap
type Entry[K comparable, V any] struct {
Key K
Value V
}
// Map is a generic data structure that maintains the order of keys.
type Map[K comparable, V any] struct {
keys []K
values map[K]V
}
func (om *Map[K, V]) Each(cb func(key K, value V)) {
for _, key := range om.keys {
cb(key, om.values[key])
}
}
// Entries returns the key-value pairs in the order they were added.
func (om *OrderedMap[K, V]) Entries() []Entry[K, V] {
func (om *Map[K, V]) Entries() []Entry[K, V] {
entries := make([]Entry[K, V], len(om.keys))
for i, key := range om.keys {
entries[i] = Entry[K, V]{
@ -23,16 +29,16 @@ func (om *OrderedMap[K, V]) Entries() []Entry[K, V] {
return entries
}
// NewOrderedMap creates a new OrderedMap.
func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
return &OrderedMap[K, V]{
// New creates a new Map.
func New[K comparable, V any]() *Map[K, V] {
return &Map[K, V]{
keys: []K{},
values: make(map[K]V),
}
}
// Set adds or updates a key-value pair in the OrderedMap.
func (om *OrderedMap[K, V]) Set(key K, value V) {
// Set adds or updates a key-value pair in the Map.
func (om *Map[K, V]) Set(key K, value V) {
// Check if the key already exists
if _, exists := om.values[key]; !exists {
om.keys = append(om.keys, key) // Append key to the keys slice if it's a new key
@ -41,18 +47,18 @@ func (om *OrderedMap[K, V]) Set(key K, value V) {
}
// Get retrieves a value by key.
func (om *OrderedMap[K, V]) Get(key K) (V, bool) {
func (om *Map[K, V]) Get(key K) (V, bool) {
value, exists := om.values[key]
return value, exists
}
// Keys returns the keys in the order they were added.
func (om *OrderedMap[K, V]) Keys() []K {
func (om *Map[K, V]) Keys() []K {
return om.keys
}
// Values returns the values in the order of their keys.
func (om *OrderedMap[K, V]) Values() []V {
func (om *Map[K, V]) Values() []V {
values := make([]V, len(om.keys))
for i, key := range om.keys {
values[i] = om.values[key]
@ -61,8 +67,8 @@ func (om *OrderedMap[K, V]) Values() []V {
return values
}
// Delete removes a key-value pair from the OrderedMap.
func (om *OrderedMap[K, V]) Delete(key K) {
// Delete removes a key-value pair from the Map.
func (om *Map[K, V]) Delete(key K) {
if _, exists := om.values[key]; exists {
// Remove the key from the map
delete(om.values, key)

View file

@ -0,0 +1,33 @@
package orderedmap
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestOrderedMap(t *testing.T) {
t.Parallel()
om := New[string, int]()
alphabet := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
for index, letter := range alphabet {
om.Set(letter, index)
}
assert.Equal(t, alphabet, om.Keys())
c, ok := om.Get("c")
assert.True(t, ok)
assert.Equal(t, 2, c)
for i, entry := range om.Entries() {
if i == 5 {
assert.Equal(t, "f", entry.Key)
}
}
om.Delete("c")
value, ok := om.Get("c")
assert.False(t, ok)
assert.Equal(t, 0, value)
}

View file

@ -1,5 +1,9 @@
package h
import (
"github.com/maddalax/htmgo/framework/datastructure/orderedmap"
)
// Unique returns a new slice with only unique items.
func Unique[T any](slice []T, key func(item T) string) []T {
var result []T
@ -14,6 +18,7 @@ func Unique[T any](slice []T, key func(item T) string) []T {
return result
}
// Find returns the first item in the slice that matches the predicate.
func Find[T any](slice []T, predicate func(item *T) bool) *T {
for _, v := range slice {
if predicate(&v) {
@ -23,6 +28,34 @@ func Find[T any](slice []T, predicate func(item *T) bool) *T {
return nil
}
// GroupBy groups the items in the slice by the key returned by the key function.
func GroupBy[T any, K comparable](slice []T, key func(item T) K) map[K][]T {
grouped := make(map[K][]T)
for _, item := range slice {
k := key(item)
items, ok := grouped[k]
if !ok {
items = []T{}
}
grouped[k] = append(items, item)
}
return grouped
}
// GroupByOrdered groups the items in the slice by the key returned by the key function, and returns an Map.
func GroupByOrdered[T any, K comparable](slice []T, key func(item T) K) *orderedmap.Map[K, []T] {
grouped := orderedmap.New[K, []T]()
for _, item := range slice {
k := key(item)
items, ok := grouped.Get(k)
if !ok {
items = []T{}
}
grouped.Set(k, append(items, item))
}
return grouped
}
// Filter returns a new slice with only items that match the predicate.
func Filter[T any](slice []T, predicate func(item T) bool) []T {
var result []T

102
framework/h/array_test.go Normal file
View file

@ -0,0 +1,102 @@
package h
import (
"github.com/stretchr/testify/assert"
"strings"
"testing"
)
func TestUnique(t *testing.T) {
t.Parallel()
slice := []string{"a", "b", "b", "c", "d", "d", "x"}
unique := Unique(slice, func(item string) string {
return item
})
assert.Equal(t, []string{"a", "b", "c", "d", "x"}, unique)
}
func TestFilter(t *testing.T) {
t.Parallel()
slice := []string{"a", "b", "b", "c", "d", "d", "x"}
filtered := Filter(slice, func(item string) bool {
return item == "b"
})
assert.Equal(t, []string{"b", "b"}, filtered)
}
func TestMap(t *testing.T) {
t.Parallel()
slice := []string{"a", "b", "c"}
mapped := Map(slice, func(item string) string {
return strings.ToUpper(item)
})
assert.Equal(t, []string{"A", "B", "C"}, mapped)
}
func TestGroupBy(t *testing.T) {
t.Parallel()
type Item struct {
Name string
Job string
}
items := []Item{
{Name: "Alice", Job: "Developer"},
{Name: "Bob", Job: "Designer"},
{Name: "Charlie", Job: "Developer"},
{Name: "David", Job: "Designer"},
{Name: "Eve", Job: "Developer"},
{Name: "Frank", Job: "Product Manager"},
}
grouped := GroupBy(items, func(item Item) string {
return item.Job
})
assert.Equal(t, 3, len(grouped))
assert.Equal(t, 3, len(grouped["Developer"]))
assert.Equal(t, 2, len(grouped["Designer"]))
assert.Equal(t, 1, len(grouped["Product Manager"]))
}
func TestGroupByOrdered(t *testing.T) {
t.Parallel()
type Item struct {
Name string
Job string
}
items := []Item{
{Name: "Alice", Job: "Developer"},
{Name: "Bob", Job: "Designer"},
{Name: "Charlie", Job: "Developer"},
{Name: "David", Job: "Designer"},
{Name: "Eve", Job: "Developer"},
{Name: "Frank", Job: "Product Manager"},
}
grouped := GroupByOrdered(items, func(item Item) string {
return item.Job
})
keys := []string{"Developer", "Designer", "Product Manager"}
assert.Equal(t, keys, grouped.Keys())
devs, ok := grouped.Get("Developer")
assert.True(t, ok)
assert.Equal(t, 3, len(devs))
assert.Equal(t, "Alice", devs[0].Name)
assert.Equal(t, "Charlie", devs[1].Name)
assert.Equal(t, "Eve", devs[2].Name)
}
func TestFind(t *testing.T) {
t.Parallel()
slice := []string{"a", "b", "c"}
found := Find(slice, func(item *string) bool {
return *item == "b"
})
assert.Equal(t, "b", *found)
}

View file

@ -2,16 +2,15 @@ package h
import (
"fmt"
"strings"
"github.com/maddalax/htmgo/framework/datastructure/orderedmap"
"github.com/maddalax/htmgo/framework/hx"
"github.com/maddalax/htmgo/framework/internal/datastructure"
"strings"
)
type AttributeMap = map[string]any
type AttributeMapOrdered struct {
data *datastructure.OrderedMap[string, string]
data *orderedmap.Map[string, string]
}
func (m *AttributeMapOrdered) Set(key string, value any) {
@ -39,12 +38,12 @@ func (m *AttributeMapOrdered) Each(cb func(key string, value string)) {
})
}
func (m *AttributeMapOrdered) Entries() []datastructure.MapEntry[string, string] {
func (m *AttributeMapOrdered) Entries() []orderedmap.Entry[string, string] {
return m.data.Entries()
}
func NewAttributeMap(pairs ...string) *AttributeMapOrdered {
m := datastructure.NewOrderedMap[string, string]()
m := orderedmap.New[string, string]()
if len(pairs)%2 == 0 {
for i := 0; i < len(pairs); i++ {
m.Set(pairs[i], pairs[i+1])

View file

@ -1,84 +0,0 @@
package datastructure
type MapEntry[K comparable, V any] struct {
Key K
Value V
}
// OrderedMap is a generic data structure that maintains the order of keys.
type OrderedMap[K comparable, V any] struct {
keys []K
values map[K]V
}
func (om *OrderedMap[K, V]) Each(cb func(key K, value V)) {
for _, key := range om.keys {
cb(key, om.values[key])
}
}
// Entries returns the key-value pairs in the order they were added.
func (om *OrderedMap[K, V]) Entries() []MapEntry[K, V] {
entries := make([]MapEntry[K, V], len(om.keys))
for i, key := range om.keys {
entries[i] = MapEntry[K, V]{
Key: key,
Value: om.values[key],
}
}
return entries
}
// NewOrderedMap creates a new OrderedMap.
func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
return &OrderedMap[K, V]{
keys: []K{},
values: make(map[K]V),
}
}
// Set adds or updates a key-value pair in the OrderedMap.
func (om *OrderedMap[K, V]) Set(key K, value V) {
// Check if the key already exists
if _, exists := om.values[key]; !exists {
om.keys = append(om.keys, key) // Append key to the keys slice if it's a new key
}
om.values[key] = value
}
// Get retrieves a value by key.
func (om *OrderedMap[K, V]) Get(key K) (V, bool) {
value, exists := om.values[key]
return value, exists
}
// Keys returns the keys in the order they were added.
func (om *OrderedMap[K, V]) Keys() []K {
return om.keys
}
// Values returns the values in the order of their keys.
func (om *OrderedMap[K, V]) Values() []V {
values := make([]V, len(om.keys))
for i, key := range om.keys {
values[i] = om.values[key]
}
return values
}
// Delete removes a key-value pair from the OrderedMap.
func (om *OrderedMap[K, V]) Delete(key K) {
if _, exists := om.values[key]; exists {
// Remove the key from the map
delete(om.values, key)
// Remove the key from the keys slice
for i, k := range om.keys {
if k == key {
om.keys = append(om.keys[:i], om.keys[i+1:]...)
break
}
}
}
}

View file

@ -10,6 +10,7 @@ type Snippet struct {
partial h.PartialFunc
externalRoute string
sourceCodePath string
category string
}
func SetSnippet(ctx *h.RequestContext, snippet *Snippet) {

View file

@ -3,6 +3,7 @@ package examples
import "htmgo-site/partials/snippets"
var FormWithLoadingStateSnippet = Snippet{
category: "Forms",
name: "Form",
description: "A simple form submission example with a loading state",
sidebarName: "Form With Loading State",
@ -11,6 +12,7 @@ var FormWithLoadingStateSnippet = Snippet{
}
var UserAuthSnippet = Snippet{
category: "Projects",
name: "User Authentication",
description: "An example showing basic user registration and login with htmgo",
sidebarName: "User Authentication",
@ -20,6 +22,7 @@ var UserAuthSnippet = Snippet{
}
var ChatSnippet = Snippet{
category: "Projects",
name: "Chat App",
description: "A simple chat application built with htmgo using SSE for real-time updates",
sidebarName: "Chat App Using SSE",
@ -29,6 +32,7 @@ var ChatSnippet = Snippet{
}
var HackerNewsSnippet = Snippet{
category: "Projects",
name: "HackerNews Clone",
description: "A hacker news reader clone built with htmgo",
sidebarName: "HackerNews Clone",
@ -38,6 +42,7 @@ var HackerNewsSnippet = Snippet{
}
var HtmgoSiteSnippet = Snippet{
category: "Projects",
name: "Htmgo Doc Site",
description: "The htmgo site built with htmgo, recursion am I right?",
sidebarName: "Htmgo Doc Site",
@ -47,6 +52,7 @@ var HtmgoSiteSnippet = Snippet{
}
var TodoListSnippet = Snippet{
category: "Projects",
name: "Todo List",
description: "A todo list built with htmgo",
sidebarName: "Todo List",
@ -56,6 +62,7 @@ var TodoListSnippet = Snippet{
}
var ClickToEditSnippet = Snippet{
category: "Forms",
name: "Inline Click To Edit",
description: "List view of items with a click to edit button and persistence",
sidebarName: "Inline Click To Edit",

View file

@ -1,10 +1,16 @@
package examples
import (
"github.com/maddalax/htmgo/framework/datastructure/orderedmap"
"github.com/maddalax/htmgo/framework/h"
)
func SnippetSidebar() *h.Element {
grouped := h.GroupByOrdered(examples, func(example Snippet) string {
return example.category
})
return h.Div(
h.Class("px-3 py-2 pr-6 md:min-h-screen pb-4 mb:pb-0 bg-neutral-50 border-r border-r-slate-300 overflow-y-auto"),
h.Div(
@ -18,11 +24,22 @@ func SnippetSidebar() *h.Element {
),
h.Div(
h.Class("flex flex-col gap-2"),
h.List(examples, func(entry Snippet, index int) *h.Element {
return h.A(
h.Href(entry.path),
h.Text(entry.sidebarName),
h.Class("text-slate-900 hover:text-rose-400"),
h.List(grouped.Entries(), func(entry orderedmap.Entry[string, []Snippet], index int) *h.Element {
return h.Div(
h.P(
h.Text(entry.Key),
h.Class("text-slate-800 font-bold"),
),
h.Div(
h.Class("pl-4 flex flex-col"),
h.List(entry.Value, func(entry Snippet, index int) *h.Element {
return h.A(
h.Href(entry.path),
h.Text(entry.sidebarName),
h.Class("text-slate-900 hover:text-rose-400"),
)
}),
),
)
}),
),

View file

@ -1,8 +1,8 @@
package partials
import (
"github.com/maddalax/htmgo/framework/datastructure/orderedmap"
"github.com/maddalax/htmgo/framework/h"
"htmgo-site/internal/datastructures"
"htmgo-site/internal/dirwalk"
"strings"
)
@ -37,8 +37,8 @@ func partsToName(parts []string) string {
return builder.String()
}
func groupByFirstPart(pages []*dirwalk.Page) *datastructures.OrderedMap[string, []*dirwalk.Page] {
grouped := datastructures.NewOrderedMap[string, []*dirwalk.Page]()
func groupByFirstPart(pages []*dirwalk.Page) *orderedmap.Map[string, []*dirwalk.Page] {
grouped := orderedmap.New[string, []*dirwalk.Page]()
for _, page := range pages {
if len(page.Parts) > 0 {
section := page.Parts[0]
@ -69,7 +69,7 @@ func DocSidebar(pages []*dirwalk.Page) *h.Element {
),
h.Div(
h.Class("flex flex-col gap-4"),
h.List(grouped.Entries(), func(entry datastructures.Entry[string, []*dirwalk.Page], index int) *h.Element {
h.List(grouped.Entries(), func(entry orderedmap.Entry[string, []*dirwalk.Page], index int) *h.Element {
return h.Div(
h.P(
h.Text(formatPart(entry.Key)),