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:
Velikiy Kirill
2025-07-26 00:28:10 +03:00
committed by GitHub
parent 9a68a5c063
commit a673b4ad66
4 changed files with 1891 additions and 0 deletions

View 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);
}

View 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();
}