How to get the index of a string in an array? - go

Here is my sample code:
slice_of_string := strings.Split("root/alpha/belta", "/")
res1 := bytes.IndexAny(slice_of_string , "alpha")
I got this error
./prog.go:16:24: cannot use a (type []string) as type []byte in argument to bytes.IndexAny
The logic here is when I input a path and a folder name (or file name), I want to know the level of the folder name (or a file name) in that path.
I do it by:
Split the path to an array
Get the index of the folder name (or a file name) in the path
If the index is 0 then the level would be 1, etc.

You probably need to loop over the slice and find the element that you are looking for.
func main() {
path := "root/alpha/belta"
key := "alpha"
index := getIndexInPath(path, key)
fmt.Println(index)
}
func getIndexInPath(path string, key string) int {
parts := strings.Split(path, "/")
if len(parts) > 0 {
for i := len(parts) - 1; i >= 0; i-- {
if parts[i] == key {
return i
}
}
}
return -1
}
Note that the loop is backwards to address the logic issue that Burak Serdar pointed out that it may fail on a path like /a/a/a/a otherwise.

Use strings.IndexAny instead of bytes.IndexAny if you want to operate on a []string.

there are no inbuild function available in standard library to search in slice of string, but if slice of string is sorted then you can use sort.SearchStrings to search. But in case of unsorted slice of string you have to implement it using for loop.

Related

How to output iterate over first N elements of slice?

I need to take applicant's first name, second name and GPA, then output only the first N applicants. For example, I have 5 applicants, but only N=3 can pass through.
To do this task, I decided to use a slice of struct.
The struct looks like this:
type Applicant struct {
firstName string
secondName string
GPA float64
}
I created a slice and initialized it:
applicants := []Applicant{}
...
fmt.Scan(&firstName, &lastName, &GPA)
applicants = append(applicants, Applicant{firstName, lastName, GPA})
Now my task is to output only names of first 3 applicants with highest GPA. I already sorted the slice from the best GPA to the worst.
I tried to do output applicants slice like this, but got error:
for _, applicant := range applicants {
fmt.Println(applicant.secondName + " " + applicant.secondName)
}
Can you help me with slice name output?
To get the first 3 with highest GPA you first sort the slice (what you alread did) and then just create a subslice:
func GetTopThree(applicants []Applicant) []Applicant {
sort.Slice(applicants, func(i, j int) bool {
return applicants[i].GPA > applicants[j].GPA
})
return applicants[:3]
}
To just get the names you can create a new slice
func GetTopThreeNames(applicants []Applicant) []string {
var topThree []string
for i := 0; i < int(math.Min(3, float64(len(applicants)))); i++ {
topThree = append(topThree, applicants[i].firstName)
}
return topThree
}
If you want to map the first names and last names separately, this could be an approach:
func TopThreeNames(applicants []Applicant) [][2]string {
top := applicants[:int(math.Min(3, float64(len(applicants))))]
var names [][2]string
for _, a := range top {
names = append(names, [2]string{a.firstName, a.secondName})
}
return names
}
The function maps each Applicant element to an array of length two, whereby the first element is equal to its first name and the second element to its second name.
For instance (unsafe since the length of the slice could be empty):
names := TopThreeNames(applicants)
first := names[0]
fmt.Printf("First name: %s and last name: %s\n", first[0], first[1])
If your task really is just to print out the names then this is one possible way
for i := 0; i < 3 && i < len(applicants); i++ {
fmt.Printf("%s %s\n", applicants[i].firstName, applicants[i].secondName)
}
Note that the list must be sorted first like is is shown in other posts.

Is there a more canonical way to manipulate a URL path in Go?

I'm working on a system in Go to act as middleware. For some of our URLs in other services that I want to parse and analyze, we've embedded something variable at the beginning of the path.
For instance, imagine for user amysmith we might have the path /amysmith/edit/profile to edit the user profile of amysmith.
For the purposes of this system, I'd like to swap the path to become /edit/profile, as if the first part of the path weren't there. Admittedly, this is not ideal, but changing that overall naming is a bigger project for another time.
This is achievable by using strings.Join and then prepending a / and lopping off the last character. I'd like to do this because it keeps the path format in line with other paths that aren't manipulated, which will always begin with a /.
func (r Resource) CleanedPath() string {
parts := strings.Split(r.URL.Path, "/")
return strings.TrimRight("/" + strings.Join(parts[2:], "/"), "/")
}
That approach feels a bit clunky to me. Is there a better more "Go" way to do this?
Chop off everything before the second /.
func cleanPath(p string) string {
if i := strings.IndexByte(p[len("/"):], '/'); i >= 0 {
return p[i+len("/"):]
}
return p
}
This approach avoids memory allocations.
Here is a method that uses no packages:
package main
func split(path string, count int) string {
for n, r := range path {
if r == '/' { count-- }
if count == 0 {
return path[n:]
}
}
return ""
}
func main() {
{ // example 1
s := split("/amysmith/edit/profile", 2)
println(s == "/edit/profile")
}
{ // example 2
s := split("/amysmith/edit/profile", 3)
println(s == "/profile")
}
}

How do I get the address of an element of an array which is an element of a struct which is a pointer

I have a recursive function which operates on a pointer to a tree-like struct.
The struct is of a directory, and one of the elements is an array of subdirectories (i.e. directory[]). When I'm iterating over that array, I want to take an element and pass its address (&) to the function. How do I do that?
I have a *directory, and I want the address of (*directory).subdirectories[7]. Apparently &directory.subdirectories[7] is not the address. What is the correct syntax?
type directory struct {
name string
subdirectories []directory
}
func compareDirectories(asIs *directory, toBe *directory) {
for k, inputDir := range toBe.subdirectories {
for l, outputDir := range asIs.subdirectories {
if inputDir.name == outputDir.name {
compareDirectories(&(asIs.subdirectories[l]), &(toBe.subdirectories[k]))
}
}
}
}
What is the correct syntax for compareDirectories?
&directory.subdirectories[7] is the proper syntax, but don't confuse the type name with a variable name. directory is the type name, you need a variable of type *directory, and use that variable's name instead of directory (not necessarily a variable, but a value of type *directory).
See this example:
dir := &directory{
name: "foo",
subdirectories: []directory{
7: directory{name: "bar"},
},
}
p := &dir.subdirectories[7]
fmt.Printf("%T %+v", p, p)
Which outputs (try it on the Go Playground):
*main.directory &{name:bar subdirectories:[]}

Appending to struct slice in Go

I have two structs, like so:
// init a struct for a single item
type Cluster struct {
Name string
Path string
}
// init a grouping struct
type Clusters struct {
Cluster []Cluster
}
What I want to do is append to new items to the clusters struct. So I wrote a method, like so:
func (c *Clusters) AddItem(item Cluster) []Cluster {
c.Cluster = append(c.Cluster, item)
return c.Cluster
}
The way my app works, I loop through some directories then append the name of the final directory and it's path. I have a function, that is called:
func getClusters(searchDir string) Clusters {
fileList := make([]string, 0)
//clusterName := make([]string, 0)
//pathName := make([]string, 0)
e := filepath.Walk(searchDir, func(path string, f os.FileInfo, err error) error {
fileList = append(fileList, path)
return err
})
if e != nil {
log.Fatal("Error building cluster list: ", e)
}
for _, file := range fileList {
splitFile := strings.Split(file, "/")
// get the filename
fileName := splitFile[len(splitFile)-1]
if fileName == "cluster.jsonnet" {
entry := Cluster{Name: splitFile[len(splitFile)-2], Path: strings.Join(splitFile[:len(splitFile)-1], "/")}
c.AddItem(entry)
}
}
Cluster := []Cluster{}
c := Clusters{Cluster}
return c
}
The problem here is that I don't know the correct way to do this.
Currently, I'm getting:
cmd/directories.go:41:4: undefined: c
So I tried moving this:
Cluster := []Cluster{}
c := Clusters{Cluster}
Above the for loop - range. The error I get is:
cmd/directories.go:43:20: Cluster is not a type
What am I doing wrong here?
The error is in the loop where you are calling AddItem function on Cluster method receiver which is not defined inside getClusters function. Define Cluster struct before for loop and then call the function c.AddItem as defined below:
func getClusters(searchDir string) Clusters {
fileList := make([]string, 0)
fileList = append(fileList, "f1", "f2", "f3")
ClusterData := []Cluster{}
c := Clusters{Cluster: ClusterData} // change the struct name passed to Clusters struct
for _, file := range fileList {
entry := Cluster{Name: "name" + file, Path: "path" + file}
c.AddItem(entry)
}
return c
}
you have defined the same struct name to Clusters struct that's why the error
cmd/directories.go:43:20: Cluster is not a type
Checkout working code on Go playground
In Golang Composite literal is defined as:
Composite literals construct values for structs, arrays, slices, and maps and create a new value each time they are evaluated. They
consist of the type of the literal followed by a brace-bound list of
elements. Each element may optionally be preceded by a corresponding
key.
Also Have a look on struct literals section defined in above link for Compositeliterals to get more description.
You need to define c before entering the loop in which you use it.
The Cluster is not a type error is due to using the same Cluster name as the type and the variable, try using a different variable name.
clusterArr := []Cluster{}
c := Clusters{clusterArr}
for _, file := range fileList {
....
}

Writing a struct's fields and values of different types to a file in Go

I'm writing a simple program that takes in input from a form, populates an instance of a struct with the received data and the writes this received data to a file.
I'm a bit stuck at the moment with figuring out the best way to iterate over the populated struct and write its contents to the file.
The struct in question contains 3 different types of fields (ints, strings, []strings).
I can iterate over them but I am unable to get their actual type.
Inspecting my posted code below with print statements reveals that each of their types is coming back as structs rather than the aforementioned string, int etc.
The desired output format is be plain text.
For example:
field_1="value_1"
field_2=10
field_3=["a", "b", "c"]
Anyone have any ideas? Perhaps I'm going about this the wrong way entirely?
func (c *Config) writeConfigToFile(file *os.File) {
listVal := reflect.ValueOf(c)
element := listVal.Elem()
for i := 0; i < element.NumField(); i++ {
field := element.Field(i)
myType := reflect.TypeOf(field)
if myType.Kind() == reflect.Int {
file.Write(field.Bytes())
} else {
file.WriteString(field.String())
}
}
}
Instead of using the Bytes method on reflect.Value which does not work as you initially intended, you can use either the strconv package or the fmt to format you fields.
Here's an example using fmt:
var s string
switch fi.Kind() {
case reflect.String:
s = fmt.Sprintf("%q", fi.String())
case reflect.Int:
s = fmt.Sprintf("%d", fi.Int())
case reflect.Slice:
if fi.Type().Elem().Kind() != reflect.String {
continue
}
s = "["
for j := 0; j < fi.Len(); j++ {
s = fmt.Sprintf("%s%q, ", s, fi.Index(i).String())
}
s = strings.TrimRight(s, ", ") + "]"
default:
continue
}
sf := rv.Type().Field(i)
if _, err := fmt.Fprintf(file, "%s=%s\n", sf.Name, s); err!= nil {
panic(err)
}
Playground: https://play.golang.org/p/KQF3CicVzA
Why not use the built-in gob package to store your struct values?
I use it to store different structures, one per line, in files. During decoding, you can test the type conversion or provide a hint in a wrapper - whichever is faster for your given use case.
You'd treat each line as a buffer when Encoding and Decoding when reading back the line. You can even gzip/zlib/compress, encrypt/decrypt, etc the stream in real-time.
No point in re-inventing the wheel when you have a polished and armorall'd wheel already at your disposal.

Resources