mirror of
https://github.com/duke-git/lancet.git
synced 2026-11-16 05:12:11 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb9ad10f25 | ||
|
|
22316061d8 | ||
|
|
5774d0e95e | ||
|
|
9555cb2fc5 | ||
|
|
9b0a0caa75 | ||
|
|
ec11fdf345 | ||
|
|
172712df30 | ||
|
|
a296281408 | ||
|
|
f220ea4c3f | ||
|
|
129da3f2ed | ||
|
|
65806afc7d | ||
|
|
4db163b4b7 | ||
|
|
0f4f73fbba | ||
|
|
145959ee62 | ||
|
|
33a7e5bf6d | ||
|
|
99ed115de4 | ||
|
|
34c7e36d29 | ||
|
|
59cd9bd06f | ||
|
|
18e9bc9cda | ||
|
|
b282ada39e | ||
|
|
b40eea78c3 | ||
|
|
c701a35586 | ||
|
|
778bd2e14f | ||
|
|
86dc6c0019 | ||
|
|
417cf68bd8 | ||
|
|
cb45ef2d36 | ||
|
|
3811d3db8e | ||
|
|
e2524f4498 | ||
|
|
7c77aa00fb | ||
|
|
eb3b1e33d2 | ||
|
|
752f1ed63d | ||
|
|
5c04ffae97 | ||
|
|
f1db13387b | ||
|
|
be9b167a9b | ||
|
|
febd1030af | ||
|
|
fe4c81a677 | ||
|
|
8225b6d7d9 | ||
|
|
6ff2966d50 | ||
|
|
70b8fc764e | ||
|
|
f525703d9f | ||
|
|
be3bc0c43f | ||
|
|
9685357daf | ||
|
|
9a8b67ac85 | ||
|
|
19ccb13893 | ||
|
|
ef22b5b339 | ||
|
|
3b94ec1932 | ||
|
|
e49a01e725 | ||
|
|
13b7b1c61e | ||
|
|
227501958f | ||
|
|
835111c832 | ||
|
|
747dddee8b |
No files matched your search
+2176
-2176
File diff suppressed because it is too large.
Load diff
@@ -77,6 +77,7 @@ func (l *LRUCache[K, V]) Delete(key K) bool {
|
||||
if ok {
|
||||
key := l.deleteNode(node)
|
||||
delete(l.cache, key)
|
||||
l.length = len(l.cache)
|
||||
return true
|
||||
}
|
||||
l.length = len(l.cache)
|
||||
|
||||
@@ -32,3 +32,27 @@ func TestLRUCache(t *testing.T) {
|
||||
_, ok = cache.Get(2)
|
||||
assert.Equal(false, ok)
|
||||
}
|
||||
|
||||
func TestLRUCacheDeleteLen(t *testing.T) {
|
||||
t.Parallel()
|
||||
cache := NewLRUCache[int, int](3)
|
||||
for key := 1; key <= 3; key++ {
|
||||
cache.Put(key, key)
|
||||
}
|
||||
|
||||
for _, key := range []int{2, 1, 3} {
|
||||
before := cache.Len()
|
||||
if !cache.Delete(key) {
|
||||
t.Fatalf("Delete(%d) = false, want true", key)
|
||||
}
|
||||
if got := cache.Len(); got != before-1 {
|
||||
t.Fatalf("Len() after Delete(%d) = %d, want %d", key, got, before-1)
|
||||
}
|
||||
if cache.Delete(key) {
|
||||
t.Fatalf("Delete(%d) = true for a missing key", key)
|
||||
}
|
||||
if got := cache.Len(); got != before-1 {
|
||||
t.Fatalf("Len() after missing Delete(%d) = %d, want %d", key, got, before-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,9 @@ func BinarySearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, com
|
||||
// If not found return -1.
|
||||
// Play: https://go.dev/play/p/Anozfr8ZLH3
|
||||
func BinaryIterativeSearch[T any](sortedSlice []T, target T, lowIndex, highIndex int, comparator constraints.Comparator) int {
|
||||
if len(sortedSlice) == 0 {
|
||||
return -1
|
||||
}
|
||||
startIndex := lowIndex
|
||||
endIndex := highIndex
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package algorithm
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBinaryIterativeSearchEmpty(t *testing.T) {
|
||||
for _, source := range [][]int{nil, {}} {
|
||||
if got := BinaryIterativeSearch(source, 1, 0, 0, &intComparator{}); got != -1 {
|
||||
t.Errorf("got %d, want -1", got)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -188,7 +188,8 @@ func CountSort[T any](slice []T, comparator constraints.Comparator) []T {
|
||||
count := 0
|
||||
for j := 0; j < size; j++ {
|
||||
//slice[i] > slice[j]
|
||||
if comparator.Compare(slice[i], slice[j]) == 1 {
|
||||
order := comparator.Compare(slice[i], slice[j])
|
||||
if order == 1 || (order == 0 && j < i) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +199,9 @@ func TestMergeSort(t *testing.T) {
|
||||
func TestCountSort(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestCountSort")
|
||||
assert.Equal([]int{1, 2, 2}, CountSort([]int{2, 1, 2}, &intComparator{}))
|
||||
assert.Equal([]people{{"young", 10}, {"first", 20}, {"second", 20}},
|
||||
CountSort([]people{{"first", 20}, {"young", 10}, {"second", 20}}, &peopleAgeComparator{}))
|
||||
|
||||
peoples := []people{
|
||||
{Name: "a", Age: 20},
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package convertor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestDeepCloneScalarTypes(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestDeepCloneScalarTypes")
|
||||
type label string
|
||||
type counter int64
|
||||
assert.Equal(label("value"), DeepClone(label("value")))
|
||||
assert.Equal(counter(42), DeepClone(counter(42)))
|
||||
assert.Equal(uintptr(42), DeepClone(uintptr(42)))
|
||||
value := label("pointed")
|
||||
cloned := DeepClone(&value)
|
||||
assert.Equal(value, *cloned)
|
||||
*cloned = "changed"
|
||||
assert.Equal(label("pointed"), value)
|
||||
var empty any
|
||||
assert.Equal(&empty, DeepClone(&empty))
|
||||
}
|
||||
@@ -16,49 +16,10 @@ func (c *cloner) clone(v reflect.Value) reflect.Value {
|
||||
case reflect.Invalid:
|
||||
return reflect.ValueOf(nil)
|
||||
|
||||
// bool
|
||||
case reflect.Bool:
|
||||
return reflect.ValueOf(v.Bool())
|
||||
|
||||
//int
|
||||
case reflect.Int:
|
||||
return reflect.ValueOf(int(v.Int()))
|
||||
case reflect.Int8:
|
||||
return reflect.ValueOf(int8(v.Int()))
|
||||
case reflect.Int16:
|
||||
return reflect.ValueOf(int16(v.Int()))
|
||||
case reflect.Int32:
|
||||
return reflect.ValueOf(int32(v.Int()))
|
||||
case reflect.Int64:
|
||||
return reflect.ValueOf(v.Int())
|
||||
|
||||
// uint
|
||||
case reflect.Uint:
|
||||
return reflect.ValueOf(uint(v.Uint()))
|
||||
case reflect.Uint8:
|
||||
return reflect.ValueOf(uint8(v.Uint()))
|
||||
case reflect.Uint16:
|
||||
return reflect.ValueOf(uint16(v.Uint()))
|
||||
case reflect.Uint32:
|
||||
return reflect.ValueOf(uint32(v.Uint()))
|
||||
case reflect.Uint64:
|
||||
return reflect.ValueOf(v.Uint())
|
||||
|
||||
// float
|
||||
case reflect.Float32:
|
||||
return reflect.ValueOf(float32(v.Float()))
|
||||
case reflect.Float64:
|
||||
return reflect.ValueOf(v.Float())
|
||||
|
||||
// complex
|
||||
case reflect.Complex64:
|
||||
return reflect.ValueOf(complex64(v.Complex()))
|
||||
case reflect.Complex128:
|
||||
return reflect.ValueOf(v.Complex())
|
||||
|
||||
// string
|
||||
case reflect.String:
|
||||
return reflect.ValueOf(v.String())
|
||||
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String:
|
||||
return v
|
||||
|
||||
// array
|
||||
case reflect.Array, reflect.Slice:
|
||||
@@ -90,11 +51,16 @@ func (c *cloner) clone(v reflect.Value) reflect.Value {
|
||||
}
|
||||
|
||||
func (c *cloner) cloneArray(v reflect.Value) reflect.Value {
|
||||
if v.IsNil() {
|
||||
if v.Kind() == reflect.Slice && v.IsNil() {
|
||||
return reflect.Zero(v.Type())
|
||||
}
|
||||
|
||||
arr := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
|
||||
var arr reflect.Value
|
||||
if v.Kind() == reflect.Array {
|
||||
arr = reflect.New(v.Type()).Elem()
|
||||
} else {
|
||||
arr = reflect.MakeSlice(v.Type(), v.Len(), v.Len())
|
||||
}
|
||||
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
val := c.clone(v.Index(i))
|
||||
@@ -179,8 +145,10 @@ func (c *cloner) clonePtr(v reflect.Value) reflect.Value {
|
||||
}
|
||||
}
|
||||
|
||||
clonedPtr := reflect.New(newVal.Type())
|
||||
clonedPtr.Elem().Set(newVal)
|
||||
clonedPtr := reflect.New(v.Type().Elem())
|
||||
if newVal.IsValid() {
|
||||
clonedPtr.Elem().Set(newVal)
|
||||
}
|
||||
|
||||
return clonedPtr
|
||||
}
|
||||
|
||||
@@ -394,6 +394,18 @@ func TestDecodeByte(t *testing.T) {
|
||||
assert.Equal("abc", obj)
|
||||
}
|
||||
|
||||
func TestDeepCloneArrays(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestDeepCloneArrays")
|
||||
value := 42
|
||||
type pair [2]*int
|
||||
src := pair{&value, nil}
|
||||
got := DeepClone(src)
|
||||
assert.Equal(src, got)
|
||||
*got[0] = 99
|
||||
assert.Equal(42, *src[0])
|
||||
assert.Equal([0]int{}, DeepClone([0]int{}))
|
||||
}
|
||||
|
||||
func TestDeepClone(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
-----BEGIN rsa private key-----
|
||||
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
|
||||
-----END rsa private key-----
|
||||
@@ -1,14 +1,14 @@
|
||||
-----BEGIN rsa public key-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuzBz+aC+e7Lvny2zYlcy
|
||||
AfG6AAtPkxZqJ9JkYkM+0CP87pe0xOXQh4dz9iJekOwAq7FKpasUEUzkTm6Z0PUo
|
||||
j/TWY/xpoPNXXzz/5dz3u6r/A5TumJ6BmX/7K/x8FsokIeP+lWaN1l+7uBKK8rgf
|
||||
m4AZOXd/plzBkTrnu6lKG/rH9cnr2leKWDqk2jcG6r15r/07MdStpWgt0OBYoHzv
|
||||
jLJWmJ08VrnF9PFtWhL939xSAIicFzJ0T9fAdmSSYmg22mKgN1zeWtZndJ/Ejv+5
|
||||
YlWmuFJ8YKvwR0+4XIRLX9qsMy7qPTh7zsZhgtKzyb7qvVqDh2pmwekGJcwxGcoC
|
||||
LHLqNKf/dD+4vXxgS+f1ObfOcJpDqBajD/U6BXtrZ/p4cvoYnZA383YR/CRG/nJ8
|
||||
jIvt2FutT4hsNSd0L6c2mfo5mTnoDOEL3mkreZ4Az+GE57jMw1Ia9jwkM1QQoy0a
|
||||
+kTiW8BedNqcRnVVGT9/OS/ggyKFwxJ/Xh1DfxZXAuyCBRUJUyVl9YCr2y30zngu
|
||||
CdaTTViA9UbrjtcE2bZtnqOMAU2s08F0IiaGLKKMhrxUoXLgngXSX7gomC4aEfcg
|
||||
5hf7ft6FA+bXB9DHwGdv/UyrGr88nve5um1OT5kmyOujKpka4QZ5/rU+RznBE0UW
|
||||
DcHAyc+Zv+te0DqPUNcAW5ECAwEAAQ==
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4TSGNCTws7pRURptw911
|
||||
vSr9rISCcjSgpjxVFtxkDpri/L+QHwDaDpTnAx2FKfFkBbYNklSl3JtY4vZQ8hND
|
||||
vfHbwt+PagbNaeiu5l1agl7s2ssWSXQ2dIKx45gtQxsuvAVGvmdgVEuyt/UwJrHg
|
||||
cyt5cqYejtRI5rcj509Pxegb3B84gkPwgy6xb2jYWBZ+pi6aLdm2TGX8SCsbCJcO
|
||||
5SD3GFI4fG9OhsSaToD0/OSYzIbQ8WP6D7KX3y5tVHFqV6eFHT2ZIskDRzN8z6cD
|
||||
xp+K2QcCtv5F7yqnjVBYbnk0dmC/woAaNMgvpSaC94cv0oFOS1TWivZDgpLkUJL2
|
||||
hu4V1xGflkHVAdz/DlSUIfiJ47sLHOHnjBQXY81ruh10Xwf7Sh0P9TfNod0KITSZ
|
||||
aqXljYaomEfMOXHn8t1xxy6mAF/cZf/nSmXn+r3ue+cYHxPD0sHYQQqCCAAsNO/V
|
||||
T0Z1NXzjLwc497LwrfXnin0J2EGDyNQf2LKCj8JhICRw72tMrzp8XAHodfxWNQzP
|
||||
L26k7ZzIGUUmlMn7ku0WcU2BsiGnSXNPlFmeOqOzfdmgmiERbQPd3uzZ3a7pxhWl
|
||||
KGZSakZPEJeZHDEobmm+uBkr4OzIgc/jBNp3xa3wXiPxftzoPB3TpUQdN/mgW/De
|
||||
HMS7V56ie3vqYZQG1vUC7rsCAwEAAQ==
|
||||
-----END rsa public key-----
|
||||
@@ -239,8 +239,8 @@ func (l *List[T]) Merge(other *List[T]) *List[T] {
|
||||
l1, l2 := len(l.data), len(other.data)
|
||||
ml := NewList(make([]T, l1+l2))
|
||||
|
||||
data := append([]T{}, append(l.data, other.data...)...)
|
||||
ml.data = data
|
||||
copy(ml.data, l.data)
|
||||
copy(ml.data[l1:], other.data)
|
||||
|
||||
return ml
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package datastructure
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestMergePreservesBackingStorage(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestMergePreservesBackingStorage")
|
||||
backing := []int{1, 99, 100}
|
||||
left := NewList(backing[:1])
|
||||
merged := left.Merge(NewList([]int{2, 3}))
|
||||
assert.Equal([]int{1, 2, 3}, merged.Data())
|
||||
assert.Equal([]int{1, 99, 100}, backing)
|
||||
merged.UpdateAt(0, 42)
|
||||
assert.Equal([]int{1}, left.Data())
|
||||
}
|
||||
@@ -136,8 +136,8 @@ func (q *ArrayQueue[T]) Clear() {
|
||||
|
||||
// Contain checks if the value is in queue or not
|
||||
func (q *ArrayQueue[T]) Contain(value T) bool {
|
||||
for _, v := range q.data {
|
||||
if reflect.DeepEqual(v, value) {
|
||||
for i := q.head; i < q.tail; i++ {
|
||||
if reflect.DeepEqual(q.data[i], value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func (q *CircularQueue[T]) IsEmpty() bool {
|
||||
|
||||
// IsFull checks if queue is full or not
|
||||
func (q *CircularQueue[T]) IsFull() bool {
|
||||
return (q.rear+1)%q.capacity == q.front
|
||||
return q.capacity == 0 || (q.rear+1)%q.capacity == q.front
|
||||
}
|
||||
|
||||
// Front return front value of queue
|
||||
@@ -101,16 +101,15 @@ func (q *CircularQueue[T]) Dequeue() (*T, error) {
|
||||
|
||||
// Clear the queue data
|
||||
func (q *CircularQueue[T]) Clear() {
|
||||
q.data = []T{}
|
||||
q.data = make([]T, q.capacity)
|
||||
q.front = 0
|
||||
q.rear = 0
|
||||
q.capacity = 0
|
||||
}
|
||||
|
||||
// Contain checks if the value is in queue or not
|
||||
func (q *CircularQueue[T]) Contain(value T) bool {
|
||||
for _, v := range q.data {
|
||||
if reflect.DeepEqual(v, value) {
|
||||
for i := q.front; i != q.rear; i = (i + 1) % q.capacity {
|
||||
if reflect.DeepEqual(q.data[i], value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package datastructure
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCircularQueueReuseAfterClear(t *testing.T) {
|
||||
queue := NewCircularQueue[int](3)
|
||||
queue.Enqueue(1)
|
||||
queue.Clear()
|
||||
if err := queue.Enqueue(2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if queue.Size() != 1 || queue.Front() != 2 {
|
||||
t.Error("cleared queue could not be reused")
|
||||
}
|
||||
zero := NewCircularQueue[int](0)
|
||||
if !zero.IsFull() || zero.Enqueue(1) == nil {
|
||||
t.Error("zero-capacity queue should reject an element")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package datastructure
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestQueueContainActiveValues(t *testing.T) {
|
||||
array := NewArrayQueue[int](5)
|
||||
array.Enqueue(1)
|
||||
array.Enqueue(2)
|
||||
array.Dequeue()
|
||||
if array.Contain(1) || !array.Contain(2) {
|
||||
t.Error("array queue includes a dequeued value")
|
||||
}
|
||||
circular := NewCircularQueue[int](4)
|
||||
if circular.Contain(0) {
|
||||
t.Error("empty circular queue contains a zero value")
|
||||
}
|
||||
circular.Enqueue(1)
|
||||
circular.Enqueue(2)
|
||||
circular.Dequeue()
|
||||
circular.Enqueue(3)
|
||||
circular.Enqueue(4)
|
||||
if circular.Contain(0) || circular.Contain(1) || !circular.Contain(4) {
|
||||
t.Error("circular queue did not restrict its search to active values")
|
||||
}
|
||||
}
|
||||
+11
-12
@@ -207,7 +207,7 @@ func FormatTimeToStr(t time.Time, format string, timezone ...string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
if len(timezone) > 0 && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return ""
|
||||
@@ -225,7 +225,7 @@ func FormatStrToTime(str, format string, timezone ...string) (time.Time, error)
|
||||
return time.Time{}, fmt.Errorf("format %s not support", format)
|
||||
}
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
if len(timezone) > 0 && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
@@ -343,11 +343,7 @@ func BetweenSeconds(t1 time.Time, t2 time.Time) int64 {
|
||||
// DayOfYear returns which day of the year the parameter date `t` is.
|
||||
// Play: https://go.dev/play/p/0hjqhTwFNlH
|
||||
func DayOfYear(t time.Time) int {
|
||||
y, m, d := t.Date()
|
||||
firstDay := time.Date(y, 1, 1, 0, 0, 0, 0, t.Location())
|
||||
nowDate := time.Date(y, m, d, 0, 0, 0, 0, t.Location())
|
||||
|
||||
return int(nowDate.Sub(firstDay).Hours() / 24)
|
||||
return t.YearDay() - 1
|
||||
}
|
||||
|
||||
// IsWeekend checks if passed time is weekend or not.
|
||||
@@ -365,7 +361,7 @@ func NowDateOrTime(format string, timezone ...string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
if len(timezone) > 0 && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return ""
|
||||
@@ -382,7 +378,7 @@ func NowDateOrTime(format string, timezone ...string) string {
|
||||
func Timestamp(timezone ...string) int64 {
|
||||
t := time.Now()
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
if len(timezone) > 0 && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return 0
|
||||
@@ -399,7 +395,7 @@ func Timestamp(timezone ...string) int64 {
|
||||
func TimestampMilli(timezone ...string) int64 {
|
||||
t := time.Now()
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
if len(timezone) > 0 && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return 0
|
||||
@@ -415,7 +411,7 @@ func TimestampMilli(timezone ...string) int64 {
|
||||
func TimestampMicro(timezone ...string) int64 {
|
||||
t := time.Now()
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
if len(timezone) > 0 && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return 0
|
||||
@@ -431,7 +427,7 @@ func TimestampMicro(timezone ...string) int64 {
|
||||
func TimestampNano(timezone ...string) int64 {
|
||||
t := time.Now()
|
||||
|
||||
if timezone != nil && timezone[0] != "" {
|
||||
if len(timezone) > 0 && timezone[0] != "" {
|
||||
loc, err := time.LoadLocation(timezone[0])
|
||||
if err != nil {
|
||||
return 0
|
||||
@@ -495,6 +491,9 @@ func GenerateDatetimesBetween(start, end time.Time, layout string, interval stri
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if duration <= 0 {
|
||||
return nil, fmt.Errorf("interval must be positive")
|
||||
}
|
||||
|
||||
for current := start; !current.After(end); current = current.Add(duration) {
|
||||
result = append(result, current.Format(layout))
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package datetime
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
func TestDayOfYearDaylightSaving(t *testing.T) {
|
||||
loc, err := time.LoadLocation("America/New_York")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, month := range []time.Month{time.January, time.July, time.December} {
|
||||
date := time.Date(2024, month, 1, 12, 0, 0, 0, loc)
|
||||
if got, want := DayOfYear(date), date.YearDay()-1; got != want {
|
||||
t.Errorf("%v: got %d, want %d", date, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package datetime
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGenerateDatetimesNonPositiveInterval(t *testing.T) {
|
||||
start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
for _, interval := range []string{"0s", "-1h"} {
|
||||
t.Run(interval, func(t *testing.T) {
|
||||
if _, err := GenerateDatetimesBetween(start, start.Add(time.Hour), "2006-01-02", interval); err == nil {
|
||||
t.Error("expected an error for a non-positive interval")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package datetime
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEmptyTimezoneOptions(t *testing.T) {
|
||||
empty := []string{}
|
||||
now := time.Now()
|
||||
if got := FormatTimeToStr(now, "yyyy-mm-dd", empty...); got != FormatTimeToStr(now, "yyyy-mm-dd") {
|
||||
t.Errorf("unexpected date: %s", got)
|
||||
}
|
||||
if _, err := FormatStrToTime("2026-01-02", "yyyy-mm-dd", empty...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if NowDateOrTime("yyyy", empty...) == "" {
|
||||
t.Error("empty date")
|
||||
}
|
||||
for _, fn := range []func(...string) int64{Timestamp, TimestampMilli, TimestampMicro, TimestampNano} {
|
||||
if fn(empty...) == 0 {
|
||||
t.Error("empty timestamp")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,7 +276,7 @@ func main() {
|
||||
func UpperCamelCase(s string) string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/65Q6iYhLRTa)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
|
||||
@@ -276,7 +276,7 @@ func main() {
|
||||
func UpperCamelCase(s string) string
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/vPHszjTXSFp)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
|
||||
+9
-20
@@ -229,6 +229,13 @@ func CopyFile(srcPath string, dstPath string) error {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
srcInfo, err := srcFile.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dstInfo, err := os.Stat(dstPath); err == nil && os.SameFile(srcInfo, dstInfo) {
|
||||
return errors.New("source and destination are the same file")
|
||||
}
|
||||
|
||||
distFile, err := os.Create(dstPath)
|
||||
if err != nil {
|
||||
@@ -754,6 +761,8 @@ func WriteCsvFile(filepath string, records [][]string, append bool, delimiter ..
|
||||
|
||||
if append {
|
||||
flag = flag | os.O_APPEND
|
||||
} else {
|
||||
flag = flag | os.O_TRUNC
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(filepath, flag, 0644)
|
||||
@@ -771,13 +780,6 @@ func WriteCsvFile(filepath string, records [][]string, append bool, delimiter ..
|
||||
writer.Comma = ','
|
||||
}
|
||||
|
||||
// 遍历所有记录并处理包含分隔符或双引号的单元格
|
||||
for i := range records {
|
||||
for j := range records[i] {
|
||||
records[i][j] = escapeCSVField(records[i][j], writer.Comma)
|
||||
}
|
||||
}
|
||||
|
||||
return writer.WriteAll(records)
|
||||
}
|
||||
|
||||
@@ -836,19 +838,6 @@ func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// escapeCSVField 处理单元格内容,如果包含分隔符,则用双引号包裹
|
||||
func escapeCSVField(field string, delimiter rune) string {
|
||||
// 替换所有的双引号为两个双引号
|
||||
escapedField := strings.ReplaceAll(field, "\"", "\"\"")
|
||||
|
||||
// 如果字段包含分隔符、双引号或换行符,用双引号包裹整个字段
|
||||
if strings.ContainsAny(escapedField, string(delimiter)+"\"\n") {
|
||||
escapedField = fmt.Sprintf("\"%s\"", escapedField)
|
||||
}
|
||||
|
||||
return escapedField
|
||||
}
|
||||
|
||||
// WriteMapsToCsv write slice of map to csv file.
|
||||
// Play: https://go.dev/play/p/umAIomZFV1c
|
||||
// filepath: Path to the CSV file.
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
@@ -430,7 +432,12 @@ func ExampleWriteBytesToFile() {
|
||||
}
|
||||
|
||||
func ExampleReadFile() {
|
||||
reader, fn, err := ReadFile("https://httpbin.org/robots.txt")
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "User-agent: *\nDisallow: /deny\n")
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
reader, fn, err := ReadFile(ts.URL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
+68
-2
@@ -1,7 +1,10 @@
|
||||
package fileutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -136,6 +139,34 @@ func TestCopyFile(t *testing.T) {
|
||||
os.Remove(destFile)
|
||||
}
|
||||
|
||||
func TestCopyFilePreservesSource(t *testing.T) {
|
||||
for _, mode := range []string{"same", "hardlink", "different"} {
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestCopyFilePreservesSource")
|
||||
src := filepath.Join(t.TempDir(), "source.txt")
|
||||
assert.IsNil(os.WriteFile(src, []byte("keep this content"), 0600))
|
||||
dst := src + ".copy"
|
||||
if mode == "same" {
|
||||
dst = src
|
||||
} else if mode == "hardlink" {
|
||||
if err := os.Link(src, dst); err != nil {
|
||||
t.Skipf("hard links unavailable: %v", err)
|
||||
}
|
||||
}
|
||||
err := CopyFile(src, dst)
|
||||
if (err != nil) != (mode != "different") {
|
||||
t.Errorf("unexpected copy result: %v", err)
|
||||
}
|
||||
data, err := os.ReadFile(src)
|
||||
assert.IsNil(err)
|
||||
assert.Equal("keep this content", string(data))
|
||||
data, err = os.ReadFile(dst)
|
||||
assert.IsNil(err)
|
||||
assert.Equal("keep this content", string(data))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFileToString(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestReadFileToString")
|
||||
|
||||
@@ -415,6 +446,36 @@ func TestWriteCsvFile(t *testing.T) {
|
||||
// RemoveFile(csvFilePath)
|
||||
}
|
||||
|
||||
func TestWriteCsvFileRoundTrip(t *testing.T) {
|
||||
for _, delimiter := range []rune{',', ';'} {
|
||||
t.Run(string(delimiter), func(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestWriteCsvFileRoundTrip")
|
||||
path := filepath.Join(t.TempDir(), "quoted.csv")
|
||||
want := [][]string{{"a,b;c", "\"quoted\"", "two\nlines"}}
|
||||
records := [][]string{append([]string(nil), want[0]...)}
|
||||
assert.IsNil(WriteCsvFile(path, records, false, delimiter))
|
||||
got, err := ReadCsvFile(path, delimiter)
|
||||
assert.IsNil(err)
|
||||
assert.Equal(want, got)
|
||||
assert.Equal(want, records)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteCsvFileOverwriteAndAppend(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestWriteCsvFileOverwriteAndAppend")
|
||||
path := filepath.Join(t.TempDir(), "overwrite.csv")
|
||||
assert.IsNil(os.WriteFile(path, []byte(strings.Repeat("old,value\n", 10)), 0600))
|
||||
assert.IsNil(WriteCsvFile(path, [][]string{{"new", "row"}}, false))
|
||||
got, err := ReadCsvFile(path)
|
||||
assert.IsNil(err)
|
||||
assert.Equal([][]string{{"new", "row"}}, got)
|
||||
assert.IsNil(WriteCsvFile(path, [][]string{{"appended", "row"}}, true))
|
||||
got, err = ReadCsvFile(path)
|
||||
assert.IsNil(err)
|
||||
assert.Equal([][]string{{"new", "row"}, {"appended", "row"}}, got)
|
||||
}
|
||||
|
||||
func TestWriteMapsToCsv(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestWriteMapsToCSV")
|
||||
|
||||
@@ -516,9 +577,14 @@ func TestWriteBytesToFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReadFile(t *testing.T) {
|
||||
reader, close, err := ReadFile("https://httpbin.org/robots.txt")
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "User-agent: *\nDisallow: /deny\n")
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
reader, close, err := ReadFile(ts.URL)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer close()
|
||||
|
||||
|
||||
@@ -26,8 +26,9 @@ func Comma[T constraints.Float | constraints.Integer | string](value T, prefixSy
|
||||
return ""
|
||||
}
|
||||
|
||||
isNegative := strings.HasPrefix(numString, "-")
|
||||
if isNegative {
|
||||
sign := ""
|
||||
if strings.HasPrefix(numString, "-") || strings.HasPrefix(numString, "+") {
|
||||
sign = numString[:1]
|
||||
numString = numString[1:]
|
||||
}
|
||||
|
||||
@@ -41,11 +42,7 @@ func Comma[T constraints.Float | constraints.Integer | string](value T, prefixSy
|
||||
numString = numString[:index] + "," + numString[index:]
|
||||
}
|
||||
|
||||
if isNegative {
|
||||
numString = "-" + numString
|
||||
}
|
||||
|
||||
return prefixSymbol + numString
|
||||
return prefixSymbol + sign + numString
|
||||
}
|
||||
|
||||
// Pretty data to JSON string.
|
||||
|
||||
@@ -11,6 +11,9 @@ func TestComma(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestComma")
|
||||
assert.Equal("+123", Comma("+123", ""))
|
||||
assert.Equal("+123,456", Comma("+123456", ""))
|
||||
assert.Equal("$+123,456.78", Comma("+123456.78", "$"))
|
||||
|
||||
assert.Equal("", Comma("", ""))
|
||||
assert.Equal("", Comma("aa", ""))
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package iterator
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTakeHasNextExhaustedSource(t *testing.T) {
|
||||
it := Take[int](FromSlice([]int{1}), 3)
|
||||
if value, ok := it.Next(); !ok || value != 1 {
|
||||
t.Fatal("missing first value")
|
||||
}
|
||||
if it.HasNext() {
|
||||
t.Error("exhausted source still reports an element")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterHasNextMatches(t *testing.T) {
|
||||
it := Filter[int](FromSlice([]int{1, 2, 3}), func(value int) bool { return value%2 == 0 })
|
||||
if !it.HasNext() || !it.HasNext() {
|
||||
t.Fatal("missing matching value")
|
||||
}
|
||||
if value, ok := it.Next(); !ok || value != 2 {
|
||||
t.Fatal("HasNext consumed the matching value")
|
||||
}
|
||||
if it.HasNext() {
|
||||
t.Error("unmatched tail reported as another element")
|
||||
}
|
||||
}
|
||||
+13
-2
@@ -44,9 +44,17 @@ func Filter[T any](iter Iterator[T], predicateFunc func(item T) bool) Iterator[T
|
||||
type filterIterator[T any] struct {
|
||||
iter Iterator[T]
|
||||
predicateFunc func(T) bool
|
||||
next T
|
||||
ready bool
|
||||
}
|
||||
|
||||
func (fr *filterIterator[T]) Next() (T, bool) {
|
||||
if fr.ready {
|
||||
item := fr.next
|
||||
var zero T
|
||||
fr.next, fr.ready = zero, false
|
||||
return item, true
|
||||
}
|
||||
for item, ok := fr.iter.Next(); ok; item, ok = fr.iter.Next() {
|
||||
if fr.predicateFunc(item) {
|
||||
return item, true
|
||||
@@ -57,7 +65,10 @@ func (fr *filterIterator[T]) Next() (T, bool) {
|
||||
}
|
||||
|
||||
func (fr *filterIterator[T]) HasNext() bool {
|
||||
return fr.iter.HasNext()
|
||||
if !fr.ready {
|
||||
fr.next, fr.ready = fr.Next()
|
||||
}
|
||||
return fr.ready
|
||||
}
|
||||
|
||||
// Join creates an iterator that join all elements of iters[0], then all elements of iters[1] and so on.
|
||||
@@ -135,5 +146,5 @@ func (iter *takeIterator[T]) Next() (T, bool) {
|
||||
}
|
||||
|
||||
func (iter *takeIterator[T]) HasNext() bool {
|
||||
return iter.num > 0
|
||||
return iter.num > 0 && iter.it.HasNext()
|
||||
}
|
||||
+13
-3
@@ -318,6 +318,10 @@ func HasKey[K comparable, V any](m map[K]V, key K) bool {
|
||||
// MapToStruct converts map to struct
|
||||
// Play: https://go.dev/play/p/7wYyVfX38Dp
|
||||
func MapToStruct(m map[string]any, structObj any) error {
|
||||
target := reflect.ValueOf(structObj)
|
||||
if target.Kind() != reflect.Ptr || target.IsNil() || target.Elem().Kind() != reflect.Struct {
|
||||
return fmt.Errorf("destination must be a non-nil pointer to a struct")
|
||||
}
|
||||
for k, v := range m {
|
||||
err := setStructField(structObj, k, v)
|
||||
if err != nil {
|
||||
@@ -347,6 +351,9 @@ func setStructField(structObj any, fieldName string, fieldValue any) error {
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(fieldValue)
|
||||
if !val.IsValid() {
|
||||
return fmt.Errorf("Map value type don't match struct field type")
|
||||
}
|
||||
|
||||
if fieldVal.Type() != val.Type() {
|
||||
|
||||
@@ -575,9 +582,12 @@ func convertSlice(src reflect.Value, dst reflect.Value) error {
|
||||
return fmt.Errorf("error type:%s", dst.Type().String())
|
||||
}
|
||||
l := src.Len()
|
||||
target := reflect.MakeSlice(dst.Type(), l, l)
|
||||
if dst.CanSet() {
|
||||
dst.Set(target)
|
||||
if dst.Kind() == reflect.Array {
|
||||
if l > dst.Len() {
|
||||
return fmt.Errorf("source length %d exceeds destination length %d", l, dst.Len())
|
||||
}
|
||||
} else if dst.CanSet() {
|
||||
dst.Set(reflect.MakeSlice(dst.Type(), l, l))
|
||||
}
|
||||
for i := 0; i < l; i++ {
|
||||
srcValue := src.Index(i)
|
||||
|
||||
@@ -11,6 +11,33 @@ import (
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestMapToArray(t *testing.T) {
|
||||
var dst [2]int
|
||||
if err := MapTo([]int{3, 4}, &dst); err != nil || dst != [2]int{3, 4} {
|
||||
t.Fatalf("got %v, %v", dst, err)
|
||||
}
|
||||
if err := MapTo([]int{1, 2, 3}, &dst); err == nil {
|
||||
t.Fatal("expected an error for an oversized source")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapToStructInvalidInputs(t *testing.T) {
|
||||
var nilTarget *struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
for _, dst := range []any{nil, nilTarget, 1, &[]int{}} {
|
||||
if err := MapToStruct(map[string]any{"name": "value"}, dst); err == nil {
|
||||
t.Errorf("expected an error for %T", dst)
|
||||
}
|
||||
}
|
||||
var dst struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := MapToStruct(map[string]any{"name": nil}, &dst); err == nil {
|
||||
t.Error("expected an error for an incompatible nil value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
+25
-27
@@ -35,8 +35,10 @@ func Fibonacci(first, second, n int) int {
|
||||
if n <= 0 {
|
||||
return 0
|
||||
}
|
||||
if n < 3 {
|
||||
return 1
|
||||
if n == 1 {
|
||||
return first
|
||||
} else if n == 2 {
|
||||
return second
|
||||
} else if n == 3 {
|
||||
return first + second
|
||||
} else {
|
||||
@@ -75,8 +77,7 @@ func Percent(val, total float64, n int) float64 {
|
||||
// Play: https://go.dev/play/p/kZwpBRAcllO
|
||||
func RoundToString[T constraints.Float | constraints.Integer](x T, n int) string {
|
||||
tmp := math.Pow(10.0, float64(n))
|
||||
x *= T(tmp)
|
||||
r := math.Round(float64(x))
|
||||
r := math.Round(float64(x) * tmp)
|
||||
result := strconv.FormatFloat(r/tmp, 'f', n, 64)
|
||||
return result
|
||||
}
|
||||
@@ -85,15 +86,14 @@ func RoundToString[T constraints.Float | constraints.Integer](x T, n int) string
|
||||
// Play: https://go.dev/play/p/ghyb528JRJL
|
||||
func RoundToFloat[T constraints.Float | constraints.Integer](x T, n int) float64 {
|
||||
tmp := math.Pow(10.0, float64(n))
|
||||
x *= T(tmp)
|
||||
r := math.Round(float64(x))
|
||||
r := math.Round(float64(x) * tmp)
|
||||
return r / tmp
|
||||
}
|
||||
|
||||
// TruncRound round off n decimal places.
|
||||
// Play: https://go.dev/play/p/aumarSHIGzP
|
||||
func TruncRound[T constraints.Float | constraints.Integer](x T, n int) T {
|
||||
floatStr := fmt.Sprintf("%."+strconv.Itoa(n+1)+"f", x)
|
||||
floatStr := fmt.Sprintf("%."+strconv.Itoa(n+1)+"f", float64(x))
|
||||
temp := strings.Split(floatStr, ".")
|
||||
var newFloat string
|
||||
if len(temp) < 2 || n >= len(temp[1]) {
|
||||
@@ -109,8 +109,7 @@ func TruncRound[T constraints.Float | constraints.Integer](x T, n int) T {
|
||||
// Play: https://go.dev/play/p/vbCBrQHZEED
|
||||
func FloorToFloat[T constraints.Float | constraints.Integer](x T, n int) float64 {
|
||||
tmp := math.Pow(10.0, float64(n))
|
||||
x *= T(tmp)
|
||||
r := math.Floor(float64(x))
|
||||
r := math.Floor(float64(x) * tmp)
|
||||
return r / tmp
|
||||
}
|
||||
|
||||
@@ -118,8 +117,7 @@ func FloorToFloat[T constraints.Float | constraints.Integer](x T, n int) float64
|
||||
// Play: https://go.dev/play/p/Qk9KPd2IdDb
|
||||
func FloorToString[T constraints.Float | constraints.Integer](x T, n int) string {
|
||||
tmp := math.Pow(10.0, float64(n))
|
||||
x *= T(tmp)
|
||||
r := math.Floor(float64(x))
|
||||
r := math.Floor(float64(x) * tmp)
|
||||
result := strconv.FormatFloat(r/tmp, 'f', n, 64)
|
||||
return result
|
||||
}
|
||||
@@ -128,8 +126,7 @@ func FloorToString[T constraints.Float | constraints.Integer](x T, n int) string
|
||||
// Play: https://go.dev/play/p/8hOeSADZPCo
|
||||
func CeilToFloat[T constraints.Float | constraints.Integer](x T, n int) float64 {
|
||||
tmp := math.Pow(10.0, float64(n))
|
||||
x *= T(tmp)
|
||||
r := math.Ceil(float64(x))
|
||||
r := math.Ceil(float64(x) * tmp)
|
||||
return r / tmp
|
||||
}
|
||||
|
||||
@@ -137,8 +134,7 @@ func CeilToFloat[T constraints.Float | constraints.Integer](x T, n int) float64
|
||||
// Play: https://go.dev/play/p/wy5bYEyUKKG
|
||||
func CeilToString[T constraints.Float | constraints.Integer](x T, n int) string {
|
||||
tmp := math.Pow(10.0, float64(n))
|
||||
x *= T(tmp)
|
||||
r := math.Ceil(float64(x))
|
||||
r := math.Ceil(float64(x) * tmp)
|
||||
result := strconv.FormatFloat(r/tmp, 'f', n, 64)
|
||||
return result
|
||||
}
|
||||
@@ -270,12 +266,17 @@ func Range[T constraints.Integer | constraints.Float](start T, count int) []T {
|
||||
func RangeWithStep[T constraints.Integer | constraints.Float](start, end, step T) []T {
|
||||
result := []T{}
|
||||
|
||||
if start >= end || step == 0 {
|
||||
if start >= end || step <= 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
for i := start; i < end; i += step {
|
||||
for i := start; i < end; {
|
||||
result = append(result, i)
|
||||
next := i + step
|
||||
if next <= i {
|
||||
break
|
||||
}
|
||||
i = next
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -298,11 +299,7 @@ func RadianToAngle(radian float64) float64 {
|
||||
// PointDistance get two points distance.
|
||||
// Play: https://go.dev/play/p/RrG4JIaziM8
|
||||
func PointDistance(x1, y1, x2, y2 float64) float64 {
|
||||
a := x1 - x2
|
||||
b := y1 - y2
|
||||
c := math.Pow(a, 2) + math.Pow(b, 2)
|
||||
|
||||
return math.Sqrt(c)
|
||||
return math.Hypot(x1-x2, y1-y2)
|
||||
}
|
||||
|
||||
// IsPrime checks if number is prime number.
|
||||
@@ -363,7 +360,7 @@ func lcm[T constraints.Integer](a, b T) T {
|
||||
if a == 0 || b == 0 {
|
||||
panic("lcm function: provide non zero integers only.")
|
||||
}
|
||||
return a * b / gcd(a, b)
|
||||
return a / gcd(a, b) * b
|
||||
}
|
||||
|
||||
// Cos returns the cosine of the radian argument.
|
||||
@@ -441,10 +438,11 @@ func Permutation(n, k uint) uint {
|
||||
return 0
|
||||
}
|
||||
|
||||
nFactorial := Factorial(n)
|
||||
nMinusKFactorial := Factorial(n - k)
|
||||
|
||||
return nFactorial / nMinusKFactorial
|
||||
result := uint(1)
|
||||
for i := uint(0); i < k; i++ {
|
||||
result *= n - i
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Combination calculate C(n, k).
|
||||
|
||||
@@ -17,6 +17,19 @@ func TestExponent(t *testing.T) {
|
||||
assert.Equal(int64(100), Exponent(10, 2))
|
||||
}
|
||||
|
||||
func TestTruncRoundInteger(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestTruncRoundInteger")
|
||||
assert.Equal(42, TruncRound(42, 0))
|
||||
assert.Equal(int8(-42), TruncRound(int8(-42), 2))
|
||||
}
|
||||
|
||||
func TestLCMIntermediateOverflow(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestLCMIntermediateOverflow")
|
||||
assert.Equal(uint8(32), LCM(uint8(32)))
|
||||
assert.Equal(uint8(192), LCM(uint8(64), uint8(96)))
|
||||
assert.Equal(uint64(1<<40), LCM(uint64(1<<40), uint64(1<<39)))
|
||||
}
|
||||
|
||||
func TestFibonacci(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -26,6 +39,10 @@ func TestFibonacci(t *testing.T) {
|
||||
assert.Equal(1, Fibonacci(1, 1, 1))
|
||||
assert.Equal(1, Fibonacci(1, 1, 2))
|
||||
assert.Equal(5, Fibonacci(1, 1, 5))
|
||||
assert.Equal(2, Fibonacci(2, 3, 1))
|
||||
assert.Equal(3, Fibonacci(2, 3, 2))
|
||||
assert.Equal(5, Fibonacci(2, 3, 3))
|
||||
assert.Equal(13, Fibonacci(2, 3, 5))
|
||||
}
|
||||
|
||||
func TestFactorial(t *testing.T) {
|
||||
@@ -49,6 +66,19 @@ func TestPercent(t *testing.T) {
|
||||
assert.EqualValues(-7.42, Percent(-30305, 408420, 2))
|
||||
}
|
||||
|
||||
func TestIntegerRoundingScale(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestIntegerRoundingScale")
|
||||
assert.Equal(float64(42), RoundToFloat(int8(42), 2))
|
||||
assert.Equal("42.00", RoundToString(int8(42), 2))
|
||||
assert.Equal(float64(42), FloorToFloat(uint8(42), 2))
|
||||
assert.Equal("42.00", FloorToString(uint8(42), 2))
|
||||
assert.Equal(float64(42), CeilToFloat(int8(42), 2))
|
||||
assert.Equal("42.00", CeilToString(int8(42), 2))
|
||||
assert.Equal(float64(150), RoundToFloat(149, -1))
|
||||
assert.Equal(float64(140), FloorToFloat(149, -1))
|
||||
assert.Equal(float64(150), CeilToFloat(149, -1))
|
||||
}
|
||||
|
||||
func TestRoundToFloat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -260,6 +290,10 @@ func TestRangeWithStep(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestRangeWithStep")
|
||||
assert.Equal([]int8{120}, RangeWithStep[int8](120, 125, 10))
|
||||
assert.Equal([]uint8{250}, RangeWithStep[uint8](250, 253, 10))
|
||||
assert.Equal([]int{}, RangeWithStep(1, 4, -1))
|
||||
assert.Equal([]float64{1e20}, RangeWithStep(1e20, 1e21, 1.0))
|
||||
|
||||
result1 := RangeWithStep(1, 4, 1)
|
||||
result2 := RangeWithStep(1, -1, 0)
|
||||
@@ -300,6 +334,15 @@ func TestRadianToAngle(t *testing.T) {
|
||||
assert.Equal(float64(45), result3)
|
||||
}
|
||||
|
||||
func TestPointDistanceExtremeScales(t *testing.T) {
|
||||
for _, scale := range []float64{1e200, 1e-200} {
|
||||
got := PointDistance(0, 0, 3*scale, 4*scale)
|
||||
if math.Abs(got/(5*scale)-1) > 1e-15 {
|
||||
t.Errorf("scale %g: got %g, want %g", scale, got, 5*scale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPointDistance(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDownloadFileStatus(t *testing.T) {
|
||||
for _, status := range []int{http.StatusOK, http.StatusNotFound, http.StatusInternalServerError} {
|
||||
t.Run(http.StatusText(status), func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(status)
|
||||
w.Write([]byte("response"))
|
||||
}))
|
||||
defer server.Close()
|
||||
path := filepath.Join(t.TempDir(), "download.txt")
|
||||
if err := os.WriteFile(path, []byte("original"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err := DownloadFile(path, server.URL)
|
||||
if (err != nil) != (status != http.StatusOK) {
|
||||
t.Errorf("unexpected result: %v", err)
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "original"
|
||||
if status == http.StatusOK {
|
||||
want = "response"
|
||||
}
|
||||
if string(data) != want {
|
||||
t.Errorf("got %q, want %q", data, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+28
-8
@@ -412,21 +412,41 @@ func TestHttpClient_AsyncSendRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProxy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Local target server
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
// Local proxy server (basic forwarding stub)
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// In real proxy tests, you'd validate request form and forward.
|
||||
// Keep deterministic: just return OK.
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("proxied"))
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
proxyURL, err := url.Parse(proxy.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse proxy URL: %v", err)
|
||||
}
|
||||
|
||||
config := &HttpClientConfig{
|
||||
HandshakeTimeout: 20 * time.Second,
|
||||
ResponseTimeout: 40 * time.Second,
|
||||
// Use the proxy ip to add it here
|
||||
//Proxy: &url.URL{
|
||||
// Scheme: "http",
|
||||
// Host: "46.17.63.166:18888",
|
||||
//},
|
||||
Proxy: proxyURL,
|
||||
}
|
||||
|
||||
httpClient := NewHttpClientWithConfig(config)
|
||||
resp, err := httpClient.Get("https://www.ipplus360.com/getLocation")
|
||||
resp, err := httpClient.Get(target.URL)
|
||||
if err != nil {
|
||||
t.Log("net error: ", err.Error())
|
||||
return
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected %d, got %d", http.StatusOK, resp.StatusCode)
|
||||
|
||||
+35
-9
@@ -70,12 +70,12 @@ func GetInternalIp() string {
|
||||
func GetRequestPublicIp(req *http.Request) string {
|
||||
var ip string
|
||||
for _, ip = range strings.Split(req.Header.Get("X-Forwarded-For"), ",") {
|
||||
if ip = strings.TrimSpace(ip); ip != "" && !IsInternalIP(net.ParseIP(ip)) {
|
||||
if ip = strings.TrimSpace(ip); net.ParseIP(ip) != nil && !IsInternalIP(net.ParseIP(ip)) {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
|
||||
if ip = strings.TrimSpace(req.Header.Get("X-Real-Ip")); ip != "" && !IsInternalIP(net.ParseIP(ip)) {
|
||||
if ip = strings.TrimSpace(req.Header.Get("X-Real-Ip")); net.ParseIP(ip) != nil && !IsInternalIP(net.ParseIP(ip)) {
|
||||
return ip
|
||||
}
|
||||
|
||||
@@ -192,18 +192,37 @@ func IsPublicIP(IP net.IP) bool {
|
||||
// IsInternalIP verify an ip is intranet or not.
|
||||
// Play: https://go.dev/play/p/sYGhXbgO4Cb
|
||||
func IsInternalIP(IP net.IP) bool {
|
||||
if IP.IsLoopback() {
|
||||
if IP.IsLoopback() || IP.IsPrivate() || IP.IsLinkLocalUnicast() {
|
||||
return true
|
||||
}
|
||||
if ip4 := IP.To4(); ip4 != nil {
|
||||
return ip4[0] == 10 ||
|
||||
(ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31) ||
|
||||
(ip4[0] == 169 && ip4[1] == 254) ||
|
||||
(ip4[0] == 192 && ip4[1] == 168)
|
||||
// RFC 6598 shared address space, 100.64.0.0/10.
|
||||
return ip4[0] == 100 && ip4[1]&0xc0 == 0x40
|
||||
}
|
||||
if embedded, ok := embeddedTransitionIPv4(IP); ok {
|
||||
return IsInternalIP(embedded)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// embeddedTransitionIPv4 returns the IPv4 address carried by a NAT64, 6to4, or Teredo address.
|
||||
func embeddedTransitionIPv4(ip net.IP) (net.IP, bool) {
|
||||
ip = ip.To16()
|
||||
if ip == nil || ip.To4() != nil {
|
||||
return nil, false
|
||||
}
|
||||
switch {
|
||||
case bytes.Equal(ip[:12], []byte{0x00, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0}):
|
||||
return net.IPv4(ip[12], ip[13], ip[14], ip[15]), true
|
||||
case ip[0] == 0x20 && ip[1] == 0x02:
|
||||
return net.IPv4(ip[2], ip[3], ip[4], ip[5]), true
|
||||
case ip[0] == 0x20 && ip[1] == 0x01 && ip[2] == 0 && ip[3] == 0:
|
||||
return net.IPv4(ip[12]^0xff, ip[13]^0xff, ip[14]^0xff, ip[15]^0xff), true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeUrl encode url.
|
||||
// Play: https://go.dev/play/p/bsZ6BRC4uKI
|
||||
func EncodeUrl(urlStr string) (string, error) {
|
||||
@@ -250,10 +269,14 @@ func UploadFile(filepath string, server string) (bool, error) {
|
||||
contentType := writer.FormDataContentType()
|
||||
writer.Close()
|
||||
|
||||
_, err = http.Post(server, contentType, bodyBuffer)
|
||||
resp, err := http.Post(server, contentType, bodyBuffer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
return false, fmt.Errorf("upload failed: %s", resp.Status)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -265,6 +288,9 @@ func DownloadFile(filepath string, url string) error {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
return fmt.Errorf("download failed: %s", resp.Status)
|
||||
}
|
||||
|
||||
out, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
@@ -296,7 +322,7 @@ func IsPingConnected(host string) bool {
|
||||
// IsTelnetConnected checks if can telnet specified host or not.
|
||||
// Play: https://go.dev/play/p/yiLCGtQv_ZG
|
||||
func IsTelnetConnected(host string, port string) bool {
|
||||
adder := host + ":" + port
|
||||
adder := net.JoinHostPort(host, port)
|
||||
conn, err := net.DialTimeout("tcp", adder, 5*time.Second)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -95,6 +95,36 @@ func TestIsInternalIP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInternalIPCoversIPv6AndSharedSpace(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsInternalIPCoversIPv6AndSharedSpace")
|
||||
|
||||
ips := []net.IP{
|
||||
net.ParseIP("fd12:3456:789a::1"),
|
||||
net.ParseIP("fe80::1"),
|
||||
net.ParseIP("100.64.0.1"),
|
||||
net.ParseIP("100.127.255.255"),
|
||||
net.ParseIP("100.63.255.255"),
|
||||
net.ParseIP("100.128.0.1"),
|
||||
net.ParseIP("::ffff:192.168.0.1"),
|
||||
net.ParseIP("64:ff9b::a9fe:a9fe"),
|
||||
net.ParseIP("64:ff9b::808:808"),
|
||||
net.ParseIP("2002:7f00:1::1"),
|
||||
net.ParseIP("2002:808:808::1"),
|
||||
net.ParseIP("2001::80ff:fffe"),
|
||||
net.ParseIP("2001:db8::1"),
|
||||
}
|
||||
expected := []bool{
|
||||
true, true, true, true, false, false, true,
|
||||
true, false, true, false, true, false,
|
||||
}
|
||||
|
||||
for i := 0; i < len(ips); i++ {
|
||||
assert.Equal(expected[i], IsInternalIP(ips[i]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIps(t *testing.T) {
|
||||
ips := GetIps()
|
||||
t.Log(ips)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetRequestPublicIpInvalidHeaders(t *testing.T) {
|
||||
for _, test := range []struct{ forwarded, real, want string }{
|
||||
{"unknown, 8.8.8.8", "", "8.8.8.8"},
|
||||
{"unknown", "invalid", "1.1.1.1"},
|
||||
{"10.0.0.1", "8.8.4.4", "8.8.4.4"},
|
||||
} {
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req.RemoteAddr = "1.1.1.1:1234"
|
||||
req.Header.Set("X-Forwarded-For", test.forwarded)
|
||||
req.Header.Set("X-Real-Ip", test.real)
|
||||
if got := GetRequestPublicIp(req); got != test.want {
|
||||
t.Errorf("got %q, want %q", got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsTelnetConnectedIPv6(t *testing.T) {
|
||||
listener, err := net.Listen("tcp6", "[::1]:0")
|
||||
if err != nil {
|
||||
t.Skipf("IPv6 loopback unavailable: %v", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
host, port, err := net.SplitHostPort(listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !IsTelnetConnected(host, port) {
|
||||
t.Error("could not connect to the IPv6 loopback listener")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type uploadResponseBody struct {
|
||||
io.Reader
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (b *uploadResponseBody) Close() error { b.closed = true; return nil }
|
||||
|
||||
type uploadTransport struct{ response *http.Response }
|
||||
|
||||
func (t uploadTransport) RoundTrip(*http.Request) (*http.Response, error) { return t.response, nil }
|
||||
|
||||
func TestUploadFileResponse(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "upload.txt")
|
||||
if err := os.WriteFile(path, []byte("content"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
original := http.DefaultClient
|
||||
defer func() { http.DefaultClient = original }()
|
||||
for _, status := range []int{http.StatusCreated, http.StatusInternalServerError} {
|
||||
body := &uploadResponseBody{Reader: strings.NewReader("response")}
|
||||
http.DefaultClient = &http.Client{Transport: uploadTransport{&http.Response{StatusCode: status, Body: body}}}
|
||||
ok, err := UploadFile(path, "https://example.com/upload")
|
||||
if ok != (status == http.StatusCreated) || (err != nil) != (status != http.StatusCreated) {
|
||||
t.Errorf("status %d: got %v, %v", status, ok, err)
|
||||
}
|
||||
if !body.closed {
|
||||
t.Errorf("status %d: response body was not closed", status)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package random
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestUniqueRandomSlicesNonPositiveLength(t *testing.T) {
|
||||
for _, length := range []int{-1, 0} {
|
||||
if got := RandUniqueIntSlice(length, 1, 10); len(got) != 0 {
|
||||
t.Errorf("unexpected integers: %v", got)
|
||||
}
|
||||
if got := RandFloats(length, 1, 10, 2); len(got) != 0 {
|
||||
t.Errorf("unexpected floats: %v", got)
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -98,7 +98,7 @@ func RandIntSlice(length, min, max int) []int {
|
||||
// RandUniqueIntSlice generate a slice of random int of length that do not repeat.
|
||||
// Play: https://go.dev/play/p/uBkRSOz73Ec
|
||||
func RandUniqueIntSlice(length, min, max int) []int {
|
||||
if min > max {
|
||||
if length <= 0 || min > max {
|
||||
return []int{}
|
||||
}
|
||||
if length > max-min {
|
||||
@@ -139,6 +139,9 @@ func RandFloat(min, max float64, precision int) float64 {
|
||||
// RandFloats generate a slice of random float64 numbers of length that do not repeat.
|
||||
// Play: https://go.dev/play/p/I3yndUQ-rhh
|
||||
func RandFloats(length int, min, max float64, precision int) []float64 {
|
||||
if length <= 0 {
|
||||
return []float64{}
|
||||
}
|
||||
if max < min {
|
||||
min, max = max, min
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package slice
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestUniqueByFieldUnreadableInputs(t *testing.T) {
|
||||
for _, input := range []any{nil, struct{ hidden string }{"value"}} {
|
||||
if _, err := UniqueByField([]any{input}, "hidden"); err == nil {
|
||||
t.Errorf("expected an error for %T", input)
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-5
@@ -187,8 +187,8 @@ func DifferenceBy[T comparable](slice []T, comparedSlice []T, iteratee func(inde
|
||||
result := make([]T, 0)
|
||||
|
||||
comparedMap := make(map[T]struct{}, len(comparedSlice))
|
||||
for _, item := range comparedSlice {
|
||||
comparedMap[iteratee(0, item)] = struct{}{}
|
||||
for i, item := range comparedSlice {
|
||||
comparedMap[iteratee(i, item)] = struct{}{}
|
||||
}
|
||||
|
||||
for i, item := range slice {
|
||||
@@ -712,6 +712,9 @@ func IntSlice(slice any) []int {
|
||||
// Play: https://go.dev/play/p/800B1dPBYyd
|
||||
func DeleteAt[T any](slice []T, index int) []T {
|
||||
result := append([]T(nil), slice...)
|
||||
if len(result) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
if index < 0 || index >= len(slice) {
|
||||
return result[:len(slice)-1]
|
||||
@@ -949,10 +952,8 @@ func UniqueByField[T any](slice []T, field string) ([]T, error) {
|
||||
|
||||
func getField[T any](item T, field string) (interface{}, error) {
|
||||
v := reflect.ValueOf(item)
|
||||
t := reflect.TypeOf(item)
|
||||
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Kind() != reflect.Struct {
|
||||
@@ -963,6 +964,9 @@ func getField[T any](item T, field string) (interface{}, error) {
|
||||
if !f.IsValid() {
|
||||
return nil, fmt.Errorf("field name %s not found", field)
|
||||
}
|
||||
if !f.CanInterface() {
|
||||
return nil, fmt.Errorf("field name %s is not exported", field)
|
||||
}
|
||||
|
||||
return v.FieldByName(field).Interface(), nil
|
||||
}
|
||||
@@ -1015,6 +1019,9 @@ func Merge[T any](slices ...[]T) []T {
|
||||
// Play: https://go.dev/play/p/anJXfB5wq_t
|
||||
func Intersection[T comparable](slices ...[]T) []T {
|
||||
result := []T{}
|
||||
if len(slices) == 0 {
|
||||
return result
|
||||
}
|
||||
elementCount := make(map[T]int)
|
||||
|
||||
for _, slice := range slices {
|
||||
|
||||
@@ -801,6 +801,8 @@ func TestDeleteAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestDeleteAt")
|
||||
assert.Equal(0, len(DeleteAt([]int(nil), 0)))
|
||||
assert.Equal(0, len(DeleteAt([]int{}, -1)))
|
||||
|
||||
tests := []struct {
|
||||
slice []int
|
||||
@@ -1103,6 +1105,7 @@ func TestIntersection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIntersection")
|
||||
assert.Equal([]int{}, Intersection[int]())
|
||||
|
||||
s1 := []int{1, 2, 2, 3}
|
||||
s2 := []int{1, 2, 3, 4}
|
||||
@@ -1203,6 +1206,9 @@ func TestDifferenceBy(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.Equal([]int{1, 2}, DifferenceBy(s1, s2, addOne))
|
||||
assert.Equal([]int{}, DifferenceBy([]int{10, 10}, []int{10, 10}, func(i, v int) int {
|
||||
return i + v
|
||||
}))
|
||||
}
|
||||
|
||||
func TestIsAscending(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package stream
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLimitLargerThanSource(t *testing.T) {
|
||||
got := Of(1, 2).Limit(math.MaxInt).ToSlice()
|
||||
if len(got) != 2 || got[0] != 1 || got[1] != 2 {
|
||||
t.Errorf("unexpected limited stream: %v", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package stream
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFromRangeSmallIntegerSpan(t *testing.T) {
|
||||
got := FromRange[int8](-120, 120, 10).ToSlice()
|
||||
if len(got) != 25 || got[0] != -120 || got[len(got)-1] != 120 {
|
||||
t.Errorf("unexpected range: %v", got)
|
||||
}
|
||||
if got := FromRange[uint8](250, 255, 5).ToSlice(); len(got) != 2 || got[1] != 255 {
|
||||
t.Errorf("unexpected unsigned range: %v", got)
|
||||
}
|
||||
}
|
||||
+11
-5
@@ -101,11 +101,14 @@ func FromRange[T constraints.Integer | constraints.Float](start, end, step T) St
|
||||
panic("stream.FromRange: param step should be positive")
|
||||
}
|
||||
|
||||
l := int((end-start)/step) + 1
|
||||
source := make([]T, l)
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
source[i] = start + (T(i) * step)
|
||||
source := make([]T, 0)
|
||||
for value := start; value <= end; {
|
||||
source = append(source, value)
|
||||
next := value + step
|
||||
if next <= value {
|
||||
break
|
||||
}
|
||||
value = next
|
||||
}
|
||||
|
||||
return FromSlice(source)
|
||||
@@ -219,6 +222,9 @@ func (s Stream[T]) Limit(maxSize int) Stream[T] {
|
||||
if maxSize < 0 {
|
||||
return FromSlice([]T{})
|
||||
}
|
||||
if maxSize > len(s.source) {
|
||||
maxSize = len(s.source)
|
||||
}
|
||||
|
||||
source := make([]T, 0, maxSize)
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package structs
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFieldIsEmbeddedUsesAnonymousFlag(t *testing.T) {
|
||||
type Embedded struct{ Promoted int }
|
||||
s := New(struct {
|
||||
Embedded
|
||||
Named Embedded
|
||||
}{})
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
want bool
|
||||
}{{"Embedded", true}, {"Promoted", true}, {"Named", false}} {
|
||||
field, ok := s.Field(tt.name)
|
||||
if !ok {
|
||||
t.Fatalf("field %s missing", tt.name)
|
||||
}
|
||||
if got := field.IsEmbedded(); got != tt.want {
|
||||
t.Errorf("%s: got %v, want %v", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -40,7 +40,7 @@ func (f *Field) Value() any {
|
||||
// IsEmbedded returns true if the given field is an embedded field.
|
||||
// Play: https://go.dev/play/p/wV2PrbYm3Ec
|
||||
func (f *Field) IsEmbedded() bool {
|
||||
return len(f.field.Index) > 1
|
||||
return f.field.Anonymous || len(f.field.Index) > 1
|
||||
}
|
||||
|
||||
// IsExported returns true if the given field is exported.
|
||||
|
||||
+12
-10
@@ -35,7 +35,7 @@ func CamelCase(s string) string {
|
||||
}
|
||||
|
||||
// UpperCamelCase coverts string to UpperCamelCase string. Non letters and numbers will be ignored.
|
||||
// Play: https://go.dev/play/p/todo
|
||||
// Play: https://go.dev/play/p/vPHszjTXSFp
|
||||
func UpperCamelCase(s string) string {
|
||||
var builder strings.Builder
|
||||
|
||||
@@ -316,10 +316,10 @@ func Substring(s string, offset int, length uint) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
end := offset + int(length)
|
||||
if end > size {
|
||||
end = size
|
||||
if length > uint(size-offset) {
|
||||
length = uint(size - offset)
|
||||
}
|
||||
end := offset + int(length)
|
||||
|
||||
return strings.ReplaceAll(string(rs[offset:end]), "\x00", "")
|
||||
}
|
||||
@@ -483,7 +483,10 @@ func IndexOffset(str string, substr string, idxFrom int) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
return strings.Index(str[idxFrom:], substr) + idxFrom
|
||||
if index := strings.Index(str[idxFrom:], substr); index >= 0 {
|
||||
return index + idxFrom
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// ReplaceWithMap returns a copy of `str`,
|
||||
@@ -516,7 +519,7 @@ func SplitAndTrim(str, delimiter string, characterMask ...string) []string {
|
||||
|
||||
var (
|
||||
// DefaultTrimChars are the characters which are stripped by Trim* functions in default.
|
||||
DefaultTrimChars = string([]byte{
|
||||
DefaultTrimChars = string([]rune{
|
||||
'\t', // Tab.
|
||||
'\v', // Vertical tab.
|
||||
'\n', // New line (line feed).
|
||||
@@ -629,12 +632,11 @@ func SubInBetween(str string, start string, end string) string {
|
||||
// This func returns an error if the input strings are of unequal lengths.
|
||||
// Play: https://go.dev/play/p/glNdQEA9HUi
|
||||
func HammingDistance(a, b string) (int, error) {
|
||||
if len(a) != len(b) {
|
||||
return -1, errors.New("a length and b length are unequal")
|
||||
}
|
||||
|
||||
ar := []rune(a)
|
||||
br := []rune(b)
|
||||
if len(ar) != len(br) {
|
||||
return -1, errors.New("a length and b length are unequal")
|
||||
}
|
||||
|
||||
var distance int
|
||||
for i, codepoint := range ar {
|
||||
|
||||
@@ -482,6 +482,8 @@ func TestSubstring(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSubstring")
|
||||
assert.Equal("好世界", Substring("你好世界", 1, ^uint(0)))
|
||||
assert.Equal("界", Substring("你好世界", -1, ^uint(0)))
|
||||
|
||||
assert.Equal("bcd", Substring("abcde", 1, 3))
|
||||
assert.Equal("bcde", Substring("abcde", 1, 5))
|
||||
@@ -658,6 +660,8 @@ func TestIndexOffset(t *testing.T) {
|
||||
{str, "d", len(str) - 1, len(str) - 1},
|
||||
{str, "d", len(str), -1},
|
||||
{str, "f", -1, -1},
|
||||
{str, "missing", 5, -1},
|
||||
{str, "foo", 5, -1},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -682,6 +686,12 @@ func TestReplaceWithMap(t *testing.T) {
|
||||
|
||||
func TestTrim(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := Trim("\u0085\u00a0value\u00a0\u0085"); got != "value" {
|
||||
t.Errorf("Trim Unicode whitespace = %q, want value", got)
|
||||
}
|
||||
if got := Trim("\ufffdvalue\ufffd"); got != "\ufffdvalue\ufffd" {
|
||||
t.Errorf("Trim removed replacement characters: %q", got)
|
||||
}
|
||||
|
||||
assert := internal.NewAssert(t, "TestTrim")
|
||||
|
||||
@@ -814,6 +824,9 @@ func TestHammingDistance(t *testing.T) {
|
||||
{"abc", "def", 3},
|
||||
{"kitten", "sitting", -1},
|
||||
{"ö", "ü", 1},
|
||||
{"abc", "你", -1},
|
||||
{"你", "abc", -1},
|
||||
{"a", "你", 1},
|
||||
{"日本語", "日本語", 0},
|
||||
{"日本語", "語日本", 3},
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@ var (
|
||||
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
|
||||
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])`)
|
||||
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
|
||||
chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}`)
|
||||
creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`)
|
||||
base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`)
|
||||
chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`^(?:\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8})$`)
|
||||
creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11}|6[27][0-9]{14})$`)
|
||||
base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$`)
|
||||
base64URLMatcher *regexp.Regexp = regexp.MustCompile(`^([A-Za-z0-9_-]{4})*([A-Za-z0-9_-]{2}(==)?|[A-Za-z0-9_-]{3}=?)?$`)
|
||||
binMatcher *regexp.Regexp = regexp.MustCompile(`^(0b)?[01]+$`)
|
||||
hexMatcher *regexp.Regexp = regexp.MustCompile(`^(#|0x|0X)?[0-9a-fA-F]+$`)
|
||||
@@ -278,7 +278,7 @@ func IsUrl(str string) bool {
|
||||
}
|
||||
|
||||
u, err := url.Parse(str)
|
||||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||
if err != nil || u.Scheme == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ func IsUrl(str string) bool {
|
||||
}
|
||||
|
||||
if u.Scheme == "file" || u.Scheme == "mailto" || u.Scheme == "data" {
|
||||
return true
|
||||
return u.Host != "" || u.Path != "" || u.Opaque != ""
|
||||
}
|
||||
|
||||
host := u.Hostname()
|
||||
@@ -505,7 +505,7 @@ func IsGBK(data []byte) bool {
|
||||
i+1 < len(data) &&
|
||||
data[i+1] >= 0x40 &&
|
||||
data[i+1] <= 0xfe &&
|
||||
data[i+1] != 0xf7 {
|
||||
data[i+1] != 0x7f {
|
||||
i += 2
|
||||
continue
|
||||
} else {
|
||||
|
||||
@@ -436,6 +436,11 @@ func TestIsUrl(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsUrl")
|
||||
assert.Equal(true, IsUrl("mailto:user@example.com"))
|
||||
assert.Equal(true, IsUrl("file:///tmp/file.txt"))
|
||||
assert.Equal(true, IsUrl("data:text/plain,hello"))
|
||||
assert.Equal(false, IsUrl("mailto:"))
|
||||
assert.Equal(false, IsUrl("http:/path"))
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
@@ -549,6 +554,9 @@ func TestIsChinesePhone(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsChinesePhone")
|
||||
for _, phone := range []string{"x010-32116675", "0464-8756213x", "0731-822515456", "010-32116675\n"} {
|
||||
assert.Equal(false, IsChinesePhone(phone))
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
@@ -593,6 +601,8 @@ func TestIsCreditCard(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsCreditCard")
|
||||
assert.Equal(true, IsCreditCard("3530111333300000"))
|
||||
assert.Equal(true, IsCreditCard("213100000000000"))
|
||||
|
||||
assert.Equal(true, IsCreditCard("4111111111111111"))
|
||||
assert.Equal(false, IsCreditCard("123456"))
|
||||
@@ -602,6 +612,9 @@ func TestIsBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsBase64")
|
||||
assert.Equal(false, IsBase64(`ab\=`))
|
||||
assert.Equal(false, IsBase64(`\\\\`))
|
||||
assert.Equal(true, IsBase64("////"))
|
||||
|
||||
assert.Equal(true, IsBase64("aGVsbG8="))
|
||||
assert.Equal(false, IsBase64("123456"))
|
||||
@@ -798,6 +811,9 @@ func TestIsGBK(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsGBK")
|
||||
if !IsGBK([]byte{0x81, 0xf7}) || IsGBK([]byte{0x81, 0x7f}) {
|
||||
t.Error("GBK trails must allow 0xf7 and reject 0x7f")
|
||||
}
|
||||
|
||||
str := "你好"
|
||||
gbkData, _ := simplifiedchinese.GBK.NewEncoder().Bytes([]byte(str))
|
||||
|
||||
Reference in new issue
Block a user