Skip to content

Commit

Permalink
Adding additional set builtins (#506)
Browse files Browse the repository at this point in the history
* Add difference, subset, superset, intersection, symmetric difference to sets

* Added documentation on new set operations

* Remove outdated comments regarding allowBitwise flag

* fixes

* go fmt

* Requested documentation fixes

* Changes requested in code review

* use fast checks based on set length in set.CompareSameType
  • Loading branch information
SamWheating committed Sep 12, 2023
1 parent 95963e0 commit 745481c
Show file tree
Hide file tree
Showing 7 changed files with 334 additions and 41 deletions.
80 changes: 80 additions & 0 deletions doc/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,14 @@ reproducibility is paramount, such as build tools.
* [list·remove](#list·remove)
* [set·add](#set·add)
* [set·clear](#set·clear)
* [set·difference](#set·difference)
* [set·discard](#set·discard)
* [set·intersection](#set·intersection)
* [set·issubset](#set·issubset)
* [set·issuperset](#set·issuperset)
* [set·pop](#set·pop)
* [set·remove](#set·remove)
* [set·symmetric_difference](#set·symmetric_difference)
* [set·union](#set·union)
* [string·capitalize](#string·capitalize)
* [string·codepoint_ords](#string·codepoint_ords)
Expand Down Expand Up @@ -975,9 +980,14 @@ A set has these methods:

* [`add`](#set·add)
* [`clear`](#set·clear)
* [`difference`](#set·difference)
* [`discard`](#set·discard)
* [`intersection`](#set·intersection)
* [`issubset`](#set·issubset)
* [`issuperset`](#set·issuperset)
* [`pop`](#set·pop)
* [`remove`](#set·remove)
* [`symmetric_difference`](#set·symmetric_difference)
* [`union`](#set·union)


Expand Down Expand Up @@ -1995,6 +2005,11 @@ which breaks several mathematical identities. For example, if `x` is
a `NaN` value, the comparisons `x < y`, `x == y`, and `x > y` all
yield false for all values of `y`.

When used to compare two `set` objects, the `<=`, and `>=` operators will report
whether one set is a subset or superset of another. Similarly, using `<` or `>` will
report whether a set is a proper subset or superset of another, thus `x > y` is
equivalent to `x >= y and x != y`.

Applications may define additional types that support ordered
comparison.

Expand Down Expand Up @@ -2045,6 +2060,8 @@ Sets
int & int # bitwise intersection (AND)
set & set # set intersection
set ^ set # set symmetric difference
set - set # set difference
Dict
dict | dict # ordered union
Expand Down Expand Up @@ -2115,6 +2132,7 @@ Implementations may impose a limit on the second operand of a left shift.
set([1, 2]) & set([2, 3]) # set([2])
set([1, 2]) | set([2, 3]) # set([1, 2, 3])
set([1, 2]) ^ set([2, 3]) # set([1, 3])
set([1, 2]) - set([2, 3]) # set([1])
```
<b>Implementation note:</b>
Expand Down Expand Up @@ -3782,6 +3800,18 @@ x.clear(2) # None
x # set([])
```
<a id='set·difference'></a>
### set·difference
`S.difference(y)` returns a new set into which have been inserted all the elements of set S which are not in y.
y can be any type of iterable (e.g. set, list, tuple).
```python
x = set([1, 2, 3])
x.difference([3, 4, 5]) # set([1, 2])
```
<a id='set·discard'></a>
### set·discard
Expand All @@ -3798,6 +3828,44 @@ x.discard(2) # None
x # set([1, 3])
```
<a id='set·intersection'></a>
### set·intersection
`S.intersection(y)` returns a new set into which have been inserted all the elements of set S which are also in y.
y can be any type of iterable (e.g. set, list, tuple).
```python
x = set([1, 2, 3])
x.intersection([3, 4, 5]) # set([3])
```
<a id='set·issubset'></a>
### set·issubset
`S.issubset(y)` returns True if all items in S are also in y, otherwise it returns False.
y can be any type of iterable (e.g. set, list, tuple).
```python
x = set([1, 2])
x.issubset([1, 2, 3]) # True
x.issubset([1, 3, 4]) # False
```
<a id='set·issuperset'></a>
### set·issuperset
`S.issuperset(y)` returns True if all items in y are also in S, otherwise it returns False.
y can be any type of iterable (e.g. set, list, tuple).
```python
x = set([1, 2, 3])
x.issuperset([1, 2]) # True
x.issuperset([1, 3, 4]) # False
```
<a id='set·pop'></a>
### set·pop
Expand Down Expand Up @@ -3826,6 +3894,18 @@ x # set([1, 3])
x.remove(2) # error: element not found
```
<a id='set·symmetric_difference'></a>
### set·symmetric_difference
`S.symmetric_difference(y)` creates a new set into which is inserted all of the items which are in S but not y, followed by all of the items which are in y but not S.
y can be any type of iterable (e.g. set, list, tuple).
```python
x = set([1, 2, 3])
x.symmetric_difference([3, 4, 5]) # set([1, 2, 4, 5])
```
<a id='set·union'></a>
### set·union
Expand Down
35 changes: 12 additions & 23 deletions starlark/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,12 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
}
return x - yf, nil
}
case *Set: // difference
if y, ok := y.(*Set); ok {
iter := y.Iterate()
defer iter.Done()
return x.Difference(iter)
}
}

case syntax.STAR:
Expand Down Expand Up @@ -1097,17 +1103,9 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
}
case *Set: // intersection
if y, ok := y.(*Set); ok {
set := new(Set)
if x.Len() > y.Len() {
x, y = y, x // opt: range over smaller set
}
for xe := x.ht.head; xe != nil; xe = xe.next {
// Has, Insert cannot fail here.
if found, _ := y.Has(xe.key); found {
set.Insert(xe.key)
}
}
return set, nil
iter := y.Iterate()
defer iter.Done()
return x.Intersection(iter)
}
}

Expand All @@ -1119,18 +1117,9 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
}
case *Set: // symmetric difference
if y, ok := y.(*Set); ok {
set := new(Set)
for xe := x.ht.head; xe != nil; xe = xe.next {
if found, _ := y.Has(xe.key); !found {
set.Insert(xe.key)
}
}
for ye := y.ht.head; ye != nil; ye = ye.next {
if found, _ := x.Has(ye.key); !found {
set.Insert(ye.key)
}
}
return set, nil
iter := y.Iterate()
defer iter.Done()
return x.SymmetricDifference(iter)
}
}

Expand Down
94 changes: 88 additions & 6 deletions starlark/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,17 @@ var (
}

setMethods = map[string]*Builtin{
"add": NewBuiltin("add", set_add),
"clear": NewBuiltin("clear", set_clear),
"discard": NewBuiltin("discard", set_discard),
"pop": NewBuiltin("pop", set_pop),
"remove": NewBuiltin("remove", set_remove),
"union": NewBuiltin("union", set_union),
"add": NewBuiltin("add", set_add),
"clear": NewBuiltin("clear", set_clear),
"difference": NewBuiltin("difference", set_difference),
"discard": NewBuiltin("discard", set_discard),
"intersection": NewBuiltin("intersection", set_intersection),
"issubset": NewBuiltin("issubset", set_issubset),
"issuperset": NewBuiltin("issuperset", set_issuperset),
"pop": NewBuiltin("pop", set_pop),
"remove": NewBuiltin("remove", set_remove),
"symmetric_difference": NewBuiltin("symmetric_difference", set_symmetric_difference),
"union": NewBuiltin("union", set_union),
}
)

Expand Down Expand Up @@ -2204,6 +2209,68 @@ func set_clear(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error)
return None, nil
}

// https://github.com/google/starlark-go/blob/master/doc/spec.md#set·difference.
func set_difference(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
// TODO: support multiple others: s.difference(*others)
var other Iterable
if err := UnpackPositionalArgs(b.Name(), args, kwargs, 0, &other); err != nil {
return nil, err
}
iter := other.Iterate()
defer iter.Done()
diff, err := b.Receiver().(*Set).Difference(iter)
if err != nil {
return nil, nameErr(b, err)
}
return diff, nil
}

// https://github.com/google/starlark-go/blob/master/doc/spec.md#set_intersection.
func set_intersection(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
// TODO: support multiple others: s.difference(*others)
var other Iterable
if err := UnpackPositionalArgs(b.Name(), args, kwargs, 0, &other); err != nil {
return nil, err
}
iter := other.Iterate()
defer iter.Done()
diff, err := b.Receiver().(*Set).Intersection(iter)
if err != nil {
return nil, nameErr(b, err)
}
return diff, nil
}

// https://github.com/google/starlark-go/blob/master/doc/spec.md#set_issubset.
func set_issubset(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
var other Iterable
if err := UnpackPositionalArgs(b.Name(), args, kwargs, 0, &other); err != nil {
return nil, err
}
iter := other.Iterate()
defer iter.Done()
diff, err := b.Receiver().(*Set).IsSubset(iter)
if err != nil {
return nil, nameErr(b, err)
}
return Bool(diff), nil
}

// https://github.com/google/starlark-go/blob/master/doc/spec.md#set_issuperset.
func set_issuperset(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
var other Iterable
if err := UnpackPositionalArgs(b.Name(), args, kwargs, 0, &other); err != nil {
return nil, err
}
iter := other.Iterate()
defer iter.Done()
diff, err := b.Receiver().(*Set).IsSuperset(iter)
if err != nil {
return nil, nameErr(b, err)
}
return Bool(diff), nil
}

// https://github.com/google/starlark-go/blob/master/doc/spec.md#set·discard.
func set_discard(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
var k Value
Expand Down Expand Up @@ -2252,6 +2319,21 @@ func set_remove(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error
return nil, nameErr(b, "missing key")
}

// https://github.com/google/starlark-go/blob/master/doc/spec.md#set·symmetric_difference.
func set_symmetric_difference(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
var other Iterable
if err := UnpackPositionalArgs(b.Name(), args, kwargs, 0, &other); err != nil {
return nil, err
}
iter := other.Iterate()
defer iter.Done()
diff, err := b.Receiver().(*Set).SymmetricDifference(iter)
if err != nil {
return nil, nameErr(b, err)
}
return diff, nil
}

// https://github.com/google/starlark-go/blob/master/doc/spec.md#set·union.
func set_union(_ *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
var iterable Iterable
Expand Down
2 changes: 1 addition & 1 deletion starlark/testdata/builtins.star
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ assert.eq(getattr(hf, "x"), 2)
assert.eq(hf.x, 2)
# built-in types can have attributes (methods) too.
myset = set([])
assert.eq(dir(myset), ["add", "clear", "discard", "pop", "remove", "union"])
assert.eq(dir(myset), ["add", "clear", "difference", "discard", "intersection", "issubset", "issuperset", "pop", "remove", "symmetric_difference", "union"])
assert.true(hasattr(myset, "union"))
assert.true(not hasattr(myset, "onion"))
assert.eq(str(getattr(myset, "union")), "<built-in method union of set value>")
Expand Down
2 changes: 0 additions & 2 deletions starlark/testdata/int.star
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ def compound():
x %= 3
assert.eq(x, 2)

# use resolve.AllowBitwise to enable the ops:
x = 2
x &= 1
assert.eq(x, 0)
Expand Down Expand Up @@ -197,7 +196,6 @@ assert.fails(lambda: int("0x-4", 16), "invalid literal with base 16: 0x-4")

# bitwise union (int|int), intersection (int&int), XOR (int^int), unary not (~int),
# left shift (int<<int), and right shift (int>>int).
# use resolve.AllowBitwise to enable the ops.
# TODO(adonovan): this is not yet in the Starlark spec,
# but there is consensus that it should be.
assert.eq(1 | 2, 3)
Expand Down
Loading

0 comments on commit 745481c

Please sign in to comment.