github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/utils/set/strset.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package set
    16  
    17  import (
    18  	"sort"
    19  	"strings"
    20  
    21  	"github.com/dolthub/dolt/go/libraries/utils/funcitr"
    22  )
    23  
    24  // StrSet is a simple set implementation providing standard set operations for strings.
    25  type StrSet struct {
    26  	items         map[string]bool
    27  	caseSensitive bool
    28  }
    29  
    30  // NewStrSet creates a set from a list of strings
    31  func newStrSet(items []string, caseSensitive bool) *StrSet {
    32  	s := &StrSet{make(map[string]bool, len(items)), caseSensitive}
    33  
    34  	for _, item := range items {
    35  		s.items[item] = true
    36  	}
    37  
    38  	return s
    39  }
    40  
    41  func NewEmptyStrSet() *StrSet {
    42  	return newStrSet(nil, true)
    43  }
    44  
    45  func NewStrSet(items []string) *StrSet {
    46  	return newStrSet(items, true)
    47  }
    48  
    49  func NewCaseInsensitiveStrSet(items []string) *StrSet {
    50  	lwrStrs := funcitr.MapStrings(items, strings.ToLower)
    51  	return newStrSet(lwrStrs, false)
    52  }
    53  
    54  // Add adds new items to the set
    55  func (s *StrSet) Add(items ...string) {
    56  	for _, item := range items {
    57  		if !s.caseSensitive {
    58  			item = strings.ToLower(item)
    59  		}
    60  		s.items[item] = true
    61  	}
    62  }
    63  
    64  // Remove removes existing items from the set
    65  func (s *StrSet) Remove(items ...string) {
    66  	for _, item := range items {
    67  		if !s.caseSensitive {
    68  			item = strings.ToLower(item)
    69  		}
    70  
    71  		delete(s.items, item)
    72  	}
    73  }
    74  
    75  // Contains returns true if the item being checked is already in the set.
    76  func (s *StrSet) Contains(item string) bool {
    77  	if s == nil {
    78  		return false
    79  	}
    80  	if !s.caseSensitive {
    81  		item = strings.ToLower(item)
    82  	}
    83  
    84  	_, present := s.items[item]
    85  	return present
    86  }
    87  
    88  // ContainsAll returns true if all the items being checked are already in the set.
    89  func (s *StrSet) ContainsAll(items []string) bool {
    90  	if s == nil {
    91  		return false
    92  	}
    93  	if !s.caseSensitive {
    94  		items = funcitr.MapStrings(items, strings.ToLower)
    95  	}
    96  
    97  	for _, item := range items {
    98  		if _, present := s.items[item]; !present {
    99  			return false
   100  		}
   101  	}
   102  
   103  	return true
   104  }
   105  
   106  func (s *StrSet) Equals(other *StrSet) bool {
   107  	// two string sets can be equal even if one is sensitive and the other is insensitive as long al the items are a
   108  	// case sensitive match.
   109  	ss := s.AsSlice()
   110  	os := other.AsSlice()
   111  	sort.Strings(ss)
   112  	sort.Strings(os)
   113  
   114  	if len(ss) != len(os) {
   115  		return false
   116  	}
   117  
   118  	for i := range ss {
   119  		if ss[i] != os[i] {
   120  			return false
   121  		}
   122  	}
   123  	return true
   124  }
   125  
   126  // Size returns the number of unique elements in the set
   127  func (s *StrSet) Size() int {
   128  	if s == nil {
   129  		return 0
   130  	}
   131  	return len(s.items)
   132  }
   133  
   134  // AsSlice converts the set to a slice of strings. If this is an insensitive set the resulting slice will be lowercase
   135  // regardless of the case that was used when adding the string to the set.
   136  func (s *StrSet) AsSlice() []string {
   137  	if s == nil {
   138  		return nil
   139  	}
   140  	size := len(s.items)
   141  	sl := make([]string, size)
   142  
   143  	i := 0
   144  	for k := range s.items {
   145  		sl[i] = k
   146  		i++
   147  	}
   148  
   149  	return sl
   150  }
   151  
   152  // AsSortedSlice converts the set to a slice of strings. If this is an insensitive set the resulting slice will be lowercase
   153  // regardless of the case that was used when adding the string to the set. The slice is sorted in ascending order.
   154  func (s *StrSet) AsSortedSlice() []string {
   155  	if s == nil {
   156  		return nil
   157  	}
   158  	slice := s.AsSlice()
   159  	sort.Slice(slice, func(i, j int) bool {
   160  		return slice[i] < slice[j]
   161  	})
   162  	return slice
   163  }
   164  
   165  // Iterate accepts a callback which will be called once for each element in the set until all items have been
   166  // exhausted or callback returns false.
   167  func (s *StrSet) Iterate(callBack func(string) (cont bool)) {
   168  	if s == nil {
   169  		return
   170  	}
   171  	for k := range s.items {
   172  		if !callBack(k) {
   173  			break
   174  		}
   175  	}
   176  }
   177  
   178  // LeftIntersectionRight takes a slice of strings and returns a slice of strings containing the intersection with the
   179  // set, and a slice of strings for the ones missing from the set.
   180  func (s *StrSet) LeftIntersectionRight(other *StrSet) (left *StrSet, intersection *StrSet, right *StrSet) {
   181  	left = NewStrSet(nil)
   182  	intersection = NewStrSet(nil)
   183  	right = NewStrSet(nil)
   184  
   185  	for os := range other.items {
   186  		if s.Contains(os) {
   187  			intersection.Add(os)
   188  		} else {
   189  			right.Add(os)
   190  		}
   191  	}
   192  	for ss := range s.items {
   193  		if !intersection.Contains(ss) {
   194  			left.Add(ss)
   195  		}
   196  	}
   197  
   198  	return left, intersection, right
   199  }
   200  
   201  // JoinStrings returns the sorted values from the set concatenated with a given sep
   202  func (s *StrSet) JoinStrings(sep string) string {
   203  	strSl := s.AsSlice()
   204  	sort.Strings(strSl)
   205  	return strings.Join(strSl, sep)
   206  }
   207  
   208  // Unique will return a slice of unique strings given an input slice
   209  func Unique(strs []string) []string {
   210  	return NewStrSet(strs).AsSlice()
   211  }