diff --git a/autowrap/ConversionProvider.py b/autowrap/ConversionProvider.py index 7d7f689..cacf5cd 100644 --- a/autowrap/ConversionProvider.py +++ b/autowrap/ConversionProvider.py @@ -857,15 +857,19 @@ def input_conversion( key_conv_code = "" key_conv_cleanup = "" + # Use mangled variable names to avoid collision with function parameters + loop_key = mangle("_loop_key_" + argument_var) + loop_value = mangle("_loop_value_" + argument_var) + if cy_tt_value.is_enum: - value_conv = "<%s> value" % cy_tt_value + value_conv = "<%s> %s" % (cy_tt_value, loop_value) elif tt_value.base_type in self.converters.names_of_wrapper_classes: - value_conv = "deref((<%s>value).inst.get())" % tt_value.base_type + value_conv = "deref((<%s>%s).inst.get())" % (tt_value.base_type, loop_value) elif tt_value.template_args is not None and tt_value.base_type == "libcpp_vector": # Special case: the value type is a std::vector< X >, maybe something we can convert? # code_top = """ - value_var = "value" + value_var = loop_value (tt,) = tt_value.template_args vtemp_var = "svec%s" % arg_num inner = self.converters.cython_type(tt) @@ -945,27 +949,27 @@ def input_conversion( elif tt_value in self.converters: value_conv_code, value_conv, value_conv_cleanup = self.converters.get( tt_value - ).input_conversion(tt_value, "value", 0) + ).input_conversion(tt_value, loop_value, 0) else: - value_conv = "<%s> value" % cy_tt_value + value_conv = "<%s> %s" % (cy_tt_value, loop_value) if cy_tt_key.is_enum: - key_conv = "<%s> key" % cy_tt_key + key_conv = "<%s> %s" % (cy_tt_key, loop_key) elif tt_key.base_type in self.converters.names_of_wrapper_classes: - key_conv = "deref(<%s *> (<%s> key).inst.get())" % (cy_tt_key, py_tt_key) + key_conv = "deref(<%s *> (<%s> %s).inst.get())" % (cy_tt_key, py_tt_key, loop_key) elif tt_key in self.converters: key_conv_code, key_conv, key_conv_cleanup = self.converters.get( tt_key - ).input_conversion(tt_key, "key", 0) + ).input_conversion(tt_key, loop_key, 0) else: - key_conv = "<%s> key" % cy_tt_key + key_conv = "<%s> %s" % (cy_tt_key, loop_key) code.add( """ |cdef libcpp_map[$cy_tt_key, $cy_tt_value] * $temp_var = new + libcpp_map[$cy_tt_key, $cy_tt_value]() - |for key, value in $argument_var.items(): + |for $loop_key, $loop_value in $argument_var.items(): """, locals(), ) @@ -2230,26 +2234,30 @@ def input_conversion( cy_tt_key = self.converters.cython_type(tt_key) cy_tt_value = self.converters.cython_type(tt_value) + # Use mangled variable names to avoid collision with function parameters + loop_key = mangle("_loop_key_" + argument_var) + loop_value = mangle("_loop_value_" + argument_var) + if cy_tt_value.is_enum: - value_conv = "<%s> value" % cy_tt_value + value_conv = "<%s> %s" % (cy_tt_value, loop_value) elif tt_value.base_type in self.converters.names_of_wrapper_classes: - value_conv = "deref((<%s>value).inst.get())" % tt_value.base_type + value_conv = "deref((<%s>%s).inst.get())" % (tt_value.base_type, loop_value) else: - value_conv = "<%s> value" % cy_tt_value + value_conv = "<%s> %s" % (cy_tt_value, loop_value) if cy_tt_key.is_enum: - key_conv = "<%s> key" % cy_tt_key + key_conv = "<%s> %s" % (cy_tt_key, loop_key) elif tt_key.base_type in self.converters.names_of_wrapper_classes: - key_conv = "deref(<%s *> (<%s> key).inst.get())" % (cy_tt_key, tt_key) + key_conv = "deref(<%s *> (<%s> %s).inst.get())" % (cy_tt_key, tt_key, loop_key) else: - key_conv = "<%s> key" % cy_tt_key + key_conv = "<%s> %s" % (cy_tt_key, loop_key) code.add( """ |cdef libcpp_unordered_map[$cy_tt_key, $cy_tt_value] * $temp_var = new + libcpp_unordered_map[$cy_tt_key, $cy_tt_value]() - |for key, value in $argument_var.items(): + |for $loop_key, $loop_value in $argument_var.items(): | deref($temp_var)[ $key_conv ] = $value_conv """, locals(), diff --git a/tests/test_code_generator_cpp17_stl.py b/tests/test_code_generator_cpp17_stl.py new file mode 100644 index 0000000..28674ac --- /dev/null +++ b/tests/test_code_generator_cpp17_stl.py @@ -0,0 +1,276 @@ +""" +C++17 STL Container Support Tests +================================= + +This module tests autowrap's support for C++17 STL containers. These containers +are automatically converted between Python and C++ types: + +Container Mappings: + std::unordered_map <-> Python dict (hash-based, O(1) average lookup) + std::unordered_set <-> Python set (hash-based, O(1) average lookup) + std::deque <-> Python list (double-ended queue) + std::list <-> Python list (doubly-linked list) + std::optional <-> Python T|None (nullable values) + std::string_view <-> Python bytes (non-owning string reference) + +Usage Examples: + # Returning containers from C++ + result = obj.getUnorderedMap() # Returns Python dict + result = obj.getUnorderedSet() # Returns Python set + + # Passing containers to C++ + obj.processMap({b"key": 42}) # Pass Python dict + obj.processSet({1, 2, 3}) # Pass Python set + + # Hash-based lookups (O(1) average) + value = obj.lookupMap(my_dict, b"key") # Direct key lookup + exists = obj.hasKey(my_dict, b"key") # Check key exists + found = obj.findInSet(my_set, item) # Set membership test + + # Optional values + result = obj.getOptional(True) # Returns value or None + obj.processOptional(None) # Pass None for empty optional + +Note: Requires C++17 compilation flag (-std=c++17) for optional and string_view. +""" +from __future__ import print_function +from __future__ import absolute_import + +import os +import pytest +import autowrap +import autowrap.Utils + +test_files = os.path.join(os.path.dirname(__file__), "test_files") + + +def test_cpp17_stl_containers(): + """ + Test C++17 STL container code generation and runtime behavior. + + This test verifies: + 1. Code generation produces correct Cython imports and method signatures + 2. Container conversions work correctly at runtime + 3. Hash-based lookups (find, count, at) work correctly + 4. Mutable references allow in-place modification + 5. Optional values handle None correctly + """ + target = os.path.join(test_files, "cpp17_stl_test.pyx") + + include_dirs = autowrap.parse_and_generate_code( + ["cpp17_stl_test.pxd"], root=test_files, target=target, debug=True + ) + + # Read the generated file to verify converters worked + with open(target) as f: + pyx_content = f.read() + + # ========================================================================= + # Verify code generation: check required imports are present + # ========================================================================= + assert "libcpp.unordered_map" in pyx_content, \ + "unordered_map import should be present" + assert "libcpp.unordered_set" in pyx_content, \ + "unordered_set import should be present" + assert "libcpp.deque" in pyx_content, \ + "deque import should be present" + assert "libcpp.list" in pyx_content, \ + "list import should be present" + assert "libcpp.optional" in pyx_content, \ + "optional import should be present" + assert "libcpp.string_view" in pyx_content, \ + "string_view import should be present" + + # Verify all methods are generated + assert "def getUnorderedMap(" in pyx_content + assert "def sumUnorderedMapValues(" in pyx_content + assert "def lookupUnorderedMap(" in pyx_content + assert "def hasKeyUnorderedMap(" in pyx_content + assert "def getValueUnorderedMap(" in pyx_content + assert "def getUnorderedSet(" in pyx_content + assert "def sumUnorderedSet(" in pyx_content + assert "def hasValueUnorderedSet(" in pyx_content + assert "def countUnorderedSet(" in pyx_content + assert "def findUnorderedSet(" in pyx_content + assert "def getDeque(" in pyx_content + assert "def sumDeque(" in pyx_content + assert "def doubleDequeElements(" in pyx_content + assert "def getList(" in pyx_content + assert "def sumList(" in pyx_content + assert "def doubleListElements(" in pyx_content + assert "def getOptionalValue(" in pyx_content + assert "def unwrapOptional(" in pyx_content + assert "def getStringViewLength(" in pyx_content + assert "def stringViewToString(" in pyx_content + + # Verify optional uses has_value() check + assert "has_value()" in pyx_content, \ + "optional should use has_value() check" + + # ========================================================================= + # Compile and run runtime tests + # ========================================================================= + mod = autowrap.Utils.compile_and_import( + "cpp17_stl_test_module", + [target], + include_dirs, + ) + + obj = mod._Cpp17STLTest() + + # ========================================================================= + # Test: std::unordered_map -> Python dict + # ========================================================================= + + # Test returning unordered_map from C++ + result_map = obj.getUnorderedMap() + assert isinstance(result_map, dict), "unordered_map should return dict" + assert result_map == {b"one": 1, b"two": 2, b"three": 3}, \ + f"Unexpected map result: {result_map}" + + # Test passing dict to C++ (iteration) + sum_result = obj.sumUnorderedMapValues({b"a": 10, b"b": 20}) + assert sum_result == 30, f"sumUnorderedMapValues returned {sum_result}" + + # Test hash-based key lookup using find() + test_map = {b"apple": 100, b"banana": 200, b"cherry": 300} + lookup_result = obj.lookupUnorderedMap(test_map, b"banana") + assert lookup_result == 200, \ + f"lookupUnorderedMap('banana') returned {lookup_result}, expected 200" + + lookup_missing = obj.lookupUnorderedMap(test_map, b"grape") + assert lookup_missing == -1, \ + f"lookupUnorderedMap('grape') should return -1 for missing key" + + # Test hash-based key existence check using count() + assert obj.hasKeyUnorderedMap(test_map, b"apple") is True, \ + "hasKeyUnorderedMap('apple') should be True" + assert obj.hasKeyUnorderedMap(test_map, b"grape") is False, \ + "hasKeyUnorderedMap('grape') should be False" + + # Test at() accessor (throws on missing key) + value_result = obj.getValueUnorderedMap(test_map, b"cherry") + assert value_result == 300, \ + f"getValueUnorderedMap('cherry') returned {value_result}, expected 300" + + # Verify at() throws exception for missing key + try: + obj.getValueUnorderedMap(test_map, b"missing") + assert False, "getValueUnorderedMap should raise exception for missing key" + except Exception: + pass # Expected - std::out_of_range from at() + + # ========================================================================= + # Test: std::unordered_set -> Python set + # ========================================================================= + + # Test returning unordered_set from C++ + result_set = obj.getUnorderedSet() + assert isinstance(result_set, set), "unordered_set should return set" + assert result_set == {1, 2, 3, 4, 5}, f"Unexpected set result: {result_set}" + + # Test passing set to C++ (iteration) + sum_set_result = obj.sumUnorderedSet({10, 20, 30}) + assert sum_set_result == 60, f"sumUnorderedSet returned {sum_set_result}" + + # Test hash-based membership check using count() + test_set = {100, 200, 300, 400} + assert obj.hasValueUnorderedSet(test_set, 200) is True, \ + "hasValueUnorderedSet(200) should be True" + assert obj.hasValueUnorderedSet(test_set, 999) is False, \ + "hasValueUnorderedSet(999) should be False" + + # Test count() returns 0 or 1 + assert obj.countUnorderedSet(test_set, 300) == 1, \ + "countUnorderedSet(300) should be 1" + assert obj.countUnorderedSet(test_set, 999) == 0, \ + "countUnorderedSet(999) should be 0" + + # Test hash-based find() + find_result = obj.findUnorderedSet(test_set, 400) + assert find_result == 400, \ + f"findUnorderedSet(400) returned {find_result}, expected 400" + + find_missing = obj.findUnorderedSet(test_set, 999) + assert find_missing == -1, \ + f"findUnorderedSet(999) should return -1 for missing element" + + # ========================================================================= + # Test: std::deque -> Python list + # ========================================================================= + + # Test returning deque from C++ + result_deque = obj.getDeque() + assert isinstance(result_deque, list), "deque should return list" + assert result_deque == [10, 20, 30, 40], \ + f"Unexpected deque result: {result_deque}" + + # Test passing list to C++ deque + sum_deque_result = obj.sumDeque([5, 10, 15]) + assert sum_deque_result == 30, f"sumDeque returned {sum_deque_result}" + + # Test mutable reference: modifications are reflected back to Python + deque_data = [1, 2, 3, 4] + obj.doubleDequeElements(deque_data) + assert deque_data == [2, 4, 6, 8], \ + f"doubleDequeElements should modify list in place: {deque_data}" + + # ========================================================================= + # Test: std::list -> Python list + # ========================================================================= + + # Test returning std::list from C++ + result_list = obj.getList() + assert isinstance(result_list, list), "std::list should return list" + expected_list = [1.1, 2.2, 3.3] + for i, (r, e) in enumerate(zip(result_list, expected_list)): + assert abs(r - e) < 0.0001, f"Unexpected list value at {i}: {r} vs {e}" + + # Test passing list to C++ std::list + sum_list_result = obj.sumList([1.0, 2.0, 3.0]) + assert abs(sum_list_result - 6.0) < 0.0001, \ + f"sumList returned {sum_list_result}" + + # Test mutable reference + list_data = [1.0, 2.0, 3.0] + obj.doubleListElements(list_data) + expected_doubled = [2.0, 4.0, 6.0] + for i, (r, e) in enumerate(zip(list_data, expected_doubled)): + assert abs(r - e) < 0.0001, \ + f"doubleListElements should modify list in place: {list_data}" + + # ========================================================================= + # Test: std::optional -> Python int | None + # ========================================================================= + + # Test returning optional with value + opt_with_value = obj.getOptionalValue(True) + assert opt_with_value == 42, \ + f"Optional with value should return 42, got {opt_with_value}" + + # Test returning empty optional -> None + opt_without_value = obj.getOptionalValue(False) + assert opt_without_value is None, \ + f"Optional without value should return None, got {opt_without_value}" + + # Test passing value to optional parameter + unwrap_result = obj.unwrapOptional(100) + assert unwrap_result == 100, f"unwrapOptional(100) returned {unwrap_result}" + + # Test passing None for empty optional + unwrap_none_result = obj.unwrapOptional(None) + assert unwrap_none_result == -1, \ + f"unwrapOptional(None) returned {unwrap_none_result}" + + # ========================================================================= + # Test: std::string_view -> Python bytes + # ========================================================================= + + # Test passing string_view to C++ + length = obj.getStringViewLength(b"hello") + assert length == 5, f"getStringViewLength('hello') returned {length}" + + # Test returning string from string_view + string_result = obj.stringViewToString(b"test") + assert string_result == b"test", \ + f"stringViewToString('test') returned {string_result}" diff --git a/tests/test_code_generator_new_stl.py b/tests/test_code_generator_new_stl.py deleted file mode 100644 index 1dc7631..0000000 --- a/tests/test_code_generator_new_stl.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -Tests for new STL container support in autowrap. -Tests: unordered_map, unordered_set, deque, list, optional, string_view -""" -from __future__ import print_function -from __future__ import absolute_import - -import os -import pytest -import autowrap -import autowrap.Utils - -test_files = os.path.join(os.path.dirname(__file__), "test_files") - - -def test_new_stl_code_generation(): - """Test code generation for new STL containers.""" - target = os.path.join(test_files, "new_stl_test.pyx") - - include_dirs = autowrap.parse_and_generate_code( - ["new_stl_test.pxd"], root=test_files, target=target, debug=True - ) - - # Read the generated file to verify converters worked - with open(target) as f: - pyx_content = f.read() - - # Check that all imports are present - assert "libcpp.unordered_map" in pyx_content, \ - "unordered_map import should be present" - assert "libcpp.unordered_set" in pyx_content, \ - "unordered_set import should be present" - assert "libcpp.deque" in pyx_content, \ - "deque import should be present" - assert "libcpp.list" in pyx_content, \ - "list import should be present" - assert "libcpp.optional" in pyx_content, \ - "optional import should be present" - assert "libcpp.string_view" in pyx_content, \ - "string_view import should be present" - - # Check that all methods are generated - assert "def getUnorderedMap(" in pyx_content, \ - "getUnorderedMap method should be generated" - assert "def sumUnorderedMapValues(" in pyx_content, \ - "sumUnorderedMapValues method should be generated" - assert "def getUnorderedSet(" in pyx_content, \ - "getUnorderedSet method should be generated" - assert "def sumUnorderedSet(" in pyx_content, \ - "sumUnorderedSet method should be generated" - assert "def getDeque(" in pyx_content, \ - "getDeque method should be generated" - assert "def sumDeque(" in pyx_content, \ - "sumDeque method should be generated" - assert "def doubleDequeElements(" in pyx_content, \ - "doubleDequeElements method should be generated" - assert "def getList(" in pyx_content, \ - "getList method should be generated" - assert "def sumList(" in pyx_content, \ - "sumList method should be generated" - assert "def doubleListElements(" in pyx_content, \ - "doubleListElements method should be generated" - assert "def getOptionalValue(" in pyx_content, \ - "getOptionalValue method should be generated" - assert "def unwrapOptional(" in pyx_content, \ - "unwrapOptional method should be generated" - assert "def getStringViewLength(" in pyx_content, \ - "getStringViewLength method should be generated" - assert "def stringViewToString(" in pyx_content, \ - "stringViewToString method should be generated" - - # Check optional-specific patterns - assert "has_value()" in pyx_content, \ - "optional should use has_value() check" - - # Compile and import for runtime tests - mod = autowrap.Utils.compile_and_import( - "new_stl_test_module", - [target], - include_dirs, - ) - - # Create the test object (class name is _NewSTLTest due to pxd naming) - obj = mod._NewSTLTest() - - # Test unordered_map - result_map = obj.getUnorderedMap() - assert isinstance(result_map, dict), "unordered_map should return dict" - assert result_map == {b"one": 1, b"two": 2, b"three": 3}, \ - f"Unexpected map result: {result_map}" - - sum_result = obj.sumUnorderedMapValues({b"a": 10, b"b": 20}) - assert sum_result == 30, f"sumUnorderedMapValues returned {sum_result}" - - # Test unordered_set - result_set = obj.getUnorderedSet() - assert isinstance(result_set, set), "unordered_set should return set" - assert result_set == {1, 2, 3, 4, 5}, f"Unexpected set result: {result_set}" - - sum_set_result = obj.sumUnorderedSet({10, 20, 30}) - assert sum_set_result == 60, f"sumUnorderedSet returned {sum_set_result}" - - # Test deque - result_deque = obj.getDeque() - assert isinstance(result_deque, list), "deque should return list" - assert result_deque == [10, 20, 30, 40], f"Unexpected deque result: {result_deque}" - - sum_deque_result = obj.sumDeque([5, 10, 15]) - assert sum_deque_result == 30, f"sumDeque returned {sum_deque_result}" - - # Test deque mutable reference (cleanup code) - deque_data = [1, 2, 3, 4] - obj.doubleDequeElements(deque_data) - assert deque_data == [2, 4, 6, 8], f"doubleDequeElements should modify list in place: {deque_data}" - - # Test list (std::list) - result_list = obj.getList() - assert isinstance(result_list, list), "std::list should return list" - expected_list = [1.1, 2.2, 3.3] - for i, (r, e) in enumerate(zip(result_list, expected_list)): - assert abs(r - e) < 0.0001, f"Unexpected list value at {i}: {r} vs {e}" - - sum_list_result = obj.sumList([1.0, 2.0, 3.0]) - assert abs(sum_list_result - 6.0) < 0.0001, f"sumList returned {sum_list_result}" - - # Test list mutable reference (cleanup code) - list_data = [1.0, 2.0, 3.0] - obj.doubleListElements(list_data) - expected_doubled = [2.0, 4.0, 6.0] - for i, (r, e) in enumerate(zip(list_data, expected_doubled)): - assert abs(r - e) < 0.0001, f"doubleListElements should modify list in place: {list_data}" - - # Test optional - opt_with_value = obj.getOptionalValue(True) - assert opt_with_value == 42, f"Optional with value should return 42, got {opt_with_value}" - - opt_without_value = obj.getOptionalValue(False) - assert opt_without_value is None, f"Optional without value should return None, got {opt_without_value}" - - unwrap_result = obj.unwrapOptional(100) - assert unwrap_result == 100, f"unwrapOptional(100) returned {unwrap_result}" - - # Test passing None for empty optional - unwrap_none_result = obj.unwrapOptional(None) - assert unwrap_none_result == -1, f"unwrapOptional(None) returned {unwrap_none_result}" - - # Test string_view - length = obj.getStringViewLength(b"hello") - assert length == 5, f"getStringViewLength('hello') returned {length}" - - string_result = obj.stringViewToString(b"test") - assert string_result == b"test", \ - f"stringViewToString('test') returned {string_result}" diff --git a/tests/test_files/cpp17_stl_test.hpp b/tests/test_files/cpp17_stl_test.hpp new file mode 100644 index 0000000..432b62d --- /dev/null +++ b/tests/test_files/cpp17_stl_test.hpp @@ -0,0 +1,206 @@ +/** + * C++17 STL Container Test Implementation + * ======================================== + * + * This file demonstrates C++ functions that use C++17 STL containers. + * Autowrap automatically generates Python bindings for these functions. + * + * Supported Containers: + * - std::unordered_map -> Python dict (hash-based, O(1) avg lookup) + * - std::unordered_set -> Python set (hash-based, O(1) avg lookup) + * - std::deque -> Python list (double-ended queue) + * - std::list -> Python list (doubly-linked list) + * - std::optional -> Python T|None (nullable value) + * - std::string_view -> Python bytes (non-owning string reference) + * + * Compilation: Requires -std=c++17 for optional and string_view. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +class Cpp17STLTest { +public: + Cpp17STLTest() {} + + // ========================================================================= + // std::unordered_map - Hash-based dictionary + // ========================================================================= + // Python: dict with bytes keys and int values + // Example: {b"one": 1, b"two": 2} + + /// Return a map - demonstrates C++ -> Python dict conversion + std::unordered_map getUnorderedMap() { + std::unordered_map m; + m["one"] = 1; + m["two"] = 2; + m["three"] = 3; + return m; + } + + /// Sum all values - demonstrates Python dict -> C++ iteration + int sumUnorderedMapValues(const std::unordered_map& m) { + int sum = 0; + for (const auto& p : m) { + sum += p.second; + } + return sum; + } + + /// Lookup by key using find() - O(1) average time + /// Returns value or -1 if not found + int lookupUnorderedMap(const std::unordered_map& m, const std::string& key) { + auto it = m.find(key); + if (it != m.end()) { + return it->second; + } + return -1; + } + + /// Check if key exists using count() - O(1) average time + bool hasKeyUnorderedMap(const std::unordered_map& m, const std::string& key) { + return m.count(key) > 0; + } + + /// Get value using at() - throws std::out_of_range if key not found + int getValueUnorderedMap(const std::unordered_map& m, const std::string& key) { + return m.at(key); + } + + // ========================================================================= + // std::unordered_set - Hash-based set + // ========================================================================= + // Python: set with int values + // Example: {1, 2, 3} + + /// Return a set - demonstrates C++ -> Python set conversion + std::unordered_set getUnorderedSet() { + return {1, 2, 3, 4, 5}; + } + + /// Sum all values - demonstrates Python set -> C++ iteration + int sumUnorderedSet(const std::unordered_set& s) { + int sum = 0; + for (int v : s) { + sum += v; + } + return sum; + } + + /// Check membership using count() - O(1) average time + bool hasValueUnorderedSet(const std::unordered_set& s, int value) { + return s.count(value) > 0; + } + + /// Count occurrences (always 0 or 1 for set) + size_t countUnorderedSet(const std::unordered_set& s, int value) { + return s.count(value); + } + + /// Find element using find() - O(1) average time + /// Returns value or -1 if not found + int findUnorderedSet(const std::unordered_set& s, int value) { + auto it = s.find(value); + if (it != s.end()) { + return *it; + } + return -1; + } + + // ========================================================================= + // std::deque - Double-ended queue + // ========================================================================= + // Python: list + // Note: std::deque allows O(1) insertion/deletion at both ends + + /// Return a deque - demonstrates C++ -> Python list conversion + std::deque getDeque() { + return {10, 20, 30, 40}; + } + + /// Sum all values - demonstrates Python list -> C++ deque + int sumDeque(const std::deque& d) { + int sum = 0; + for (int v : d) { + sum += v; + } + return sum; + } + + /// Modify in place - demonstrates mutable reference handling + /// Changes are reflected back to the Python list + void doubleDequeElements(std::deque& d) { + for (size_t i = 0; i < d.size(); i++) { + d[i] *= 2; + } + } + + // ========================================================================= + // std::list - Doubly-linked list + // ========================================================================= + // Python: list + // Note: std::list allows O(1) insertion/deletion anywhere with iterator + + /// Return a list - demonstrates C++ -> Python list conversion + std::list getList() { + return {1.1, 2.2, 3.3}; + } + + /// Sum all values - demonstrates Python list -> C++ std::list + double sumList(const std::list& l) { + double sum = 0; + for (double v : l) { + sum += v; + } + return sum; + } + + /// Modify in place - demonstrates mutable reference handling + void doubleListElements(std::list& l) { + for (auto& v : l) { + v *= 2; + } + } + + // ========================================================================= + // std::optional - Nullable value (C++17) + // ========================================================================= + // Python: int | None + // Use for values that may or may not be present + + /// Return optional value - demonstrates C++ optional -> Python value/None + std::optional getOptionalValue(bool hasValue) { + if (hasValue) { + return 42; + } + return std::nullopt; + } + + /// Accept optional value - demonstrates Python value/None -> C++ optional + /// Returns the value or -1 if empty + int unwrapOptional(std::optional opt) { + return opt.value_or(-1); + } + + // ========================================================================= + // std::string_view - Non-owning string reference (C++17) + // ========================================================================= + // Python: bytes + // Note: string_view doesn't own the data - source must remain valid + + /// Get string length - demonstrates Python bytes -> C++ string_view + size_t getStringViewLength(std::string_view sv) { + return sv.size(); + } + + /// Convert to string - demonstrates string_view -> Python bytes + std::string stringViewToString(std::string_view sv) { + return std::string(sv); + } +}; diff --git a/tests/test_files/cpp17_stl_test.pxd b/tests/test_files/cpp17_stl_test.pxd new file mode 100644 index 0000000..7099684 --- /dev/null +++ b/tests/test_files/cpp17_stl_test.pxd @@ -0,0 +1,127 @@ +# cython: language_level=3 +# +# ============================================================================= +# C++17 STL Container Declarations for Autowrap +# ============================================================================= +# +# This PXD file demonstrates how to declare C++ functions that use C++17 STL +# containers. Autowrap automatically generates Python bindings with proper +# type conversions. +# +# Container Type Mappings: +# ------------------------ +# C++ Type Python Type Notes +# ------------------------- ------------- -------------------------------- +# std::unordered_map dict Hash-based, O(1) avg lookup +# std::unordered_set set Hash-based, O(1) avg lookup +# std::deque list Double-ended queue +# std::list list Doubly-linked list +# std::optional T | None Nullable value (C++17) +# std::string_view bytes Non-owning string ref (C++17) +# +# Required Cython Imports: +# ------------------------ +# from libcpp.unordered_map cimport unordered_map as libcpp_unordered_map +# from libcpp.unordered_set cimport unordered_set as libcpp_unordered_set +# from libcpp.deque cimport deque as libcpp_deque +# from libcpp.list cimport list as libcpp_list +# from libcpp.optional cimport optional as libcpp_optional +# from libcpp.string_view cimport string_view as libcpp_string_view +# +# Compilation: +# ------------ +# Requires C++17 flag: -std=c++17 (for optional and string_view) +# +# ============================================================================= + +from libc.stddef cimport size_t +from libcpp cimport bool +from libcpp.string cimport string as libcpp_string +from libcpp.unordered_map cimport unordered_map as libcpp_unordered_map +from libcpp.unordered_set cimport unordered_set as libcpp_unordered_set +from libcpp.deque cimport deque as libcpp_deque +from libcpp.list cimport list as libcpp_list +from libcpp.optional cimport optional as libcpp_optional +from libcpp.string_view cimport string_view as libcpp_string_view + +cdef extern from "cpp17_stl_test.hpp": + + cdef cppclass _Cpp17STLTest "Cpp17STLTest": + _Cpp17STLTest() + + # ===================================================================== + # std::unordered_map - Hash-based dictionary + # ===================================================================== + # Python usage: + # result = obj.getUnorderedMap() # Returns dict + # obj.sumUnorderedMapValues({b"a": 1}) # Pass dict + # value = obj.lookupUnorderedMap(d, b"k") # O(1) key lookup + # exists = obj.hasKeyUnorderedMap(d, b"k") # O(1) key check + # --------------------------------------------------------------------- + libcpp_unordered_map[libcpp_string, int] getUnorderedMap() + int sumUnorderedMapValues(libcpp_unordered_map[libcpp_string, int]& m) + int lookupUnorderedMap(libcpp_unordered_map[libcpp_string, int]& m, libcpp_string& key) + bool hasKeyUnorderedMap(libcpp_unordered_map[libcpp_string, int]& m, libcpp_string& key) + int getValueUnorderedMap(libcpp_unordered_map[libcpp_string, int]& m, libcpp_string& key) except + + + # ===================================================================== + # std::unordered_set - Hash-based set + # ===================================================================== + # Python usage: + # result = obj.getUnorderedSet() # Returns set + # obj.sumUnorderedSet({1, 2, 3}) # Pass set + # exists = obj.hasValueUnorderedSet(s, 42) # O(1) membership test + # found = obj.findUnorderedSet(s, 42) # O(1) lookup + # --------------------------------------------------------------------- + libcpp_unordered_set[int] getUnorderedSet() + int sumUnorderedSet(libcpp_unordered_set[int]& s) + bool hasValueUnorderedSet(libcpp_unordered_set[int]& s, int value) + size_t countUnorderedSet(libcpp_unordered_set[int]& s, int value) + int findUnorderedSet(libcpp_unordered_set[int]& s, int value) + + # ===================================================================== + # std::deque - Double-ended queue + # ===================================================================== + # Python usage: + # result = obj.getDeque() # Returns list + # obj.sumDeque([1, 2, 3]) # Pass list + # obj.doubleDequeElements(data) # Modifies list in-place + # --------------------------------------------------------------------- + libcpp_deque[int] getDeque() + int sumDeque(libcpp_deque[int]& d) + void doubleDequeElements(libcpp_deque[int]& d) + + # ===================================================================== + # std::list - Doubly-linked list + # ===================================================================== + # Python usage: + # result = obj.getList() # Returns list + # obj.sumList([1.0, 2.0]) # Pass list + # obj.doubleListElements(data) # Modifies list in-place + # --------------------------------------------------------------------- + libcpp_list[double] getList() + double sumList(libcpp_list[double]& l) + void doubleListElements(libcpp_list[double]& l) + + # ===================================================================== + # std::optional - Nullable value (C++17) + # ===================================================================== + # Python usage: + # result = obj.getOptionalValue(True) # Returns 42 or None + # obj.unwrapOptional(100) # Pass value + # obj.unwrapOptional(None) # Pass empty optional + # --------------------------------------------------------------------- + libcpp_optional[int] getOptionalValue(bool hasValue) + int unwrapOptional(libcpp_optional[int] opt) + + # ===================================================================== + # std::string_view - Non-owning string reference (C++17) + # ===================================================================== + # Python usage: + # length = obj.getStringViewLength(b"hi") # Pass bytes + # result = obj.stringViewToString(b"hi") # Returns bytes + # Note: string_view is a non-owning reference - the source must remain + # valid during the call. + # --------------------------------------------------------------------- + size_t getStringViewLength(libcpp_string_view sv) + libcpp_string stringViewToString(libcpp_string_view sv) diff --git a/tests/test_files/new_stl_test.pyx b/tests/test_files/cpp17_stl_test.pyx similarity index 56% rename from tests/test_files/new_stl_test.pyx rename to tests/test_files/cpp17_stl_test.pyx index 5d60110..f0fb9f9 100644 --- a/tests/test_files/new_stl_test.pyx +++ b/tests/test_files/cpp17_stl_test.pyx @@ -23,18 +23,18 @@ from cython.operator cimport dereference as deref, preincrement as inc, address from AutowrapRefHolder cimport AutowrapRefHolder from AutowrapPtrHolder cimport AutowrapPtrHolder from AutowrapConstPtrHolder cimport AutowrapConstPtrHolder -from smart_ptr cimport shared_ptr -from new_stl_test cimport _NewSTLTest as __NewSTLTest +from libcpp.memory cimport shared_ptr +from cpp17_stl_test cimport _Cpp17STLTest as __Cpp17STLTest cdef extern from "autowrap_tools.hpp": char * _cast_const_away(char *) -cdef class _NewSTLTest: +cdef class _Cpp17STLTest: """ - Cython implementation of __NewSTLTest + Cython implementation of __Cpp17STLTest """ - cdef shared_ptr[__NewSTLTest] inst + cdef shared_ptr[__Cpp17STLTest] inst def __dealloc__(self): self.inst.reset() @@ -44,7 +44,7 @@ cdef class _NewSTLTest: """ __init__(self) -> None """ - self.inst = shared_ptr[__NewSTLTest](new __NewSTLTest()) + self.inst = shared_ptr[__Cpp17STLTest](new __Cpp17STLTest()) def getUnorderedMap(self): """ @@ -64,8 +64,8 @@ cdef class _NewSTLTest: """ assert isinstance(m, dict) and all(isinstance(k, bytes) for k in m.keys()) and all(isinstance(v, int) for v in m.values()), 'arg m wrong type' cdef libcpp_unordered_map[libcpp_string, int] * v0 = new libcpp_unordered_map[libcpp_string, int]() - for key, value in m.items(): - deref(v0)[ key ] = value + for _loop_key_m, _loop_value_m in m.items(): + deref(v0)[ _loop_key_m ] = _loop_value_m cdef int _r = self.inst.get().sumUnorderedMapValues(deref(v0)) replace = dict() cdef libcpp_unordered_map[libcpp_string, int].iterator it_m = v0.begin() @@ -78,6 +78,72 @@ cdef class _NewSTLTest: py_result = _r return py_result + def lookupUnorderedMap(self, dict m , bytes key ): + """ + lookupUnorderedMap(self, m: Dict[bytes, int] , key: bytes ) -> int + """ + assert isinstance(m, dict) and all(isinstance(k, bytes) for k in m.keys()) and all(isinstance(v, int) for v in m.values()), 'arg m wrong type' + assert isinstance(key, bytes), 'arg key wrong type' + cdef libcpp_unordered_map[libcpp_string, int] * v0 = new libcpp_unordered_map[libcpp_string, int]() + for _loop_key_m, _loop_value_m in m.items(): + deref(v0)[ _loop_key_m ] = _loop_value_m + + cdef int _r = self.inst.get().lookupUnorderedMap(deref(v0), (key)) + replace = dict() + cdef libcpp_unordered_map[libcpp_string, int].iterator it_m = v0.begin() + while it_m != v0.end(): + replace[ deref(it_m).first] = deref(it_m).second + inc(it_m) + m.clear() + m.update(replace) + del v0 + py_result = _r + return py_result + + def hasKeyUnorderedMap(self, dict m , bytes key ): + """ + hasKeyUnorderedMap(self, m: Dict[bytes, int] , key: bytes ) -> bool + """ + assert isinstance(m, dict) and all(isinstance(k, bytes) for k in m.keys()) and all(isinstance(v, int) for v in m.values()), 'arg m wrong type' + assert isinstance(key, bytes), 'arg key wrong type' + cdef libcpp_unordered_map[libcpp_string, int] * v0 = new libcpp_unordered_map[libcpp_string, int]() + for _loop_key_m, _loop_value_m in m.items(): + deref(v0)[ _loop_key_m ] = _loop_value_m + + cdef bool _r = self.inst.get().hasKeyUnorderedMap(deref(v0), (key)) + replace = dict() + cdef libcpp_unordered_map[libcpp_string, int].iterator it_m = v0.begin() + while it_m != v0.end(): + replace[ deref(it_m).first] = deref(it_m).second + inc(it_m) + m.clear() + m.update(replace) + del v0 + py_result = _r + return py_result + + def getValueUnorderedMap(self, dict m , bytes key ): + """ + getValueUnorderedMap(self, m: Dict[bytes, int] , key: bytes ) -> int + """ + assert isinstance(m, dict) and all(isinstance(k, bytes) for k in m.keys()) and all(isinstance(v, int) for v in m.values()), 'arg m wrong type' + assert isinstance(key, bytes), 'arg key wrong type' + cdef libcpp_unordered_map[libcpp_string, int] * v0 = new libcpp_unordered_map[libcpp_string, int]() + for _loop_key_m, _loop_value_m in m.items(): + deref(v0)[ _loop_key_m ] = _loop_value_m + + cdef int _r = self.inst.get().getValueUnorderedMap(deref(v0), (key)) + replace = dict() + cdef libcpp_unordered_map[libcpp_string, int].iterator it_m = v0.begin() + while it_m != v0.end(): + replace[ deref(it_m).first] = deref(it_m).second + inc(it_m) + m.clear() + m.update(replace) + del v0 + py_result = _r + return py_result + def getUnorderedSet(self): """ getUnorderedSet(self) -> Set[int] @@ -110,6 +176,72 @@ cdef class _NewSTLTest: py_result = _r return py_result + def hasValueUnorderedSet(self, set s , int value ): + """ + hasValueUnorderedSet(self, s: Set[int] , value: int ) -> bool + """ + assert isinstance(s, set) and all(isinstance(li, int) for li in s), 'arg s wrong type' + assert isinstance(value, int), 'arg value wrong type' + cdef libcpp_unordered_set[int] * v0 = new libcpp_unordered_set[int]() + for item0 in s: + v0.insert( item0) + + cdef bool _r = self.inst.get().hasValueUnorderedSet(deref(v0), (value)) + replace = set() + cdef libcpp_unordered_set[int].iterator it_s = v0.begin() + while it_s != v0.end(): + replace.add( deref(it_s)) + inc(it_s) + s.clear() + s.update(replace) + del v0 + py_result = _r + return py_result + + def countUnorderedSet(self, set s , int value ): + """ + countUnorderedSet(self, s: Set[int] , value: int ) -> int + """ + assert isinstance(s, set) and all(isinstance(li, int) for li in s), 'arg s wrong type' + assert isinstance(value, int), 'arg value wrong type' + cdef libcpp_unordered_set[int] * v0 = new libcpp_unordered_set[int]() + for item0 in s: + v0.insert( item0) + + cdef size_t _r = self.inst.get().countUnorderedSet(deref(v0), (value)) + replace = set() + cdef libcpp_unordered_set[int].iterator it_s = v0.begin() + while it_s != v0.end(): + replace.add( deref(it_s)) + inc(it_s) + s.clear() + s.update(replace) + del v0 + py_result = _r + return py_result + + def findUnorderedSet(self, set s , int value ): + """ + findUnorderedSet(self, s: Set[int] , value: int ) -> int + """ + assert isinstance(s, set) and all(isinstance(li, int) for li in s), 'arg s wrong type' + assert isinstance(value, int), 'arg value wrong type' + cdef libcpp_unordered_set[int] * v0 = new libcpp_unordered_set[int]() + for item0 in s: + v0.insert( item0) + + cdef int _r = self.inst.get().findUnorderedSet(deref(v0), (value)) + replace = set() + cdef libcpp_unordered_set[int].iterator it_s = v0.begin() + while it_s != v0.end(): + replace.add( deref(it_s)) + inc(it_s) + s.clear() + s.update(replace) + del v0 + py_result = _r + return py_result + def getDeque(self): """ getDeque(self) -> List[int] @@ -211,9 +343,9 @@ cdef class _NewSTLTest: py_result = _r return py_result - def getStringViewLength(self, sv ): + def getStringViewLength(self, bytes sv ): """ - getStringViewLength(self, sv: Union[bytes, str] ) -> int + getStringViewLength(self, sv: bytes ) -> int """ assert isinstance(sv, (bytes, str)), 'arg sv wrong type' cdef bytes v0 @@ -225,9 +357,9 @@ cdef class _NewSTLTest: py_result = _r return py_result - def stringViewToString(self, sv ): + def stringViewToString(self, bytes sv ): """ - stringViewToString(self, sv: Union[bytes, str] ) -> bytes + stringViewToString(self, sv: bytes ) -> bytes """ assert isinstance(sv, (bytes, str)), 'arg sv wrong type' cdef bytes v0 diff --git a/tests/test_files/new_stl_test.hpp b/tests/test_files/new_stl_test.hpp deleted file mode 100644 index 38f9d6b..0000000 --- a/tests/test_files/new_stl_test.hpp +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Test file for new STL container support in autowrap. - * Tests: unordered_map, unordered_set, deque, list, optional, string_view - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -class NewSTLTest { -public: - NewSTLTest() {} - - // ========================================================================= - // std::unordered_map tests - // ========================================================================= - - std::unordered_map getUnorderedMap() { - std::unordered_map m; - m["one"] = 1; - m["two"] = 2; - m["three"] = 3; - return m; - } - - int sumUnorderedMapValues(const std::unordered_map& m) { - int sum = 0; - for (const auto& p : m) { - sum += p.second; - } - return sum; - } - - // ========================================================================= - // std::unordered_set tests - // ========================================================================= - - std::unordered_set getUnorderedSet() { - return {1, 2, 3, 4, 5}; - } - - int sumUnorderedSet(const std::unordered_set& s) { - int sum = 0; - for (int v : s) { - sum += v; - } - return sum; - } - - // ========================================================================= - // std::deque tests - // ========================================================================= - - std::deque getDeque() { - return {10, 20, 30, 40}; - } - - int sumDeque(const std::deque& d) { - int sum = 0; - for (int v : d) { - sum += v; - } - return sum; - } - - // Mutable reference test - doubles each element - void doubleDequeElements(std::deque& d) { - for (size_t i = 0; i < d.size(); i++) { - d[i] *= 2; - } - } - - // ========================================================================= - // std::list tests - // ========================================================================= - - std::list getList() { - return {1.1, 2.2, 3.3}; - } - - double sumList(const std::list& l) { - double sum = 0; - for (double v : l) { - sum += v; - } - return sum; - } - - // Mutable reference test - doubles each element - void doubleListElements(std::list& l) { - for (auto& v : l) { - v *= 2; - } - } - - // ========================================================================= - // std::optional tests - // ========================================================================= - - std::optional getOptionalValue(bool hasValue) { - if (hasValue) { - return 42; - } - return std::nullopt; - } - - int unwrapOptional(std::optional opt) { - return opt.value_or(-1); - } - - // ========================================================================= - // std::string_view tests - // ========================================================================= - - size_t getStringViewLength(std::string_view sv) { - return sv.size(); - } - - std::string stringViewToString(std::string_view sv) { - return std::string(sv); - } -}; diff --git a/tests/test_files/new_stl_test.pxd b/tests/test_files/new_stl_test.pxd deleted file mode 100644 index 7155fa8..0000000 --- a/tests/test_files/new_stl_test.pxd +++ /dev/null @@ -1,58 +0,0 @@ -# cython: language_level=3 -# -# ============================================================================ -# Example PXD file demonstrating new STL container support in autowrap. -# -# This file shows how to declare C++ functions using the following C++17 -# STL containers: -# - std::unordered_map -> Python dict -# - std::unordered_set -> Python set -# - std::deque -> Python list -# - std::list -> Python list -# - std::optional -> Python T | None -# - std::string_view -> Python bytes/str -# -# Required imports for each container type are shown below. -# Note: Requires C++17 compilation (-std=c++17) for optional and string_view. -# ============================================================================ - -from libc.stddef cimport size_t -from libcpp cimport bool -from libcpp.string cimport string as libcpp_string -from libcpp.unordered_map cimport unordered_map as libcpp_unordered_map -from libcpp.unordered_set cimport unordered_set as libcpp_unordered_set -from libcpp.deque cimport deque as libcpp_deque -from libcpp.list cimport list as libcpp_list -from libcpp.optional cimport optional as libcpp_optional -from libcpp.string_view cimport string_view as libcpp_string_view - -cdef extern from "new_stl_test.hpp": - - cdef cppclass _NewSTLTest "NewSTLTest": - _NewSTLTest() - - # unordered_map tests - libcpp_unordered_map[libcpp_string, int] getUnorderedMap() - int sumUnorderedMapValues(libcpp_unordered_map[libcpp_string, int]& m) - - # unordered_set tests - libcpp_unordered_set[int] getUnorderedSet() - int sumUnorderedSet(libcpp_unordered_set[int]& s) - - # deque tests - libcpp_deque[int] getDeque() - int sumDeque(libcpp_deque[int]& d) - void doubleDequeElements(libcpp_deque[int]& d) - - # list tests - libcpp_list[double] getList() - double sumList(libcpp_list[double]& l) - void doubleListElements(libcpp_list[double]& l) - - # optional tests - libcpp_optional[int] getOptionalValue(bool hasValue) - int unwrapOptional(libcpp_optional[int] opt) - - # string_view tests - size_t getStringViewLength(libcpp_string_view sv) - libcpp_string stringViewToString(libcpp_string_view sv) diff --git a/tests/test_files/wrapped_container_test.hpp b/tests/test_files/wrapped_container_test.hpp index 2da210c..3de4b87 100644 --- a/tests/test_files/wrapped_container_test.hpp +++ b/tests/test_files/wrapped_container_test.hpp @@ -36,12 +36,16 @@ class Item { std::string getName() const { return name_; } }; -// Hash function for Item - required for unordered_map +// Hash function for Item - required for unordered_map/unordered_set +// Uses both value_ and name_ members for hash computation namespace std { template<> struct hash { size_t operator()(const Item& item) const { - return std::hash()(item.value_); + // Combine hashes of both members using XOR and bit shifting + size_t h1 = std::hash()(item.value_); + size_t h2 = std::hash()(item.name_); + return h1 ^ (h2 << 1); // Combine hashes } }; } @@ -115,6 +119,20 @@ class WrappedContainerTest { return result; } + // Lookup Item by key - returns Item value_ or -1 if not found + int lookupMapIntToItem(const std::map& m, int key) { + auto it = m.find(key); + if (it != m.end()) { + return it->second.value_; + } + return -1; + } + + // Check if key exists in map + bool hasKeyMapIntToItem(const std::map& m, int key) { + return m.count(key) > 0; + } + // ======================================== // MAP WITH WRAPPED CLASS AS KEY // ======================================== @@ -230,6 +248,20 @@ class WrappedContainerTest { return result; } + // Lookup Item by key in unordered_map - returns Item value_ or -1 if not found + int lookupUnorderedMapIntToItem(const std::unordered_map& m, int key) { + auto it = m.find(key); + if (it != m.end()) { + return it->second.value_; + } + return -1; + } + + // Check if key exists in unordered_map + bool hasKeyUnorderedMapIntToItem(const std::unordered_map& m, int key) { + return m.count(key) > 0; + } + // ======================================== // UNORDERED_MAP WITH WRAPPED CLASS AS BOTH KEY AND VALUE // ======================================== @@ -325,6 +357,20 @@ class WrappedContainerTest { return result; } + // Check if Item exists in unordered_set (membership test using hash) + bool hasItemUnorderedSet(const std::unordered_set& items, const Item& item) { + return items.count(item) > 0; + } + + // Find Item and return its value_ or -1 if not found + int findItemUnorderedSet(const std::unordered_set& items, const Item& item) { + auto it = items.find(item); + if (it != items.end()) { + return it->value_; + } + return -1; + } + // ======================================== // NESTED CONTAINERS: list> // ======================================== diff --git a/tests/test_files/wrapped_container_test.pxd b/tests/test_files/wrapped_container_test.pxd index 4d2021c..47a6896 100644 --- a/tests/test_files/wrapped_container_test.pxd +++ b/tests/test_files/wrapped_container_test.pxd @@ -51,6 +51,8 @@ cdef extern from "wrapped_container_test.hpp": # ======================================== int sumMapValues(libcpp_map[int, Item]& m) libcpp_map[int, Item] createMapIntToItem(int count) + int lookupMapIntToItem(libcpp_map[int, Item]& m, int key) + bool hasKeyMapIntToItem(libcpp_map[int, Item]& m, int key) # ======================================== # MAP WITH WRAPPED CLASS AS KEY @@ -89,6 +91,8 @@ cdef extern from "wrapped_container_test.hpp": # ======================================== int sumUnorderedMapValues(libcpp_unordered_map[int, Item]& m) libcpp_unordered_map[int, Item] createUnorderedMapIntToItem(int count) + int lookupUnorderedMapIntToItem(libcpp_unordered_map[int, Item]& m, int key) + bool hasKeyUnorderedMapIntToItem(libcpp_unordered_map[int, Item]& m, int key) # ======================================== # UNORDERED_MAP WITH WRAPPED CLASS AS BOTH KEY AND VALUE @@ -118,6 +122,8 @@ cdef extern from "wrapped_container_test.hpp": # ======================================== int sumUnorderedSetItems(libcpp_unordered_set[Item]& items) libcpp_unordered_set[Item] createUnorderedSetItems(int count) + bool hasItemUnorderedSet(libcpp_unordered_set[Item]& items, Item& item) + int findItemUnorderedSet(libcpp_unordered_set[Item]& items, Item& item) # ======================================== # NESTED CONTAINERS: list> diff --git a/tests/test_wrapped_containers.py b/tests/test_wrapped_containers.py index b4dba74..6dc759b 100644 --- a/tests/test_wrapped_containers.py +++ b/tests/test_wrapped_containers.py @@ -144,6 +144,38 @@ def test_map_int_to_item_create(self, wrapped_container_module): assert result[1].value_ == 10 assert result[2].value_ == 20 + def test_map_int_to_item_lookup(self, wrapped_container_module): + """Test looking up Item by key in map.""" + m = wrapped_container_module + t = m.WrappedContainerTest() + + item1 = m.Item(100) + item2 = m.Item(200) + item3 = m.Item(300) + map_data = {1: item1, 5: item2, 10: item3} + + # Lookup existing keys + assert t.lookupMapIntToItem(map_data, 1) == 100 + assert t.lookupMapIntToItem(map_data, 5) == 200 + assert t.lookupMapIntToItem(map_data, 10) == 300 + + # Lookup missing key + assert t.lookupMapIntToItem(map_data, 999) == -1 + + def test_map_int_to_item_has_key(self, wrapped_container_module): + """Test checking if key exists in map.""" + m = wrapped_container_module + t = m.WrappedContainerTest() + + item1 = m.Item(100) + item2 = m.Item(200) + map_data = {1: item1, 2: item2} + + assert t.hasKeyMapIntToItem(map_data, 1) is True + assert t.hasKeyMapIntToItem(map_data, 2) is True + assert t.hasKeyMapIntToItem(map_data, 3) is False + assert t.hasKeyMapIntToItem(map_data, 999) is False + class TestMapWithWrappedClassKey: """Tests for map.""" @@ -306,6 +338,38 @@ def test_unordered_map_int_to_item_create(self, wrapped_container_module): assert result[1].value_ == 10 assert result[2].value_ == 20 + def test_unordered_map_int_to_item_lookup(self, wrapped_container_module): + """Test looking up Item by key in unordered_map (hash-based O(1) lookup).""" + m = wrapped_container_module + t = m.WrappedContainerTest() + + item1 = m.Item(100) + item2 = m.Item(200) + item3 = m.Item(300) + map_data = {1: item1, 5: item2, 10: item3} + + # Lookup existing keys - tests hash-based access + assert t.lookupUnorderedMapIntToItem(map_data, 1) == 100 + assert t.lookupUnorderedMapIntToItem(map_data, 5) == 200 + assert t.lookupUnorderedMapIntToItem(map_data, 10) == 300 + + # Lookup missing key + assert t.lookupUnorderedMapIntToItem(map_data, 999) == -1 + + def test_unordered_map_int_to_item_has_key(self, wrapped_container_module): + """Test checking if key exists in unordered_map (hash-based O(1) lookup).""" + m = wrapped_container_module + t = m.WrappedContainerTest() + + item1 = m.Item(100) + item2 = m.Item(200) + map_data = {1: item1, 2: item2} + + assert t.hasKeyUnorderedMapIntToItem(map_data, 1) is True + assert t.hasKeyUnorderedMapIntToItem(map_data, 2) is True + assert t.hasKeyUnorderedMapIntToItem(map_data, 3) is False + assert t.hasKeyUnorderedMapIntToItem(map_data, 999) is False + class TestUnorderedMapWithWrappedClassBoth: """Tests for unordered_map - wrapped class as both key and value.""" @@ -436,6 +500,85 @@ def test_unordered_set_items_create(self, wrapped_container_module): values = sorted([item.value_ for item in items]) assert values == [0, 10, 20] + def test_unordered_set_has_item(self, wrapped_container_module): + """Test checking if Item exists in unordered_set (hash-based O(1) membership test).""" + m = wrapped_container_module + t = m.WrappedContainerTest() + + item1 = m.Item(10) + item2 = m.Item(20) + item3 = m.Item(30) + items = {item1, item2, item3} + + # Items that should be found (tests hash function) + search_item1 = m.Item(10) + search_item2 = m.Item(20) + assert t.hasItemUnorderedSet(items, search_item1) is True + assert t.hasItemUnorderedSet(items, search_item2) is True + + # Item that should NOT be found + missing_item = m.Item(999) + assert t.hasItemUnorderedSet(items, missing_item) is False + + def test_unordered_set_find_item(self, wrapped_container_module): + """Test finding Item in unordered_set and returning its value (hash-based O(1) lookup).""" + m = wrapped_container_module + t = m.WrappedContainerTest() + + item1 = m.Item(10) + item2 = m.Item(20) + item3 = m.Item(30) + items = {item1, item2, item3} + + # Find existing items - tests hash-based lookup + search_item1 = m.Item(10) + search_item2 = m.Item(30) + assert t.findItemUnorderedSet(items, search_item1) == 10 + assert t.findItemUnorderedSet(items, search_item2) == 30 + + # Find missing item should return -1 + missing_item = m.Item(999) + assert t.findItemUnorderedSet(items, missing_item) == -1 + + def test_unordered_set_two_member_hash(self, wrapped_container_module): + """Test that hash function uses both value_ AND name_ members. + + This verifies that items with the same value_ but different name_ + are treated as different items (different hash + equality check). + """ + m = wrapped_container_module + t = m.WrappedContainerTest() + + # Create items with same value but different names + item_alice = m.Item(100, b"alice") + item_bob = m.Item(100, b"bob") + item_charlie = m.Item(200, b"charlie") + + items = {item_alice, item_bob, item_charlie} + + # All three should be in the set (even though two have same value_) + assert len(items) == 3, "Set should have 3 items despite same value_" + + # Search for exact match (value_ AND name_ must match) + search_alice = m.Item(100, b"alice") + search_bob = m.Item(100, b"bob") + + assert t.hasItemUnorderedSet(items, search_alice) is True, \ + "Should find alice (100, 'alice')" + assert t.hasItemUnorderedSet(items, search_bob) is True, \ + "Should find bob (100, 'bob')" + + # Search with wrong name should NOT find item + # Same value_ but different name_ = different hash/different item + wrong_name = m.Item(100, b"eve") + assert t.hasItemUnorderedSet(items, wrong_name) is False, \ + "Should NOT find (100, 'eve') - name doesn't match" + + # Search with wrong value should NOT find item + wrong_value = m.Item(999, b"alice") + assert t.hasItemUnorderedSet(items, wrong_value) is False, \ + "Should NOT find (999, 'alice') - value doesn't match" + class TestNestedListOfVectors: """Tests for list> - nested containers with primitives."""