Skip to content

Commit 146e4d6

Browse files
committed
Same for Difference and SymDifference
1 parent 4e8ed6c commit 146e4d6

5 files changed

Lines changed: 179 additions & 76 deletions

File tree

Source/simba.array_algorithm.pas

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,12 @@ class function TArrayRelationship.Intersection(x, y: TArr): TArr;
170170
y := tmp;
171171
end;
172172

173-
SetLength(Result, Min(Length(y),Length(x)));
173+
SetLength(Result, Length(x));
174174
for i:=0 to High(x) do dict[x[i]] := 1;
175175
for i:=0 to High(y) do
176-
if dict.Contains(y[i]) then
176+
if (dict.GetDef(y[i], 0) = 1) then // in x, and not taken yet
177177
begin
178+
dict[y[i]] := 0; // take once
178179
Result[c] := y[i];
179180
Inc(c);
180181
end;
@@ -386,7 +387,12 @@ class function TArrayEquals.Equals(A, B: TArr): Boolean;
386387
if IsManagedType(_T) then
387388
SimbaException('Requires EqualsFunc');
388389

389-
Result := (Length(A) = Length(B)) and ((Length(A) = 0) and (Length(B) = 0)) or CompareMem(@A[0], @B[0], Length(A) * SizeOf(_T))
390+
if (Length(A) <> Length(B)) then
391+
Exit(False);
392+
if (Length(A) = 0) then
393+
Exit(True);
394+
395+
Result := CompareMem(@A[0], @B[0], Length(A) * SizeOf(_T));
390396
end;
391397

392398
class function TArrayEquals.Equals(A, B: TArr; EqualFunc: TEqualFunc): Boolean;

Source/simba.vartype_pointarray.pas

Lines changed: 34 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2284,64 +2284,51 @@ function TPointArrayHelper.Intersection(Other: TPointArray): TPointArray;
22842284
end;
22852285

22862286
function TPointArrayHelper.SymmetricDifference(Other: TPointArray): TPointArray;
2287+
var
2288+
Buffer: TSimbaPointBuffer;
22872289
begin
2288-
Result := specialize TArrayRelationship<TPoint>.SymmetricDifference(Self, Other);
2290+
Buffer.Init();
2291+
Buffer.Add(Self.Difference(Other));
2292+
Buffer.Add(Other.Difference(Self));
2293+
Result := Buffer.ToArray(False);
22892294
end;
22902295

22912296
function TPointArrayHelper.Difference(Other: TPointArray): TPointArray;
22922297
var
2293-
box: TBox;
2294-
test: TBooleanArray;
2295-
x,y: TPointArray;
2296-
w,h,i,c: Integer;
2298+
I, Count: Integer;
2299+
Box: TBox;
2300+
ScanSet: TPointSet;
2301+
HashSet: TPointHashSet;
22972302
begin
2298-
x := Self;
2299-
y := Other;
2300-
2301-
box := y.Bounds(); // bounds of the array we're subtracting
2303+
SetLength(Result, Length(Self));
2304+
Count := 0;
23022305

2303-
{ Fallback to safe dictionary:
2304-
* Fewer than 1 in area of 5000
2305-
* Larger than 512MB in memory (approx 25K*25K area)
2306-
}
2307-
if (box.Area*SizeOf(TPoint) > $20000000) or
2308-
(Length(y)/box.Area < 0.0002) then
2306+
Box := Self.Bounds();
2307+
if ShouldHashSet(Box, Length(Self) + Length(Other)) then
23092308
begin
2310-
Exit(specialize TArrayRelationship<TPoint>.Difference(x, y));
2311-
end;
2312-
2313-
SetLength(test, box.Area);
2314-
w := box.Width;
2315-
h := box.Height;
2316-
2317-
// Mark points of `Other` (y) within the bounds as True in `test`
2318-
for i := 0 to High(y) do
2309+
HashSet.Init(Length(Self) + Length(Other));
2310+
for I := 0 to High(Other) do
2311+
HashSet.Add(Other[I]);
2312+
for I := 0 to High(Self) do
2313+
if HashSet.Add(Self[I]) then
2314+
begin
2315+
Result[Count] := Self[I];
2316+
Inc(Count);
2317+
end;
2318+
end else
23192319
begin
2320-
if InRange(y[i].x - box.x1, 0, w - 1) and InRange(y[i].y - box.y1, 0, h - 1) then
2321-
test[(y[i].y - box.y1) * w + (y[i].x - box.x1)] := True;
2320+
ScanSet.Init(Box);
2321+
for I := 0 to High(Other) do
2322+
ScanSet.Add(Other[I]);
2323+
for I := 0 to High(Self) do
2324+
if ScanSet.Add(Self[I]) then
2325+
begin
2326+
Result[Count] := Self[I];
2327+
Inc(Count);
2328+
end;
23222329
end;
23232330

2324-
SetLength(Result, Length(x));
2325-
c := 0;
2326-
2327-
// Iterate through 'Self' (x)
2328-
for i := 0 to High(x) do
2329-
begin
2330-
// Check if the point is within the bounds of 'Other
2331-
// if it's outside the bounds we can add this difference
2332-
if not (InRange(x[i].x - box.x1, 0, w - 1) and InRange(x[i].y - box.y1, 0, h - 1)) then
2333-
begin
2334-
Result[c] := x[i];
2335-
Inc(c);
2336-
end
2337-
else if not test[(x[i].y - box.y1) * w + (x[i].x - box.x1)] then
2338-
begin
2339-
// If it's INSIDE the bounds but NOT marked in 'test', it's also in the difference
2340-
Result[c] := x[i];
2341-
Inc(c);
2342-
end;
2343-
end;
2344-
SetLength(Result, c);
2331+
SetLength(Result, Count);
23452332
end;
23462333

23472334
function TPointArrayHelper.DistanceTransform: TSingleMatrix;

Tests/array_relationship.simba

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ begin
6161
Assert(Success(a.Intersection(b), [3, 4]));
6262
end;
6363

64-
procedure TestPointArray;
64+
procedure TestBoxArray;
6565

66-
function Success(a,b: TPointArray): Boolean;
66+
function Success(a,b: TBoxArray): Boolean;
6767
var i: Int32;
6868
begin
6969
if (Length(a) <> Length(b)) then
@@ -75,22 +75,27 @@ procedure TestPointArray;
7575
end;
7676

7777
var
78-
a,b: TPointArray;
78+
a,b: TBoxArray;
7979
begin
80-
a := [[1,1],[2,2],[3,3]];
81-
b := [[3,3],[2,2],[1,1]];
80+
a := [[1,1,1,1],[2,2,2,2],[3,3,3,3]];
81+
b := [[3,3,3,3],[2,2,2,2],[1,1,1,1]];
82+
8283
Assert(Success(a.Difference(b), []));
8384
Assert(Success(a.SymDifference(b), []));
85+
Assert(Success(a.Intersection(b), [[3,3,3,3],[2,2,2,2],[1,1,1,1]]));
86+
87+
a := [[1,1,1,1],[2,2,2,2],[3,3,3,3]];
88+
b := [[1,1,1,1],[2,2,2,2]];
8489

85-
a := [[1,2],[2,3],[3,4]];
86-
b := [[1,2],[2,3]];
87-
Assert(Success(a.Difference(b), [[3,4]]));
88-
Assert(Success(a.SymDifference(b), [[3,4]]));
90+
Assert(Success(a.Difference(b), [[3,3,3,3]]));
91+
Assert(Success(a.SymDifference(b), [[3,3,3,3]]));
92+
Assert(Success(a.Intersection(b), [[1,1,1,1],[2,2,2,2]]));
8993
end;
9094

91-
procedure TestBoxArray;
9295

93-
function Success(a,b: TBoxArray): Boolean;
96+
procedure TestDuplicates;
97+
98+
function Success(a,b: TIntegerArray): Boolean;
9499
var i: Int32;
95100
begin
96101
if (Length(a) <> Length(b)) then
@@ -102,27 +107,22 @@ procedure TestBoxArray;
102107
end;
103108

104109
var
105-
a,b: TBoxArray;
110+
a,b: TIntegerArray;
106111
begin
107-
a := [[1,1,1,1],[2,2,2,2],[3,3,3,3]];
108-
b := [[3,3,3,3],[2,2,2,2],[1,1,1,1]];
109-
110-
Assert(Success(a.Difference(b), []));
111-
Assert(Success(a.SymDifference(b), []));
112-
Assert(Success(a.Intersection(b), [[3,3,3,3],[2,2,2,2],[1,1,1,1]]));
113-
114-
a := [[1,1,1,1],[2,2,2,2],[3,3,3,3]];
115-
b := [[1,1,1,1],[2,2,2,2]];
116-
117-
Assert(Success(a.Difference(b), [[3,3,3,3]]));
118-
Assert(Success(a.SymDifference(b), [[3,3,3,3]]));
119-
Assert(Success(a.Intersection(b), [[1,1,1,1],[2,2,2,2]]));
112+
a := [1,2,2,3];
113+
b := [2,2,3,3,4];
114+
Assert(Success(a.Intersection(b), [2,3]), 'intersection dups');
115+
Assert(Success(a.Difference(b), [1]), 'difference dups');
116+
Assert(Success(a.SymDifference(b), [1,4]), 'symdifference dups');
117+
118+
Assert(Success(TIntegerArray([1]).Intersection([1,1,1]), [1]), 'intersection overflow');
119+
Assert(Success(TIntegerArray([5,5]).Intersection([5,5,5,5]), [5]), 'intersection overflow 2');
120120
end;
121121

122122

123123
begin
124124
TestIntegerArray();
125125
TestInt64Array();
126-
TestPointArray();
127126
TestBoxArray();
127+
TestDuplicates();
128128
end;

Tests/tpa_difference.simba

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{$assertions on}
2+
3+
{ TPointArray.Difference(Other) = the distinct points of Self that are not in
4+
Other. It is a set difference, so Self's own duplicates collapse to one.
5+
6+
The backend is chosen from Self: small coordinates keep its bounds dense for a
7+
scanline presence set; coordinates spread by D force the hash set. }
8+
9+
// Compared as a set - order is not part of the contract.
10+
function SameSet(Got, Want: TPointArray): Boolean;
11+
begin
12+
Result := Got.SortByColumn().Equals(Want.SortByColumn());
13+
end;
14+
15+
// dense bounds -> scanline backend
16+
procedure ScanLine;
17+
var
18+
A, B: TPointArray;
19+
begin
20+
A := [Point(1, 1), Point(2, 2), Point(3, 3), Point(4, 4)];
21+
B := [Point(2, 2), Point(4, 4), Point(9, 9)];
22+
23+
Assert(SameSet(TPointArray([]).Difference([]), []), 'both empty');
24+
Assert(SameSet(A.Difference([]), A), 'minus nothing');
25+
Assert(SameSet(TPointArray([]).Difference(A), []), 'empty self');
26+
Assert(SameSet(A.Difference(B), [Point(1, 1), Point(3, 3)]), 'some removed');
27+
Assert(SameSet(A.Difference(A), []), 'minus self');
28+
29+
// set semantics: Self's duplicates collapse to one
30+
Assert(SameSet(TPointArray([Point(1, 1), Point(1, 1), Point(2, 2)]).Difference([Point(2, 2)]), [Point(1, 1)]), 'dedups self');
31+
// a Self point outside Other's bounds survives
32+
Assert(SameSet(TPointArray([Point(-9, -9), Point(2, 2)]).Difference([Point(2, 2)]), [Point(-9, -9)]), 'kept when outside other');
33+
end;
34+
35+
// huge bounds -> hash fallback backend
36+
procedure Hashing;
37+
const
38+
D = 1000000;
39+
var
40+
A, B, Big, Sub, Want: TPointArray;
41+
I: Int32;
42+
begin
43+
A := [Point(1*D, 1*D), Point(2*D, 2*D), Point(3*D, 3*D), Point(4*D, 4*D)];
44+
B := [Point(2*D, 2*D), Point(4*D, 4*D)];
45+
46+
Assert(SameSet(A.Difference(B), [Point(1*D, 1*D), Point(3*D, 3*D)]), 'some removed');
47+
Assert(SameSet(A.Difference(A), []), 'minus self');
48+
Assert(SameSet(A.Difference([]), A), 'minus nothing');
49+
50+
// larger: subtract the odd-indexed points, keep the even ones
51+
for I := 0 to 4999 do
52+
begin
53+
Big += Point(I * D, I * D);
54+
if (I mod 2 = 0) then
55+
Want += Point(I * D, I * D)
56+
else
57+
Sub += Point(I * D, I * D);
58+
end;
59+
Assert(SameSet(Big.Difference(Sub), Want), 'large difference');
60+
end;
61+
62+
begin
63+
ScanLine();
64+
Hashing();
65+
end.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{$assertions on}
2+
3+
// SymDifference: the distinct points in exactly one of the two arrays
4+
5+
function SameSet(Got, Want: TPointArray): Boolean;
6+
begin
7+
Result := Got.SortByColumn().Equals(Want.SortByColumn());
8+
end;
9+
10+
// scanline backend
11+
procedure ScanLine;
12+
var
13+
A, B: TPointArray;
14+
begin
15+
A := [Point(1, 1), Point(2, 2), Point(3, 3)];
16+
B := [Point(2, 2), Point(3, 3), Point(4, 4)];
17+
18+
Assert(SameSet(TPointArray([]).SymDifference([]), []), 'both empty');
19+
Assert(SameSet(A.SymDifference([]), A), 'other empty');
20+
Assert(SameSet(TPointArray([]).SymDifference(A), A), 'self empty');
21+
Assert(SameSet(A.SymDifference(B), [Point(1, 1), Point(4, 4)]), 'symmetric');
22+
Assert(SameSet(A.SymDifference(A), []), 'same');
23+
Assert(SameSet(TPointArray([Point(1, 1), Point(1, 1)]).SymDifference([Point(2, 2)]), [Point(1, 1), Point(2, 2)]), 'dedups');
24+
Assert(SameSet(TPointArray([Point(1, 1), Point(2, 2), Point(2, 2)]).SymDifference([Point(2, 2)]), [Point(1, 1)]), 'common dropped');
25+
end;
26+
27+
// hash backend
28+
procedure Hashing;
29+
const
30+
D = 1000000;
31+
var
32+
A, B: TPointArray;
33+
begin
34+
A := [Point(1*D, 1*D), Point(2*D, 2*D), Point(3*D, 3*D)];
35+
B := [Point(2*D, 2*D), Point(3*D, 3*D), Point(4*D, 4*D)];
36+
37+
Assert(SameSet(A.SymDifference(B), [Point(1*D, 1*D), Point(4*D, 4*D)]), 'symmetric');
38+
Assert(SameSet(A.SymDifference(A), []), 'same');
39+
Assert(SameSet(A.SymDifference([]), A), 'other empty');
40+
end;
41+
42+
begin
43+
ScanLine();
44+
Hashing();
45+
end.

0 commit comments

Comments
 (0)