I'm trying to split a comma separated string and use the values to initalize a struct. This is how I do it right now:
type Address struct {
Street string
City string
ZipCode string
}
s := strings.Split("street,city,zip", ",")
data := Address{Street: s[0], City: s[1], ZipCode: s[2]}
The problem I'm having is that I have to handle this input as well:
"street,"
"street,city"
Any idea how to do it without going out of range? I've looked into unpacking with triple dots syntax ... but structs does not seem to support it.
Check the length of the slice before accessing the element:
data := Address{}
s := strings.Split("street,city,zip", ",")
data.Street = s[0]
if len(s) > 1 {
data.City = s[1]
}
if len(s) > 2 {
data.ZipCode = s[2]
}
If this comes up a lot, then write a simple helper function:
func get(s []string, i int) string {
if i >= len(s) {
return ""
}
return s[i]
}
Use it like this:
data := Address{Street: get(s, 0), City: get(s, 1), ZipCode: get(s, 2)}
If you'd rather use slightly more memory and less checks, you could also do:
s := strings.Split("street,city,zip", ",")
s = append(s, make([]string, 3 - len(s))...) // Change 3 to however many fields you expect
data := Address{Street: s[0], City: s[1], ZipCode: s[2]}
What this does is append empty strings to the slice to ensure it always has the right number of elements. Playground example: https://play.golang.org/p/Igj6yT5fffl
Related
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.
I just want a function that having a slice of a struct type "t", returns the returns the element I'm looking for and the remaining, I tried with the partial solution for my problem like pointed out here:
Delete element in a slice
But for a weird reason, it does not work as expected
https://play.golang.org/p/tvJwkF5c_tj
func main() {
var names = []string{"john", "julio", "pepito","carlos"}
fmt.Println(getMe("john", names))
}
func getMe(me string, names []string) (string, []string, bool) {
for i := range names {
if names[i] == me {
return names[i], append(names[:i], names[i+1:]...), true
}
}
return "", nil, false
}
but the result gives me:
julio [julio pepito carlos] true
UPDATE:
https://play.golang.org/p/1xbu01rOiMg
Taking the answer from #Ullaakut
If I do: append(names[:i], names[i+1:]...), it changes the original slice, so this does not work for me, I do not want my slice to change, because I will be using it later on
Simply use the range to get both the value and the index, instead of accessing the value by using the index.
package main
import (
"fmt"
)
func main() {
var names = []string{"john", "julio", "pepito", "carlos"}
name, newNames, _ := getMe("john", names)
fmt.Println("extracted name:\t\t\t\t", name)
fmt.Println("new slice without extracted name:\t", newNames)
fmt.Println("old slice still intact:\t\t\t", names)
}
func getMe(me string, names []string) (string, []string, bool) {
var newSlice []string
for i := 0; i < len(names); i++ {
if names[i] == me {
newSlice = append(newSlice, names[:i]...)
newSlice = append(newSlice, names[i+1:]...)
return names[i], newSlice, true
}
}
return "", nil, false
}
Outputs
extracted name: john
new slice without extracted name: [julio pepito carlos]
old slice still intact: [john julio pepito carlos]
See playground example
Edit after request for a faster version: Using the manual for instead of the range loop is much faster. Since you need to create a new slice without the element, it's necessary to build a new slice within the function, which is always going to take some processing power.
I have a struct that looks like this:
type guitaristT struct {
Surname string `required=true`
Year int64 `required=false`
American bool // example of missing tag
Rating float32 `required=true`
Styles []string `required=true,minsize=1`
}
I have an environment variable that looks like the following, and I'm using reflection to fill the struct based on the keys.
jimiEnvvar :="surname=Hendrix|year=1942|american=true|rating=9.99
|styles=blues|styles=rock|styles=psychedelic"
I'm able to set the string, int64, bool and float32 fields using reflection, but I'm stuck on how to append to the slice field Styles. For example, based on the above jimiEnvvar I would like the field jimi.Styles to have the values ["blues","rock", "psychedelic"].
I have the following (simplified) code:
result := guitaristT{}
// result.Styles = make([]string, 10) // XXX causes 10 empty strings at start
result.Styles = make([]string, 0) // EDIT: Alessandro's solution
...
v := reflect.ValueOf(&result).Elem()
...
field := v.FieldByName(key) // eg key = "styles"
...
switch field.Kind() {
case reflect.Slice:
// this is where I get stuck
//
// reflect.Append() has signature:
// func Append(s Value, x ...Value) Value
// so I convert my value to a reflect.Value
stringValue := reflect.ValueOf(value) // eg value = "blues"
// field = reflect.Append(field, stringValue) // XXX doesn't append
field.Set(reflect.Append(field, stringValue)) // EDIT: Alessandro's solution
EDIT:
A second part (which I solved) was filling a map in the struct. For example:
type guitaristT struct {
...
Styles []string `required=true,minsize=1`
Cities map[string]int
}
Where jimiEnvvar looks like:
jimiEnvvar += "|cities=New York^17|cities=Los Angeles^14"
I wrote in this manner:
case reflect.Map:
fmt.Println("keyAsString", keyAsString, "is Map, has value:", valueAsString)
mapKV := strings.Split(valueAsString, "^")
if len(mapKV) != 2 {
log.Fatalln("malformed map key/value:", mapKV)
}
mapK := mapKV[0]
mapV := mapKV[1]
thisMap := fieldAsValue.Interface().(map[string]int)
thisMap[mapK] = atoi(mapV)
thisMapAsValue := reflect.ValueOf(thisMap)
fieldAsValue.Set(thisMapAsValue)
The final result was:
main.guitaristT{
Surname: "Hendrix",
Year: 1942,
American: true,
Rating: 9.989999771118164,
Styles: {"blues", "rock", "psychedelic"},
Cities: {"London":11, "Bay Area":9, "New York":17, "Los Angeles":14},
}
If you're interested the full code is at https://github.com/soniah/reflect/blob/master/structs.go. Code is just some notes/exercises I'm writing.
EDIT2:
Chapter 11 of "Go in Practice" (Butcher and Farina) has detailed explanations of reflection, structs and tags.
You were not too far off. Just replace with
field.Set(reflect.Append(field, stringValue))
and you are done. Also, make sure you that you initialise the slice using
result.Styles = make([]string, 0)
or you will end up having 10 blank string at the top of the Styles array.
Hope this helps and good luck with your project.
I'm new to Go. Currently I have two arrays that look like:
words: ["apple", "banana", "peach"]
freq: [2, 3, 1]
where "freq" stores the count of each word in "words". I hope to combine the two arrays into a Json formatted byte slice that looks like
[{"w":"apple","c":2},{"w":"banana","c":3},{"w":"peach","c":1}]
How can I achieve this goal?
Currently I've declared a struct
type Entry struct {
w string
c int
}
and when I loop through the two arrays, I did
res := make([]byte, len(words))
for i:=0;i<len(words);i++ {
obj := Entry{
w: words[i],
c: freq[i],
}
b, err := json.Marshal(obj)
if err==nil {
res = append(res, b...)
}
}
return res // {}{}{}
which doesn't gives me the desired result. Any help is appreciated. Thanks in advance.
json.Marshal requires the struct fields to be exported.
You can use json tags to have json with small letter keys.
type Entry struct {
W string `json:"w"`
C int `json:"c"`
}
Also it would be easier to use a []Entry to generate the output json.
Sample code.
I have unusual task:
1. parse json message to Go struct
2. verify that all fields in JSON are within specific limits:
- string fields length no longer fixed constant
- maps contain no more than fixed number elements
- if values of map keys are nested structs verify for above 2 rules
To do this I use reflect, then iterating over elements,
and doing type checking:
- if int or float - nothing to do - no verification
- if string - verify length (and return if failed)
- if map verify map length (and return if failed), then iterate over map values and recursively check if their fields violate string/map rules
- default (I assume that this is struct nested JSON structure): convert it to interface slice and do recursive call.
Problem:
In JSON, I would have different map value types like:
- map[string]MyStruct1
- map[string]MyStruct2
- etc.
So when I'm doing type checking I write:
case map[string]interface{}
But in my program this case is never matched and goes to case default,
causing some error.
Any possible way to match type with case - map[string]interface{} ????
Here is my code for reference:
http://play.golang.org/p/IVXHLBRuPK
func validate(vals []interface{}) bool {
result := true
for _, elem := range vals {
switch v := elem.(type) {
case int, float64:
fmt.Println("Got int or float: ", v)
case string:
fmt.Println("Got string", v)
if len(elem.(string)) > 5 {
fmt.Println("String rule Violation!")
result = false
break
fmt.Println("After Break")
}
case map[string]interface{}:
fmt.Println("Got map", v)
if len(elem.(map[string]interface{})) > 1 || !validate(elem.([]interface{})) {
fmt.Println("Map length rule Violation!")
result = false
break
}
default:
fmt.Println("Got struct:", v)
// Convert to interface list all other structures no string/int/float/map:
new_v := reflect.ValueOf(elem)
new_values := make([]interface{}, new_v.NumField())
for j := 0; j < new_v.NumField(); j++ {
new_values[j] = new_v.Field(j).Interface()
}
// Recursively call for validate nested structs
if !validate(new_values) {
result = false
break
}
}
}
fmt.Println("After Break 2")
return result
}
func main() {
// Test truct:
x := C{1, B{"abc", A{10, 0.1, map[string]Host{"1,2,3,4": Host{"1.2.3.4"}}}}}
// Conversion:
v := reflect.ValueOf(x)
values := make([]interface{}, v.NumField())
for i := 0; i < v.NumField(); i++ {
values[i] = v.Field(i).Interface()
}
// Validate function verification
fmt.Println(validate(values))
}
In this example I can't ever reach case: map[string]interface{}
Big kudos on helpful suggestions!
The problem is case map[string]interface{} won't match map[string]Host so it will get parsed as a struct, which it isn't.
You will either have to check new_v.Kind() and handle maps via reflection or add a special case for map[string]Host.