mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Add LinkedHashMap and LinkedHashSet implementations (#2324)
* Add LinkedHashMap and LinkedHashSet implementations Add two new ordered collection types to std::collections: - LinkedHashMap: insertion-order preserving hash map - LinkedHashSet: insertion-order preserving hash set
This commit is contained in:
218
test/unit/stdlib/collections/linked_map.c3
Normal file
218
test/unit/stdlib/collections/linked_map.c3
Normal file
@@ -0,0 +1,218 @@
|
||||
module linked_map_test @test;
|
||||
import std::collections::list;
|
||||
import std::collections::map;
|
||||
import std::sort;
|
||||
import std::io;
|
||||
|
||||
alias TestLinkedHashMap = LinkedHashMap{String, usz};
|
||||
|
||||
struct MapTest
|
||||
{
|
||||
String key;
|
||||
usz value;
|
||||
}
|
||||
alias List = List{MapTest};
|
||||
|
||||
fn void linked_map_basic()
|
||||
{
|
||||
TestLinkedHashMap m;
|
||||
assert(!m.is_initialized());
|
||||
m.tinit();
|
||||
assert(m.is_initialized());
|
||||
assert(m.is_empty());
|
||||
assert(m.len() == 0);
|
||||
|
||||
m.set("a", 1);
|
||||
assert(!m.is_empty());
|
||||
assert(m.len() == 1);
|
||||
m.remove("a");
|
||||
assert(m.is_empty());
|
||||
|
||||
MapTest[] tcases = { {"key1", 0}, {"key2", 1}, {"key3", 2} };
|
||||
foreach (tc : tcases)
|
||||
{
|
||||
m.set(tc.key, tc.value);
|
||||
}
|
||||
assert(m.len() == tcases.len);
|
||||
foreach (tc : tcases)
|
||||
{
|
||||
usz v = m.get(tc.key)!!;
|
||||
assert(tc.value == v);
|
||||
}
|
||||
}
|
||||
|
||||
fn void linked_map_insertion_order()
|
||||
{
|
||||
TestLinkedHashMap m;
|
||||
m.tinit();
|
||||
|
||||
String[] keys = { "first", "second", "third", "fourth" };
|
||||
foreach (i, key : keys)
|
||||
{
|
||||
m.set(key, i);
|
||||
}
|
||||
|
||||
usz index = 0;
|
||||
m.@each(; String key, usz value)
|
||||
{
|
||||
assert(key == keys[index]);
|
||||
assert(value == index);
|
||||
index++;
|
||||
};
|
||||
|
||||
m.remove("second");
|
||||
m.set("second", 1);
|
||||
|
||||
String[] new_order = { "first", "third", "fourth", "second" };
|
||||
index = 0;
|
||||
m.@each(; String key, usz value)
|
||||
{
|
||||
assert(key == new_order[index]);
|
||||
index++;
|
||||
};
|
||||
}
|
||||
|
||||
fn void linked_map_init_with_values()
|
||||
{
|
||||
TestLinkedHashMap m;
|
||||
m.tinit_with_key_values("a", 1, "b", 2, "c", 3);
|
||||
|
||||
assert(m.len() == 3);
|
||||
assert(m.get("a")!! == 1);
|
||||
assert(m.get("b")!! == 2);
|
||||
assert(m.get("c")!! == 3);
|
||||
|
||||
// Verify order
|
||||
String[] expected_order = { "a", "b", "c" };
|
||||
usz index = 0;
|
||||
m.@each(; String key, usz value)
|
||||
{
|
||||
assert(key == expected_order[index]);
|
||||
index++;
|
||||
};
|
||||
}
|
||||
|
||||
fn void linked_map_remove()
|
||||
{
|
||||
TestLinkedHashMap m;
|
||||
assert(!@ok(m.remove("A")));
|
||||
m.tinit();
|
||||
assert(!@ok(m.remove("A")));
|
||||
m.set("A", 0);
|
||||
assert(@ok(m.remove("A")));
|
||||
|
||||
m.set("a", 1);
|
||||
m.set("b", 2);
|
||||
m.set("c", 3);
|
||||
m.remove("b");
|
||||
|
||||
String[] expected_order = { "a", "c" };
|
||||
usz index = 0;
|
||||
m.@each(; String key, usz value)
|
||||
{
|
||||
assert(key == expected_order[index]);
|
||||
index++;
|
||||
};
|
||||
}
|
||||
|
||||
fn void linked_map_copy()
|
||||
{
|
||||
TestLinkedHashMap hash_map;
|
||||
hash_map.tinit();
|
||||
|
||||
hash_map.set("aa", 1);
|
||||
hash_map.set("b", 2);
|
||||
hash_map.set("bb", 1);
|
||||
|
||||
TestLinkedHashMap hash_map_copy;
|
||||
hash_map_copy.tinit_from_map(&hash_map);
|
||||
|
||||
assert(hash_map_copy.len() == hash_map.len());
|
||||
|
||||
String[] expected_order = { "aa", "b", "bb" };
|
||||
usz index = 0;
|
||||
hash_map_copy.@each(; String key, usz value)
|
||||
{
|
||||
assert(key == expected_order[index]);
|
||||
index++;
|
||||
};
|
||||
}
|
||||
|
||||
fn void linked_map_iterators()
|
||||
{
|
||||
TestLinkedHashMap m;
|
||||
m.tinit_with_key_values("a", 1, "b", 2, "c", 3);
|
||||
|
||||
usz count = 0;
|
||||
LinkedHashMapIterator{String, ulong} iter = m.iter();
|
||||
while (iter.next())
|
||||
{
|
||||
count++;
|
||||
LinkedEntry {String, ulong} * current = iter.get()!!;
|
||||
assert(current.key.len > 0);
|
||||
assert(current.value > 0);
|
||||
}
|
||||
assert(count == 3);
|
||||
|
||||
count = 0;
|
||||
|
||||
LinkedHashMapKeyIterator{String, ulong} key_iter = m.key_iter();
|
||||
while (key_iter.next())
|
||||
{
|
||||
count++;
|
||||
assert(key_iter.get()!!.len > 0);
|
||||
}
|
||||
assert(count == 3);
|
||||
|
||||
count = 0;
|
||||
usz sum = 0;
|
||||
LinkedHashMapValueIterator{String, ulong} value_iter = m.value_iter();
|
||||
|
||||
while (value_iter.next())
|
||||
{
|
||||
count++;
|
||||
sum += *(value_iter.get()!!);
|
||||
}
|
||||
assert(count == 3);
|
||||
assert(sum == 6);
|
||||
}
|
||||
|
||||
alias FooLinkedMap = LinkedHashMap{char, Foobar};
|
||||
enum Foobar : inline char
|
||||
{
|
||||
FOO,
|
||||
BAR,
|
||||
BAZ
|
||||
}
|
||||
|
||||
enum Foobar2 : const inline int
|
||||
{
|
||||
ABC = 3,
|
||||
DEF = 5,
|
||||
}
|
||||
|
||||
fn void linked_map_inline_enum()
|
||||
{
|
||||
FooLinkedMap x;
|
||||
x.tinit();
|
||||
x[Foobar.BAZ] = FOO;
|
||||
x[Foobar2.ABC] = BAR;
|
||||
test::eq(string::tformat("%s", x), "{ 2: FOO, 3: BAR }");
|
||||
x.free();
|
||||
}
|
||||
|
||||
fn void linked_map_clear()
|
||||
{
|
||||
TestLinkedHashMap m;
|
||||
m.tinit_with_key_values("a", 1, "b", 2, "c", 3);
|
||||
|
||||
assert(m.len() == 3);
|
||||
m.clear();
|
||||
assert(m.len() == 0);
|
||||
assert(m.is_empty());
|
||||
|
||||
m.set("x", 10);
|
||||
assert(m.len() == 1);
|
||||
assert(@ok(m.get("x")));
|
||||
assert((m.get("x")??0) == 10);
|
||||
}
|
||||
300
test/unit/stdlib/collections/linked_set.c3
Normal file
300
test/unit/stdlib/collections/linked_set.c3
Normal file
@@ -0,0 +1,300 @@
|
||||
module linked_set_test @test;
|
||||
import std::collections::set;
|
||||
|
||||
alias TestLinkedHashSet = LinkedHashSet{String};
|
||||
|
||||
fn void linked_set_basic()
|
||||
{
|
||||
TestLinkedHashSet set;
|
||||
assert(!set.is_initialized());
|
||||
set.tinit();
|
||||
assert(set.is_initialized());
|
||||
assert(set.is_empty());
|
||||
assert(set.len() == 0);
|
||||
|
||||
assert(set.add("a"));
|
||||
assert(!set.is_empty());
|
||||
assert(set.len() == 1);
|
||||
set.remove("a");
|
||||
assert(set.is_empty());
|
||||
|
||||
String[] values = { "key1", "key2", "key3" };
|
||||
foreach (value : values)
|
||||
{
|
||||
assert(set.add(value));
|
||||
}
|
||||
assert(set.len() == values.len);
|
||||
foreach (value : values)
|
||||
{
|
||||
assert(set.contains(value));
|
||||
}
|
||||
}
|
||||
|
||||
fn void linked_set_insertion_order()
|
||||
{
|
||||
TestLinkedHashSet set;
|
||||
set.tinit();
|
||||
|
||||
String[] values = { "first", "second", "third", "fourth" };
|
||||
foreach (value : values)
|
||||
{
|
||||
set.add(value);
|
||||
}
|
||||
|
||||
// Test iteration follows insertion order
|
||||
usz index = 0;
|
||||
set.@each(; String value)
|
||||
{
|
||||
assert(value == values[index]);
|
||||
index++;
|
||||
};
|
||||
|
||||
// Test that removing and re-inserting changes order
|
||||
set.remove("second");
|
||||
set.add("second");
|
||||
|
||||
String[] new_order = { "first", "third", "fourth", "second" };
|
||||
index = 0;
|
||||
set.@each(; String value)
|
||||
{
|
||||
assert(value == new_order[index]);
|
||||
index++;
|
||||
};
|
||||
}
|
||||
|
||||
fn void linked_set_init_with_values()
|
||||
{
|
||||
TestLinkedHashSet set;
|
||||
set.tinit_with_values("a", "b", "c");
|
||||
|
||||
assert(set.len() == 3);
|
||||
assert(set.contains("a"));
|
||||
assert(set.contains("b"));
|
||||
assert(set.contains("c"));
|
||||
|
||||
// Verify order
|
||||
String[] expected_order = { "a", "b", "c" };
|
||||
usz index = 0;
|
||||
set.@each(; String value)
|
||||
{
|
||||
assert(value == expected_order[index]);
|
||||
index++;
|
||||
};
|
||||
}
|
||||
|
||||
fn void linked_set_remove()
|
||||
{
|
||||
TestLinkedHashSet set;
|
||||
assert(!@ok(set.remove("A")));
|
||||
set.tinit();
|
||||
assert(!@ok(set.remove("A")));
|
||||
set.add("A");
|
||||
assert(@ok(set.remove("A")));
|
||||
|
||||
set.add("a");
|
||||
set.add("b");
|
||||
set.add("c");
|
||||
set.remove("b");
|
||||
|
||||
String[] expected_order = { "a", "c" };
|
||||
usz index = 0;
|
||||
set.@each(; String value)
|
||||
{
|
||||
assert(value == expected_order[index]);
|
||||
index++;
|
||||
};
|
||||
}
|
||||
|
||||
fn void linked_set_copy()
|
||||
{
|
||||
TestLinkedHashSet original;
|
||||
original.tinit();
|
||||
|
||||
original.add("aa");
|
||||
original.add("b");
|
||||
original.add("bb");
|
||||
|
||||
TestLinkedHashSet copy;
|
||||
copy.tinit_from_set(&original);
|
||||
|
||||
assert(copy.len() == original.len());
|
||||
|
||||
String[] expected_order = { "aa", "b", "bb" };
|
||||
usz index = 0;
|
||||
copy.@each(; String value)
|
||||
{
|
||||
assert(value == expected_order[index]);
|
||||
index++;
|
||||
};
|
||||
}
|
||||
|
||||
fn void linked_set_iterators()
|
||||
{
|
||||
TestLinkedHashSet set;
|
||||
set.tinit_with_values("a", "b", "c");
|
||||
|
||||
// Test entry iterator
|
||||
usz count = 0;
|
||||
LinkedHashSetIterator{String} iter = set.iter();
|
||||
while (iter.next())
|
||||
{
|
||||
count++;
|
||||
assert(iter.get()!!.len > 0);
|
||||
}
|
||||
assert(count == 3);
|
||||
|
||||
// Test direct each macro
|
||||
count = 0;
|
||||
set.@each(; String value) {
|
||||
count++;
|
||||
assert(value.len > 0);
|
||||
};
|
||||
assert(count == 3);
|
||||
}
|
||||
|
||||
fn void linked_set_clear()
|
||||
{
|
||||
TestLinkedHashSet set;
|
||||
set.tinit_with_values("a", "b", "c");
|
||||
|
||||
assert(set.len() == 3);
|
||||
set.clear();
|
||||
assert(set.len() == 0);
|
||||
assert(set.is_empty());
|
||||
|
||||
// Should be able to reuse after clear
|
||||
set.add("x");
|
||||
assert(set.len() == 1);
|
||||
assert(set.contains("x"));
|
||||
}
|
||||
|
||||
fn void linked_set_operations()
|
||||
{
|
||||
TestLinkedHashSet set1;
|
||||
set1.tinit_with_values("a", "b", "c");
|
||||
|
||||
TestLinkedHashSet set2;
|
||||
set2.tinit_with_values("b", "c", "d");
|
||||
|
||||
// Test union
|
||||
TestLinkedHashSet union_set = set1.tset_union(&set2);
|
||||
defer union_set.free();
|
||||
assert(union_set.contains("a"));
|
||||
assert(union_set.contains("b"));
|
||||
assert(union_set.contains("c"));
|
||||
assert(union_set.contains("d"));
|
||||
assert(union_set.len() == 4);
|
||||
|
||||
// Verify union preserves order (elements from first set first)
|
||||
String[] expected_union_order = { "a", "b", "c", "d" };
|
||||
usz index = 0;
|
||||
union_set.@each(; String value) {
|
||||
assert(value == expected_union_order[index]);
|
||||
index++;
|
||||
};
|
||||
|
||||
// Test intersection
|
||||
TestLinkedHashSet intersect_set = set1.tintersection(&set2);
|
||||
defer intersect_set.free();
|
||||
assert(intersect_set.contains("b"));
|
||||
assert(intersect_set.contains("c"));
|
||||
assert(!intersect_set.contains("a"));
|
||||
assert(!intersect_set.contains("d"));
|
||||
assert(intersect_set.len() == 2);
|
||||
|
||||
// Test difference
|
||||
TestLinkedHashSet diff_set = set1.tdifference(&set2);
|
||||
defer diff_set.free();
|
||||
assert(diff_set.contains("a"));
|
||||
assert(!diff_set.contains("b"));
|
||||
assert(!diff_set.contains("c"));
|
||||
assert(!diff_set.contains("d"));
|
||||
assert(diff_set.len() == 1);
|
||||
|
||||
// Test subset
|
||||
TestLinkedHashSet subset;
|
||||
subset.tinit_with_values("b", "c");
|
||||
assert(subset.is_subset(&set1));
|
||||
assert(!set1.is_subset(&subset));
|
||||
}
|
||||
|
||||
alias IntLinkedSet = LinkedHashSet{int};
|
||||
|
||||
fn void linked_set_edge_cases()
|
||||
{
|
||||
// Test empty set
|
||||
IntLinkedSet empty;
|
||||
empty.tinit();
|
||||
defer empty.free();
|
||||
|
||||
assert(empty.is_empty());
|
||||
assert(!empty.contains(0));
|
||||
empty.remove(0); // Shouldn't crash
|
||||
|
||||
// Test large set
|
||||
IntLinkedSet large;
|
||||
large.tinit();
|
||||
defer large.free();
|
||||
|
||||
// Insert in reverse order to test ordering
|
||||
for (int i = 1000; i > 0; i--) {
|
||||
large.add(i);
|
||||
}
|
||||
assert(large.len() == 1000);
|
||||
|
||||
// Verify order is maintained
|
||||
int expected = 1000;
|
||||
large.@each(; int value) {
|
||||
assert(value == expected);
|
||||
expected--;
|
||||
};
|
||||
|
||||
// Test clear
|
||||
large.clear();
|
||||
assert(large.is_empty());
|
||||
for (int i = 1; i <= 1000; i++) {
|
||||
assert(!large.contains(i));
|
||||
}
|
||||
}
|
||||
|
||||
fn void linked_set_string_values()
|
||||
{
|
||||
TestLinkedHashSet set;
|
||||
set.tinit();
|
||||
defer set.free();
|
||||
|
||||
assert(set.add("hello"));
|
||||
assert(set.add("world"));
|
||||
assert(!set.add("hello"));
|
||||
|
||||
assert(set.contains("hello"));
|
||||
assert(set.contains("world"));
|
||||
assert(!set.contains("foo"));
|
||||
|
||||
// Test order
|
||||
String[] expected_order = { "hello", "world" };
|
||||
usz index = 0;
|
||||
set.@each(; String value) {
|
||||
assert(value == expected_order[index]);
|
||||
index++;
|
||||
};
|
||||
|
||||
set.remove("hello");
|
||||
assert(!set.contains("hello"));
|
||||
assert(set.len() == 1);
|
||||
|
||||
// Test order after removal
|
||||
assert(set.contains("world"));
|
||||
set.@each(; String value) {
|
||||
assert(value == "world");
|
||||
};
|
||||
}
|
||||
|
||||
fn void linked_set_is_initialized()
|
||||
{
|
||||
TestLinkedHashSet test;
|
||||
assert(!test.is_initialized());
|
||||
test.tinit();
|
||||
assert(test.is_initialized());
|
||||
test.free();
|
||||
}
|
||||
Reference in New Issue
Block a user