Many languages have powerful and useful (binary safe) string manipulation functions, while others don't, making it harder for these languages to accomplish some tasks.

This task is about creating functions to handle binary strings (strings made of arbitrary bytes, i.e. byte strings according to Wikipedia) for those languages that don't have built-in support for them.

If your language of choice does have this built-in support, show a possible alternative implementation for the functions or abilities already provided by the language.

In particular the functions you need to create are:

 String creation and destruction (when needed and if there's no garbage collection or similar mechanism)
 String assignment
 String comparison
 String cloning and copying
 Check if a string is empty
 Append a byte to a string
 Extract a substring from a string
 Replace every occurrence of a byte (or a string) in a string with another string
 Join strings


Possible contexts of use: compression algorithms (like LZW compression), L-systems (manipulation of symbols), many more.




package main
 
import (
    "bytes"
    "fmt"
)
 
// Strings in Go allow arbitrary bytes.  They are implemented basically as
// immutable byte slices and syntactic sugar.  This program shows functions
// required by the task on byte slices, thus it mostly highlights what
// happens behind the syntactic sugar.  The program does not attempt to
// reproduce the immutability property of strings, as that does not seem
// to be the intent of the task.
 
func main() {
    // Task point: String creation and destruction.
    // Strings are most often constructed from literals as in s := "binary"
    // With byte slices,
    b := []byte{'b', 'i', 'n', 'a', 'r', 'y'}
    fmt.Println(b) // output shows numeric form of bytes.
    // Go is garbage collected.  There are no destruction operations.
 
    // Task point: String assignment.
    // t = s assigns strings.  Since strings are immutable, it is irrelevant
    // whether the string is copied or not.
    // With byte slices, the same works,
    var c []byte
    c = b
    fmt.Println(c)
 
    // Task point: String comparison.
    // operators <, <=, ==, >=, and > work directly on strings comparing them
    // by lexicographic order.
    // With byte slices, there are standard library functions, bytes.Equal
    // and bytes.Compare.
    fmt.Println(bytes.Equal(b, c)) // prints true
 
    // Task point: String cloning and copying.
    // The immutable property of Go strings makes cloning and copying
    // meaningless for strings.
    // With byte slices though, it is relevant.  The assignment c = b shown
    // above does a reference copy, leaving both c and b based on the same
    // underlying data.  To clone or copy the underlying data,
    d := make([]byte, len(b)) // allocate new space
    copy(d, b)                // copy the data
    // The data can be manipulated independently now:
    d[1] = 'a'
    d[4] = 'n'
    fmt.Println(string(b)) // convert to string for readable output
    fmt.Println(string(d))
 
    // Task point: Check if a string is empty.
    // Most typical for strings is s == "", but len(s) == 0 works too.
    // For byte slices, "" does not work, len(b) == 0 is correct.
    fmt.Println(len(b) == 0)
 
    // Task point: Append a byte to a string.
    // The language does not provide a way to do this directly with strings.
    // Instead, the byte must be converted to a one-byte string first, as in,
    // s += string('z')
    // For byte slices, the language provides the append function,
    z := append(b, 'z')
    fmt.Printf("%s\n", z) // another way to get readable output
 
    // Task point: Extract a substring from a string.
    // Slicing syntax is the for both strings and slices.
    sub := b[1:3]
    fmt.Println(string(sub))
 
    // Task point: Replace every occurrence of a byte (or a string)
    // in a string with another string.
    // Go supports this with similar library functions for strings and
    // byte slices.  Strings:  t = strings.Replace(s, "n", "m", -1).
    // The byte slice equivalent returns a modified copy, leaving the
    // original byte slice untouched,
    f := bytes.Replace(d, []byte{'n'}, []byte{'m'}, -1)
    fmt.Printf("%s -> %s\n", d, f)
 
    // Task point: Join strings.
    // Using slicing syntax again, with strings,
    // rem := s[:1] + s[3:] leaves rem == "bary".
    // Only the concatenation of the parts is different with byte slices,
    rem := append(append([]byte{}, b[:1]...), b[3:]...)
    fmt.Println(string(rem))
}