Golang for seems to be skipping some entities - go

So I have an out string like this:
out :=
"mobile" + "\n" +
"mobile/communicators" + "\n" +
"mobile/communicators/mock" + "\n" +
"mobile/handlers" + "\n" +
"mobile/mocks" + "\n" +
"mobile/models" + "\n" +
"mobile/requests"
Which I'm trying to transform into a slice and then remove the entries which have matching sub-strings in the pkgsToIgnore slice:
pkgs := strings.Split(strings.TrimSpace(string(out)), "\n")
pkgsToIgnore := []string{"mock", "models"}
for i, pkg := range pkgs {
for _, pkgToIgnore := range pkgsToIgnore {
if strings.Contains(pkg, pkgToIgnore){
pkgs = append(pkgs[:i], pkgs[i+1:]...)
}
}
}
for _, pkg := range pkgs {
fmt.Println(pkg)
}
And then this is the result I get from the Println iterations:
mobile
mobile/communicators
mobile/handlers
mobile/models
mobile/requests
My code seems to work fine for the lines containing mock as a sub-string, as they get removed. But I can't understand why mobile/models doesn't.

There are several ways to do this without accidentally skipping elements. One way is simply to copy the elements you want into a temporary slice and then replace the original once you've done everything (it's much harder to make mistakes that way). But if you don't like that, it helps to use two indices:
src, dst := 0, 0
for src < len(pkgs) {
var ignored bool
for _, pkgToIgnore := range pkgsToIgnore {
if strings.Contains(pkg, pkgToIgnore) {
ignored = true
break
}
}
if !ignored {
pkgs[dst] = pkgs[src]
dst += 1
}
src += 1
}
pkgs = pkgs[:dst]
src will range from 0 to the last index of pkgs; dst will always be less than or equal to src (so we never overwrite an element we haven't seen yet); each element is only copied once, to its final destination, instead of shifting all elements left by one each time an element is removed (which is potentially quadratic). The length of the slice is only adjusted at the end, to reflect the number of elements actually retained.

The easiest way to see what is happening is to output i and pkg during each iteration i.e.
for i, pkg := range pkgs {
fmt.Println(i, pkg)
for _, pkgToIgnore := range pkgsToIgnore {
if strings.Contains(pkg, pkgToIgnore) {
pkgs = append(pkgs[:i], pkgs[i+1:]...)
}
}
}
The output of this is:
0 mobile
1 mobile/communicators
2 mobile/communicators/mock
3 mobile/mocks
4 mobile/requests
5 mobile/requests
6 mobile/requests
This is probably not what you were expecting! The reason for that is that you are removing elements from pkgs whilst iterating over it. Lets work through this when i = 2; pkg == "mobile/communicators/mock" so the string will be removed from pkgs. Element 2 of pkgs is now "mobile/handlers". We loop around to the for and move to the next element (3) which is "mobile/mocks" (not "mobile/handler" - that is now element 2 so "mobile/handler" does not get checked).
The relevant section of the go spec may help in understanding this:
For an array, pointer to array, or slice value a, the index iteration
values are produced in increasing order, starting at element index 0.
If at most one iteration variable is present, the range loop produces
iteration values from 0 up to len(a)-1 and does not index into the
array or slice itself. For a nil slice, the number of iterations is 0.
It is possible to update a slice within a loop like this but doing so requires care; generally its simpler to copy the elements you want to keep into a new slice.

Related

Golang map[string]float where float is getting overwirtten instead of adding to the existing value present for the key

I have a string like so
00:01:07,400-234-090 00:05:01, 701-080-080 00:05:00, 400-234-090
where the on the right side is the phone number and on the left is the duration of the call in hh:mm:ss format. I am trying to put this in a map[string]float64 by splitting the string first on "," and the split the left side on ":". Then make a Duration from the duration of the call and convert in to minutes. It works fine till this.
Now I am trying to put this in a map, I expected that if the key which is the phone number on the right is already present in the map then it will just add the float64 value to the existing value of the key. However, that is not the case, it seems to be having the same key twice in the map.
Here is my code:
phoneBill := `00:01:07,400-234-090
00:05:01, 701-080-080
00:05:00, 400-234-090`
callLog := strings.Split(phoneBill, "\n")
mapDetails := make(map[string]float64)
for _, v := range callLog {
callDetails := strings.Split(strings.TrimSpace(v), ",")
timeDetails := strings.Split(strings.TrimSpace(callDetails[0]), ":")
durationString := strings.TrimSpace(timeDetails[0]) + "h" + strings.TrimSpace(timeDetails[1]) + "m" + strings.TrimSpace(timeDetails[2]) + "s"
t, _ := time.ParseDuration(durationString)
total := t.Minutes()
fmt.Printf("phone number is: %v \n", callDetails[1])
fmt.Printf("duration of call in minutes is %v \n", total)
if v, found := mapDetails[callDetails[1]]; found {
total += v
fmt.Println("total is :v", total)
}
mapDetails[(callDetails[1])] = total
}
fmt.Println("Values in map are: %v", mapDetails)
https://go.dev/play/p/fLcEDbgQ-7q
Fix by trimming spaces on the duration and the number. The current code does not handle spaces before or after the comma.
i := strings.Index(v, ",")
if i < 0 {
log.Fatal("bad line", v)
}
dur := strings.TrimSpace(v[:i])
num := strings.TrimSpace(v[i+1:])
Taking advantage of the fact that maps return the zero value for missing keys, the code to update the map can be simplified to the following.
mapDetails[num] += total
Run the code on the playground.
When debugging code that parses strings, it's helpful to make whitespace visible by printing with %q. The bug in the original program is more visible with:
fmt.Printf("phone number is: %q \n", callDetails[1])

How to slice a slice for eliminating matching values from identical slice

I'm looking for an easy way to iterate through a slice and on every value that's present in the current slice, remove the element from another slice.
I have a struct:
a := enter{
uid: 1234,
status: []StatusEntry{
{
rank: 1,
iterate: ierationState_Ongoing,
},
{
rank: 2,
iterate: ierationState_Completed,
},
},
}
In my .go file, I have a constant
Steps = [5]int64{0,1,2,3,4}
According to my requirement I want to copy the Steps in another variable and perform remove operation :
Steps2 := Steps // Make a copy of Steps
for _, element := enter.status {
// Remove that element from Steps
}
But I find it difficult to do so since Golang doesn't give me direct method to iterate and remove every element from enter.status from Steps.
I tried multiple things like creating a removeIndex function as posted on various stackoverflow answers like this:
for i, element := enter.status {
Steps2 = removeIndex(enter.status, i)
}
func removeIndex(s []int, index int) []int {
ret := make([]int, 0)
ret = append(ret, s[:index]...)
return append(ret, s[index+1:]...)
}
But it doesn't make sense to use this because I'm trying to remove a matching value (element) and not a specific index (for eg index 5) from Steps2.
Basically, for every element that's in slice enter.status, I want to remove that element/value from slice Steps2
Careful:
[5]int64{0,1,2,3,4}
This is an array (of 5 ints), not a slice. And:
Steps2 := Steps
If Steps were a slice, this would copy the slice header without copying the underlying array.
In any case, given some slice s of type T and length len(s), if you are allowed to modify s in place and order is relevant, you generally want to use this algorithm:
func trim(s []T) []T {
out := 0
for i := range s {
if keep(s[i]) {
s[out] = s[i]
out++
}
}
return s[:out]
}
where keep is your boolean function to decide whether to keep an element. To make this produce a new slice, allocate an output slice of the appropriate length (len(s)) at the start and optionally shrink it later, or, if you expect to throw out most elements, make it empty at the start and use append.
When the keep function is "the value of some field in the output slice does not match the value of any earlier kept field" and the type of that field is usable as a key type, you can use a simple map[T2]struct{} to determine whether the value has occurred yet:
seen := make(map[T2]struct{}, len(s))
and then the keep test and copy sequence becomes:
_, ok := seen[s[i].field]
if !ok {
seen[s[i].field] = struct{}{}
s[out] = s[i]
out++
}
The initial size of seen here is optimized on the theory that most values will be kept; if most values will be discarded, make the map initially empty, or small.

Why not just assign value to target in method header.clone

the official version
my question is What's the role of "sv"
func (h Header) Clone() Header {
if h == nil {
return nil
}
// Find total number of values.
nv := 0
for _, vv := range h {
nv += len(vv)
}
sv := make([]string, nv) // shared backing array for headers' values
h2 := make(Header, len(h))
for k, vv := range h {
n := copy(sv, vv)
h2[k] = sv[:n:n]
sv = sv[n:]
}
return h2
}
Why not just write it like this
just assgin value to h2 instead of creating a slice
for k, vv := range h {
// changed here
h2[k] = vv
}
It looks like Header is a map[string][]string, (maybe http.Header?). If it did what you suggested, then the new Header would be a shallow copy of the map, containing the original slices from the source Header. If the contents of the backing array of those slices are modified, the copied header would be modified as well. For instance:
s:=make([]string,0,10)
s=append(s,"a")
header[key]=s
h2:=header.Clone()
s=append(s,"b")
// Here, both header[key] and h2[key] has two elements, "a" and "b"
The prevent this, Clone is doing a deep copy, where each []string is also copied. That would need len(h) string slice allocations, one for each key. Instead, this algorithm uses one allocation, a single shared string slice containing all the strings of the original header.
The algorithm first counts the number of strings contained in the header, and allocates a string slice of that size. In the second for loop, it copies the strings from the value of a key to the shared string slice, creates a slice using that shared slice array and sets it as the value of the key, and then updates the shared slice to point to the next available empty slot. In the end, it is a deep-copy with a single memory allocation instead of len(h).

Why does my append function change every member of my slice to the new item to add?

I am making a simple prime factor function that can put a slice that is [][]int like
prime_factors_mult(315) => [[3,2],[5,1],[7,1]]
but instead of this result I get [[7,1][7,1][7,1][7,1]] which is afterwards reduced to a empty slice because they repeat them selfs. I tried to look at it with step by step and it changes all the values to the last append. What should I do to avoid this ?
func prime_factors_mult(x []int)(y [][]int){// Problem 36
in :=[]int{0,0}
var k [][]int
for _,item := range x{
tok := 0
for i:=0;i<len(x);i++{
if item == x[i]{
tok++
}
}
in[0]=item
in[1]=tok
k=append(k,in)
}
for _,item := range k{
for i:=0;i<len(k);i++{
if item[0] != k[i][0]{
y = append(y,item)
}
}
}
return
}
You create your in slice once and then modify it each time, so you add the exact same object to k each time.
Append a new slice instead:
k=append(k,[]int{item, tok})
Range's syntax returns the index and a reference of the value each time. You choose to use the reference both times, and the value gets modified but you keep on appending the same address. That's why you're only getting the last value, the range loop keeps modifying it and you're left with the last value from the loop.
approach 1:
To avoid this, change your loops to be for index := range k instead of for _,item := range k use the index from your loop and append k[index] instead of item.
approach 2:
for _, item range something {
item := item
If you want to keep your syntax as is, you can create a local variable inside the loop, using the variable you got from range. Add item := item in the first line of both your loop blocks. though it looks weird, this will allocate new item every time, and you will append and use that variable instead.

How to efficiently concatenate string array and string in golang

I have a bunch of strings and []strings in golang which I need to concatenate. But for some reason I am getting a lot of whitespaces along the way which I need to get rid of.
Here's the code
tests := strings.TrimSpace(s[0])
dep_string := make ([]string, len(tests) + len(sfinal))
dep_string = append (dep_string,tests)
for _,v := range sfinal {
dep_string = append(dep_string,v)
}
fmt.Println("dep_String is ",dep_string)
Input:
s[0] = "filename"
sfinal = [test test1]
expected output
[filename test test1]
actual output
[ filename test test1]
It's really weird; even after using TrimSpace I am not able to get rid of excess space. Is there any efficient way to concatenate them?
The whitespace is due to all of the empty elements in dep_string. When you use the make function, it creates a slice with the specified length and capacity, filled with a bunch of nothing. Then, when you use append, it sees that the slice has reached its maximum capacity, extends the slice, then adds your elements, after all of the nothing. The solution is to make a slice with the capacity to hold all of your elements, but with initial length zero:
dep_string := make ([]string, 0, len(tests) + len(sfinal))
strings.TrimSpace is unnecessary. You can read more at http://blog.golang.org/slices
Bill DeRose and Saposhiente are correct about how slices work.
As for a simpler way of solving your problem, you could also do (play):
fmt.Println("join is",strings.Join(append(s[:1],sfinal...)," "))
When you do the assignment dep_string := make ([]string, len(tests) + len(sfinal)), Go zeros out the allocated memory so dep_string then has len(tests) + len(sfinal) empty strings at the front of it. As it's written now you append to the end of the slice, after all those zeroed out strings.
Run this to see where those blanks are showing up in your code. You can fix it by making a slice of length 0 and capacity len(tests) + len(sfinal) instead. You can then concatenate them by using strings.Join.
Here's a simple and efficient solution to your problem.
package main
import "fmt"
func main() {
s := make([]string, 1, 4)
s[0] = "filename"
sfinal := []string{"test", "test1"}
dep_string := append(s[:1], sfinal...)
fmt.Println("dep_String is ", dep_string)
}
Output:
dep_String is [filename test test1]
When you do the assignment dep_string := make ([]string, len(tests) + len(sfinal)), it allocate len(tests) + len(sfinal) null strings ,it is 10 null strings in your case,so when you do the assignment fmt.Println("dep_String is ",dep_string) ,it will print 10 null strings, because fmt.Println(slice of string) will add blank between two elements,so it will print 9 blanks ,so it will prints [ filename test test1] after you append, the whitespaces is the blanks between the 10 null string.

Resources