Skip to content

Commit

Permalink
strings package review
Browse files Browse the repository at this point in the history
  • Loading branch information
spring1843 authored Jan 28, 2025
1 parent c985525 commit be26326
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 18 deletions.
2 changes: 1 addition & 1 deletion array/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ To provide a real-world analogy, consider an array of athletes preparing for a s

## Implementation

In the Go programming language, arrays are considered values rather than pointers and represent the entirety of the array. Whenever an array is passed to a function, a copy is created, resulting in additional memory usage. To avoid this, it is possible to pass a pointer to an array, or use slices instead. The size of the array is constant and it must be known at compile time, and there is no need to use the built-in `make` function when defining arrays.
In the Go programming language, [arrays](https://go.dev/blog/slices) are considered values rather than pointers and represent the entirety of the array. Whenever an array is passed to a function, a copy is created, resulting in additional memory usage. To avoid this, it is possible to pass a pointer to an array, or use slices instead. The size of the array is constant and it must be known at compile time, and there is no need to use the built-in `make` function when defining arrays.

```Go
package main
Expand Down
85 changes: 78 additions & 7 deletions strings/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# String

A string is a ubiquitous data structure, typically a built-in data type in programming languages. However, beneath the surface, strings are essentially arrays of characters that enable textual data storage and manipulation.
A string is a ubiquitous data structure, typically a built-in data type in programming languages. However, beneath the surface, strings are essentially [slices](../array/) of characters that enable textual data storage and manipulation.

## Implementation

In Go, strings are a data type. Behind the scenes strings are a slice of bytes. The `strings` package provides several useful convenience functions. Examples include:
In Go, [strings](https://go.dev/blog/strings) are a data type. Behind the scenes strings are an immutable slice of bytes. Since Go is a UTF-8 compliant language, each character in Go can take up to 4 bytes of storage.

The `strings` package provides several useful convenience functions. Examples include:

* [Index](https://golang.org/pkg/strings/#Index), [Contains](https://golang.org/pkg/strings/#Contains), [HasPrefix](https://golang.org/pkg/strings/#HasPrefix), [HasSuffix](https://golang.org/pkg/strings/#HasSuffix)
* [Split](https://golang.org/pkg/strings/#Split), [Fields](https://golang.org/pkg/strings/#Split), [Join](https://golang.org/pkg/strings/#Join)
Expand All @@ -13,27 +15,96 @@ In Go, strings are a data type. Behind the scenes strings are a slice of bytes.
* [Title](https://golang.org/pkg/strings/#Title), [ToLower](https://golang.org/pkg/strings/#ToLower), [ToUpper](https://golang.org/pkg/strings/#ToUpper)
* [Trim](https://golang.org/pkg/strings/#Trim), [TrimSpace](https://golang.org/pkg/strings/#TrimSpace), [TrimSuffix](https://golang.org/pkg/strings/#TrimSuffix), [TrimPrefix](https://golang.org/pkg/strings/#TrimPrefix)

When a string is iterated in Go using the `range` keyword, every element becomes a [rune](https://blog.golang.org/strings#TOC_5.) which is an alias for the type `int32`. If the code being written works with many single-character strings, it is better to define variables and function parameters as `rune`. The following code shows how to iterate through a string.
When a iterating every character in a string in Go using the `range` keyword, every element becomes a [rune](https://blog.golang.org/strings#TOC_5.) which is an alias for the type `int32`. If the code being written works with many single-character strings, it is better to define variables and function parameters as `rune` rather than convert them many times. The following code shows how to iterate through a string.

```Go
package main

import "fmt"

/*
main outputs the rune (int32) value of each character:
Char #0 "a" has value 97
Char #1 "A" has value 65
Char #2 "和" has value 21644
Char #5 "平" has value 24179
Char #8 "😊" has value 128522
*/
func main() {
for i, r := range "aA𓅚😊" {
fmt.Printf("Char #%d %q has value %d\n", i, string(r), r)
}
}
```

A very common tool to use for manipulating strings in Go is the `fmt.Sprintf` function. This is specially useful when converting many values into a string.

```Go
package main

import "fmt"

func main() {
number := 1
value := 1.1
name := "foo"

output := fmt.Sprintf("%d %f %s", number, value, name)
fmt.Println(output) // 1 1.100000 foo
}
```

### Regular Expressions

Unlike many other programming languages, in Go [regular expressions](https://golang.org/pkg/regexp/) are [guaranteed](https://swtch.com/~rsc/regexp/regexp1.html) to have O(n) time complexity where n is the length of the input, making them a viable and practical option for pattern matching in a string.

Here is an example of how you can find a pattern using regular expressions in Go. Given a string return the string if it contains a fish word. A fish word is a word that starts with `fi` optionally followed by other character(s) and ends with `sh`. Examples include {`fish`, `finish`}.

```GO
package main

import (
"fmt"
"regexp"
)

var fishPattern = regexp.MustCompile(`(?i).*fi\w*sh\b`)

// main outputs [selfish][shellfish][fish][finish][Finnish]
func main() {
for i, r := range "abcd" {
fmt.Printf("Char #%d %q has value %d\n", i, string(r), r)
inputs := []string{"shift", "selfish", "shellfish", "fish dish", "finish", "Finnish"}

for _, input := range inputs {
matches := fishPattern.FindAllString(input, -1)
if len(matches) > 0 {
fmt.Print(matches)
}
}
}
```

## Complexity

Strings have the same complexity as [arrays](../array/) and slices in Go.
Since strings are slices of bytes, the time complexity of string operations should be similar to [arrays](../array/). Reading a character at a given index is O(1), but since strings are immutable modifying them involves creating a new string making it a O(n) operation. Go standard library includes `strings.Builder` for more efficient string building.

The space complexity to store a string depends on the type of characters. This following example shows how we can index a string and print the hexadecimal value of every byte in it.

```GO
package main

import "fmt"

// main Outputs 41 f0 93 85 9a f0 9f 98 8a.
func main() {
input := "A𓅚😊"
for i := 0; i < len(input); i++ {
fmt.Printf("%x ", input[i])
}
}
```

Unlike many other programming languages, [regular expressions](https://golang.org/pkg/regexp/) are guaranteed to have O(n) time complexity in Go, allowing efficient string pattern matching.
The output of the above code indicates that 9 bytes are used to store the 3 input characters. 1 byte for the first character and 4 bytes for each of the remaining two.

## Application

Expand Down
29 changes: 29 additions & 0 deletions strings/in_memory_database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,34 @@ TestInMemoryDictionary tests solution(s) with the following signature and proble
Write an in memory database that stores string key-value pairs and supports SET, GET, EXISTS,
and UNSET commands. It should also allow transactions with BEGIN, COMMIT and ROLLBACK commands.
For example:
GET A // outputs nil
BEGIN
SET A 1
GET A // outputs 1
COMMIT
GET A // outputs 1
At the first GET A, nil is returned because it has never been set. The second and third
GET A will output 1 because the value of A was set as 1.
BEGIN, ROLLBACK and COMMIT are referred to as transactions in databases. A transaction is
started by BEGIN. The commands that are followed by a BEGIN are completely ignored if a ROLLBACK
command is given and actually applied only when COMMIT command is given.
For example:
BEGIN
SET A 1
COMMIT
BEGIN
SET A 2
ROLLBACK
GET A // outputs 1
The output is 1 because SET A 2 was never committed so the value of A remains 1.
*/
func TestInMemoryDictionary(t *testing.T) {
tests := []struct {
Expand All @@ -21,6 +49,7 @@ func TestInMemoryDictionary(t *testing.T) {
{"EXISTS A\nSET A 1\nGET A\nEXISTS A\nUNSET A\nGET A\nEXISTS A", "false 1 true nil false"},
{"GET A\nBEGIN\nSET A 1\nGET A\nROLLBACK\nGET A", "nil 1 nil"},
{"GET A\nBEGIN\nSET A 1\nGET A\nCOMMIT\nGET A", "nil 1 1"},
{"BEGIN\nSET A 1\nCOMMIT\nBEGIN\nSET A 2\nROLLBACK\nGET A", "1"},
{"SET A 1\nGET A\nBEGIN\nSET A 2\nGET A\nBEGIN\nUNSET A\nGET A\nCOMMIT\nROLLBACK\nGET A", "1 2 nil 1"},
{"SET A 2\nGET A\nBEGIN\nSET A 1\nGET A\nCOMMIT\nGET A\nBEGIN\nSET A 2\nGET A\nROLLBACK\nGET A", "2 1 1 2 1"},
}
Expand Down
7 changes: 5 additions & 2 deletions strings/longest_dictionary_word_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ TestLongestDictionaryWordContainingKey tests solution(s) with the following sign
func LongestDictionaryWordContainingKey(key string, dic []string) string
Given a key like "car", and a dictionary like {"rectify", "race", "archeology", "racoon"} return the longest
dictionary word that contains every letter of the key like "archeology".
Given a key as string, and a slice of strings containing a dictionary of words, return the longest
word that contains all letters of the key.
For example given "cat" and {"rectify", "race", "archeology", "racoon"}, it should return "archeology",
because "archeology" is the longest word in the given set that contains "c","a",and "t".
*/
func TestLongestDictionaryWordContainingKey(t *testing.T) {
tests := []struct {
Expand Down
12 changes: 10 additions & 2 deletions strings/longest_substring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ TestLongestSubstrings tests solution(s) with the following signature and problem
func LongestSubstringOfTwoChars(input string) string
Given a string like "aabbc" return the longest substring of two unique characters like "aabb".
Given a string return the longest substring of two unique characters.
For example given "aabbc", return "aabb" because it's the longest substring that has two unique
characters "a" and "b".
Other substrings of "aabc" include:
* "aabbc", contains more than 2 unique characters.
* "bbc", shorter than "aabb".
*/
func TestLongestSubstrings(t *testing.T) {
tests := []struct {
Expand All @@ -21,7 +29,7 @@ func TestLongestSubstrings(t *testing.T) {
{"aabbc", "aabb"},
{"ada", "ada"},
{"dafff", "afff"},
{"assdeeeddfffha", "deeedd"},
{"abbdeeeddfffha", "deeedd"},
}

for i, test := range tests {
Expand Down
23 changes: 21 additions & 2 deletions strings/look_and_tell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,27 @@ TestFindDuplicate tests solution(s) with the following signature and problem des
func LookAndTell(depth int) []string
Given a depth, return the output of look and tell an algorithm where each line reads the
last line. For example "1" is read as "11" (one one), and "11" is read as "21" (two ones).
Given a positive integer n, return the output of the Look and Tell algorithm until the nth depth.
The Look and Tell algorithm starts by outputting 1 at first level, then at each subsequent level
it reads the previous line by counting the number of times a digit is repeated and then writes
the count and the digit.
For example given 4, return:
1
11
21
1211
Which reads:
one
one one
two ones
one two one one.
The third output (two ones) reads the level before it which is 11. Two ones means repeat
1 two times i.e. 11.
*/
func TestFindDuplicate(t *testing.T) {
tests := []struct {
Expand Down
10 changes: 8 additions & 2 deletions strings/number_in_english_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ TestReadNumberInEnglish tests solution(s) with the following signature and probl
func NumberInEnglish(num int) string
Given a number like 34, return how the number would be read in English e.g. (Thirty Four) for
integers smaller than one Trillion.
Given n a positive integer smaller than a Trillion, return the number in English words.
For example given 0, return "Zero".
For example given 34, return "Thirty Four".
For example given 10, return "Ten".
For example given 900000000001, return "Nine Hundred Billion One".
*/
func TestReadNumberInEnglish(t *testing.T) {
tests := []struct {
Expand All @@ -18,10 +22,12 @@ func TestReadNumberInEnglish(t *testing.T) {
{0, "Zero"},
{1, "One"},
{2, "Two"},
{10, "Ten"},
{34, "Thirty Four"},
{123456789, "One Hundred Twenty Three Million Four Hundred Fifty Six Thousand Seven Hundred Eighty Nine"},
{1000000000, "One Billion"},
{100000000000, "One Hundred Billion"},
{900000000001, "Nine Hundred Billion One"},
}

for i, test := range tests {
Expand Down
6 changes: 5 additions & 1 deletion strings/reverse_vowels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ TestReverseVowels tests solution(s) with the following signature and problem des
func ReverseVowels(str string) (string, error)
Given a string e.g. "coat", reverse the order in which vowels appear e.g. "caot".
Given a string, return the same string while reversing the vowels {"a", "e", "i", "o", "u"}
appear in it.
For example given "coat", return "caot", because the vowels are "o" and "a" and their positions
are reversed.
*/
func TestReverseVowels(t *testing.T) {
tests := []struct {
Expand Down
19 changes: 18 additions & 1 deletion strings/roman_numerals_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,24 @@ TestIntToRoman tests solution(s) with the following signature and problem descri
func IntToRoman(input int) string
Given a positive integer like 1917 return the Roman numeral like MCMXVII.
Given a positive integer like return the equivalent inRoman numerals:
For example:
* Given 1, return I
* Given 4, return IV
* Given 5, return V
* Given 9, return IX
* Given 10, return X
* Given 40, return XL
* Given 50, return L
* Given 90, return XC
* Given 100, return C
* Given 400, return CD
* Given 500, return D
* Given 900, return CM
* Given 1000, return M
* Given 1917, return MCMXVII.
*/
func TestIntToRoman(t *testing.T) {
tests := []struct {
Expand Down

0 comments on commit be26326

Please sign in to comment.