395 lines
9.5 KiB
Go
395 lines
9.5 KiB
Go
// Copyright 2019 Jimmy Zelinskie
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package stringz implements a collection of utility functions for
|
|
// manipulating strings and lists of strings.
|
|
package stringz
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
)
|
|
|
|
// ErrInconsistentUnpackLen is returned when Unpack is provided two slices
|
|
// without the same length.
|
|
var ErrInconsistentUnpackLen = errors.New("the length of the unpacked is not equal to the provided input")
|
|
|
|
// SliceContains returns true if the provided string is in the provided string
|
|
// slice.
|
|
func SliceContains(ys []string, x string) bool {
|
|
for _, y := range ys {
|
|
if x == y {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// SliceIndex returns the index of the first instance of x in ys, or -1 if it
|
|
// is not present.
|
|
func SliceIndex(ys []string, x string) int {
|
|
for i, y := range ys {
|
|
if x == y {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// Dedup returns a new slice with any duplicates removed.
|
|
func Dedup(xs []string) []string {
|
|
set := make(map[string]struct{}, 0)
|
|
ys := make([]string, 0, len(xs))
|
|
for _, x := range xs {
|
|
if _, alreadyExists := set[x]; alreadyExists {
|
|
continue
|
|
}
|
|
ys = append(ys, x)
|
|
set[x] = struct{}{}
|
|
}
|
|
|
|
return ys
|
|
}
|
|
|
|
// DefaultEmpty returns the fallback when val is empty string.
|
|
//
|
|
// This function is inspired by Python's `dict.get()`.
|
|
func DefaultEmpty(val, fallback string) string {
|
|
return Default(val, fallback, "")
|
|
}
|
|
|
|
// Default returns a fallback value when the provided value is equal to any
|
|
// of the zero values.
|
|
func Default(val, fallback string, zeroValues ...string) string {
|
|
for _, zeroValue := range zeroValues {
|
|
if val == zeroValue {
|
|
return fallback
|
|
}
|
|
}
|
|
|
|
return val
|
|
}
|
|
|
|
// SliceEqual returns true if two string slices are the same.
|
|
// This function is sensitive to order.
|
|
func SliceEqual(xs, ys []string) bool {
|
|
if len(xs) != len(ys) {
|
|
return false
|
|
}
|
|
|
|
for i, x := range xs {
|
|
if x != ys[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// MatrixEqual returns true if two [][]string are equal.
|
|
// This function is sensitive to order.
|
|
func MatrixEqual(xs, ys [][]string) bool {
|
|
if len(xs) != len(ys) {
|
|
return false
|
|
}
|
|
|
|
for i, x := range xs {
|
|
if !SliceEqual(x, ys[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// TrimPrefixIndex trims everything before the provided index.
|
|
func TrimPrefixIndex(s, index string) string {
|
|
i := strings.Index(s, index)
|
|
if i <= 0 {
|
|
return s
|
|
}
|
|
return s[i+len(index):]
|
|
}
|
|
|
|
// TrimSurrounding returns a string with both a prefix and suffix trimmed from
|
|
// it.
|
|
//
|
|
// Do not confuse this with strings.Trim() which removes characters in a cutset
|
|
// rather than working on prefixes and suffixes.
|
|
func TrimSurrounding(s, surrounding string) string {
|
|
s = strings.TrimPrefix(s, surrounding)
|
|
return strings.TrimSuffix(s, surrounding)
|
|
}
|
|
|
|
// SliceMap is a functional-style mapping function for slices of strings.
|
|
//
|
|
// This is particularly useful when you would normally use a for-loop, but want
|
|
// `defer` to execute for each iteration.
|
|
func SliceMap(xs []string, fn func(string) error) error {
|
|
for _, x := range xs {
|
|
err := fn(x)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Join is strings.Join, but variadic.
|
|
func Join(prefix string, xs ...string) string { return strings.Join(xs, prefix) }
|
|
|
|
// CopyStringMap returns a new copy of a map of strings.
|
|
func CopyStringMap(xs map[string]string) map[string]string {
|
|
// Zero allocation path.
|
|
if xs == nil {
|
|
return nil
|
|
}
|
|
|
|
ys := make(map[string]string, len(xs))
|
|
for k, v := range xs {
|
|
ys[k] = v
|
|
}
|
|
return ys
|
|
}
|
|
|
|
// Unpack assigns a slice into local variables.
|
|
func Unpack(xs []string, vars ...*string) error {
|
|
if len(xs) != len(vars) {
|
|
return ErrInconsistentUnpackLen
|
|
}
|
|
for i, x := range xs {
|
|
*vars[i] = x
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SplitExact splits the string `s` into `len(vars)` number of strings and
|
|
// unpacks them into those vars.
|
|
//
|
|
// Returns ErrInconsistentUnpackLen if len(vars) doesn't match the number of
|
|
// split segments.
|
|
func SplitExact(s, sep string, vars ...*string) error {
|
|
exploded := strings.Split(s, sep)
|
|
return Unpack(exploded, vars...)
|
|
}
|
|
|
|
// SplitInto splits the string `s` into `len(vars)` number of strings and
|
|
// unpacks them into those vars. If there are more substrings that would be
|
|
// split after len(vars), they will be all be put into the final variable.
|
|
//
|
|
// Returns ErrInconsistentUnpackLen if len(vars) is greater than the number
|
|
// of split substrings.
|
|
func SplitInto(s, sep string, vars ...*string) error {
|
|
exploded := strings.SplitN(s, sep, len(vars))
|
|
return Unpack(exploded, vars...)
|
|
}
|
|
|
|
// SlicePermutations returns all permutations of a string slice.
|
|
//
|
|
// It is equivalent to `SliceCombinationsR(xs, len(xs))`.
|
|
func SlicePermutations(xs []string) [][]string {
|
|
return SlicePermutationsR(xs, len(xs))
|
|
}
|
|
|
|
// SlicePermutationsR returns successive r-length permutations of elements in
|
|
// the provided string slice.
|
|
//
|
|
// If r is less than 0 or larger than the length of the pool, nil is returned.
|
|
//
|
|
// The permutation tuples are emitted in lexicographic ordering according to
|
|
// the order of the input iterable. So, if the input iterable is sorted, the
|
|
// combination tuples will be produced in sorted order.
|
|
//
|
|
// Elements are treated as unique based on their position, not on their value.
|
|
// So if the input elements are unique, there will be no repeat values in each
|
|
// permutation.
|
|
//
|
|
// This is the algorithm used in Python's itertools library:
|
|
// itertools.permutations(iterable, r=None)
|
|
func SlicePermutationsR(pool []string, r int) [][]string {
|
|
if r <= 0 || pool == nil || r > len(pool) {
|
|
return nil
|
|
}
|
|
n := len(pool)
|
|
|
|
indices := make([]int, n)
|
|
for i := range pool {
|
|
indices[i] = i
|
|
}
|
|
|
|
var ys [][]string
|
|
|
|
{
|
|
var y []string
|
|
for _, i := range indices[:r] {
|
|
y = append(y, pool[i])
|
|
}
|
|
ys = append(ys, y)
|
|
}
|
|
|
|
cycles := make([]int, n-(n-r))
|
|
for i := range cycles {
|
|
cycles[i] = n - i
|
|
}
|
|
|
|
for {
|
|
broke := false
|
|
|
|
for i := r - 1; i >= 0; i-- {
|
|
cycles[i] = cycles[i] - 1
|
|
if cycles[i] == 0 {
|
|
indices = append(indices[:i], append(indices[i+1:], indices[i:i+1]...)...)
|
|
cycles[i] = n - i
|
|
} else {
|
|
j := cycles[i]
|
|
indices[i], indices[len(indices)-j] = indices[len(indices)-j], indices[i]
|
|
|
|
var y []string
|
|
for _, i := range indices[:r] {
|
|
y = append(y, pool[i])
|
|
}
|
|
ys = append(ys, y)
|
|
|
|
broke = true
|
|
break
|
|
}
|
|
}
|
|
if !broke {
|
|
return ys
|
|
}
|
|
}
|
|
}
|
|
|
|
// SliceCombinationsR returns r-length subsequences of elements from the
|
|
// provided string slice.
|
|
//
|
|
// If r is less than 0 or larger than the length of the pool, nil is returned.
|
|
//
|
|
// The combinations are emitted in lexicographic ordering according to the
|
|
// order of the input iterable. So, if the input iterable is sorted, the
|
|
// combination tuples will be produced in sorted order.
|
|
//
|
|
// Elements are treated as unique based on their position, not on their value.
|
|
// So if the input elements are unique, there will be no repeat values in each
|
|
// combination.
|
|
//
|
|
// This is the algorithm used in Python's itertools library:
|
|
// itertools.combinations(iterable, r)
|
|
func SliceCombinationsR(pool []string, r int) [][]string {
|
|
if r <= 0 || pool == nil || r > len(pool) {
|
|
return nil
|
|
}
|
|
n := len(pool)
|
|
|
|
indices := make([]int, r)
|
|
for i := range indices {
|
|
indices[i] = i
|
|
}
|
|
|
|
var ys [][]string
|
|
|
|
{
|
|
var y []string
|
|
for _, j := range indices {
|
|
y = append(y, pool[j])
|
|
}
|
|
ys = append(ys, y)
|
|
}
|
|
|
|
for {
|
|
var i int
|
|
broke := false
|
|
for i = r - 1; i >= 0; i-- {
|
|
if indices[i] != i+n-r {
|
|
broke = true
|
|
break
|
|
}
|
|
}
|
|
if !broke {
|
|
return ys
|
|
}
|
|
|
|
indices[i] = indices[i] + 1
|
|
for j := i + 1; j < r; j++ {
|
|
indices[j] = indices[j-1] + 1
|
|
}
|
|
|
|
var y []string
|
|
for _, j := range indices {
|
|
y = append(y, pool[j])
|
|
}
|
|
ys = append(ys, y)
|
|
}
|
|
}
|
|
|
|
// SliceCombinationsWithReplacement returns r-length subsequences of elements
|
|
// from the provided string slice allowing individual elements to be repeated
|
|
// more than once.
|
|
//
|
|
// If r is less than 0 or larger than the length of the pool, nil is returned.
|
|
//
|
|
// The combination tuples are emitted in lexicographic ordering according to
|
|
// the order of the input iterable. So, if the input iterable is sorted, the
|
|
// combination tuples will be produced in sorted order.
|
|
//
|
|
// Elements are treated as unique based on their position, not on their value.
|
|
// So if the input elements are unique, the generated combinations will also be
|
|
// unique.
|
|
//
|
|
// This is the algorithm used in Python's itertools library:
|
|
// itertools.combinations_with_replacement(iterable, r)
|
|
func SliceCombinationsWithReplacement(pool []string, r int) [][]string {
|
|
if r <= 0 || pool == nil || r > len(pool) {
|
|
return nil
|
|
}
|
|
n := len(pool)
|
|
|
|
indices := make([]int, r)
|
|
var ys [][]string
|
|
|
|
{
|
|
var y []string
|
|
for _, j := range indices {
|
|
y = append(y, pool[j])
|
|
}
|
|
ys = append(ys, y)
|
|
}
|
|
|
|
for {
|
|
var i int
|
|
broke := false
|
|
for i = r - 1; i >= 0; i-- {
|
|
if indices[i] != n-1 {
|
|
broke = true
|
|
break
|
|
}
|
|
}
|
|
if !broke {
|
|
return ys
|
|
}
|
|
|
|
newIndices := make([]int, r-i)
|
|
for j := range newIndices {
|
|
newIndices[j] = indices[i] + 1
|
|
}
|
|
indices = append(indices[:i], newIndices...)
|
|
|
|
var y []string
|
|
for _, j := range indices {
|
|
y = append(y, pool[j])
|
|
}
|
|
ys = append(ys, y)
|
|
}
|
|
}
|