Skip to content
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,10 @@ To propose a new idea or package, please open an issue or discussion with:
## License

[MIT](./LICENSE)

---

## TODO common repo

- [ ] Add linter in ci
- [ ] Add go sec in ci
2 changes: 1 addition & 1 deletion concurrency/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ For full documentation, see [https://pkg.go.dev/github.com/lif0/pkg/concurrency]

## ⚙️ Requirements

- **go 1.22 or higher**
- **go 1.19 or higher**

## 📦 Installation

Expand Down
2 changes: 1 addition & 1 deletion sync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ For full documentation, see [https://pkg.go.dev/github.com/lif0/pkg/sync](https:

## ⚙️ Requirements

- **go 1.18 or higher**
- **go 1.19 or higher**

## 📦 Installation

Expand Down
8 changes: 8 additions & 0 deletions utils/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
### Changed

## [v2.1.0] 2025-11-XX
### Added
- [PKG-18] Set minimum go version as 1.23
- [PKG-18] Add struct OrderedMap[K]V;
- [PKG-28] Add struct ObjectPool[T];
### Fixed
### Changed

## [v2.0.0] 2025-10-23
### Added
- Set minimum go version as 1.22
Expand Down
115 changes: 108 additions & 7 deletions utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
- [Examples](#-examples)
- [Package: `errx`](#-package-errx)
- [MultiError](#multierror)
- [Package: `structx`](#-package-structx)
- [OrderedMap](#orderedmap)
- [Roadmap](#️-roadmap)
- [License](#-license)

Expand All @@ -34,7 +36,7 @@ For full documentation, see [https://pkg.go.dev/github.com/lif0/pkg/utils](https

## ⚙️ Requirements

- **go 1.22 or higher**
- **go 1.23 or higher**

## 📦 Installation

Expand Down Expand Up @@ -169,6 +171,8 @@ size := EstimatePayloadOf(&arr)

## 📚 Package `errx`

Provide additional feature for error.

### MultiError

MultiError is a slice of errors implementing the error interface.
Expand Down Expand Up @@ -210,16 +214,113 @@ for _, job := range jobs {
return me.MaybeUnwrap()
```

## 🗺️ Roadmap
## 📚 Package `structx`

Provide additional golang type.

### OrderedMap

OrderedMap is a map[Type]Type1-like collection that preserves the order in which keys were inserted. It behaves like a regular map but allows deterministic iteration over its elements.

Useful:
Imagine you are making a closer or graceful shutdown lib, and you need to register/unregister some functions/service in it, and finally handle them in the order they were added. Use it structure. You are welcome🤗

The structure provide provice

#### API

| Func | Complexity (time / mem) |
| ---------------------------------------------------------------------- | ---------------------------- |
| `(m *OrderedMap[K, V]) Get(key K) (V, bool)` | O(1) / O(1) |
| `(m *OrderedMap[K, V]) Put(key K, value V)` | O(1) / O(1) |
| `(m *OrderedMap[K, V]) GetValues() []V` | O(N) / O(N) |
| `(m *OrderedMap[K, V]) Iter() []V` | for k,v := range m.Iter() {} |
| `structx.Delete[K comparable, V any](m *OrderedMap[K, V], key K)` | O(1) / O(1) |


#### Benchmarks: OrderedMap[Type, Type1] vs map[Type]Type1

Environment:

```text
goos: darwin
goarch: arm64
cpu: Apple M2
pkg: github.com/lif0/pkg/utils/structx
```

##### TL;DR

- Inserts (`put`): `map` is faster and uses less memory.
- Lookups (`get_hit`): `OrderedMap` is faster on string keys; a bit slower on int keys.
- Deletes (`delete`): almost the same.
- Iteration (`iterate_values`): OrderedMap is much faster and ordered.

---

##### Key/Value: `int, int`

| Operation | ns/op (`OrderedMap`) | ns/op (`map`) | B/op (`OrderedMap`) | B/op (`map`) | allocs/op (`OrderedMap`) | allocs/op (`map`) | time (`OrderedMap` vs `map`) |
| -------------- | --------------: | ------------: | -------------: | -----------: | ------------------: | ----------------: | -------------------------: |
| put | 220,267 | 100,546 | 705,330 | 295,557 | 39 | 33 | **+119.1%** (2.19× slower) |
| get_hit | 74,626 | 65,668 | 0 | 0 | 0 | 0 | **+13.6%** (1.14× slower) |
| delete | 19,322 | 19,348 | 0 | 0 | 0 | 0 | **−0.1%** (≈ same) |
| iterate_values | 11,131 | 61,998 | 0 | 0 | 0 | 0 | **−82.0%** (5.6× faster) |

The future direction of this package is community-driven! Ideas and contributions are highly welcome.
##### Key/Value: `string, []string`

☹️ No idea
| Operation | ns/op (`OrderedMap`) | ns/op (`map`) | B/op (`OrderedMap`) | B/op (`map`) | allocs/op (`OrderedMap`) | allocs/op (`map`) | time (Ordered vs `map`) |
| -------------- | --------------: | ------------: | -------------: | -----------: | ------------------: | ----------------: | ------------------------: |
| put | 507,196 | 360,451 | 1,084,229 | 787,101 | 40 | 33 | **+40.7%** (1.41× slower) |
| get_hit | 136,184 | 193,829 | 0 | 0 | 0 | 0 | **−29.7%** (1.43× faster) |
| delete | 20,713 | 20,758 | 0 | 0 | 0 | 0 | **−0.2%** (≈ same) |
| iterate_values | 17,822 | 63,645 | 0 | 0 | 0 | 0 | **−72.0%** (3.6× faster) |

Contributions and ideas are welcome! 🤗
##### Key/Value: `string, ComplexStruct`

| Operation | ns/op (`OrderedMap`) | ns/op (`map`) | B/op (`OrderedMap`) | B/op (`map`) | allocs/op (`OrderedMap`) | allocs/op (`map`) | time (Ordered vs `map`) |
| -------------- | --------------: | ------------: | -------------: | -----------: | ------------------: | ----------------: | ------------------------: |
| put | 493,887 | 329,433 | 1,166,137 | 918,167 | 40 | 33 | **+49.9%** (1.50× slower) |
| get_hit | 117,553 | 174,528 | 0 | 0 | 0 | 0 | **−32.6%** (1.49× faster) |
| delete | 20,471 | 20,420 | 0 | 0 | 0 | 0 | **+0.2%** (≈ same) |
| iterate_values | 19,729 | 62,435 | 0 | 0 | 0 | 0 | **−68.4%** (3.2× faster) |

##### How to run

```bash
go test -benchmem -run=^$ -bench ^Benchmark_OrderedMap -v github.com/lif0/pkg/utils/structx
```


#### Examples

```go
import "github.com/lif0/pkg/utils/structx"


func main() {
m := structx.NewOrderedMap[string, int]()

m.Put("key", 10)

v, ok := m.Get("key") // v = 10

structx.Delete(m, "key") // or build-in func m.Delete("key"), but prefer build-in function.

for k,v := range m.Iter() {
fmt.Println(k,v)
}
}
```

## 🗺️ Roadmap

- [ ] improve Object Order
- [ ] improve perf for OrderedMap

---

**Contributions:**
Feel free to open an Issue to discuss a new idea or a Pull Request to implement it! 🤗
Contributions and feature suggestions are welcome 🤗.

---

Expand Down
2 changes: 1 addition & 1 deletion utils/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/lif0/pkg/utils

go 1.19
go 1.23

require github.com/stretchr/testify v1.11.1

Expand Down
90 changes: 90 additions & 0 deletions utils/internal/chain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package internal

type Chain[T any] struct {
size int
head *ChainLink[T]
tail *ChainLink[T]
}

type ChainLink[T any] struct {
Val T
Prev *ChainLink[T]
Next *ChainLink[T]
}

// Remove ...
// time: O(1); mem: O(1)
func (c *Chain[T]) Remove(node *ChainLink[T]) {
if node == nil {
return
}

// If a previous node exists, link it to the next one, skipping the current node.
if node.Prev != nil {
node.Prev.Next = node.Next
}

// If a next node exists, set its previous pointer to the current node’s previous.
if node.Next != nil {
node.Next.Prev = node.Prev
}

// if the node and the head is equal, set to head node's next.
if node == c.head {
c.head = c.head.Next
}

// if the node and the tail is equal, set to tail node's previous.
if node == c.tail {
c.tail = c.tail.Prev
}

c.size -= 1
}

// Append ...
// time: O(1); mem: O(1)
func (c *Chain[T]) Append(node *ChainLink[T]) {
if c.tail == nil {
c.head = node
c.tail = node
} else {
c.tail.Next = node
node.Prev = c.tail
c.tail = node
}

c.size += 1
}

// GetHead ...
// time: O(1); mem: O(1)
func (c *Chain[T]) GetHead() *ChainLink[T] {
return c.head
}

// Len ...
// time: O(1); mem: O(1)
func (c *Chain[T]) Len() int {
return c.size
}

// Iter iteration on chain
//
// Example:
//
// m := Chain[int, string]()
// for i, v := range m.Iter() {
// fmt.Println(i,v)
// }
func (c *Chain[T]) Iter() func(func(int, T) bool) {
return func(yield func(int, T) bool) {
i := 0
for n := c.head; n != nil; n = n.Next {
if !yield(i, n.Val) {
return
}
i++
}
}
}
Loading