Cleaner way to iterate through array + create a string from values - go

With this code, is there a better way to loop through all the users and create a new string containing all their Nick values?
package main
import "fmt"
type User struct {
Nick string
}
func main() {
var users [2]User
users[0] = User{ Nick: "Radar" }
users[1] = User{ Nick: "NotRadar" }
names := ":"
for _, u := range users {
names += u.Nick + " "
}
fmt.Println(names)
}

For example,
package main
import (
"bytes"
"fmt"
)
type User struct {
Nick string
}
func main() {
var users [2]User
users[0] = User{Nick: "Radar"}
users[1] = User{Nick: "NotRadar"}
var buf bytes.Buffer
buf.WriteByte(':')
for _, u := range users {
buf.WriteString(u.Nick)
buf.WriteByte(' ')
}
names := buf.String()
fmt.Println(names)
}
This avoids a lot of allocations due to the concatenation of strings.
You could also write:
package main
import (
"fmt"
)
type User struct {
Nick string
}
func main() {
var users [2]User
users[0] = User{Nick: "Radar"}
users[1] = User{Nick: "NotRadar"}
var buf []byte
buf = append(buf, ':')
for _, u := range users {
buf = append(buf, u.Nick...)
buf = append(buf, ' ')
}
names := string(buf)
fmt.Println(names)
}

It really looks like you want a strings.Join here. You probably want to avoid that tight loop of repeated string concatenations in the original code; I'm fairly certain that Go doesn't implement a rope-like data structure for its primitive strings.
package main
import (
"fmt"
"strings"
)
type User struct {
Nick string
}
func main() {
var users [2]User
users[0] = User{Nick: "Radar"}
users[1] = User{Nick: "NotRadar"}
userNames := []string{}
for _, u := range users {
userNames = append(userNames, u.Nick)
}
names := ":" + strings.Join(userNames, " ")
fmt.Println(names)
}

Unfortunately, I do not know of a more elegant way to write that code.
Go does have a String.Join method so if you made a helper that converted your array of users to a slice of strings ([]string) then you could pass that to String.Join.
I think that Go's static typing and lack of templates makes it hard to write a general purpose map function like Ruby has.

This is what I was talking about in the comments of dyoo's post. Effectively a rewrite of join to prevent having to iterate over the list an extra time and allocate an extra slice.
func Usernames(users []User) string {
if len(users) == 0 {
return ""
}
if len(users) == 1 {
return users[0].Name
}
sep := " "
n := len(users)-1 // From len(sep) * len(a)-1, sep is always len 1 unlike in Join
for i := 0; i < len(users); i++ {
n += len(users[i].Name)
}
names := make([]byte,n)
namesp := copy(names, users[0].Name)
for _,u := range users[1:] {
namesp += copy(names[namesp:], sep)
namesp += copy(names[namesp:], u.Name)
}
return string(names)
}
For reference, strings.go with the strings.Join source:
http://golang.org/src/pkg/strings/strings.go
See line 356

Related

How to convert strings to lower case in GO?

I am new to the language GO and working on an assignment where i should write a code that return the word frequencies of the text. However I know that the words 'Hello', 'HELLO' and 'hello' are all counted as 'hello', so I need to convert all strings to lower case.
I know that I should use strings.ToLower(), however I dont know where I should Included that in the class. Can someone please help me?
package main
import (
"fmt"
"io/ioutil"
"log"
"strings"
"time"
)
const DataFile = "loremipsum.txt"
// Return the word frequencies of the text argument.
func WordCount(text string) map[string]int {
fregs := make(map[string]int)
words := strings.Fields(text)
for _, word := range words {
fregs[word] += 1
}
return fregs
}
// Benchmark how long it takes to count word frequencies in text numRuns times.
//
// Return the total time elapsed.
func benchmark(text string, numRuns int) int64 {
start := time.Now()
for i := 0; i < numRuns; i++ {
WordCount(text)
}
runtimeMillis := time.Since(start).Nanoseconds() / 1e6
return runtimeMillis
}
// Print the results of a benchmark
func printResults(runtimeMillis int64, numRuns int) {
fmt.Printf("amount of runs: %d\n", numRuns)
fmt.Printf("total time: %d ms\n", runtimeMillis)
average := float64(runtimeMillis) / float64(numRuns)
fmt.Printf("average time/run: %.2f ms\n", average)
}
func main() {
// read in DataFile as a string called data
data, err:= ioutil.ReadFile("loremipsum.txt")
if err != nil {
log.Fatal(err)
}
// Convert []byte to string and print to screen
text := string(data)
fmt.Println(text)
fmt.Printf("%#v",WordCount(string(data)))
numRuns := 100
runtimeMillis := benchmark(string(data), numRuns)
printResults(runtimeMillis, numRuns)
}
You should convert words to lowercase when you are using them as map key
for _, word := range words {
fregs[strings.ToLower(word)] += 1
}
I get [a:822 a.:110 I want all a in the same. How do i a change the code so that a and a. is the same? – hello123
You need to carefully define a word. For example, a string of consecutive letters and numbers converted to lowercase.
func WordCount(s string) map[string]int {
wordFunc := func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
}
counts := make(map[string]int)
for _, word := range strings.FieldsFunc(s, wordFunc) {
counts[strings.ToLower(word)]++
}
return counts
}
to remove all non-word characters you could use a regular expression:
package main
import (
"bufio"
"fmt"
"log"
"regexp"
"strings"
)
func main() {
str1 := "This is some text! I want to count each word. Is it cool?"
re, err := regexp.Compile(`[^\w]`)
if err != nil {
log.Fatal(err)
}
str1 = re.ReplaceAllString(str1, " ")
scanner := bufio.NewScanner(strings.NewReader(str1))
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
fmt.Println(strings.ToLower(scanner.Text()))
}
}
See strings.EqualFold.
Here is an example.

Setting struct fields from function

I'm sure there is a better way to do this, and I understand it's simple but I am new to go so bear with me. I am trying to set the fields of a struct (playersObject) from two functions (setCalculations and Calculations), more specifically, I am passing in values of two arrays (playerData and playerData2 from main to those functions, performing calculations in those functions, and want to return the values so that they can be set within the struct.
package main
import (
"fmt"
"os"
"log"
"strings"
"bufio"
"strconv"
)
type playersObject struct {
firstname, lastname string
batting_average, slugging_percentage, OBP, teamaverage float64
}
func strToFloat(playerData []string, playerData2 []float64) []float64 {
for _, i := range playerData[2:] {
j, err := strconv.ParseFloat(i, 64)
if err != nil {
panic(err)
}
playerData2 = append(playerData2, j)
}
return playerData2
}
func (player *playersObject) setCalculations (playerData []string, playerData2 []float64) {
player.firstname = playerData[1]
player.lastname = playerData[0]
player.batting_average = (playerData2[2] + playerData2[3] + playerData2[4] + playerData2[5]) / (playerData2[1])
player.slugging_percentage = ((playerData2[2]) + (playerData2[3]*2) + (playerData2[4]*3) + (playerData2[5]*4) )/(playerData2[1])
player.OBP = (( playerData2[2] + playerData2[3] + playerData2[4] + playerData2[5] +playerData2[6] +playerData2[7])/ (playerData2[0]))
}
func (player *playersObject) Calculations () (string, string, float64, float64, float64, ) {
return player.firstname, player.lastname, player.batting_average, player.slugging_percentage, player.OBP
}
func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter file name: ")
fileName, err := reader.ReadString('\n')
if err != nil {
log.Fatalf("failed opening file: %s", err)
}
fileName = strings.TrimSuffix(fileName, "\n")
//fmt.Printf("%q\n", fileName)
file, err := os.Open(fileName)
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
var fileOfPlayers []string
for scanner.Scan() {
fileOfPlayers = append(fileOfPlayers, scanner.Text())
}
file.Close()
// var total_Average_sum float64 = 0
var countofplayers float64 = 0
//var total_average float64 = 0
for _, player := range fileOfPlayers {
countofplayers ++
playerData := strings.Split(player, " ")
var playerData2 = []float64{}
playerData2 = strToFloat(playerData, playerData2)
player := playersObject{}
player.setCalculations(playerData, playerData2)
calcs := player.Calculations()
fmt.Println(firstname, lastname, batting_average, slugging_percentage, OBP)
}
}
I recieve the errors multiple-value player.Calculations() in single-value contextand undefined: firstname, lastname, batting_average, slugging_percentage, OBP
I know this is very incorrect but again I am new to go and OOP. If this can be done in any simpler way I am open to it and appreciate all help and tips. Thank you
Here, the error is thrown because Calculations() returns multiple values but you are trying to assign it to a single variable.
You need to change the player.Calculations() method invocation from
calcs := player.Calculations()
to
firstname, lastname, batting_average, slugging_percentage, OBP := player.Calculations()
Having said that I would recommend you to read more about golang may be here. You need to re-write the code in view of go best practises

Need to convert 2 Dimensional array into string and replace the last comma with full stop.(Golang)

How can I create string out of multi-dimensional array, preferably using goroutine or channel, in order to replace the last comma of the element with a full-stop?
Thanks
package main
import (
"fmt"
)
func main() {
pls := [][]string {
{"C", "C++"},
{"JavaScript"},
{"Go", "Rust"},
}
for _, v1 := range pls {
for _, v2 := range v1 {
fmt.Print(v2,", ")
}
}
}
I guess classic strings.Join would be easier to implement and maintain:
package main
import (
"fmt"
"strings"
)
func main() {
pls := [][]string{
{"C", "C++"},
{"JavaScript"},
{"Go", "Rust"},
}
var strs []string
for _, v1 := range pls {
s := strings.Join(v1, ", ")
strs = append(strs, s)
}
s := strings.Join(strs, ", ")
fmt.Println(s)
}
https://play.golang.org/p/2Nuv00PV5j

Convert slice of string input from console to slice of numbers

I'm trying to write a Go script that takes in as many lines of comma-separated coordinates as the user wishes, split and convert the string of coordinates to float64, store each line as a slice, and then append each slice in a slice of slices for later usage.
Example inputs are:
1.1,2.2,3.3
3.14,0,5.16
Example outputs are:
[[1.1 2.2 3.3],[3.14 0 5.16]]
The equivalent in Python is
def get_input():
print("Please enter comma separated coordinates:")
lines = []
while True:
line = input()
if line:
line = [float(x) for x in line.replace(" ", "").split(",")]
lines.append(line)
else:
break
return lines
But what I wrote in Go seems way too long (pasted below), and I'm creating a lot of variables without the ability to change variable type as in Python. Since I literally just started writing Golang to learn it, I fear my script is long as I'm trying to convert Python thinking into Go. Therefore, I would like to ask for some advice as to how to write this script shorter and more concise in Go style? Thank you.
package main
import (
"fmt"
"os"
"bufio"
"strings"
"strconv"
)
func main() {
inputs := get_input()
fmt.Println(inputs)
}
func get_input() [][]float64 {
fmt.Println("Please enter comma separated coordinates: ")
var inputs [][]float64
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
if len(scanner.Text()) > 0 {
raw_input := strings.Replace(scanner.Text(), " ", "", -1)
input := strings.Split(raw_input, ",")
converted_input := str2float(input)
inputs = append(inputs, converted_input)
} else {
break
}
}
return inputs
}
func str2float(records []string) []float64 {
var float_slice []float64
for _, v := range records {
if s, err := strconv.ParseFloat(v, 64); err == nil {
float_slice = append(float_slice, s)
}
}
return float_slice
}
Using only string functions:
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
var result [][]float64
var txt string
for scanner.Scan() {
txt = scanner.Text()
if len(txt) > 0 {
values := strings.Split(txt, ",")
var row []float64
for _, v := range values {
fl, err := strconv.ParseFloat(strings.Trim(v, " "), 64)
if err != nil {
panic(fmt.Sprintf("Incorrect value for float64 '%v'", v))
}
row = append(row, fl)
}
result = append(result, row)
}
}
fmt.Printf("Result: %v\n", result)
}
Run:
$ printf "1.1,2.2,3.3
3.14,0,5.16
2,45,76.0, 45 , 69" | go run experiment2.go
Result: [[1.1 2.2 3.3] [3.14 0 5.16] [2 45 76 45 69]]
With given input, you can concatenate them to make a JSON string and then unmarshal (deserialize) that:
func main() {
var lines []string
for {
var line string
fmt.Scanln(&line)
if line == "" {
break
}
lines = append(lines, "["+line+"]")
}
all := "[" + strings.Join(lines, ",") + "]"
inputs := [][]float64{}
if err := json.Unmarshal([]byte(all), &inputs); err != nil {
fmt.Println(err)
return
}
fmt.Println(inputs)
}

How to access a capturing group from regexp.ReplaceAllFunc?

How can I access a capture group from inside ReplaceAllFunc()?
package main
import (
"fmt"
"regexp"
)
func main() {
body := []byte("Visit this page: [PageName]")
search := regexp.MustCompile("\\[([a-zA-Z]+)\\]")
body = search.ReplaceAllFunc(body, func(s []byte) []byte {
// How can I access the capture group here?
})
fmt.Println(string(body))
}
The goal is to replace [PageName] with PageName.
This is the last task under the "Other tasks" section at the bottom of the Writing Web Applications Go tutorial.
I agree that having access to capture group while inside of your function would be ideal, I don't think it's possible with regexp.ReplaceAllFunc.
Only thing that comes to my mind right now regard how to do this with that function is this:
package main
import (
"fmt"
"regexp"
)
func main() {
body := []byte("Visit this page: [PageName] [OtherPageName]")
search := regexp.MustCompile("\\[[a-zA-Z]+\\]")
body = search.ReplaceAllFunc(body, func(s []byte) []byte {
m := string(s[1 : len(s)-1])
return []byte("" + m + "")
})
fmt.Println(string(body))
}
EDIT
There is one other way I know how to do what you want. First thing you need to know is that you can specify non capturing group using syntax (?:re) where re is your regular expression. This is not essential, but will reduce number of not interesting matches.
Next thing to know is regexp.FindAllSubmatcheIndex. It will return slice of slices, where each internal slice represents ranges of all submatches for given matching of regexp.
Having this two things, you can construct somewhat generic solution:
package main
import (
"fmt"
"regexp"
)
func ReplaceAllSubmatchFunc(re *regexp.Regexp, b []byte, f func(s []byte) []byte) []byte {
idxs := re.FindAllSubmatchIndex(b, -1)
if len(idxs) == 0 {
return b
}
l := len(idxs)
ret := append([]byte{}, b[:idxs[0][0]]...)
for i, pair := range idxs {
// replace internal submatch with result of user supplied function
ret = append(ret, f(b[pair[2]:pair[3]])...)
if i+1 < l {
ret = append(ret, b[pair[1]:idxs[i+1][0]]...)
}
}
ret = append(ret, b[idxs[len(idxs)-1][1]:]...)
return ret
}
func main() {
body := []byte("Visit this page: [PageName] [OtherPageName][XYZ] [XY]")
search := regexp.MustCompile("(?:\\[)([a-zA-Z]+)(?:\\])")
body = ReplaceAllSubmatchFunc(search, body, func(s []byte) []byte {
m := string(s)
return []byte("" + m + "")
})
fmt.Println(string(body))
}
If you want to get group in ReplaceAllFunc, you can use ReplaceAllString to get the subgroup.
package main
import (
"fmt"
"regexp"
)
func main() {
body := []byte("Visit this page: [PageName]")
search := regexp.MustCompile("\\[([a-zA-Z]+)\\]")
body = search.ReplaceAllFunc(body, func(s []byte) []byte {
// How can I access the capture group here?
group := search.ReplaceAllString(string(s), `$1`)
fmt.Println(group)
// handle group as you wish
newGroup := "<a href='/view/" + group + "'>" + group + "</a>"
return []byte(newGroup)
})
fmt.Println(string(body))
}
And when there are many groups, you are able to get each group by this way, then handle each group and return desirable value.
You have to call ReplaceAllFunc first and within the function call FindStringSubmatch on the same regex again. Like:
func (p parser) substituteEnvVars(data []byte) ([]byte, error) {
var err error
substituted := p.envVarPattern.ReplaceAllFunc(data, func(matched []byte) []byte {
varName := p.envVarPattern.FindStringSubmatch(string(matched))[1]
value := os.Getenv(varName)
if len(value) == 0 {
log.Printf("Fatal error substituting environment variable %s\n", varName)
}
return []byte(value)
});
return substituted, err
}

Resources