Skip to content

Commit ae9287c

Browse files
authored
symmetric difference operation for sets via xor (#24286)
closes nim-lang/RFCs#554 Adds a symmetric difference operation to the language bitset type. This maps to a simple `xor` operation on the backend and thus is likely faster than the current alternatives, namely `(a - b) + (b - a)` or `a + b - a * b`. The compiler VM implementation of bitsets already implemented this via `symdiffSets` but it was never used. The standalone binary operation is added to `setutils`, named `symmetricDifference` in line with [hash sets](https://nim-lang.org/docs/sets.html#symmetricDifference%2CHashSet%5BA%5D%2CHashSet%5BA%5D). An operator version `-+-` and an in-place version like `toggle` as described in the RFC are also added, implemented as trivial sugar.
1 parent 0a058a6 commit ae9287c

File tree

12 files changed

+85
-7
lines changed

12 files changed

+85
-7
lines changed

changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ rounding guarantees (via the
1414

1515
## Standard library additions and changes
1616

17+
[//]: # "Additions:"
18+
- `setutils.symmetricDifference` along with its operator version
19+
`` setutils.`-+-` `` and in-place version `setutils.toggle` have been added
20+
to more efficiently calculate the symmetric difference of bitsets.
21+
1722
[//]: # "Changes:"
1823
- `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type.
1924

compiler/ast.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ type
491491
mAnd, mOr,
492492
mImplies, mIff, mExists, mForall, mOld,
493493
mEqStr, mLeStr, mLtStr,
494-
mEqSet, mLeSet, mLtSet, mMulSet, mPlusSet, mMinusSet,
494+
mEqSet, mLeSet, mLtSet, mMulSet, mPlusSet, mMinusSet, mXorSet,
495495
mConStrStr, mSlice,
496496
mDotDot, # this one is only necessary to give nice compile time warnings
497497
mFields, mFieldPairs, mOmpParFor,
@@ -559,7 +559,7 @@ const
559559
mStrToStr, mEnumToStr,
560560
mAnd, mOr,
561561
mEqStr, mLeStr, mLtStr,
562-
mEqSet, mLeSet, mLtSet, mMulSet, mPlusSet, mMinusSet,
562+
mEqSet, mLeSet, mLtSet, mMulSet, mPlusSet, mMinusSet, mXorSet,
563563
mConStrStr, mAppendStrCh, mAppendStrStr, mAppendSeqElem,
564564
mInSet, mRepr, mOpenArrayToSeq}
565565

compiler/ccgexprs.nim

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2044,7 +2044,7 @@ proc genInOp(p: BProc, e: PNode, d: var TLoc) =
20442044

20452045
proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
20462046
const
2047-
lookupOpr: array[mLeSet..mMinusSet, string] = [
2047+
lookupOpr: array[mLeSet..mXorSet, string] = [
20482048
"for ($1 = 0; $1 < $2; $1++) { $n" &
20492049
" $3 = (($4[$1] & ~ $5[$1]) == 0);$n" &
20502050
" if (!$3) break;}$n",
@@ -2054,7 +2054,8 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
20542054
"if ($3) $3 = (#nimCmpMem($4, $5, $2) != 0);$n",
20552055
"&",
20562056
"|",
2057-
"& ~"]
2057+
"& ~",
2058+
"^"]
20582059
var a, b: TLoc
20592060
var i: TLoc
20602061
var setType = skipTypes(e[1].typ, abstractVar)
@@ -2085,6 +2086,7 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
20852086
of mMulSet: binaryExpr(p, e, d, "($1 & $2)")
20862087
of mPlusSet: binaryExpr(p, e, d, "($1 | $2)")
20872088
of mMinusSet: binaryExpr(p, e, d, "($1 & ~ $2)")
2089+
of mXorSet: binaryExpr(p, e, d, "($1 ^ $2)")
20882090
of mInSet:
20892091
genInOp(p, e, d)
20902092
else: internalError(p.config, e.info, "genSetOp()")
@@ -2112,7 +2114,7 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
21122114
var a = initLocExpr(p, e[1])
21132115
var b = initLocExpr(p, e[2])
21142116
putIntoDest(p, d, e, ropecg(p.module, "(#nimCmpMem($1, $2, $3)==0)", [a.rdCharLoc, b.rdCharLoc, size]))
2115-
of mMulSet, mPlusSet, mMinusSet:
2117+
of mMulSet, mPlusSet, mMinusSet, mXorSet:
21162118
# we inline the simple for loop for better code generation:
21172119
i = getTemp(p, getSysType(p.module.g.graph, unknownLineInfo, tyInt)) # our counter
21182120
a = initLocExpr(p, e[1])
@@ -2548,7 +2550,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
25482550
of mSetLengthStr: genSetLengthStr(p, e, d)
25492551
of mSetLengthSeq: genSetLengthSeq(p, e, d)
25502552
of mIncl, mExcl, mCard, mLtSet, mLeSet, mEqSet, mMulSet, mPlusSet, mMinusSet,
2551-
mInSet:
2553+
mInSet, mXorSet:
25522554
genSetOp(p, e, d, op)
25532555
of mNewString, mNewStringOfCap, mExit, mParseBiggestFloat:
25542556
var opr = e[0].sym

compiler/condsyms.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,4 @@ proc initDefines*(symbols: StringTableRef) =
170170
defineSymbol("nimHasGenericsOpenSym3")
171171
defineSymbol("nimHasJsNoLambdaLifting")
172172
defineSymbol("nimHasDefaultFloatRoundtrip")
173+
defineSymbol("nimHasXorSet")

compiler/jsgen.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2458,6 +2458,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
24582458
of mMulSet: binaryExpr(p, n, r, "SetMul", "SetMul($1, $2)")
24592459
of mPlusSet: binaryExpr(p, n, r, "SetPlus", "SetPlus($1, $2)")
24602460
of mMinusSet: binaryExpr(p, n, r, "SetMinus", "SetMinus($1, $2)")
2461+
of mXorSet: binaryExpr(p, n, r, "SetXor", "SetXor($1, $2)")
24612462
of mIncl: binaryExpr(p, n, r, "", "$1[$2] = true")
24622463
of mExcl: binaryExpr(p, n, r, "", "delete $1[$2]")
24632464
of mInSet:

compiler/semfold.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; idgen: IdGenerator; g: ModuleGraph): P
302302
of mMinusSet:
303303
result = nimsets.diffSets(g.config, a, b)
304304
result.info = n.info
305+
of mXorSet:
306+
result = nimsets.symdiffSets(g.config, a, b)
307+
result.info = n.info
305308
of mConStrStr: result = newStrNodeT(getStrOrChar(a) & getStrOrChar(b), n, g)
306309
of mInSet: result = newIntNodeT(toInt128(ord(inSet(a, b))), n, idgen, g)
307310
of mRepr:

compiler/vm.nim

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,11 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
12761276
createSet(regs[ra])
12771277
move(regs[ra].node.sons,
12781278
nimsets.diffSets(c.config, regs[rb].node, regs[rc].node).sons)
1279+
of opcXorSet:
1280+
decodeBC(rkNode)
1281+
createSet(regs[ra])
1282+
move(regs[ra].node.sons,
1283+
nimsets.symdiffSets(c.config, regs[rb].node, regs[rc].node).sons)
12791284
of opcConcatStr:
12801285
decodeBC(rkNode)
12811286
createStr regs[ra]

compiler/vmdef.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ type
100100
opcEqRef, opcEqNimNode, opcSameNodeType,
101101
opcXor, opcNot, opcUnaryMinusInt, opcUnaryMinusFloat, opcBitnotInt,
102102
opcEqStr, opcEqCString, opcLeStr, opcLtStr, opcEqSet, opcLeSet, opcLtSet,
103-
opcMulSet, opcPlusSet, opcMinusSet, opcConcatStr,
103+
opcMulSet, opcPlusSet, opcMinusSet, opcXorSet, opcConcatStr,
104104
opcContainsSet, opcRepr, opcSetLenStr, opcSetLenSeq,
105105
opcIsNil, opcOf, opcIs,
106106
opcParseFloat, opcConv, opcCast,

compiler/vmgen.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}, m: TMag
12121212
of mMulSet: genBinarySet(c, n, dest, opcMulSet)
12131213
of mPlusSet: genBinarySet(c, n, dest, opcPlusSet)
12141214
of mMinusSet: genBinarySet(c, n, dest, opcMinusSet)
1215+
of mXorSet: genBinarySet(c, n, dest, opcXorSet)
12151216
of mConStrStr: genVarargsABC(c, n, dest, opcConcatStr)
12161217
of mInSet: genBinarySet(c, n, dest, opcContainsSet)
12171218
of mRepr: genUnaryABC(c, n, dest, opcRepr)

lib/std/setutils.nim

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,31 @@ func `[]=`*[T](t: var set[T], key: T, val: bool) {.inline.} =
7575
s[a3] = true
7676
assert s == {a2, a3}
7777
if val: t.incl key else: t.excl key
78+
79+
when defined(nimHasXorSet):
80+
func symmetricDifference*[T](x, y: set[T]): set[T] {.magic: "XorSet".} =
81+
## This operator computes the symmetric difference of two sets,
82+
## equivalent to but more efficient than `x + y - x * y` or
83+
## `(x - y) + (y - x)`.
84+
runnableExamples:
85+
assert symmetricDifference({1, 2, 3}, {2, 3, 4}) == {1, 4}
86+
else:
87+
func symmetricDifference*[T](x, y: set[T]): set[T] {.inline.} =
88+
result = x + y - (x * y)
89+
90+
proc `-+-`*[T](x, y: set[T]): set[T] {.inline.} =
91+
## Operator alias for `symmetricDifference`.
92+
runnableExamples:
93+
assert {1, 2, 3} -+- {2, 3, 4} == {1, 4}
94+
result = symmetricDifference(x, y)
95+
96+
proc toggle*[T](x: var set[T], y: set[T]) {.inline.} =
97+
## Toggles the existence of each value of `y` in `x`.
98+
## If any element in `y` is also in `x`, it is excluded from `x`;
99+
## otherwise it is included.
100+
## Equivalent to `x = symmetricDifference(x, y)`.
101+
runnableExamples:
102+
var x = {1, 2, 3}
103+
x.toggle({2, 3, 4})
104+
assert x == {1, 4}
105+
x = symmetricDifference(x, y)

0 commit comments

Comments
 (0)