Files
godot-shader-experiments/addons/gecs/lib/set.gd
2026-01-15 15:27:48 +01:00

322 lines
10 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 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