How to format a string containing a dynamic number of elements? - go

I am trying to format a string based on the elements received from the function calling it. This number of elements can varyfrom one to many.
Is there a way to call fmt.Sprintf with a variable number of elements. Something along the lines of:
receivedElements := []interface{}{"some","values"}
formattedString := fmt.Sprintf("Received elements: ...%s", receivedElements...)
Output: Received elements: some values

You can use https://golang.org/pkg/strings/#Repeat like that:
args := []interface{}{"some", "values"}
fmt.Println(fmt.Sprintf("values: " + strings.Repeat("%s ", len(args)), args...))
https://play.golang.org/p/75J6i2fSCaM
Or if you don't want to have last space in the line, you can create slice of %s and then use https://golang.org/pkg/strings/#Join
args := []interface{}{"some", "values"}
ph := make([]string, len(args))
for i, _ := range args {
ph[i] = "%s"
}
fmt.Println(fmt.Sprintf("values: " + strings.Join(ph, ", "), args...))
https://play.golang.org/p/QNZT-i9Rrgn

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 split a string by a delimiter in Golang

I want to split a string by a delimiter, and the result should be a slice.
For example:
The given string might be like this:
foo := "foo=1&bar=2&&a=8"
The result should be like
result := []string{
"foo=1",
"bar=2&",
"a=8"
}
I mean i can use strings.split(foo, "&") to split but the result does not meet my requirements.
// the result with strings.split()
result := []string{
"foo=1",
"bar=2",
"",
"a=8"
}
Why not use url.ParseQuery. If you need a special characters like & (which is the delimiter) to not be treated as such, then it needs to be escaped (%26):
//m, err := url.ParseQuery("foo=1&bar=2&&a=8")
m, err := url.ParseQuery("foo=1&bar=2%26&a=8") // escape bar's & as %26
fmt.Println(m) // map[a:[8] bar:[2&] foo:[1]]
https://play.golang.org/p/zG3NEL70HxE
You would typically URL encode parameters like so:
q := url.Values{}
q.Add("foo", "1")
q.Add("bar", "2&")
q.Add("a", "8")
fmt.Println(q.Encode()) // a=8&bar=2%26&foo=1
https://play.golang.org/p/hSdiLtgVj7m

How can I log the value of passed parameters to a function?

My aim is to create a logging function that lists the name of a function and the list of passed parameters.
An example would be the following:
func MyFunc(a string, b int){
... some code ...
if err != nil{
errorDescription := myLoggingFunction(err)
fmt.Println(errorDescription)
}
}
func main(){
MyFunc("hello", 42)
}
// where MyLoggingFunction should return something like:
// "MyFunc: a = hello, b = 42, receivedError = "dummy error description"
So far it seems that in Go there is no way to get the name of the parameters of a function at runtime, as answered in this question, but I could give up this feature.
I've managed to get the function name and the memory address of the passed parameters by analysing the stack trace, but I'm hitting a wall when it comes to print somehow the parameters starting from their address (I understand that it might not be trivial depending on the type of the parameters, but even something very simple will do for now)
This is an implementation of the logging function I'm building (you can test it on this playground), is there away to print the parameter values?
func MyLoggingFunction(err error) string {
callersPCs := make([]uintptr, 10)
n := runtime.Callers(2, callersPCs) //skip first 2 entries, (Callers, GetStackTrace)
callersPCs = callersPCs[:n]
b := make([]byte, 1000)
runtime.Stack(b, false)
stackString := string(b)
frames := runtime.CallersFrames(callersPCs)
frame, _ := frames.Next()
trimmedString := strings.Split(strings.Split(stackString, "(")[2], ")")[0]
trimmedString = strings.Replace(trimmedString, " ", "", -1)
parametersPointers := strings.Split(trimmedString, ",")
return fmt.Sprintf("Name: %s \nParameters: %s \nReceived Error: %s", frame.Function, parametersPointers, err.Error())
}
If there are other ideas for building such logging function without analysing the stack trace, except the one that consists in passing a map[string]interface{} containing all the passed parameter names as keys and their values as values (that is my current implementation and is tedious since I'd like to log errors very often), I'd be glad to read them.

How to turn array into nested json object after splitting

I'm trying to deal with some error descriptions from this library because I need them to be nested JSON objects.
The errors seem to be an array originally, like this:
["String length must be greater than or equal to 3","Does not match format 'email'"]
I needed that to also include the field name of the containing error:
["Field1: String length must be greater than or equal to 3","Email1: Does not match format 'email'"]
After that I need to split each array value by colon : so I can have the field name and error description in separate variables like slice[0] and slice[1].
With that I want to make a nested JSON object like so:
{
"errors": {
"Field1": "String length must be greater than or equal to 3",
"Email1": "Does not match format 'email'"
}
}
This is my way of trying to achieve this:
var errors []string
for _, err := range result.Errors() {
// Append the errors into an array that we can use to split later
errors = append(errors, err.Field() + ":" + err.Description())
}
// Make the JSON map we want to append values to
resultMap := map[string]interface{}{
"errors": map[string]string {
"Field1": "",
"Email1": ""
},
}
// So we actually can use the index keys when appending
resultMapErrors, _ := resultMap["errors"].(map[string]string)
for _, split := range errors {
slice := strings.Split(split, ":")
for _, appendToMap := range resultMapErrors {
appendToMap[slice[0]] = slice[1] // append it like so?
}
}
finalErrors, _ := json.Marshal(resultMapErrors)
fmt.Println(string(finalErrors))
But this throws the errors
main.go:59:28: non-integer string index slice[0]
main.go:59:39: cannot assign to appendToMap[slice[0]]
Any clue how I can achieve this?
var errors = make(map[string]string)
for _, err := range result.Errors() {
errors[err.Field()] = err.Description()
}
// Make the JSON map we want to append values to
resultMap := map[string]interface{}{
"errors": errors,
}
finalErrors, _ := json.Marshal(resultMap)
fmt.Println(string(finalErrors))

Dynamically built formatting strings in Golang

I am attempting to create a formatting string dynamically in Go. The idea is that a map is created with the formatting types. Then a loop goes through them to output the various types.
The end result is to see how the formatting affects the output.
(I appreciate the example will produce the same output, but I would change f over time to other types)
Below is an example of this:
import (
"fmt"
"strings"
)
var formats = []string{"%f", "%v"}
var f float32 = 1 << 24
func main() {
for format := range formats {
// Generate formatting string here
parts := "%q => " + format + "\n"
fmt.Printf(parts, format, f)
}
}
The compiler is complaining about a int() conversion at the parts: line:
at line 11, file ch3/floating_point.go cannot convert "%q => " to type int
at line 11, file ch3/floating_point.go invalid operation: "%q => " + format` (mismatched types string and int)
I have attempted joining strings, but no luck:
parts:= strings.Join([]string{"%q =>",format,"\n"), " ")
fmt.Printf(parts,format,f)
Also fmt.Fprintf isn't helping either:
for format := range formats {
// Generate formatting string here
parts := fmt.Fprintf("%q => " + format, format, f)
fmt.Println(parts)
}
The issue is in your for format := range formats construct. Keep in mind that range formats will return two values: the actual value from the slice *and its index (which is the first value). So "%q => " + format + "\n" will actually try to concatenate "%s => " with the numeric iteration index.
If you want to iterate over the values contained in the slice, use the following loop for that:
for _, format := range formats {
// ...
}
See golang For and blank identifier
If you're looping over an array, slice, string, or map, or reading
from a channel, a range clause can manage the loop.
for key, value := range oldMap {
newMap[key] = value
}
Right way to range with array
for index, value := range formats {
if you want to skip index do
for _, value := range formats {

Resources