Skip to content

Commit 16c55ea

Browse files
committed
Add Python-native dict/set lookup tests with wrapped class keys
- Add wrap-hash annotation and operator==/!= to Item class for Python hashability - Add getHashValue() method that combines both value_ and name_ for proper hashing - Update tests to use Python d[key] and 'in' operator instead of iteration - Remove unnecessary C++ lookup functions (lookupMapIntToItem, hasKeyMapIntToItem, etc.) - Tests now verify that wrapped classes work correctly as Python dict keys This enables Python code like: result = t.createMapItemToInt(3) item = m.Item(1) assert result[item] == 10 # Direct Python dict lookup!
1 parent a72596f commit 16c55ea

File tree

3 files changed

+63
-167
lines changed

3 files changed

+63
-167
lines changed

tests/test_files/wrapped_container_test.hpp

Lines changed: 10 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,20 @@ class Item {
3030
bool operator==(const Item& other) const {
3131
return value_ == other.value_ && name_ == other.name_;
3232
}
33+
bool operator!=(const Item& other) const {
34+
return !(*this == other);
35+
}
3336

3437
int getValue() const { return value_; }
3538
void setValue(int v) { value_ = v; }
3639
std::string getName() const { return name_; }
40+
41+
// For Python __hash__ - returns combined hash of both members
42+
size_t getHashValue() const {
43+
size_t h1 = std::hash<int>()(value_);
44+
size_t h2 = std::hash<std::string>()(name_);
45+
return h1 ^ (h2 << 1);
46+
}
3747
};
3848

3949
// Hash function for Item - required for unordered_map/unordered_set
@@ -119,20 +129,6 @@ class WrappedContainerTest {
119129
return result;
120130
}
121131

122-
// Lookup Item by key - returns Item value_ or -1 if not found
123-
int lookupMapIntToItem(const std::map<int, Item>& m, int key) {
124-
auto it = m.find(key);
125-
if (it != m.end()) {
126-
return it->second.value_;
127-
}
128-
return -1;
129-
}
130-
131-
// Check if key exists in map
132-
bool hasKeyMapIntToItem(const std::map<int, Item>& m, int key) {
133-
return m.count(key) > 0;
134-
}
135-
136132
// ========================================
137133
// MAP WITH WRAPPED CLASS AS KEY
138134
// ========================================
@@ -248,20 +244,6 @@ class WrappedContainerTest {
248244
return result;
249245
}
250246

251-
// Lookup Item by key in unordered_map - returns Item value_ or -1 if not found
252-
int lookupUnorderedMapIntToItem(const std::unordered_map<int, Item>& m, int key) {
253-
auto it = m.find(key);
254-
if (it != m.end()) {
255-
return it->second.value_;
256-
}
257-
return -1;
258-
}
259-
260-
// Check if key exists in unordered_map
261-
bool hasKeyUnorderedMapIntToItem(const std::unordered_map<int, Item>& m, int key) {
262-
return m.count(key) > 0;
263-
}
264-
265247
// ========================================
266248
// UNORDERED_MAP WITH WRAPPED CLASS AS BOTH KEY AND VALUE
267249
// ========================================
@@ -357,20 +339,6 @@ class WrappedContainerTest {
357339
return result;
358340
}
359341

360-
// Check if Item exists in unordered_set (membership test using hash)
361-
bool hasItemUnorderedSet(const std::unordered_set<Item>& items, const Item& item) {
362-
return items.count(item) > 0;
363-
}
364-
365-
// Find Item and return its value_ or -1 if not found
366-
int findItemUnorderedSet(const std::unordered_set<Item>& items, const Item& item) {
367-
auto it = items.find(item);
368-
if (it != items.end()) {
369-
return it->value_;
370-
}
371-
return -1;
372-
}
373-
374342
// ========================================
375343
// NESTED CONTAINERS: list<vector<int>>
376344
// ========================================

tests/test_files/wrapped_container_test.pxd

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ from libcpp cimport bool
1313
cdef extern from "wrapped_container_test.hpp":
1414

1515
# A simple wrapped class used in containers
16+
# wrap-hash and operator== enable Python dict/set lookups with d[item]
1617
cdef cppclass Item:
18+
# wrap-hash:
19+
# getHashValue()
20+
1721
int value_
1822
libcpp_string name_
1923

@@ -22,9 +26,13 @@ cdef extern from "wrapped_container_test.hpp":
2226
Item(int v, libcpp_string n)
2327
Item(Item&)
2428

29+
bool operator==(Item)
30+
bool operator!=(Item)
31+
2532
int getValue()
2633
void setValue(int v)
2734
libcpp_string getName()
35+
size_t getHashValue()
2836

2937
# Test class with methods that use containers of wrapped classes
3038
cdef cppclass WrappedContainerTest:
@@ -51,8 +59,6 @@ cdef extern from "wrapped_container_test.hpp":
5159
# ========================================
5260
int sumMapValues(libcpp_map[int, Item]& m)
5361
libcpp_map[int, Item] createMapIntToItem(int count)
54-
int lookupMapIntToItem(libcpp_map[int, Item]& m, int key)
55-
bool hasKeyMapIntToItem(libcpp_map[int, Item]& m, int key)
5662

5763
# ========================================
5864
# MAP WITH WRAPPED CLASS AS KEY
@@ -91,8 +97,6 @@ cdef extern from "wrapped_container_test.hpp":
9197
# ========================================
9298
int sumUnorderedMapValues(libcpp_unordered_map[int, Item]& m)
9399
libcpp_unordered_map[int, Item] createUnorderedMapIntToItem(int count)
94-
int lookupUnorderedMapIntToItem(libcpp_unordered_map[int, Item]& m, int key)
95-
bool hasKeyUnorderedMapIntToItem(libcpp_unordered_map[int, Item]& m, int key)
96100

97101
# ========================================
98102
# UNORDERED_MAP WITH WRAPPED CLASS AS BOTH KEY AND VALUE
@@ -122,8 +126,6 @@ cdef extern from "wrapped_container_test.hpp":
122126
# ========================================
123127
int sumUnorderedSetItems(libcpp_unordered_set[Item]& items)
124128
libcpp_unordered_set[Item] createUnorderedSetItems(int count)
125-
bool hasItemUnorderedSet(libcpp_unordered_set[Item]& items, Item& item)
126-
int findItemUnorderedSet(libcpp_unordered_set[Item]& items, Item& item)
127129

128130
# ========================================
129131
# NESTED CONTAINERS: list<vector<int>>

tests/test_wrapped_containers.py

Lines changed: 45 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -144,37 +144,6 @@ def test_map_int_to_item_create(self, wrapped_container_module):
144144
assert result[1].value_ == 10
145145
assert result[2].value_ == 20
146146

147-
def test_map_int_to_item_lookup(self, wrapped_container_module):
148-
"""Test looking up Item by key in map."""
149-
m = wrapped_container_module
150-
t = m.WrappedContainerTest()
151-
152-
item1 = m.Item(100)
153-
item2 = m.Item(200)
154-
item3 = m.Item(300)
155-
map_data = {1: item1, 5: item2, 10: item3}
156-
157-
# Lookup existing keys
158-
assert t.lookupMapIntToItem(map_data, 1) == 100
159-
assert t.lookupMapIntToItem(map_data, 5) == 200
160-
assert t.lookupMapIntToItem(map_data, 10) == 300
161-
162-
# Lookup missing key
163-
assert t.lookupMapIntToItem(map_data, 999) == -1
164-
165-
def test_map_int_to_item_has_key(self, wrapped_container_module):
166-
"""Test checking if key exists in map."""
167-
m = wrapped_container_module
168-
t = m.WrappedContainerTest()
169-
170-
item1 = m.Item(100)
171-
item2 = m.Item(200)
172-
map_data = {1: item1, 2: item2}
173-
174-
assert t.hasKeyMapIntToItem(map_data, 1) is True
175-
assert t.hasKeyMapIntToItem(map_data, 2) is True
176-
assert t.hasKeyMapIntToItem(map_data, 3) is False
177-
assert t.hasKeyMapIntToItem(map_data, 999) is False
178147

179148

180149
class TestMapWithWrappedClassKey:
@@ -193,16 +162,25 @@ def test_map_item_to_int_sum(self, wrapped_container_module):
193162
assert result == 30 # 10 + 20 (sum of keys)
194163

195164
def test_map_item_to_int_create(self, wrapped_container_module):
196-
"""Test returning map<Item, int>."""
165+
"""Test returning map<Item, int> and using Python d[key] lookup."""
197166
m = wrapped_container_module
198167
t = m.WrappedContainerTest()
199168

200169
result = t.createMapItemToInt(3)
201170
assert len(result) == 3
202-
# Values should be 0, 10, 20 for keys with value_ 0, 1, 2
203-
keys_values = [(k.value_, v) for k, v in result.items()]
204-
keys_values.sort()
205-
assert keys_values == [(0, 0), (1, 10), (2, 20)]
171+
172+
# Direct Python dict lookup with wrapped class key - NOT iteration!
173+
key0 = m.Item(0)
174+
key1 = m.Item(1)
175+
key2 = m.Item(2)
176+
assert result[key0] == 0
177+
assert result[key1] == 10
178+
assert result[key2] == 20
179+
180+
# Test 'in' operator
181+
assert key1 in result
182+
missing = m.Item(999)
183+
assert missing not in result
206184

207185

208186
class TestNestedVectorOfWrappedClass:
@@ -300,16 +278,26 @@ def test_unordered_map_item_to_int_sum(self, wrapped_container_module):
300278
assert result == 30 # 10 + 20 (sum of keys)
301279

302280
def test_unordered_map_item_to_int_create(self, wrapped_container_module):
303-
"""Test returning unordered_map<Item, int>."""
281+
"""Test returning unordered_map<Item, int> and using Python d[key] lookup."""
304282
m = wrapped_container_module
305283
t = m.WrappedContainerTest()
306284

307285
result = t.createUnorderedMapItemToInt(3)
308286
assert len(result) == 3
309-
# Values should be 0, 10, 20 for keys with value_ 0, 1, 2
310-
keys_values = [(k.value_, v) for k, v in result.items()]
311-
keys_values.sort()
312-
assert keys_values == [(0, 0), (1, 10), (2, 20)]
287+
288+
# Direct Python dict lookup with wrapped class key - NOT iteration!
289+
# This tests that the wrapped Item class has proper __hash__ and __eq__
290+
key0 = m.Item(0)
291+
key1 = m.Item(1)
292+
key2 = m.Item(2)
293+
assert result[key0] == 0
294+
assert result[key1] == 10
295+
assert result[key2] == 20
296+
297+
# Test 'in' operator (uses hash)
298+
assert key1 in result
299+
missing = m.Item(999)
300+
assert missing not in result
313301

314302

315303
class TestUnorderedMapWithWrappedClassValue:
@@ -338,37 +326,6 @@ def test_unordered_map_int_to_item_create(self, wrapped_container_module):
338326
assert result[1].value_ == 10
339327
assert result[2].value_ == 20
340328

341-
def test_unordered_map_int_to_item_lookup(self, wrapped_container_module):
342-
"""Test looking up Item by key in unordered_map (hash-based O(1) lookup)."""
343-
m = wrapped_container_module
344-
t = m.WrappedContainerTest()
345-
346-
item1 = m.Item(100)
347-
item2 = m.Item(200)
348-
item3 = m.Item(300)
349-
map_data = {1: item1, 5: item2, 10: item3}
350-
351-
# Lookup existing keys - tests hash-based access
352-
assert t.lookupUnorderedMapIntToItem(map_data, 1) == 100
353-
assert t.lookupUnorderedMapIntToItem(map_data, 5) == 200
354-
assert t.lookupUnorderedMapIntToItem(map_data, 10) == 300
355-
356-
# Lookup missing key
357-
assert t.lookupUnorderedMapIntToItem(map_data, 999) == -1
358-
359-
def test_unordered_map_int_to_item_has_key(self, wrapped_container_module):
360-
"""Test checking if key exists in unordered_map (hash-based O(1) lookup)."""
361-
m = wrapped_container_module
362-
t = m.WrappedContainerTest()
363-
364-
item1 = m.Item(100)
365-
item2 = m.Item(200)
366-
map_data = {1: item1, 2: item2}
367-
368-
assert t.hasKeyUnorderedMapIntToItem(map_data, 1) is True
369-
assert t.hasKeyUnorderedMapIntToItem(map_data, 2) is True
370-
assert t.hasKeyUnorderedMapIntToItem(map_data, 3) is False
371-
assert t.hasKeyUnorderedMapIntToItem(map_data, 999) is False
372329

373330

374331
class TestUnorderedMapWithWrappedClassBoth:
@@ -491,63 +448,34 @@ def test_unordered_set_items_sum(self, wrapped_container_module):
491448
assert result == 60
492449

493450
def test_unordered_set_items_create(self, wrapped_container_module):
494-
"""Test returning unordered_set<Item>."""
451+
"""Test returning unordered_set<Item> and using Python 'in' operator."""
495452
m = wrapped_container_module
496453
t = m.WrappedContainerTest()
497454

498455
items = t.createUnorderedSetItems(3)
499456
assert len(items) == 3
500-
values = sorted([item.value_ for item in items])
501-
assert values == [0, 10, 20]
502-
503-
def test_unordered_set_has_item(self, wrapped_container_module):
504-
"""Test checking if Item exists in unordered_set (hash-based O(1) membership test)."""
505-
m = wrapped_container_module
506-
t = m.WrappedContainerTest()
507457

458+
# Direct Python 'in' operator with wrapped class - NOT iteration!
459+
# This tests that the wrapped Item class has proper __hash__ and __eq__
460+
item0 = m.Item(0)
508461
item1 = m.Item(10)
509462
item2 = m.Item(20)
510-
item3 = m.Item(30)
511-
items = {item1, item2, item3}
512-
513-
# Items that should be found (tests hash function)
514-
search_item1 = m.Item(10)
515-
search_item2 = m.Item(20)
516-
assert t.hasItemUnorderedSet(items, search_item1) is True
517-
assert t.hasItemUnorderedSet(items, search_item2) is True
518-
519-
# Item that should NOT be found
520-
missing_item = m.Item(999)
521-
assert t.hasItemUnorderedSet(items, missing_item) is False
522-
523-
def test_unordered_set_find_item(self, wrapped_container_module):
524-
"""Test finding Item in unordered_set and returning its value (hash-based O(1) lookup)."""
525-
m = wrapped_container_module
526-
t = m.WrappedContainerTest()
463+
assert item0 in items
464+
assert item1 in items
465+
assert item2 in items
527466

528-
item1 = m.Item(10)
529-
item2 = m.Item(20)
530-
item3 = m.Item(30)
531-
items = {item1, item2, item3}
532-
533-
# Find existing items - tests hash-based lookup
534-
search_item1 = m.Item(10)
535-
search_item2 = m.Item(30)
536-
assert t.findItemUnorderedSet(items, search_item1) == 10
537-
assert t.findItemUnorderedSet(items, search_item2) == 30
538-
539-
# Find missing item should return -1
540-
missing_item = m.Item(999)
541-
assert t.findItemUnorderedSet(items, missing_item) == -1
467+
# Test missing item
468+
missing = m.Item(999)
469+
assert missing not in items
542470

543471
def test_unordered_set_two_member_hash(self, wrapped_container_module):
544472
"""Test that hash function uses both value_ AND name_ members.
545473
546474
This verifies that items with the same value_ but different name_
547475
are treated as different items (different hash + equality check).
476+
Uses Python's native 'in' operator - no C++ lookup functions.
548477
"""
549478
m = wrapped_container_module
550-
t = m.WrappedContainerTest()
551479

552480
# Create items with same value but different names
553481
item_alice = m.Item(100, b"alice")
@@ -559,24 +487,22 @@ def test_unordered_set_two_member_hash(self, wrapped_container_module):
559487
# All three should be in the set (even though two have same value_)
560488
assert len(items) == 3, "Set should have 3 items despite same value_"
561489

562-
# Search for exact match (value_ AND name_ must match)
490+
# Search for exact match using Python 'in' (value_ AND name_ must match)
563491
search_alice = m.Item(100, b"alice")
564492
search_bob = m.Item(100, b"bob")
565493

566-
assert t.hasItemUnorderedSet(items, search_alice) is True, \
567-
"Should find alice (100, 'alice')"
568-
assert t.hasItemUnorderedSet(items, search_bob) is True, \
569-
"Should find bob (100, 'bob')"
494+
assert search_alice in items, "Should find alice (100, 'alice')"
495+
assert search_bob in items, "Should find bob (100, 'bob')"
570496

571497
# Search with wrong name should NOT find item
572498
# Same value_ but different name_ = different hash/different item
573499
wrong_name = m.Item(100, b"eve")
574-
assert t.hasItemUnorderedSet(items, wrong_name) is False, \
500+
assert wrong_name not in items, \
575501
"Should NOT find (100, 'eve') - name doesn't match"
576502

577503
# Search with wrong value should NOT find item
578504
wrong_value = m.Item(999, b"alice")
579-
assert t.hasItemUnorderedSet(items, wrong_value) is False, \
505+
assert wrong_value not in items, \
580506
"Should NOT find (999, 'alice') - value doesn't match"
581507

582508

0 commit comments

Comments
 (0)