322 lines
10 KiB
GDScript
322 lines
10 KiB
GDScript
## Set is Mathematical set data structure for collections of unique values.[br]
|
||
##
|
||
## Built on Dictionary for O(1) membership testing. Used throughout GECS for
|
||
## entity filtering and component indexing.
|
||
##
|
||
## Supports standard set operations like union, intersection, and difference.
|
||
## No inherent ordering - elements are stored by hash.
|
||
##
|
||
## [codeblock]
|
||
## var numbers = Set.new([1, 2, 3, 4, 5])
|
||
## numbers.add(6)
|
||
## print(numbers.has(3)) # true
|
||
##
|
||
## var set_a = Set.new([1, 2, 3, 4])
|
||
## var set_b = Set.new([3, 4, 5, 6])
|
||
## var intersection = set_a.intersect(set_b) # [3, 4]
|
||
## [/codeblock]
|
||
class_name Set
|
||
extends RefCounted
|
||
|
||
## Internal storage using Dictionary keys for O(1) average-case operations.
|
||
## Values in the dictionary are always [code]true[/code] and ignored.
|
||
var _data: Dictionary = {}
|
||
|
||
|
||
## Initializes a new Set from Array, Dictionary keys, or another Set.
|
||
## [param data]: Optional initial data. Duplicates are automatically removed.
|
||
func _init(data = null) -> void:
|
||
if data:
|
||
if data is Array:
|
||
# Add array elements, automatically deduplicating
|
||
for value in data:
|
||
_data[value] = true
|
||
elif data is Set:
|
||
# Copy from another set
|
||
for value in data._data.keys():
|
||
_data[value] = true
|
||
elif data is Dictionary:
|
||
# Use dictionary keys as set elements
|
||
for key in data.keys():
|
||
_data[key] = true
|
||
|
||
#region Basic Set Operations
|
||
|
||
|
||
## Adds a value to the set. Has no effect if the value is already present.
|
||
## [param value]: The value to add to the set. Can be any hashable type.
|
||
##
|
||
## [b]Time Complexity:[/b] O(1) average case
|
||
## [codeblock]
|
||
## var my_set = Set.new([1, 2, 3])
|
||
## my_set.add(4) # Set now contains [1, 2, 3, 4]
|
||
## my_set.add(2) # No change, 2 already exists
|
||
## [/codeblock]
|
||
func add(value) -> void:
|
||
_data[value] = true
|
||
|
||
|
||
## Removes a value from the set. Has no effect if the value is not present.
|
||
## [param value]: The value to remove from the set
|
||
##
|
||
## [b]Time Complexity:[/b] O(1) average case
|
||
## [codeblock]
|
||
## var my_set = Set.new([1, 2, 3, 4])
|
||
## my_set.erase(3) # Set now contains [1, 2, 4]
|
||
## my_set.erase(5) # No change, 5 doesn't exist
|
||
## [/codeblock]
|
||
func erase(value) -> void:
|
||
_data.erase(value)
|
||
|
||
|
||
## Tests whether a value exists in the set.
|
||
## [param value]: The value to test for membership
|
||
## [return]: [code]true[/code] if the value exists in the set, [code]false[/code] otherwise
|
||
##
|
||
## [b]Time Complexity:[/b] O(1) average case
|
||
## [codeblock]
|
||
## var my_set = Set.new(["apple", "banana", "cherry"])
|
||
## print(my_set.has("banana")) # true
|
||
## print(my_set.has("grape")) # false
|
||
## [/codeblock]
|
||
func has(value) -> bool:
|
||
return _data.has(value)
|
||
|
||
|
||
## Removes all elements from the set, making it empty.
|
||
## [b]Time Complexity:[/b] O(1)
|
||
## [codeblock]
|
||
## var my_set = Set.new([1, 2, 3, 4, 5])
|
||
## my_set.clear()
|
||
## print(my_set.is_empty()) # true
|
||
## [/codeblock]
|
||
func clear() -> void:
|
||
_data.clear()
|
||
|
||
|
||
## Returns the number of elements in the set.
|
||
## [return]: Integer count of unique elements in the set
|
||
##
|
||
## [b]Time Complexity:[/b] O(1)
|
||
## [codeblock]
|
||
## var my_set = Set.new(["a", "b", "c", "a", "b"]) # Duplicates ignored
|
||
## print(my_set.size()) # 3
|
||
## [/codeblock]
|
||
func size() -> int:
|
||
return _data.size()
|
||
|
||
|
||
## Tests whether the set contains no elements.
|
||
## [return]: [code]true[/code] if the set is empty, [code]false[/code] otherwise
|
||
##
|
||
## [b]Time Complexity:[/b] O(1)
|
||
## [codeblock]
|
||
## var empty_set = Set.new()
|
||
## var filled_set = Set.new([1, 2, 3])
|
||
## print(empty_set.is_empty()) # true
|
||
## print(filled_set.is_empty()) # false
|
||
## [/codeblock]
|
||
func is_empty() -> bool:
|
||
return _data.is_empty()
|
||
|
||
|
||
## Returns all elements in the set as an Array.
|
||
## The order of elements is not guaranteed and may vary between calls.
|
||
## [return]: Array containing all set elements
|
||
##
|
||
## [b]Time Complexity:[/b] O(n) where n is the number of elements
|
||
## [codeblock]
|
||
## var my_set = Set.new([3, 1, 4, 1, 5])
|
||
## var elements = my_set.values() # [1, 3, 4, 5] (order may vary)
|
||
## [/codeblock]
|
||
func values() -> Array:
|
||
return _data.keys()
|
||
|
||
#endregion
|
||
|
||
#region Set Algebra Operations
|
||
|
||
|
||
## Returns the union of this set with another set (A ∪ B).
|
||
## Creates a new set containing all elements that exist in either set.
|
||
## [param other]: The other set to union with
|
||
## [return]: New [Set] containing all elements from both sets
|
||
##
|
||
## [b]Time Complexity:[/b] O(|A| + |B|) where |A| and |B| are set sizes
|
||
## [codeblock]
|
||
## var set_a = Set.new([1, 2, 3])
|
||
## var set_b = Set.new([3, 4, 5])
|
||
## var union_set = set_a.union(set_b) # Contains [1, 2, 3, 4, 5]
|
||
## [/codeblock]
|
||
func union(other: Set) -> Set:
|
||
var result = Set.new()
|
||
result._data = _data.duplicate()
|
||
for key in other._data.keys():
|
||
result._data[key] = true
|
||
return result
|
||
|
||
|
||
## Returns the intersection of this set with another set (A ∩ B).
|
||
## Creates a new set containing only elements that exist in both sets.
|
||
## Automatically optimizes by iterating over the smaller set.
|
||
## [param other]: The other set to intersect with
|
||
## [return]: New [Set] containing elements common to both sets
|
||
##
|
||
## [b]Time Complexity:[/b] O(min(|A|, |B|)) - optimized for smaller set
|
||
## [codeblock]
|
||
## var set_a = Set.new([1, 2, 3, 4])
|
||
## var set_b = Set.new([3, 4, 5, 6])
|
||
## var intersection = set_a.intersect(set_b) # Contains [3, 4]
|
||
## [/codeblock]
|
||
func intersect(other: Set) -> Set:
|
||
# Optimization: iterate over smaller set for better performance
|
||
if other.size() < _data.size():
|
||
return other.intersect(self )
|
||
|
||
var result = Set.new()
|
||
for key in _data.keys():
|
||
if other._data.has(key):
|
||
result._data[key] = true
|
||
return result
|
||
|
||
|
||
## Returns the difference of this set minus another set (A - B).
|
||
## Creates a new set containing elements in this set but not in the other.
|
||
## [param other]: The set whose elements to exclude
|
||
## [return]: New [Set] containing elements only in this set
|
||
##
|
||
## [b]Time Complexity:[/b] O(|A|) where |A| is the size of this set
|
||
## [codeblock]
|
||
## var set_a = Set.new([1, 2, 3, 4])
|
||
## var set_b = Set.new([3, 4, 5, 6])
|
||
## var difference = set_a.difference(set_b) # Contains [1, 2]
|
||
## [/codeblock]
|
||
func difference(other: Set) -> Set:
|
||
var result = Set.new()
|
||
for key in _data.keys():
|
||
if not other._data.has(key):
|
||
result._data[key] = true
|
||
return result
|
||
|
||
|
||
## Returns the symmetric difference of this set with another set (A ⊕ B).
|
||
## Creates a new set containing elements in either set, but not in both.
|
||
## Equivalent to (A - B) ∪ (B - A).
|
||
## [param other]: The other set for symmetric difference
|
||
## [return]: New [Set] containing elements in exactly one of the two sets
|
||
##
|
||
## [b]Time Complexity:[/b] O(|A| + |B|)
|
||
## [codeblock]
|
||
## var set_a = Set.new([1, 2, 3, 4])
|
||
## var set_b = Set.new([3, 4, 5, 6])
|
||
## var sym_diff = set_a.symmetric_difference(set_b) # Contains [1, 2, 5, 6]
|
||
## [/codeblock]
|
||
func symmetric_difference(other: Set) -> Set:
|
||
var result = Set.new()
|
||
# Add elements from this set that aren't in other
|
||
for key in _data.keys():
|
||
if not other._data.has(key):
|
||
result._data[key] = true
|
||
# Add elements from other set that aren't in this set
|
||
for key in other._data.keys():
|
||
if not _data.has(key):
|
||
result._data[key] = true
|
||
return result
|
||
|
||
#endregion
|
||
|
||
#region Set Relationship Testing
|
||
|
||
|
||
## Tests whether this set is a subset of another set (A ⊆ B).
|
||
## Returns [code]true[/code] if every element in this set also exists in the other set.
|
||
## [param other]: The potential superset to test against
|
||
## [return]: [code]true[/code] if this set is a subset of other, [code]false[/code] otherwise
|
||
##
|
||
## [b]Time Complexity:[/b] O(|A|) where |A| is the size of this set
|
||
## [codeblock]
|
||
## var small_set = Set.new([1, 2])
|
||
## var large_set = Set.new([1, 2, 3, 4, 5])
|
||
## print(small_set.is_subset(large_set)) # true
|
||
## print(large_set.is_subset(small_set)) # false
|
||
## [/codeblock]
|
||
func is_subset(other: Set) -> bool:
|
||
for key in _data.keys():
|
||
if not other._data.has(key):
|
||
return false
|
||
return true
|
||
|
||
|
||
## Tests whether this set is a superset of another set (A ⊇ B).
|
||
## Returns [code]true[/code] if this set contains every element from the other set.
|
||
## [param other]: The potential subset to test
|
||
## [return]: [code]true[/code] if this set is a superset of other, [code]false[/code] otherwise
|
||
##
|
||
## [b]Time Complexity:[/b] O(|B|) where |B| is the size of the other set
|
||
## [codeblock]
|
||
## var large_set = Set.new([1, 2, 3, 4, 5])
|
||
## var small_set = Set.new([2, 4])
|
||
## print(large_set.is_superset(small_set)) # true
|
||
## [/codeblock]
|
||
func is_superset(other: Set) -> bool:
|
||
return other.is_subset(self )
|
||
|
||
|
||
## Tests whether this set contains exactly the same elements as another set (A = B).
|
||
## Two sets are equal if they have the same size and this set is a subset of the other.
|
||
## [param other]: The set to compare for equality
|
||
## [return]: [code]true[/code] if sets contain identical elements, [code]false[/code] otherwise
|
||
##
|
||
## [b]Time Complexity:[/b] O(min(|A|, |B|)) - fails fast on size mismatch
|
||
## [codeblock]
|
||
## var set_a = Set.new([1, 2, 3])
|
||
## var set_b = Set.new([3, 1, 2]) # Order doesn't matter
|
||
## var set_c = Set.new([1, 2, 3, 4])
|
||
## print(set_a.is_equal(set_b)) # true
|
||
## print(set_a.is_equal(set_c)) # false
|
||
## [/codeblock]
|
||
func is_equal(other) -> bool:
|
||
# Quick size check for early exit
|
||
if _data.size() != other._data.size():
|
||
return false
|
||
return self.is_subset(other)
|
||
|
||
#endregion
|
||
|
||
#region Utility Methods
|
||
|
||
|
||
## Creates a shallow copy of this set.
|
||
## The returned set is independent - modifications to either set won't affect the other.
|
||
## However, if the set contains reference types, the references are shared.
|
||
## [return]: New [Set] containing the same elements
|
||
##
|
||
## [b]Time Complexity:[/b] O(n) where n is the number of elements
|
||
## [codeblock]
|
||
## var original = Set.new([1, 2, 3])
|
||
## var copy = original.duplicate()
|
||
## copy.add(4) # Only affects copy
|
||
## print(original.size()) # 3
|
||
## print(copy.size()) # 4
|
||
## [/codeblock]
|
||
func duplicate() -> Set:
|
||
var result = Set.new()
|
||
result._data = _data.duplicate()
|
||
return result
|
||
|
||
|
||
## Converts the set to an Array containing all elements.
|
||
## This is an alias for [method values] provided for API consistency.
|
||
## The order of elements is not guaranteed.
|
||
## [return]: Array containing all set elements
|
||
##
|
||
## [b]Time Complexity:[/b] O(n) where n is the number of elements
|
||
## [codeblock]
|
||
## var my_set = Set.new(["x", "y", "z"])
|
||
## var array = my_set.to_array() # ["x", "y", "z"] (order may vary)
|
||
## [/codeblock]
|
||
func to_array() -> Array:
|
||
return _data.keys()
|
||
|
||
#endregion
|