Related
I'd like to create a generic method to extract the keys from a map as an array - so that the type of that array should be the same as the type of the keys in the map. I know I want the usage to look something like this:
mapWithStringKeys := map[string]string {
"one": "1",
"ten": "10",
"hundred": "100",
}
mapWithIntKeys := map[int]string {
1: "one",
10: "ten",
100: "hundred",
}
keys(mapWithStringKeys) // []string{"one", "ten", "hundred"}
keys(mapWithStringKeys) // []int{1, 10, 100}
And the internal logic for extracting keys would look like this:
keys := make([]<TYPE>, 0, len(mymap))
for k := range mymap {
keys = append(keys, k)
}
I'm mostly having trouble defining the function so that golang knows how to "pass along" the types. I'm imagining something like this:
func keys[T infer](m map[T]any) []T {
keysArr := make([]T, 0, len(mymap))
for k := range mymap {
keysArr = append(keysArr, k)
}
return keysArr
}
But I can't really figure it out so I'm wondering if this is even possible, and if not, why?
You almost got it. Declare the key type as comparable and use a type constraint for the value type.
func keys[T comparable, V any](mymap map[T]V) []T {
keysArr := make([]T, 0, len(mymap))
for k := range mymap {
keysArr = append(keysArr, k)
}
return keysArr
}
To handle named map types, include a constraint for the map type with the ~ thing.
func keys[M ~map[T]V, T comparable, V any](mymap M) []T {
keysArr := make([]T, 0, len(mymap))
for k := range mymap {
keysArr = append(keysArr, k)
}
return keysArr
}
H/T to Burak for pointing out the existing library function.
How can I implement RemoveRange method in golang? It is a method in C# as shown here
I want to implement RemoveRange method on my hashCode string array and return new modified array back if possible with those ranges remove.
func removeRange(hashCode []string, idx int, count int) []string {
var temp []string
for i, s := range hashCode {
fmt.Println(i, s)
// confuse here on what to do
}
return temp
}
Simply slice the slice up until idx, skip count elements and append the rest to the result of the first slicing:
func removeRange(hashCode []string, idx int, count int) []string {
return append(hashCode[:idx], hashCode[idx+count:]...)
}
Testing it:
s := []string{"0", "1", "2", "3", "4", "5"}
fmt.Println(s)
s = removeRange(s, 1, 2)
fmt.Println(s)
Which outputs (try it on the Go Playground):
[0 1 2 3 4 5]
[0 3 4 5]
Note: the above implementation does not check whether indices are valid (whether they are in range). If not, the code could panic. Add necessary checks if you need to.
Note #2: the above implementation modifies the elements of the passed slice, the returned slice will share the backing array of the parameter. If you want to avoid this, if you want to leave the input intact and allocate a new slice for the result, then do so:
func removeRange(hashCode []string, idx int, count int) []string {
result := make([]string, 0, len(hashCode)-count)
result = append(result, hashCode[:idx]...)
result = append(result, hashCode[idx+count:]...)
return result
}
Try this one on the Go Playground.
You don't need a method or function for this at all in golang. Go slices can be subsliced and appended in place, which is how you can quickly and easily remove subsets from any slice.
Say you want to remove 2 elements, starting at index 2, you'd simply write:
Sub := append(original [:2], original [4:]...)
Demo
How this works:
original[:2] creates a sub-slice starting at 0, with a length of 2 elements (so index 0 and 1)
append because to this first part, we want to add the rest of the slice, minus the range we want to skip/remove
original[4:] creates another sub-slice, this time starting at index 4, and ending wherever original ends. Just like we don't explicitly mention 0 as the starting point in the first sub-slice, by not specifying a number of elements here, golang will just include all of the remaining elements in the slice.
... because append is a variadic function (built-in, but you get the point), we need to pass in every element we want to append as a new argument. The ... operator expands the sub-slice and passes in every element as a separate argument.
Because we assigned the new slice to a new variable, original will remain unchanged, so if you want to overwrite the slice, you just assign it to the same variable.
Note I wrote this on my phone, so markup and code may not be quite right, but this should answer your question at least
I've explained the code using // comments and if not commented, code is self explanatory.
package main
import (
"fmt"
"os"
)
func RemoveRange(s []string, index, count int) []string {
sLen := len(s)
// Similar semantics to match (similar) the behavior of
// C# implementation
switch {
case index < 0, count < 0: // arguments are not valid
fmt.Fprintln(os.Stderr, "error: argument out of range error")
return s
case index+count-1 >= sLen: // range results in exceeding the limit
fmt.Fprintln(os.Stderr, "error: argument error")
return s
}
// Create a slice p and pre-allocate the size required
// to store the resultant slice after removing range.
// Result := s[:] -> s[:index] + s[index+count:]
// Remove := s[index:index+count-1]
p := make([]string, 0, sLen-count)
p = append(p, s[:index]...)
p = append(p, s[index+count:]...)
return p
}
func main() {
s := []string{"0", "1", "2", "3", "4", "5"}
fmt.Println(s)
r := RemoveRange(s, 1, 3)
fmt.Println(r)
}
Output:
[0 1 2 3 4 5]
[0 4 5]
For example, this is a slice:
[1, 2, 3, 3, 4]
want to get single data 1, 2, 4 's count and return count = 3.
Maybe remove duplicate items(include itself) is an idea, but didn't find suitalbe method.
What I tried:
func removeDuplicateItems() {
intSlice := []int{1, 2, 3, 3, 4}
fmt.Println(intSlice)
keys := make(map[int]bool)
list := []int{}
for _, entry := range intSlice {
if _, value := keys[entry]; !value {
keys[entry] = true
list = append(list, entry)
}
}
fmt.Println(list)
}
Got
[1 2 3 3 4]
[1 2 3 4]
I just changed your function a little bit:
func removeDuplicateItems() {
intSlice := []int{1, 2, 3, 3, 4}
fmt.Println(intSlice)
keys := make(map[int]int)
list := []int{}
for _, entry := range intSlice {
keys[entry]++
}
for k, v := range keys {
if v == 1 {
list = append(list, k)
}
}
fmt.Println(list)
}
https://play.golang.org/p/ESFLhC4VC-l
At this point the list is not sorted. If you want to sort your list afterward you need to use the sortpackage.
I suppose a really easy & quick way to get the count of unique values would be to use a map:
data := map[int]bool{}
cnt := 0 // count of unique values
for _, i := range intSlice {
if dup, ok := data[i]; !ok {
// we haven't seen value i before, assume it's unique
data[i] = false // add to map, mark as non-duplicate
cnt++ // increment unique count
} else if !dup {
// we have seen value i before, but this is only the second time
cnt-- // unique count goes down here
data[i] = true // mark this value as known duplicate
}
}
At the end of this loop, you'll have cnt with the count of unique values, and you can iterate the map data for all keys with value false to get the unique values that you found in the slice. All keys combined basically are the values in the slice, de-duplicated.
Is there any efficient way to get intersection of two slices in Go?
I want to avoid nested for loop like solution
slice1 := []string{"foo", "bar","hello"}
slice2 := []string{"foo", "bar"}
intersection(slice1, slice2)
=> ["foo", "bar"]
order of string does not matter
How do I get the intersection between two arrays as a new array?
Simple Intersection: Compare each element in A to each in B (O(n^2))
Hash Intersection: Put them into a hash table (O(n))
Sorted Intersection: Sort A and do an optimized intersection (O(n*log(n)))
All of which are implemented here
https://github.com/juliangruber/go-intersect
simple, generic and mutiple slices ! (Go 1.18)
Time Complexity : may be linear
func interSection[T constraints.Ordered](pS ...[]T) []T {
hash := make(map[T]*int) // value, counter
result := make([]T, 0)
for _, slice := range pS {
duplicationHash := make(map[T]bool) // duplication checking for individual slice
for _, value := range slice {
if _, isDup := duplicationHash[value]; !isDup { // is not duplicated in slice
if counter := hash[value]; counter != nil { // is found in hash counter map
if *counter++; *counter >= len(pS) { // is found in every slice
result = append(result, value)
}
} else { // not found in hash counter map
i := 1
hash[value] = &i
}
duplicationHash[value] = true
}
}
}
return result
}
func main() {
slice1 := []string{"foo", "bar", "hello"}
slice2 := []string{"foo", "bar"}
fmt.Println(interSection(slice1, slice2))
// [foo bar]
ints1 := []int{1, 2, 3, 9, 8}
ints2 := []int{10, 4, 2, 4, 8, 9} // have duplicated values
ints3 := []int{2, 4, 8, 1}
fmt.Println(interSection(ints1, ints2, ints3))
// [2 8]
}
playground : https://go.dev/play/p/lE79D0kOznZ
It's a best method for intersection two slice. Time complexity is too low.
Time Complexity : O(m+n)
m = length of first slice.
n = length of second slice.
func intersection(s1, s2 []string) (inter []string) {
hash := make(map[string]bool)
for _, e := range s1 {
hash[e] = true
}
for _, e := range s2 {
// If elements present in the hashmap then append intersection list.
if hash[e] {
inter = append(inter, e)
}
}
//Remove dups from slice.
inter = removeDups(inter)
return
}
//Remove dups from slice.
func removeDups(elements []string)(nodups []string) {
encountered := make(map[string]bool)
for _, element := range elements {
if !encountered[element] {
nodups = append(nodups, element)
encountered[element] = true
}
}
return
}
if there exists no blank in your []string, maybe you need this simple code:
func filter(src []string) (res []string) {
for _, s := range src {
newStr := strings.Join(res, " ")
if !strings.Contains(newStr, s) {
res = append(res, s)
}
}
return
}
func intersections(section1, section2 []string) (intersection []string) {
str1 := strings.Join(filter(section1), " ")
for _, s := range filter(section2) {
if strings.Contains(str1, s) {
intersection = append(intersection, s)
}
}
return
}
Try it
https://go.dev/play/p/eGGcyIlZD6y
first := []string{"one", "two", "three", "four"}
second := []string{"two", "four"}
result := intersection(first, second) // or intersection(second, first)
func intersection(first, second []string) []string {
out := []string{}
bucket := map[string]bool{}
for _, i := range first {
for _, j := range second {
if i == j && !bucket[i] {
out = append(out, i)
bucket[i] = true
}
}
}
return out
}
https://github.com/viant/toolbox/blob/a46fd679bbc5d07294b1d1b646aeacd44e2c7d50/collections.go#L869-L920
Another O(m+n) Time Complexity solution that uses a hashmap.
It has two differences compared to the other solutions discussed here.
Passing the target slice as a parameter instead of new slice returned
Faster to use for commonly used types like string/int instead of reflection for all
Yes there are a few different ways to go about it.. Here's an example that can be optimized.
package main
import "fmt"
func intersection(a []string, b []string) (inter []string) {
// interacting on the smallest list first can potentailly be faster...but not by much, worse case is the same
low, high := a, b
if len(a) > len(b) {
low = b
high = a
}
done := false
for i, l := range low {
for j, h := range high {
// get future index values
f1 := i + 1
f2 := j + 1
if l == h {
inter = append(inter, h)
if f1 < len(low) && f2 < len(high) {
// if the future values aren't the same then that's the end of the intersection
if low[f1] != high[f2] {
done = true
}
}
// we don't want to interate on the entire list everytime, so remove the parts we already looped on will make it faster each pass
high = high[:j+copy(high[j:], high[j+1:])]
break
}
}
// nothing in the future so we are done
if done {
break
}
}
return
}
func main() {
slice1 := []string{"foo", "bar", "hello", "bar"}
slice2 := []string{"foo", "bar"}
fmt.Printf("%+v\n", intersection(slice1, slice2))
}
Now the intersection method defined above will only operate on slices of strings, like your example.. You can in theory create a definition that looks like this func intersection(a []interface, b []interface) (inter []interface), however you would be relying on reflection and type casting so that you can compare, which will add latency and make your code harder to read. It's probably easier to maintain and read to write a separate function for each type you care about.
func intersectionString(a []string, b []string) (inter []string),
func intersectionInt(a []int, b []int) (inter []int),
func intersectionFloat64(a []Float64, b []Float64) (inter []Float64), ..ect
You can then create your own package and reuse once you settle how you want to implement it.
package intersection
func String(a []string, b []string) (inter []string)
func Int(a []int, b []int) (inter []int)
func Float64(a []Float64, b []Float64) (inter []Float64)
I encountered weird behaviour in go code today: when I append elements to slice in loop and then try to create new slices based on the result of the loop, last append overrides slices from previous appends.
In this particular example it means that sliceFromLoop j,g and h slice's last element are not 100,101 and 102 respectively, but...always 102!
Second example - sliceFromLiteral behaves as expected.
package main
import "fmt"
func create(iterations int) []int {
a := make([]int, 0)
for i := 0; i < iterations; i++ {
a = append(a, i)
}
return a
}
func main() {
sliceFromLoop()
sliceFromLiteral()
}
func sliceFromLoop() {
fmt.Printf("** NOT working as expected: **\n\n")
i := create(11)
fmt.Println("initial slice: ", i)
j := append(i, 100)
g := append(i, 101)
h := append(i, 102)
fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h)
}
func sliceFromLiteral() {
fmt.Printf("\n\n** working as expected: **\n")
i := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println("initial slice: ", i)
j := append(i, 100)
g := append(i, 101)
h := append(i, 102)
fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h)
}
link to play.golang:
https://play.golang.org/p/INADVS3Ats
After some reading, digging and experimenting I found that this problem is originated in slices referencing the same underlaying array values and can be solved by copying slice to new one before appending anything, however it looks quite... hesitantly.
What's the idomatic way for creating many new slices based on old ones and not worrying about changing values of old slices?
Don't assign append to anything other than itself.
As you mention in the question, the confusion is due to the fact that append both changes the underlying array and returns a new slice (since the length might be changed). You'd imagine that it copies that backing array, but it doesn't, it just allocates a new slice object that points at it. Since i never changes, all those appends keep changing the value of backingArray[12] to a different number.
Contrast this to appending to an array, which allocates a new literal array every time.
So yes, you need to copy the slice before you can work on it.
func makeFromSlice(sl []int) []int {
result := make([]int, len(sl))
copy(result, sl)
return result
}
func main() {
i := make([]int, 0)
for ii:=0; ii<11; ii++ {
i = append(i, ii)
}
j := append(makeFromSlice(i), 100) // works fine
}
The slice literal behavior is explained because a new array is allocated if the append would exceed the cap of the backing array. This has nothing to do with slice literals and everything to do with the internals of how exceeding the cap works.
a := []int{1,2,3,4,5,6,7}
fmt.Printf("len(a) %d, cap(a) %d\n", a, len(a), cap(a))
// len(a) 7, cap(a) 7
b := make([]int, 0)
for i:=1; i<8, i++ {
b = append(b, i)
} // b := []int{1,2,3,4,5,6,7}
// len(b) 7, cap(b) 8
b = append(b, 1) // any number, just so it hits cap
i := append(b, 100)
j := append(b, 101)
k := append(b, 102) // these work as expected now
If you need a copy of a slice, there's no other way to do it other than, copying the slice. You should almost never assign the result of append to a variable other than the first argument of append. It leads to hard to find bugs, and will behave differently depending on whether the slice has the required capacity or not.
This isn't a commonly needed pattern, but as with all things of this nature if you need to repeate a few lines of code multiple times, then you can use a small helper function:
func copyAndAppend(i []int, vals ...int) []int {
j := make([]int, len(i), len(i)+len(vals))
copy(j, i)
return append(j, vals...)
}
https://play.golang.org/p/J99_xEbaWo
There is also a little bit simpler way to implement copyAndAppend function:
func copyAndAppend(source []string, items ...string) []string {
l := len(source)
return append(source[:l:l], items...)
}
Here we just make sure that source has no available capacity and so copying is forced.