Skip to content

Commit c94e4f0

Browse files
authored
Merge pull request #1 from vadimInshakov/main
add conversion from byte slice to string
2 parents 5d4dded + c5ed378 commit c94e4f0

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

stringutils.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package stringutils
22

33
import (
44
"fmt"
5+
"reflect"
56
"strings"
7+
"unsafe"
68
)
79

810
// Contains string in slice
@@ -27,6 +29,9 @@ func ContainsAnySubstring(s string, subStrings []string) bool {
2729

2830
// DeDup remove duplicates from slice. optimized for performance, good for short slices only!
2931
func DeDup(keys []string) []string {
32+
if len(keys) == 0 {
33+
return nil
34+
}
3035
l := len(keys) - 1
3136
for i := 0; i < l; i++ {
3237
for j := i + 1; j <= l; j++ {
@@ -43,6 +48,9 @@ func DeDup(keys []string) []string {
4348

4449
// DeDupBig remove duplicates from slice. Should be used instead of DeDup for large slices
4550
func DeDupBig(keys []string) (result []string) {
51+
if len(keys) == 0 {
52+
return nil
53+
}
4654
result = make([]string, 0, len(keys))
4755
visited := map[string]bool{}
4856
for _, k := range keys {
@@ -56,13 +64,28 @@ func DeDupBig(keys []string) (result []string) {
5664

5765
// SliceToString converts slice of any to slice of string
5866
func SliceToString(s []any) []string {
67+
if len(s) == 0 {
68+
return nil
69+
}
5970
strSlice := make([]string, len(s))
6071
for i, v := range s {
72+
if vb, ok := v.([]byte); ok {
73+
strSlice[i] = bytesToString(vb)
74+
continue
75+
}
6176
strSlice[i] = fmt.Sprintf("%v", v)
6277
}
6378
return strSlice
6479
}
6580

81+
func bytesToString(bytes []byte) string {
82+
sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
83+
return *(*string)(unsafe.Pointer(&reflect.StringHeader{
84+
Data: sliceHeader.Data,
85+
Len: sliceHeader.Len,
86+
}))
87+
}
88+
6689
// HasCommonElement checks if any element of the second slice is in the first slice
6790
func HasCommonElement(a, b []string) bool {
6891
for _, second := range b {

stringutils_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package stringutils
22

33
import (
4+
"fmt"
45
"testing"
56

67
"github.com/stretchr/testify/assert"
@@ -48,6 +49,8 @@ func TestDeDup(t *testing.T) {
4849
keys []string
4950
want []string
5051
}{
52+
{"nil input", nil, nil},
53+
{"empty input", []string{}, nil},
5154
{"removes duplicates", []string{"test", "test", "example"}, []string{"test", "example"}},
5255
{"no duplicates", []string{"test", "test2", "example"}, []string{"test", "test2", "example"}},
5356
}
@@ -65,6 +68,8 @@ func TestDeDupBig(t *testing.T) {
6568
keys []string
6669
want []string
6770
}{
71+
{"nil input", nil, nil},
72+
{"empty input", []string{}, nil},
6873
{"removes duplicates", []string{"test", "test", "example"}, []string{"test", "example"}},
6974
{"no duplicates", []string{"test", "test2", "example"}, []string{"test", "test2", "example"}},
7075
}
@@ -82,8 +87,11 @@ func TestSliceToString(t *testing.T) {
8287
in []interface{}
8388
want []string
8489
}{
90+
{"nil input", nil, nil},
91+
{"empty input", []any{}, nil},
8592
{"converts number to string", []any{1, 2, 3}, []string{"1", "2", "3"}},
8693
{"converts mixed slice to string", []any{1, "aaa", true, 0.55}, []string{"1", "aaa", "true", "0.55"}},
94+
{"converts slice of byte slices to string", []any{[]byte("hi"), []byte("there")}, []string{"hi", "there"}},
8795
}
8896

8997
for _, tt := range tests {
@@ -169,3 +177,47 @@ func TestHasSuffixSlice(t *testing.T) {
169177
})
170178
}
171179
}
180+
181+
func BenchmarkSliceToString(b *testing.B) {
182+
tmpl := []any{[]byte("fdjndfg")}
183+
b.Run("unsafe (small slice)", func(b *testing.B) {
184+
for i := 0; i < b.N; i++ {
185+
SliceToString(tmpl)
186+
}
187+
})
188+
b.Run("type assert (small slice)", func(b *testing.B) {
189+
for i := 0; i < b.N; i++ {
190+
sliceToStringAllocs(tmpl)
191+
}
192+
})
193+
194+
for i := 0; i < 20; i++ {
195+
tmpl = append(tmpl, tmpl...)
196+
}
197+
198+
b.Run("unsafe (big slice)", func(b *testing.B) {
199+
for i := 0; i < b.N; i++ {
200+
SliceToString(tmpl)
201+
}
202+
})
203+
b.Run("type assert (big slice)", func(b *testing.B) {
204+
for i := 0; i < b.N; i++ {
205+
sliceToStringAllocs(tmpl)
206+
}
207+
})
208+
}
209+
210+
func sliceToStringAllocs(s []any) []string {
211+
if len(s) == 0 {
212+
return nil
213+
}
214+
strSlice := make([]string, len(s))
215+
for i, v := range s {
216+
if vb, ok := v.([]byte); ok {
217+
strSlice[i] = string(vb)
218+
continue
219+
}
220+
strSlice[i] = fmt.Sprintf("%v", v)
221+
}
222+
return strSlice
223+
}

0 commit comments

Comments
 (0)