Meet Rajesh Gor

Golang: String Manipulation

Introduction

In the 15th post of the Series, we will be looking into the details of the String manipulation and performing types of operations in Golang. We will explore string manipulation, concatenation, helper functions, etc. which will help in working with strings in Golang.

String Concatenation

String Concatenation refers to the combining and formatting of strings in Golang. We can combine multiple strings and formating the way we display the strings in Golang. We have a few ways and functions to concatenate strings in Golang.

Using the + operator

We can simply concatenate strings using the + operator, though keep in mind you should only concatenate the string with a string and not any other data type like integer, or float, it will throw out errors for mismatched string types.

package main

import (
    "fmt"
)
func main() {
    s1 := "Go"
    s2 := "Programming"
    s3 := "Language"

    s := s1 + s2 + s3
    fmt.Println(s)
}
$ go run concatenate.go

GoProgrammingLanguage

The + operator will literally join the strings as it is and form a string.

Using the += operator

The other way to continuously append a string to an existing string, we can use the += operator. This operator will append the provided string to the end of the original string.

p := "Story"
p += "Book"
fmt.Println(p)
go run concatenate.go

StoryBook

Using the Join method

The join method is a function available in the string package in Golang. We can join strings elements in a slice or an array using the Join method in the strings package in golang. The Join method will combine all the elements in between the elements with a particular string. So, the function takes two parameters Join(array, string), the array or a slice is parsed into the function which will be used to insert the provided string in between the elements of the slice.

Join

  • Parameters : array/slice, string
  • Return Value : string
q := []string{"meetgor.com", "tags", "golang", "string"}
r := strings.Join(q, "/")
fmt.Println(r)
go run concatenate.go

meetgor.com/tags/golang/string

In the above example, we use have used the Join method to insert a string in between the elements of a slice of strings. The string "/" has been inserted in between the elements, and the elements are combined as a single string. So, each individual element starting from the 0 index meetgor.com is appended the string / and further the next element tags have been appended and the procedure caries on till the last element. Note that the string is not inserted after the last element. The function Join returns a string and thereby we store the string in a variable.

Using Sprintf method

We can use the Sprintf function from the fmt package to format the string by storing the string rather than printing it to the console. The sprintf function is quite similar to the Printf but it only parses strings rather than printing them directly to the console.

// Using Sprintf function to format strings

name := "peter"
domain := "telecom"
service := "ceo"

email := fmt.Sprintf("%s.%s@%s.com", service, name, domain)
fmt.Println(email)
go run concatenate.go

ceo.peter@telecom.com

The sprintf function basically allows us to concatenate strings in a defined format just like we use printf to print formatted strings. In the above example, we have formatted three strings in the form of an email by assigning a placeholder for the string i.e. %s, and adding the required characters in the formatted string.

Using Go string Builder method

The Builder type is provided by the strings package in Golang. The Builder type helps in building strings in an efficient way. By creating a string builder object, we can perform operations on a String.

package main

import (
	"fmt"
	"strings"
)

func main() {
  // Using Builder function

  c := []string{"j", "a", "v", "a"}
  var builder strings.Builder
  for _, item := range c {
    builder.WriteString(item)
  }
  fmt.Println("builder = ", builder.String())
  b := []byte{'s', 'c', 'r', 'i', 'p', 't'}
  for _, item := range b {
    builder.WriteByte(item)
  }
  fmt.Println("builder = ", builder.String())
  builder.WriteRune('s')
  fmt.Println("builder = ", builder.String())
  fmt.Println("builder = ", builder)
}
go run concatenate.go

builder =  java
builder =  javascript
builder =  javascripts
builder =  {0xc000088dd8 [106 97 118 97 115 99 114 105 112 116 115]}

The builder structure provided by the strings package is quite important for working with strings in an efficient manner. Its usually used for string concatenation operations. We can perform write operations to the buffer which is a byte slice. Here we have created the builder variable which is of type strings.Builder, further we have appended the string to it in a for a loop. So, we construct a string from the string list elements, they can be even rune slice or byte slice. We have used three methods here, the WriteString, WriteByte, and WriteRune which are quite obliviously used for writing string, byte, and runeto the string builder object.

Using the Bytes buffer method

The bytes package also has something similar to Builder type in string as Buffer. It has almost the same set of methods and properties. The main difference is the efficiency, strings.Builder is comparatively faster than the bytes.Buffer type due to several low-level implementations. We can discuss those fine details in a separate article but right now we’ll focus on the ways we can utilize this type for string concatenation.

package main

import (
	"fmt"
	"bytes"
)

func main() {
	// Using bytes buffer method

	var buf bytes.Buffer

	for i := 0; i < 2; i++ {
		buf.WriteString("ja")
	}
	fmt.Println(buf.String())

	buf.WriteByte('r')

	fmt.Println(buf.String())

	k := []rune{'s', 'c', 'r', 'i', 'p', 't'}
	for _, item := range k {
		buf.WriteRune(item)
	}
	fmt.Println(buf.String())
}
go run concatenate.go

jaja
jajar
jajarscript
{[106 97 106 97 114 115 99 114 105 112 116] 0 0}

So, like for the strings.Builder type, we have WriteString, WriteByte, and WriteRune in the bytes.Buffer type. We can use it exactly the way we do with the previous example. Also, the bytes.Buffer type returns a slice of bytes so we will have to use the String() method to format it as a string.

If we look at the bytes.Buffer type, it returns a slice of bytes and two more properties viz. off and lastRead. These two properties are used for indicating the index of the byte in the buffer and reallocation of the buffer. This is too low-level stuff that can be explored and explained in a separate section. For further readings on the bytes Buffer or String Builder types, you can follow up with these articles:

String Comparison

Now, we can move into the comparison of Strings in Golang. We have quite a few ways to compare strings in golang. We cover some of them in this section.

Using Comparison operators

The basic comparison can be done with the comparison operators provided by Golang. Just like we compare numeric data we can compare strings. Though the factor with which we compare them is different. We compare them by the lexical order of the string characters.

package main

import "fmt"

func main() {
	s1 := "gopher"
	s2 := "Gopher"
	s3 := "gopher"

	isEqual := s1 == s2

  //"gopher" is not same as "Gopher" and hence `false`
	fmt.Printf("S1 and S2 are Equal? %t \n", isEqual)
	fmt.Println(s1 == s2)

  // "gopher" is not equal to "Gopher" and hence `true`
	fmt.Println(s1 != s2)

  // "Gopher" comes first lexicographically than "gopher" so return true 
  // G -> 71 in ASCII and g -> 103
	fmt.Println(s2 < s3)
	fmt.Println(s2 <= s3)

  // "Gopher" is not greater than "gopher" as `G` comes first in ASCII table
  // So G value is less than g i.e. 71 > 103 which is false
	fmt.Println(s2 > s3)
	fmt.Println(s2 >= s3)

}
go run comparison.go

S1 and S2 are Equal? false 
false
true
true
true
false
false

In the above examples, we are able to see the comparison of two strings. There are three strings, two of which are identical, and the third is identical as well but is not equal considering the case of the characters in the string. We have compared the strings in order of the ASCII value of the characters of the strings. For example, A (65) comes before a (97). Similarly, numbers come before letters. So accordingly the comparison of these string characters decides the result.

For the ASCII table, you can take a look over the below image:

ASCII Table

Using Compare method

We also have the Compare method in the strings package for comparing two strings. The comparison method returns an integer value of either -1, 0, or 1. If the two strings are equal, it will return 0. Else if the first string is lexicographically smaller than the second string, it will return -1, else it will return +1.

strings.Compare

  • Return Type: Int (-1, 0, 1)
  • Parameters: string, string

You can check out the source code for further clarity.

package main

import(
  "fmt"
  "strings"
)

func main() {
	s1 := "gopher"
	s2 := "Gopher"
	s3 := "gopher"

	fmt.Println(strings.Compare(s1, s2))
	fmt.Println(strings.Compare(s1, s3))
	fmt.Println(strings.Compare(s2, s3))
}
go run comparison.go

1
0
-1

In the above example, the two strings s1 and s2 are compared and it returns the integer value +1, indicating the first string is lexicographically greater than the second string which is true "gopher" will be lexicographically after "Gopher" due to the presence of G.

In the second example, we are comparing the strings s1 and s3 which are equal, and hence the function returns 0 as expected.

In the third example, we are comparing the strings s2 and s3 identical to the first case but here order matters. We are comparing "Gopher" with "gopher" so the first string is lexicographically smaller than the second string and thereby returning -1 as discussed.

Using strings EqualFold

We also have another method in the strings library called EqualFold which compares two strings lexicographically but without considering the case precedence. That is the upper case or lower case is ignored and considered equal. So we are truly case-insensitively comparing the strings.

strings.EqualFold

  • Return Type: bool (true or false)
  • Parameters: string, string
package main

import(
  "fmt"
  "strings"
)

func main() {

	s1 := "gopher"
	s2 := "Gopher"
	s3 := "gophy"

	fmt.Println(strings.EqualFold(s1, s2))
	fmt.Println(strings.EqualFold(s1, s3))
	fmt.Println(strings.EqualFold(s2, s3))
}
go run comparison.go

true
false
false

So, in the above example, we are comparing the strings "gopher" and "Gopher" i.e. s1 and s2, which are equal if we think case-insensitively. Hence the method returns true, they are equal. In the next example, we compare the strings, s1 and s3 i.e. "gopher" "gophy" which are not equal, and hence we return false. Similar is the case for "Gopher" and "gophy" which is false. Also, if we consider two strings "gophy" and "gophy" it will quite obliviously return true.

String Manipulation and utility methods

The strings package in golang has some great utility methods for working with string or any form of text. We will explore some of the quite useful and widely used utilities in the strings package.

ToLower, ToUpper and Title functions

The strings package also provides some utility functions for operating on the case of the characters in the strings. We have functions like ToLower, ToUpper, and Title which can be used to convert the string into lower case, uppercased or Capitalised(Title) cased characters respectively.

strings.ToLower

  • Return Type: string
  • Parameters: string

strings.ToUpper

  • Return Type: string
  • Parameters: string

cases.Title

  • Return Type: Caser
  • Parameters: Language Options
package main

import (
	"fmt"
	"strings"

	"golang.org/x/text/cases"
	"golang.org/x/text/language"
)

func main() {
	s1 := "Ubuntu 22"
	s2 := "meet"
	s3 := "IND"
	fmt.Println(strings.ToLower(s1))
	fmt.Println(strings.ToLower(s2))
	fmt.Println(strings.ToLower(s3))

	fmt.Printf("\n")
	fmt.Println(strings.ToUpper(s1))
	fmt.Println(strings.ToUpper(s2))
	fmt.Println(strings.ToUpper(s3))

	fmt.Printf("\n")
	cases := cases.Title(language.English)
	fmt.Println(cases.String(s1))
	fmt.Println(cases.String(s2))
	fmt.Println(cases.String(s3))
}
# 100-days-of-golang/scripts/strings

go mod init
go get golang.org/x/text/cases
go get golang.org/x/text/language

go run utility.go                                                                                             
ubuntu 22
meet
ind

UBUNTU 22
MEET
IND

Ubuntu 22
Meet
Ind

Here, we can see that the function, ToLower has converted all the characters of a string to the lower case of the alphabet. Similarly, the ToUpper function has turned the characters of the strings to their respective alphabetical upper case.

The Title method in the strings package has been deprecated due to incompatibility with certain languages and cases. So, we are using the text/cases package to get the Title method that appropriately converts a string to Title cased. To set up this function, you need to perform a certain package installation process which is quite straightforward. Just create a go mod which is used for managing dependencies and packages for a project. So run the commands given below in the same order in your local setup:

go mod init
go get golang.org/x/text/cases
go get golang.org/x/text/language

This will set up a go.mod file and install the packages namely the cases and language packages. After doing this you will be able to access the functions Title from the cases package which can be imported by the format "golang.org/x/text/cases" and "golang.org/x/text/language". Now, we can use the Title function and parse the parameters which is the language type. Here we have used the language.English which is a language Tag to say use the semantics of English language while parsing the title cased characters. We now assign the value of the function Title to a variable as it will be of type Caseer and we want to still parse the string into the function. The caser object will have certain methods and properties attached to it, we will use the method Strings that will convert the given string into the title cased string. Hence we return the title cased string using the title function with the help of cases and language packages.

Contains and ContainsAny functions

In the strings package, we have the Contains and ContainsAny method which checks for the presence of substrings within a string. This will help in looking up hidden patterns and find for substrings in a string.

strings.Contains

  • Parameters: string, string
  • Return Type: bool (true, false)

The Contains method helps in getting the exact match of the substring in the given string. Firstly, the method takes two parameters one being the actual string and the other being the substring that you want to find inside the string. Let’s say we have the string="bootcamp" and substr="camp", then the Contains method will return true because the string contains the substring camp.

strings.ContainsAny

  • Parameters: string, string
  • Return Type: bool (true, false)

The ContainsAny method just like the Contains method takes two parameters string and the other as the substring, but it would return true if it finds any character or a single byte(Unicode chars) inside the string. Let’s say the string="google photos" and substring="abcde", then the ContainsAny method will return true because the string contains at least one character in the substring which is e.

package main

import (
	"fmt"
	"strings"
)

func main() {
	str := "javascript"
	substr := "script"
	s := "python"

	fmt.Println(strings.Contains(str, substr))
	fmt.Println(strings.Contains(str, s))

	fmt.Println(strings.ContainsAny(str, "joke"))
	fmt.Println(strings.ContainsAny(str, "xyz"))
	fmt.Println(strings.ContainsAny(str, ""))
}

Here, we have used the string methods like Contains and ContainsAny to find the substring inside a string. In the first example, the str variable is assigned as "javascript" and substr string as "script". We use the Contains function with parameters (str, substr). This will return true as we can see the "script" is a substring of "javascript". Also, we have initialized the variable s to "python". We use the Contains method with the parameters (str, s) which will return false as "python" is not a substring of "javascript".

The next set of examples is of the ContainsAny method which will return true for any character present in the substring is present in the string :). Quite a Simple right, Just understand that any character in your substring present in the string will return true. As we see in the example, ContainsAny method is used with the parameters ("javascript", "joke"), It will return true as j is present inside the string, though the entire substring is not present.

The next example is by passing ("javascript", "xyz") to the method ContainsAny will return false as we don’t have either "x", "y", or "z" in the string. So this is how the ContainsAny method works.

Other similar methods you might be interested in learning are: Index, IndexAny, LastIndex, etc. you can find the list of methods in the strings package.

Split and SplitAfter functions

We also have methods to manipulate the string for splitting the characters or bytes with certain patterns. In the strings package, the Split and SplitAfter methods are quite handy to know about.

package main

import (
	"fmt"
	"strings"
)

func main() {
	// Split method for splitting string into slice of string
	link := "meetgor.com/blog/golang/strings"
	fmt.Println(strings.Split(link, "/"))
	fmt.Println(strings.SplitAfter(link, "/"))

	// SplitAfter method for splitting string into slice of string with the pattern
	data := "200kms50kms120kms"
	fmt.Println(strings.Split(data, "kms"))
	fmt.Println(strings.SplitAfter(data, "kms"))
}
go run utility.go

[meetgor.com blog golang strings]
[meetgor.com/ blog/ golang/ strings]
[200 50 120 ]
[200kms 50kms 120kms ]

So, from the above examples, we can see how Split and SplitAfter methods work. The Split method splits the text before and after the pattern string also removing the pattern string whereas the SplitAfter method keeps the pattern and splits it after it, hence we see the pattern string getting attached to the left string.

In the first example, we see the link variable being split into strings as "/" being the separator. The Split method returns the slice of the string elements which have been split. In the data variable, the "kms" is the separator, so we get the resultant slice as [200, 50, 120], the "kms" string acts as a separator and it is ignored.

In the next example, we see the link variable being split into strings as "/" being the separator as previously but here, the splitting occurs after the separator has been parsed. So, we see "meetgor/" being split after the separator string and then "blog/" and so on. Also the next example, in the data variable, we see "200kms" being split instead of 200 using Split.

Repeat and Fields functions

In the strings package, we have methods like Repeat and Fields for manipulating the text inside the string. These methods are used to populate or extract data from the string.

strings.Repeat
  • Parameters: string, int
  • Return Type: string

The Repeat method is used to create a string by repeating it n number of times and appending it to the string which is returned as the final string. So, the Repeat method takes in two parameters the string to repeat, an integer as a count to repeat the string, and returns a string.

package main

import (
	"fmt"
	"strings"
)

func main() {
	// Repeat method for creating strings with given string and integer
	pattern := "OK"
	fmt.Println(strings.Repeat(pattern, 3))
}
go run utility.go

OKOKOK

So in this example, we can see that the string "OK" is passed to the method Repeat with the integer 3 and thus it is appended into itself three times and the resultant string becomes "OKOKOK".

strings.Fields
  • Parameters: string
  • Return Type: []string

The Fields method is used to extract the words from the string, that is the characters/bytes and group them with one or more consecutive white spaces. The function returns a slice of string.

package main

import (
	"fmt"
	"strings"
)

func main() {
	// Fields method for extracting string from the given string with white space as delimiters
	text := "Python is a prgramming language.   Go is not"
	text_data := strings.Fields(text)
	fmt.Println(text_data)
	for _, d := range text_data {
		fmt.Println("data = ", d)
	}
}
go run utility.go

[Python is a prgramming language. Go is not]
data =  Python
data =  is
data =  a
data =  prgramming
data =  language.
data =  Go
data =  is
data =  not

The above example demonstrates the usage of Fields which will extract characters and split after encountering whitespace. So, we return a slice of string in which the string elements are split before white space. Thus, using the Fields method we get the words or characters as space-separated values in the slice.

You can even expand on this method with the FieldsFunc method which allows you to write a custom delimiter option and extract data according to your requirement. There are tons of methods in the strings package for working with strings, you can see a detailed list of functions in the documentation.

That’s it from this part. Reference for all the code examples and commands can be found in the 100 days of Golang GitHub repository.

Conclusion

So, from this post, we were able to understand the different methods and types to concatenate and interpolate strings in golang. We explored different types of concatenating strings, string comparison, and various methods for manipulating and interpolating strings.

Thank you for reading, if you have any queries, feedback, or questions, you can freely ask me on my social handles. Happy Coding :)