303

I'm trying to solve "The go programming lanaguage" exercise #1.4 which requires me to have a set. I can create a set type but why doesn't the language come with one ? go, having come from google, where guava also originated, why didn't the language designers opt for adding support for fundamental data structures ? why force your users to create their own implementations for something so basic as a set ?

anjanb
  • 12,999
  • 18
  • 77
  • 106
  • 87
    hmmm. wondering why the negative votes ? I'm coming from the java world where we had a set almost right from the beginning, even without generics. So, I find this behavior a lil bizarre – anjanb Dec 02 '15 at 05:02
  • 4
    Use this one: https://github.com/deckarep/golang-set – Eric Jan 26 '22 at 04:20
  • GoDS (Go Data Structures) looks promiting: https://github.com/emirpasic/Gods – Ed Randall Feb 26 '23 at 10:21

4 Answers4

156

One reason is that it is easy to create a set from map:

s := map[int]bool{5: true, 2: true}
_, ok := s[6] // check for existence
s[8] = true // add element 
delete(s, 2) // remove element

Union

s_union := map[int]bool{}
for k, _ := range s1{
    s_union[k] = true
}
for k, _ := range s2{
    s_union[k] = true
}

Intersection

s_intersection := map[int]bool{}
if len(s1) > len(s2) {
  s1, s2 = s2, s1 // better to iterate over a shorter set
}
for k,_ := range s1 { 
  if s2[k] {
    s_intersection[k] = true
  }
}

It is not really that hard to implement all other set operations.

Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
  • 19
    Check for existence is simply indexing the map. Since if it's not in it, the zero value (which is `false`) will properly tell that. No need the comma-ok idiom for testing. – icza Dec 01 '15 at 12:44
  • 2
    The implementation for intersection looks like that for difference. – musiphil Aug 22 '16 at 17:15
  • 1
    @Kittsil thank you. Updated. – Salvador Dali Feb 22 '17 at 00:58
  • 103
    It is more optimal to use `map[int]struct{}` instead of `bool`, because an empty struct occupies 0 bytes in the memory. I've recently written a gist for this https://gist.github.com/bgadrian/cb8b9344d9c66571ef331a14eb7a2e80 – BG Adrian Jul 16 '18 at 20:20
  • 132
    That's not quite so easy. Just having to write that code everywhere you need to use a Set seems ridiculous to me these days. Collections support should be provided by any language. Think a better answer is that Go is not yet as mature. I'm sure there will be libraries to cover that soon enough. – Stef Mar 13 '19 at 06:22
  • 1
    How do you serialize such a map as a set? – Kedar Mhaswade May 10 '19 at 06:32
  • 67
    Not easy nor intuitive. This is not a set, it is just a code pattern that behaves like a set. It is not a set since it doesn't store the data not provides operations as a set does. The correct answer is that GO doesn't have this functionality. Having a way to do something doesn't mean it is a reason not to have it. – Lucas Montenegro Carvalhaes Dec 01 '20 at 17:30
  • Set is backed by the red-black tree. I think it is hard to implement a red-black tree whenever you want. – user9716692 May 01 '21 at 17:33
  • 37
    so glad to reinvent the wheel – gotch4 May 04 '21 at 10:19
  • Love blinds :). I'll settle for a lack of generics and move on. – keni May 05 '21 at 18:18
  • 3
    Even it's easy (I'm not saying that's true), that's not a reason not to provide it at all. – Eric Jan 26 '22 at 03:42
  • 1
    I am not really satisfied with the answer, it doesn't exist, because there is a workaround. – Blcknx Feb 11 '23 at 23:19
  • this will not work for a set of functions... https://stackoverflow.com/questions/27267042/how-to-use-function-as-maps-key – Asiel Diaz Benitez Feb 19 '23 at 17:30
  • 2
    Golang philosophy: Let's just discard that pesky computer science concept of abstraction and re-code everything from first principles Every time. – Ed Randall Feb 26 '23 at 10:23
  • Golang - "Reinvent the wheel. Every f*cking time." © – Lem Jul 13 '23 at 14:10
128

Partly, because Go doesn't have generics (so you would need one set-type for every type, or fall back on reflection, which is rather inefficient).

Partly, because if all you need is "add/remove individual elements to a set" and "relatively space-efficient", you can get a fair bit of that simply by using a map[yourtype]bool (and set the value to true for any element in the set) or, for more space efficiency, you can use an empty struct as the value and use _, present = the_setoid[key] to check for presence.

Vatine
  • 20,782
  • 4
  • 54
  • 70
  • 1
    Also, it seems to be in Go's spirit to just write it out in your own code, either by "inlining" the set in other code, or define your own set type when needed. Anyway, using for example an std::set in C++ is always happening as part of a function implementation or implementing some other data structure. Therefore, just implement that other data structure directly using maps and slices and whatever other building blocks you need, but without any builtin set. Each usage of a set is going to use it slightly differently anyway :) – Bjarke Ebert Dec 01 '15 at 23:10
  • Well, if Go had generics, I would probably implement and contribute a generic set implementation (built on top of maps, at least initially). But Go doesn't have generics, the code needed is pretty small and side-tracking through reflection is probably going to take enough of a speed hit that it's not worth it. But there's a world of difference writing a robust general library and just writing what you need, for now. – Vatine Dec 02 '15 at 10:38
  • 1
    But usually you don't really need a set by itself, you use a set as part of implementing something bigger. I've seen tons of code where the things you need to do in order to include a "reusable" component is much worse than just avoiding the need. So here we have a set, that function over there needs a set, oops, do we have covariance yet, or how about making a WrapperFactory to make this thing look like this thing, etc. Maybe that other function really just needs an interface that can check for membership, so don't send it a set. – Bjarke Ebert Dec 02 '15 at 21:00
  • 90
    So if it doesn't have generics, how is the 'generic' map implemented at all? – Fermin Silva May 26 '16 at 16:25
  • 5
    @FerminSilva It's done by the compiler, using methods not directly accessible to developers. – Vatine Jun 06 '16 at 16:43
  • 46
    Note that if you want to save bytes, you can use `map[T]struct{}` instead of `map[T]bool`. – emersion May 24 '17 at 09:00
  • @emersion if using that type, what would a map literal look like? I can't figure it out – Big Money Jan 17 '18 at 21:14
  • 9
    Have a look at https://emersion.fr/blog/2017/sets-in-go/ – emersion Jan 18 '18 at 21:50
  • 66
    The usual answer for golang question: "Why provide a feature when you can rewrite it in just a few lines?". This is why something that can be done in 3 explicit lines in python (or many other languages) takes 50+ obscure lines in go. This is one of the reasons (along with single letter variables) why I hate reading go code. It's uselessly long, just doing with for loops what should be done by a clear, efficient and well tested properly named function. Go "spirit" is just throwing away 50 years of good software engineering practice with dubious justifications. – Colin Pitrat Jul 08 '21 at 11:34
  • 5
    Given that generics is coming to Go in 1.18 (https://bitfieldconsulting.com/golang/generics#:~:text=Know%20Go%3F,to%20coincide%20with%20Go%201.18.) the reasons listed here may become outdated. – Nikhil Vandanapu Jan 19 '22 at 00:49
  • I once read that golang was initially created by google for their interns, who don't have much coding experience, so they decide to make the language very basic and simple, to make it only one way to express an idea. – sify Apr 13 '22 at 10:37
  • 2
    Is there any common implementation of the Set DS now Golang is in version 1.19? – Alexandre Mahdhaoui Nov 12 '22 at 23:21
5

Another possibility is to use bit sets, for which there is at least one package or you can use the built-in big package. In this case, basically you need to define a way to convert your object to an index.

Will Fitzgerald
  • 1,372
  • 10
  • 14
4

Like Vatine wrote: Since go lacks generics it would have to be part of the language and not the standard library. For that you would then have to pollute the language with keywords set, union, intersection, difference, subset...

The other reason is, that it's not clear at all what the "right" implementation of a set is:

  1. There is a functional approach:

    func IsInEvenNumbers(n int) bool {
        if n % 2 == 0 {
            return true
        }
       return false
    }
    

This is a set of all even ints. It has a very efficient lookup and union, intersect, difference and subset can easily be done by functional composition.

  1. Or you do a has-like approach like Dali showed.

A map does not have that problem, since you store something associated with the value.

andersonvom
  • 11,701
  • 4
  • 35
  • 40
0x434D53
  • 1,425
  • 12
  • 20
  • 2
    To handle buit-in sets, Pascal overloads a bunch of binary (two-argmnent) operators: `+` for union, `-` for difference, `*` for intersection, `<=` for subset, `>=` for superset, `=` for equality, `<>` for inequality and `in` for membership. So in Go, it would be only a single new keyword -- `in`. On the other hand, Pascal's built-in sets only work on "ordinals"--that is, any type which has an underlying representation of an integer value of some size. – kostix Dec 01 '15 at 16:03
  • 65
    The fact that there are multiple ways to implement a set hasn't stopped many other languages from providing them. – augurar Dec 02 '15 at 07:29
  • 1
    @kostix: Go could even use the syntax `s[key]` (as if `s` were a `map[T]bool`) instead of `key in s`. – musiphil Aug 22 '16 at 17:12
  • 4
    Any reason for not simply returning `n % 2 == 0`? – João Andrade Aug 19 '19 at 14:42
  • 9
    I know this is 5 years old, but `pollute the language with keywords set, union, intersection, difference, subset`, really? Beside `set` the rest are operations on sets, so they are functions. – foobarna Nov 03 '20 at 11:50