Skip to content

Commit b667b16

Browse files
Add Set.intersect function (#25)
1 parent 986150b commit b667b16

File tree

5 files changed

+250
-0
lines changed

5 files changed

+250
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Changelog
22

3+
- add `Set.intersect` ([#25](https://github.com/seaofvoices/luau-disk/pull/25))
34
- add `Set.toArray` ([#24](https://github.com/seaofvoices/luau-disk/pull/24))
45
- add `Set.removeValues` ([#23](https://github.com/seaofvoices/luau-disk/pull/23))
56
- add `Set.filter` ([#22](https://github.com/seaofvoices/luau-disk/pull/22))

docs/Set.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,19 @@ local result = Set.isEmpty({ key = true }) -- false
6363

6464
*Related [count](#count)*
6565

66+
## intersect
67+
68+
Returns a new set containing only the values that are present in all given sets.
69+
70+
```lua
71+
local set1 = Set.fromArray({ "a", "b", "c" })
72+
local set2 = Set.fromArray({ "b", "c", "d" })
73+
local result = Set.intersect(set1, set2)
74+
-- result is { b = true, c = true }
75+
```
76+
77+
*Related [merge](#merge)*
78+
6679
## map
6780

6881
Returns a new set where each value is converted with a mapping function. When the mapping function returns a `nil` value, the entry is removed.
@@ -88,6 +101,8 @@ local result = Set.merge(set1, set2)
88101

89102
The function will skip `nil` values when merging.
90103

104+
*Related [intersect](#intersect)*
105+
91106
## removeValues
92107

93108
Returns a new set with all the given values removed.

src/init.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ local Disk = {
7070
count = require('./set/count'),
7171
filter = require('./set/filter'),
7272
fromArray = require('./set/fromArray'),
73+
intersect = require('./set/intersect'),
7374
isEmpty = require('./set/isEmpty'),
7475
map = require('./set/map'),
7576
merge = require('./set/merge'),
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
local jestGlobals = require('@pkg/@jsdotlua/jest-globals')
2+
3+
local fromArray = require('../fromArray')
4+
local intersect = require('../intersect')
5+
6+
local expect = jestGlobals.expect
7+
local it = jestGlobals.it
8+
9+
it('returns intersection of two sets with common elements', function()
10+
local set1 = fromArray({ 1, 2, 3, 4 })
11+
local set2 = fromArray({ 3, 4, 5, 6 })
12+
local result = intersect(set1, set2)
13+
14+
expect(result).toEqual(fromArray({ 3, 4 }))
15+
end)
16+
17+
it('returns empty set when sets have no common elements', function()
18+
local set1 = fromArray({ 1, 2, 3 })
19+
local set2 = fromArray({ 4, 5, 6 })
20+
local result = intersect(set1, set2)
21+
22+
expect(result).toEqual({})
23+
end)
24+
25+
it('returns intersection of multiple sets', function()
26+
local set1 = fromArray({ 1, 2, 3, 4, 5 })
27+
local set2 = fromArray({ 2, 3, 4, 5, 6 })
28+
local set3 = fromArray({ 3, 4, 5, 6, 7 })
29+
local result = intersect(set1, set2, set3)
30+
31+
expect(result).toEqual(fromArray({ 3, 4, 5 }))
32+
end)
33+
34+
it('returns empty set when any set is empty', function()
35+
local set1 = fromArray({ 1, 2, 3 })
36+
local set2 = {}
37+
local set3 = fromArray({ 2, 3, 4 })
38+
local result = intersect(set1, set2, set3)
39+
40+
expect(result).toEqual({})
41+
end)
42+
43+
it('returns empty set when no sets are provided', function()
44+
local result = intersect()
45+
46+
expect(result).toEqual({})
47+
end)
48+
49+
it('returns the original set when only one set is provided', function()
50+
local set1 = fromArray({ 1, 2, 3, 4 })
51+
local result = intersect(set1)
52+
53+
expect(result).toBe(set1)
54+
end)
55+
56+
it('returns original set when other sets are nil', function()
57+
local set1 = fromArray({ 1, 2, 3 })
58+
local result = intersect(set1, nil, nil)
59+
60+
expect(result).toBe(set1)
61+
end)
62+
63+
it('handles identical sets', function()
64+
local set1 = fromArray({ 1, 2, 3 })
65+
local set2 = fromArray({ 1, 2, 3 })
66+
local result = intersect(set1, set2)
67+
68+
expect(result).toEqual(fromArray({ 1, 2, 3 }))
69+
end)
70+
71+
it('works with string sets', function()
72+
local set1 = fromArray({ 'apple', 'banana', 'cherry' })
73+
local set2 = fromArray({ 'banana', 'cherry', 'date' })
74+
local result = intersect(set1, set2)
75+
76+
expect(result).toEqual(fromArray({ 'banana', 'cherry' }))
77+
end)
78+
79+
it('works with mixed type sets', function()
80+
local set1 = fromArray({ 1 :: any, 'hello', true })
81+
local set2 = fromArray({ 1 :: any, 'hello', false })
82+
local result = intersect(set1, set2)
83+
84+
expect(result).toEqual(fromArray({ 1 :: any, 'hello' }))
85+
end)
86+
87+
it('handles single element sets', function()
88+
local set1 = fromArray({ 42 })
89+
local set2 = fromArray({ 42 })
90+
local result = intersect(set1, set2)
91+
92+
expect(result).toEqual(fromArray({ 42 }))
93+
end)
94+
95+
it('returns empty set for single element sets with different elements', function()
96+
local set1 = fromArray({ 42 })
97+
local set2 = fromArray({ 24 })
98+
local result = intersect(set1, set2)
99+
100+
expect(result).toEqual({})
101+
end)
102+
103+
it('handles subsets correctly', function()
104+
local set1 = fromArray({ 1, 2, 3, 4, 5 })
105+
local set2 = fromArray({ 2, 3 })
106+
local result = intersect(set1, set2)
107+
108+
expect(result).toEqual(fromArray({ 2, 3 }))
109+
end)
110+
111+
it('handles supersets correctly', function()
112+
local set1 = fromArray({ 2, 3 })
113+
local set2 = fromArray({ 1, 2, 3, 4, 5 })
114+
local result = intersect(set1, set2)
115+
116+
expect(result).toEqual(fromArray({ 2, 3 }))
117+
end)
118+
119+
it('does not modify original sets', function()
120+
local set1 = fromArray({ 1, 2, 3, 4 })
121+
local set2 = fromArray({ 3, 4, 5, 6 })
122+
local originalSet1 = table.clone(set1)
123+
local originalSet2 = table.clone(set2)
124+
125+
local result = intersect(set1, set2)
126+
127+
expect(set1).toEqual(originalSet1)
128+
expect(set2).toEqual(originalSet2)
129+
expect(result).never.toBe(set1)
130+
expect(result).never.toBe(set2)
131+
end)
132+
133+
it('works with four sets', function()
134+
local set1 = fromArray({ 1, 2, 3, 4, 5, 6 })
135+
local set2 = fromArray({ 2, 3, 4, 5, 6, 7 })
136+
local set3 = fromArray({ 3, 4, 5, 6, 7, 8 })
137+
local set4 = fromArray({ 4, 5, 6, 7, 8, 9 })
138+
local result = intersect(set1, set2, set3, set4)
139+
140+
expect(result).toEqual(fromArray({ 4, 5, 6 }))
141+
end)
142+
143+
it('returns first set when all elements are common', function()
144+
local set1 = fromArray({ 1, 2, 3 })
145+
local set2 = fromArray({ 1, 2, 3, 4, 5 })
146+
local set3 = fromArray({ 1, 2, 3, 6, 7 })
147+
local result = intersect(set1, set2, set3)
148+
149+
expect(result).toBe(set1)
150+
end)
151+
152+
it('handles boolean values in sets', function()
153+
local set1 = fromArray({ true, false })
154+
local set2 = fromArray({ true })
155+
local result = intersect(set1, set2)
156+
157+
expect(result).toEqual(fromArray({ true }))
158+
end)
159+
160+
it('handles empty intersection with multiple sets', function()
161+
local set1 = fromArray({ 1, 2 })
162+
local set2 = fromArray({ 3, 4 })
163+
local set3 = fromArray({ 5, 6 })
164+
local result = intersect(set1, set2, set3)
165+
166+
expect(result).toEqual({})
167+
end)
168+
169+
it('handles nested tables as set elements', function()
170+
local table1 = { 1, 2, 3 }
171+
local table2 = { 4, 5, 6 }
172+
local set1 = fromArray({ table1, table2 })
173+
local set2 = fromArray({ table1 })
174+
local result = intersect(set1, set2)
175+
176+
expect(result).toEqual(fromArray({ table1 }))
177+
end)
178+
179+
return nil

src/set/intersect.lua

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
local SetType = require('./type')
2+
3+
type Set<T> = SetType.Set<T>
4+
5+
local function intersect<T>(...: Set<T>?): Set<T>
6+
local sets = {}
7+
8+
for i = 1, select('#', ...) do
9+
local setValue = select(i, ...)
10+
if setValue then
11+
if next(setValue) == nil then
12+
return {}
13+
end
14+
15+
table.insert(sets, setValue)
16+
end
17+
end
18+
19+
local length = #sets
20+
21+
if length == 0 then
22+
return {}
23+
elseif length == 1 then
24+
return sets[1]
25+
else
26+
local removeKeys = {}
27+
local first = sets[1]
28+
29+
for key in first do
30+
for i = 2, length do
31+
local currentSet = sets[i]
32+
33+
if not currentSet[key] then
34+
table.insert(removeKeys, key)
35+
break
36+
end
37+
end
38+
end
39+
40+
if #removeKeys == 0 then
41+
return first
42+
end
43+
44+
local result: Set<T> = table.clone(first)
45+
46+
for _, key in removeKeys do
47+
result[key] = nil
48+
end
49+
50+
return result
51+
end
52+
end
53+
54+
return intersect

0 commit comments

Comments
 (0)