diff --git a/array/view_test.mbt b/array/view_test.mbt index cb874af46..a560f32fd 100644 --- a/array/view_test.mbt +++ b/array/view_test.mbt @@ -195,8 +195,14 @@ test "arrayview_arbitrary" { ///| test "arrayview_hash" { let arr : Array[ArrayView[Int]] = @quickcheck.samples(20) - inspect(arr[5:9].hash(), content="-966877954") - inspect(arr[10:15].hash(), content="-951019668") + inspect( + Hasher::new(seed=0)..combine(arr[5:9]).finalize(), + content="-966877954", + ) + inspect( + Hasher::new(seed=0)..combine(arr[10:15]).finalize(), + content="-951019668", + ) } ///| diff --git a/bigint/bigint.mbt b/bigint/bigint.mbt index 692fe3dec..134cfd865 100644 --- a/bigint/bigint.mbt +++ b/bigint/bigint.mbt @@ -221,7 +221,7 @@ test "can_convert_to_int64" { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// let big = 12345N /// hasher.combine(big) /// inspect(hasher.finalize(), content="890579181") diff --git a/bigint/bigint_test.mbt b/bigint/bigint_test.mbt index 0612e3d17..b03eb1137 100644 --- a/bigint/bigint_test.mbt +++ b/bigint/bigint_test.mbt @@ -1015,38 +1015,43 @@ test "compare_int64" { ///| test "@bigint.BigInt::hash" { // Test zero - inspect(0N.hash(), content="-813420232") + inspect(Hasher::new(seed=0)..combine(0N).finalize(), content="-813420232") assert_eq(0N.hash(), (-0N).hash()) // Positive and negative of same magnitude should have different hashes assert_not_eq(1N.hash(), (-1N).hash()) // Test small positive numbers - inspect(42N.hash(), content="2103260413") + inspect(Hasher::new(seed=0)..combine(42N).finalize(), content="2103260413") // Test small negative numbers - inspect((-42N).hash(), content="-504604139") + inspect(Hasher::new(seed=0)..combine(-42N).finalize(), content="-504604139") // Test larger numbers let large = 123456789N - assert_not_eq(large.hash(), (-large).hash()) + inspect(Hasher::new(seed=0)..combine(large).finalize(), content="275171670") + inspect(Hasher::new(seed=0)..combine(-large).finalize(), content="-200902609") // Test very large numbers (multi-limb) let very_large = 12345678901234567890123456789N - inspect(very_large.hash(), content="2115080653") - inspect((-very_large).hash(), content="-1781872667") - assert_not_eq(very_large.hash(), (-very_large).hash()) + inspect( + Hasher::new(seed=0)..combine(very_large).finalize(), + content="2115080653", + ) + inspect( + Hasher::new(seed=0)..combine(-very_large).finalize(), + content="-1781872667", + ) // Test that equal BigInts have equal hashes let a = 987654321098765432N let b = @bigint.BigInt::from_string("00987654321098765432") - inspect(a.hash(), content="-1950963429") - assert_eq(a.hash(), b.hash()) + inspect(Hasher::new(seed=0)..combine(a).finalize(), content="-1950963429") + inspect(Hasher::new(seed=0)..combine(b).finalize(), content="-1950963429") // Test that different BigInts have different hashes (usually) let c = a + 1N - inspect(c.hash(), content="-959383658") - assert_not_eq(a.hash(), c.hash()) + inspect(Hasher::new(seed=0)..combine(c).finalize(), content="-959383658") } ///| diff --git a/bool/bool.mbt b/bool/bool.mbt index 33e2c0da7..e8df502c6 100644 --- a/bool/bool.mbt +++ b/bool/bool.mbt @@ -107,11 +107,6 @@ pub fn to_uint64(self : Bool) -> UInt64 { } } -///| -pub impl Hash for Bool with hash(self) { - self.to_int() -} - ///| pub impl Hash for Bool with hash_combine(self, hasher) { hasher.combine_bool(self) diff --git a/bool/bool_test.mbt b/bool/bool_test.mbt index 9f47396d3..562fb1970 100644 --- a/bool/bool_test.mbt +++ b/bool/bool_test.mbt @@ -14,8 +14,8 @@ ///| test "Bool hash function" { - inspect(true.hash(), content="1") - inspect(false.hash(), content="0") + inspect(Hasher::new(seed=0)..combine(true).finalize(), content="-205818221") + inspect(Hasher::new(seed=0)..combine(false).finalize(), content="148298089") } ///| diff --git a/builtin/byte.mbt b/builtin/byte.mbt index 09529c6b2..8f48276d1 100644 --- a/builtin/byte.mbt +++ b/builtin/byte.mbt @@ -160,26 +160,6 @@ pub fn Byte::to_string(self : Byte) -> String { "b'\\x\{hi}\{lo}'" } -///| -/// Implements the `Hash` trait for `Byte` type by converting the byte to an -/// integer and using it as the hash value. -/// -/// Parameters: -/// -/// * `self` : The byte value to be hashed. -/// -/// Returns an integer representing the hash value of the byte. -/// -/// Example: -/// -/// ```moonbit -/// let b = b'\x42' -/// inspect(Hash::hash(b), content="66") // ASCII value of 'B' -/// ``` -pub impl Hash for Byte with hash(self) { - self.to_int() -} - ///| /// Implements the `Hash` trait for `Byte` type by providing a `hash_combine` /// method that combines a byte value with a hasher. @@ -193,7 +173,7 @@ pub impl Hash for Byte with hash(self) { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_byte(b'\xFF') /// inspect(hasher.finalize(), content="1955036104") /// ``` diff --git a/builtin/hasher.mbt b/builtin/hasher.mbt index 1e9068beb..a4c1a45fa 100644 --- a/builtin/hasher.mbt +++ b/builtin/hasher.mbt @@ -39,7 +39,7 @@ const GPRIME5 : UInt = 0x165667B1 /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_int(42) /// hasher.combine_string("hello") /// inspect(hasher.finalize(), content="860601284") @@ -61,17 +61,38 @@ struct Hasher { /// Example: /// /// ```moonbit -/// let h1 = Hasher::new() // Create a hasher with default seed +/// let h1 = Hasher::new(seed=0) // Create a hasher with default seed /// let h2 = Hasher::new(seed=42) // Create a hasher with custom seed /// let x = 123 /// h1.combine(x) /// h2.combine(x) /// inspect(h1.finalize() != h2.finalize(), content="true") // Different seeds produce different hashes /// ``` -pub fn Hasher::new(seed? : Int = 0) -> Hasher { +pub fn Hasher::new(seed? : Int = seed) -> Hasher { { acc: seed.reinterpret_as_uint() + GPRIME5 } } +///| +#cfg(not(target="js")) +let seed : Int = 0 + +///| +#cfg(target="js") +let seed : Int = random_seed() + +///| +#cfg(target="js") +extern "js" fn random_seed() -> Int = + #|() => { + #| if (crypto?.getRandomValues) { + #| const array = new Uint32Array(1); + #| crypto.getRandomValues(array); + #| return array[0] | 0; // Convert to signed 32 + #| } else { + #| return Math.floor(Math.random() * 0x100000000) | 0; // Fallback to Math.random + #| } + #|} + ///| /// Combines a hashable value with the current state of the hasher. This is /// typically used to incrementally build a hash value from multiple components. @@ -85,7 +106,7 @@ pub fn Hasher::new(seed? : Int = 0) -> Hasher { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine(42) /// hasher.combine("hello") /// inspect(hasher.finalize(), content="860601284") @@ -105,7 +126,7 @@ pub fn[T : Hash] Hasher::combine(self : Hasher, value : T) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_unit() /// inspect(hasher.finalize(), content="148298089") /// ``` @@ -126,7 +147,7 @@ pub fn Hasher::combine_unit(self : Hasher) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_bool(true) /// inspect(hasher.finalize(), content="-205818221") /// ``` @@ -147,7 +168,7 @@ pub fn Hasher::combine_bool(self : Hasher, value : Bool) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_int(42) /// inspect(hasher.finalize(), content="1161967057") /// ``` @@ -169,7 +190,7 @@ pub fn Hasher::combine_int(self : Hasher, value : Int) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_int64(42L) /// inspect(hasher.finalize(), content="-1962516083") /// ``` @@ -192,7 +213,7 @@ pub fn Hasher::combine_int64(self : Hasher, value : Int64) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_uint(42U) /// inspect(hasher.finalize(), content="1161967057") /// ``` @@ -214,7 +235,7 @@ pub fn Hasher::combine_uint(self : Hasher, value : UInt) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_uint64(42UL) /// inspect(hasher.finalize(), content="-1962516083") /// ``` @@ -236,7 +257,7 @@ pub fn Hasher::combine_uint64(self : Hasher, value : UInt64) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_double(3.14) /// inspect(hasher.finalize(), content="-428265677") /// ``` @@ -258,7 +279,7 @@ pub fn Hasher::combine_double(self : Hasher, value : Double) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_float(3.14) /// inspect(hasher.finalize(), content="635116317") // Hash of the bits of 3.14 /// ``` @@ -277,7 +298,7 @@ pub fn Hasher::combine_float(self : Hasher, value : Float) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_byte(b'\xFF') /// inspect(hasher.finalize(), content="1955036104") /// ``` @@ -298,7 +319,7 @@ pub fn Hasher::combine_byte(self : Hasher, value : Byte) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_bytes(b"\xFF\x00\xFF\x00") /// inspect(hasher.finalize(), content="-686861102") /// ``` @@ -329,7 +350,7 @@ pub fn Hasher::combine_bytes(self : Hasher, value : Bytes) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_string("hello") /// inspect(hasher.finalize(), content="-655549713") /// ``` @@ -352,7 +373,7 @@ pub fn Hasher::combine_string(self : Hasher, value : String) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_char('A') /// inspect(hasher.finalize(), content="-1625495534") /// ``` @@ -373,7 +394,7 @@ pub fn Hasher::combine_char(self : Hasher, value : Char) -> Unit { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_byte(b'\xFF') /// inspect(hasher.finalize(), content="1955036104") /// ``` @@ -440,40 +461,6 @@ pub impl Hash for String with hash_combine(self, hasher) { hasher.combine_string(self) } -///| -/// Implements the `Hash` trait for integer values using a combination of shifts -/// and multiplications to produce a well-distributed hash value. Based on the -/// hash algorithm from hash-prospector -/// (https://github.com/skeeto/hash-prospector). -/// -/// Parameters: -/// -/// * `integer` : The integer value to be hashed. The value will be reinterpreted -/// as an unsigned integer before hashing to ensure consistent behavior across -/// positive and negative values. -/// -/// Returns a 32-bit hash value derived from the input integer. -/// -/// Example: -/// -/// ```moonbit -/// let x = 42 -/// inspect(Hash::hash(x), content="-1704501356") -/// let y = -42 -/// inspect(Hash::hash(y), content="1617647962") -/// ``` -pub impl Hash for Int with hash(self) { - let self = self.reinterpret_as_uint() - let mut x = self ^ (self >> 17) - x = x * 0xed5ad4bb - x = x ^ (x >> 11) - x = x * 0xac4c1b51 - x = x ^ (x >> 15) - x = x * 0x31848bab - x = x ^ (x >> 14) - x.reinterpret_as_int() -} - ///| /// Implements hash combination for integers by combining the integer value with /// a hasher. This implementation ensures that integers can be used as keys in @@ -487,7 +474,7 @@ pub impl Hash for Int with hash(self) { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_int(42) /// inspect(hasher.finalize(), content="1161967057") /// ``` @@ -509,7 +496,7 @@ pub impl Hash for Int with hash_combine(self, hasher) { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_uint(42U) /// inspect(hasher.finalize(), content="1161967057") /// ``` @@ -529,7 +516,7 @@ pub impl Hash for UInt with hash_combine(self, hasher) { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine_uint64(42UL) /// inspect(hasher.finalize(), content="-1962516083") /// ``` @@ -549,12 +536,12 @@ pub impl Hash for UInt64 with hash_combine(self, hasher) { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// let some_value : Int? = Some(42) /// let none_value : Int? = None /// hasher.combine(some_value) /// inspect(hasher.finalize(), content="2103260413") -/// let hasher2 = Hasher::new() +/// let hasher2 = Hasher::new(seed=0) /// hasher2.combine(none_value) /// inspect(hasher2.finalize(), content="148298089") /// ``` @@ -577,12 +564,12 @@ pub impl[X : Hash] Hash for X? with hash_combine(self, hasher) { /// Example: /// /// ```moonbit -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// let ok_result : Result[Int, String] = Ok(42) /// let err_result : Result[Int, String] = Err("error") /// hasher.combine(ok_result) /// inspect(hasher.finalize(), content="-1948635851") -/// let hasher = Hasher::new() +/// let hasher = Hasher::new(seed=0) /// hasher.combine(err_result) /// inspect(hasher.finalize(), content="1953766574") /// ``` diff --git a/builtin/hasher_test.mbt b/builtin/hasher_test.mbt index b14651b30..5d3e4adc7 100644 --- a/builtin/hasher_test.mbt +++ b/builtin/hasher_test.mbt @@ -15,7 +15,7 @@ ///| test "combine int" { let hash = v => { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) hasher.combine_int(v) hasher.finalize() } @@ -32,7 +32,7 @@ test "combine int" { ///| test "combine char" { let hash = v => { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) hasher.combine_char(v) hasher.finalize() } @@ -42,7 +42,7 @@ test "combine char" { ///| test "combine int64" { let hash = v => { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) hasher.combine_int64(v) hasher.finalize() } @@ -59,7 +59,7 @@ test "combine int64" { ///| test "combine uint" { let hash = v => { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) hasher.combine_uint(v) hasher.finalize() } @@ -75,7 +75,7 @@ test "combine uint" { ///| test "combine uint64" { let hash = v => { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) hasher.combine_uint64(v) hasher.finalize() } @@ -91,7 +91,7 @@ test "combine uint64" { ///| test "combine double" { let hash = v => { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) hasher.combine_double(v) hasher.finalize() } @@ -106,7 +106,7 @@ test "combine double" { ///| test "combine float" { let hash = (v : Float) => { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) hasher.combine_float(v) hasher.finalize() } @@ -121,7 +121,7 @@ test "combine float" { ///| test "combine string" { let hash = v => { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) hasher.combine_string(v) hasher.finalize() } @@ -141,7 +141,7 @@ test "combine string" { ///| test "combine bytes" { let hash = v => { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) hasher.combine_bytes(v) hasher.finalize() } @@ -165,7 +165,7 @@ test "combine bytes" { ///| test "combine byte" { let hash = v => { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) hasher.combine_byte(v) hasher.finalize() } @@ -179,7 +179,7 @@ test "combine byte" { ///| test "combine all" { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) hasher ..combine_int(1) ..combine_int64(2147483648) @@ -194,10 +194,10 @@ test "combine all" { ///| test "combine_unit" { - let hasher = @builtin.Hasher::new() + let hasher = @builtin.Hasher::new(seed=0) hasher.combine_unit() let hash = hasher.finalize() - let hasher2 = @builtin.Hasher::new() + let hasher2 = @builtin.Hasher::new(seed=0) hasher2.combine_int(0) let hash2 = hasher2.finalize() inspect(hash == hash2, content="true") @@ -205,17 +205,17 @@ test "combine_unit" { ///| test "combine_bool" { - let hasher = @builtin.Hasher::new() + let hasher = @builtin.Hasher::new(seed=0) hasher.combine_bool(true) let hash1 = hasher.finalize() - let hasher2 = @builtin.Hasher::new() + let hasher2 = @builtin.Hasher::new(seed=0) hasher2.combine_int(1) let hash2 = hasher2.finalize() assert_eq(hash1, hash2) - let hasher3 = @builtin.Hasher::new() + let hasher3 = @builtin.Hasher::new(seed=0) hasher3.combine_bool(false) let hash3 = hasher3.finalize() - let hasher4 = @builtin.Hasher::new() + let hasher4 = @builtin.Hasher::new(seed=0) hasher4.combine_int(0) let hash4 = hasher4.finalize() assert_eq(hash3, hash4) @@ -224,7 +224,7 @@ test "combine_bool" { ///| test "Result::hash_combine Ok case" { // Test the Ok case - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) let ok_result : Result[Int, String] = Ok(42) hasher.combine(ok_result) inspect(hasher.finalize(), content="-1948635851") @@ -232,7 +232,7 @@ test "Result::hash_combine Ok case" { ///| test "Result::hash_combine Err case" { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) let err_result : Result[Int, String] = Err("error") hasher.combine(err_result) inspect(hasher.finalize(), content="1953766574") @@ -240,11 +240,11 @@ test "Result::hash_combine Err case" { ///| test "option hash" { - let hasher = @builtin.Hasher::new() + let hasher = @builtin.Hasher::new(seed=0) let some_val : Int? = Some(42) hasher.combine(some_val) inspect(hasher.finalize(), content="2103260413") - let hasher2 = @builtin.Hasher::new() + let hasher2 = @builtin.Hasher::new(seed=0) let none_val : Int? = None hasher2.combine(none_val) inspect(hasher2.finalize(), content="148298089") @@ -252,7 +252,7 @@ test "option hash" { ///| test "UInt64::hash_combine test" { - let hasher = @builtin.Hasher::new() + let hasher = @builtin.Hasher::new(seed=0) let value = 42UL @builtin.Hash::hash_combine(value, hasher) inspect(hasher.finalize(), content="-1962516083") @@ -260,7 +260,7 @@ test "UInt64::hash_combine test" { ///| test "hash_combine for UInt" { - let hasher = Hasher::new() + let hasher = Hasher::new(seed=0) let value : UInt = 42U Hash::hash_combine(value, hasher) inspect(hasher.finalize(), content="1161967057") diff --git a/builtin/linked_hash_map_wbtest.mbt b/builtin/linked_hash_map_wbtest.mbt index 056b429ea..937edb615 100644 --- a/builtin/linked_hash_map_wbtest.mbt +++ b/builtin/linked_hash_map_wbtest.mbt @@ -27,12 +27,6 @@ impl Hash for MyString with hash_combine(self, hasher) { hasher.combine_string(s) } -///| -impl Show for MyString with output(self, logger) { - let MyString(s) = self - logger.write_string(s) -} - ///| test "new" { let m : Map[Int, Int] = Map::new() @@ -51,10 +45,10 @@ test "set" { m.set("c", 1) m.set("d", 1) assert_eq(m.size, 7) - assert_eq( - m.debug_entries(), - "_,(0,a,1),(1,b,1),(2,c,1),(3,d,1),(3,bc,2),(4,cd,2),(4,abc,3),_,_,_,_,_,_,_,_", - ) + // assert_eq( + // m._debug_entries(), + // "_,(0,a,1),(1,b,1),(2,c,1),(3,d,1),(3,bc,2),(4,cd,2),(4,abc,3),_,_,_,_,_,_,_,_", + // ) } ///| @@ -101,9 +95,9 @@ test "get_or_init on push_back" { inspect(m.grow_at, content="3") m.set("x", Array::new()) m.set("xx", Array::new()) - inspect(m.debug_entries(), content="_,(0,x,[]),(0,xx,[]),_") + // inspect(m._debug_entries(), content="_,(0,x,[]),(0,xx,[]),_") m.get_or_init("a", () => Array::new()).push(1) - inspect(m.debug_entries(), content="_,(0,x,[]),(1,a,[1]),(1,xx,[])") + // inspect(m._debug_entries(), content="_,(0,x,[]),(1,a,[1]),(1,xx,[])") } ///| @@ -240,10 +234,10 @@ test "grow" { m.set("Rescript", 8) assert_eq(m.length(), 10) assert_eq(m.capacity(), 16) - assert_eq( - m.debug_entries(), - "_,(0,C,1),(0,Go,2),(0,C++,3),(0,Java,4),(0,Scala,5),(1,Julia,5),(2,Cobol,5),(2,Python,6),(2,Haskell,7),(2,Rescript,8),_,_,_,_,_", - ) + // assert_eq( + // m._debug_entries(), + // "_,(0,C,1),(0,Go,2),(0,C++,3),(0,Java,4),(0,Scala,5),(1,Julia,5),(2,Cobol,5),(2,Python,6),(2,Haskell,7),(2,Rescript,8),_,_,_,_,_", + // ) } ///| @@ -769,12 +763,15 @@ test "remove" { m.set("abcdef", 6) m.remove("ab") assert_eq(m.length(), 5) - assert_eq( - m.debug_entries(), - "_,(0,a,1),(0,bc,2),(1,cd,2),(1,abc,3),_,(0,abcdef,6),_", - ) + // assert_eq( + // m._debug_entries(), + // "_,(0,a,1),(0,bc,2),(1,cd,2),(1,abc,3),_,(0,abcdef,6),_", + // ) m.remove("abc") - assert_eq(m.debug_entries(), "_,(0,a,1),(0,bc,2),(1,cd,2),_,_,(0,abcdef,6),_") + // assert_eq( + // m._debug_entries(), + // "_,(0,a,1),(0,bc,2),(1,cd,2),_,_,(0,abcdef,6),_", + // ) } ///| @@ -785,7 +782,7 @@ test "remove_unexist_key" { m.set("abc", 3) m.remove("d") assert_eq(m.length(), 3) - assert_eq(m.debug_entries(), "_,(0,a,1),(0,ab,2),(0,abc,3),_,_,_,_") + // assert_eq(m._debug_entries(), "_,(0,a,1),(0,ab,2),(0,abc,3),_,_,_,_") } ///| @@ -910,7 +907,7 @@ test "equal_key_value_mismatch" { } ///| -fn[K : Show, V : Show] Map::debug_entries(self : Map[K, V]) -> String { +fn[K : Show, V : Show] Map::_debug_entries(self : Map[K, V]) -> String { let buf = StringBuilder::new() for i in 0.. 0 { diff --git a/bytes/bytes.mbt b/bytes/bytes.mbt index 7b41b27a1..38cae225a 100644 --- a/bytes/bytes.mbt +++ b/bytes/bytes.mbt @@ -316,3 +316,8 @@ pub impl Add for Bytes with add(self : Bytes, other : Bytes) -> Bytes { } unsafe_to_bytes(rv) } + +///| +pub impl Hash for Bytes with hash_combine(self, hasher) { + hasher.combine(self[:]) +} diff --git a/bytes/bytes_test.mbt b/bytes/bytes_test.mbt index 409d3efb8..7af25d6c0 100644 --- a/bytes/bytes_test.mbt +++ b/bytes/bytes_test.mbt @@ -88,10 +88,10 @@ test "hash" { let b4 = @bytes.of([ b'\x7f', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', b'\xff', ]) - inspect(b1.hash(), content="273427599") - inspect(b2.hash(), content="2013728637") - inspect(b3.hash(), content="-983520567") - inspect(b4.hash(), content="-1652773543") + inspect(Hasher::new(seed=0)..combine(b1).finalize(), content="-892426428") + inspect(Hasher::new(seed=0)..combine(b2).finalize(), content="404189") + inspect(Hasher::new(seed=0)..combine(b3).finalize(), content="-1666368731") + inspect(Hasher::new(seed=0)..combine(b4).finalize(), content="1297183151") inspect(b1.hash() == b1.hash(), content="true") inspect(b2.hash() == b2.hash(), content="true") inspect(b1.hash() == b2.hash(), content="false") diff --git a/bytes/view.mbt b/bytes/view.mbt index 6a0ac638b..56c5c8737 100644 --- a/bytes/view.mbt +++ b/bytes/view.mbt @@ -641,14 +641,15 @@ pub fn BytesView::to_bytes(self : BytesView) -> Bytes { ///| pub impl Hash for BytesView with hash_combine(self : BytesView, hasher : Hasher) { - for i in 0..= 4 { + hasher.combine_uint(data.to_uint_le()) + data = data[4:] + } + while data.length() >= 1 { + hasher.combine_byte(data[0]) + data = data[1:] } -} - -///| -pub impl Hash for BytesView with hash(self : BytesView) -> Int { - xxhash32(self.data(), 0, offset=self.start_offset(), len=self.length()) } ///| diff --git a/bytes/xxhash.mbt b/bytes/xxhash.mbt deleted file mode 100644 index 4e5837b8e..000000000 --- a/bytes/xxhash.mbt +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2025 International Digital Economy Academy -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// https://github.com/Cyan4973/xxHash/blob/dev/doc/xxhash_spec.md#xxh32-algorithm-description - -///| -let gPRIME1 = 0x9E3779B1 - -///| -let gPRIME2 = 0x85EBCA77 - -///| -let gPRIME3 = 0xC2B2AE3D - -///| -let gPRIME4 = 0x27D4EB2F - -///| -let gPRIME5 = 0x165667B1 - -///| -fn xxhash32( - input : Bytes, - offset? : Int = 0, - len? : Int = input.length(), - seed : Int, -) -> Int { - let h = (if len >= 16 { - h16bytes(input, offset, len, seed) - } else { - seed + gPRIME5 - }) + - len - finalize(h, input, offset + (len & -16), len & 0xF) -} - -///| -pub impl Hash for Bytes with hash(self) { - xxhash32(self, 0) -} - -///| -pub impl Hash for Bytes with hash_combine(self, hasher) { - hasher.combine_bytes(self) -} - -///| -fn rotl(x : Int, r : Int) -> Int { - (x << r) | (x.reinterpret_as_uint() >> (32 - r)).reinterpret_as_int() -} - -///| -fn round(acc : Int, input : Int) -> Int { - rotl(acc + input * gPRIME2, 13) * gPRIME1 -} - -///| -fn avalanche_step(h : Int, rshift : Int, prime : Int) -> Int { - (h ^ (h.reinterpret_as_uint() >> rshift).reinterpret_as_int()) * prime -} - -///| -fn avalanche(h : Int) -> Int { - avalanche_step( - avalanche_step(avalanche_step(h, 15, gPRIME2), 13, gPRIME3), - 16, - 1, - ) -} - -///| -fn endian32(input : Bytes, cur : Int) -> Int { - input[cur + 0].to_int() | - ( - (input[cur + 1].to_int() << 8) | - ((input[cur + 2].to_int() << 16) | (input[cur + 3].to_int() << 24)) - ) -} - -///| -fn fetch32(input : Bytes, cur : Int, v : Int) -> Int { - round(v, endian32(input, cur)) -} - -///| -fn finalize(h : Int, input : Bytes, cur : Int, remain : Int) -> Int { - if remain >= 4 { - finalize( - rotl(h + endian32(input, cur) * gPRIME3, 17) * gPRIME4, - input, - cur + 4, - remain - 4, - ) - } else if remain > 0 { - finalize( - rotl(h + input[cur].to_int() * gPRIME5, 11) * gPRIME1, - input, - cur + 1, - remain - 1, - ) - } else { - avalanche(h) - } -} - -///| -fn _h16bytes( - input : Bytes, - cur : Int, - remain : Int, - v1 : Int, - v2 : Int, - v3 : Int, - v4 : Int, -) -> Int { - if remain >= 16 { - _h16bytes( - input, - cur + 16, - remain - 16, - fetch32(input, cur, v1), - fetch32(input, cur + 4, v2), - fetch32(input, cur + 8, v3), - fetch32(input, cur + 12, v4), - ) - } else { - rotl(v1, 1) + rotl(v2, 7) + rotl(v3, 12) + rotl(v4, 18) - } -} - -///| -fn h16bytes(input : Bytes, cur : Int, len : Int, seed : Int) -> Int { - _h16bytes( - input, - cur, - len, - seed + gPRIME1 + gPRIME2, - seed + gPRIME2, - seed, - seed - gPRIME1, - ) -} - -///| -test "Bytes hash_combine" { - let data : Bytes = [1, 2, 3, 4] - let hasher = Hasher::new() - data.hash_combine(hasher) - assert_true(hasher.finalize() != 0) -} diff --git a/char/char.mbt b/char/char.mbt index 976708a5f..61b4a778e 100644 --- a/char/char.mbt +++ b/char/char.mbt @@ -12,11 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -///| -pub impl Hash for Char with hash(self) { - self.to_int() -} - ///| pub impl Hash for Char with hash_combine(self, hasher) -> Unit { hasher.combine_char(self) diff --git a/char/char_test.mbt b/char/char_test.mbt index f2e1e491c..de580501a 100644 --- a/char/char_test.mbt +++ b/char/char_test.mbt @@ -411,24 +411,6 @@ test "to ascii lowercase" { assert_eq('❤', '❤'.to_ascii_lowercase()) } -///| -test "Hash implementation edge cases" { - // Test that hash function works for various characters - let hash_a = 'a'.hash() - let hash_b = 'b'.hash() - assert_true(hash_a >= 0) - assert_true(hash_b >= 0) - assert_false(hash_a == hash_b) // Different characters should have different hashes - - // Test with null character - let null_hash = '\u{0000}'.hash() - assert_true(null_hash >= 0) - - // Test with high Unicode characters - let unicode_hash = '🚀'.hash() - assert_true(unicode_hash >= 0) -} - ///| test "ToJson implementation" { // Test that to_json works without crashing for various characters diff --git a/double/double.mbt b/double/double.mbt index ca88781b4..1b14adab9 100644 --- a/double/double.mbt +++ b/double/double.mbt @@ -237,11 +237,6 @@ test "min equal to neg max" { assert_true(min_value == -max_value) } -///| -pub impl Hash for Double with hash(self) { - self.reinterpret_as_int64() |> Hash::hash() -} - ///| pub impl Hash for Double with hash_combine(self, hasher) { hasher.combine_double(self) diff --git a/float/float.mbt b/float/float.mbt index 80140f393..68976b1e6 100644 --- a/float/float.mbt +++ b/float/float.mbt @@ -161,28 +161,6 @@ pub fn default() -> Float { 0.0 } -///| -/// Computes a hash value for a floating-point number by first converting it to -/// its integer representation and then applying the standard integer hashing -/// function. -/// -/// Parameters: -/// -/// * `value` : The floating-point number to be hashed. -/// -/// Returns an integer hash value for the given floating-point number. -/// -/// Example: -/// -/// ```moonbit -/// let f : Float = 3.14 -/// let h = Hash::hash(f) -/// inspect(h != 0, content="true") -/// ``` -pub impl Hash for Float with hash(self) { - self.reinterpret_as_int() |> Hash::hash() -} - ///| /// Combines the hash value of a floating-point number with an existing hasher. /// diff --git a/float/float_test.mbt b/float/float_test.mbt index b68091551..f89b79a20 100644 --- a/float/float_test.mbt +++ b/float/float_test.mbt @@ -71,9 +71,12 @@ test "Hash" { a : Float } derive(Hash) let a = A::{ a: 1.0 } - inspect(a.hash(), content="1986884538") - inspect(a.a.hash(), content="749479517") - inspect((2.0 : Float).hash(), content="-1861484393") + inspect(Hasher::new(seed=0)..combine(a).finalize(), content="1986884538") + inspect(Hasher::new(seed=0)..combine(a.a).finalize(), content="1986884538") + inspect( + Hasher::new(seed=0)..combine((2.0 : Float)).finalize(), + content="250978825", + ) } ///| diff --git a/hashmap/README.mbt.md b/hashmap/README.mbt.md index 8e10f1d57..39d645c8f 100644 --- a/hashmap/README.mbt.md +++ b/hashmap/README.mbt.md @@ -42,7 +42,7 @@ You can use `remove()` to remove a key-value pair. test { let map = @hashmap.of([("a", 1), ("b", 2), ("c", 3)]) map.remove("a") |> ignore - assert_eq(map.to_array(), [("c", 3), ("b", 2)]) + assert_false(map.contains("a")) } ``` diff --git a/hashmap/hashmap.mbt b/hashmap/hashmap.mbt index 87aa61805..a94284688 100644 --- a/hashmap/hashmap.mbt +++ b/hashmap/hashmap.mbt @@ -601,21 +601,73 @@ impl Show for MyString with output(self, logger) { logger.write_string(self.0) } +///| +fn[K : Hash + Eq, V : Eq] verify_content( + map : HashMap[K, V], + expected : Array[(K, V)], +) -> Unit raise { + for entry in expected { + let (k, v) = entry + assert_true(map.contains_kv(k, v)) + } + assert_eq(map.length(), expected.length()) +} + ///| test "arbitrary" { let samples : Array[HashMap[String, Int]] = @quickcheck.samples(20) - inspect( - samples[5:10], - content=( - #|[HashMap::of([]), HashMap::of([]), HashMap::of([("", 0)]), HashMap::of([("", 0)]), HashMap::of([("", 0)])] - ), - ) - inspect( - samples[11:15], - content=( - #|[HashMap::of([("Q", 1), ("", 0), ("\u{1e}", 0)]), HashMap::of([("", 0)]), HashMap::of([("F:", 0), ("A&", 2), ("v\b", 0), ("", 0), ("#", 0)]), HashMap::of([("p(", -2), ("^\u{1e}", 3), ("2x", 1), ("", 3)])] - ), - ) + let data = [ + [], + [], + [], + [("", 0)], + [("", 0)], + [], + [], + [("", 0)], + [("", 0)], + [("", 0)], + [("}\u{11}e", -1), ("", 0), ("0f", -1), ("k", -2)], + [("Q", 1), ("", 0), ("\u{1e}", 0)], + [("", 0)], + [("F:", 0), ("A&", 2), ("v\b", 0), ("", 0), ("#", 0)], + [("p(", -2), ("^\u{1e}", 3), ("2x", 1), ("", 3)], + [("A", -3), ("", 0), ("/w", 0), ("lD", 0), ("jlF0", 3)], + [ + ("zd", 2), + ("[z\r]j", 4), + ("\u{1d}r", -3), + ("", 0), + ("p=61s\u{14}", -7), + ("t|,7X4\"\u{14}", -10), + ("HeH\b\u{15}d", 1), + ("LC\u{19}", 1), + ("\u{0b}", 0), + ("\n", 0), + ("3", -9), + ("v", -1), + ], + [ + ("", -1), + ("&\b", -4), + ("ob%>j\bD?xboR5", 0), + (";:B", -3), + ("`", 1), + ("OwQCP+@", -1), + ("O]k", -3), + ("\n^", -3), + ("U_-*\u{1c}x\u{18}", -11), + ("shU^\u{1d}hd8\u{1d}", 0), + ("!\u{1b}63p", 3), + ("q", 1), + ("R@", 4), + ], + [("", -4), ("v", -2), (")xh", 1), ("C\u{10}\u{0e}", -3), ("|", 0)], + [("", 1), ("b", -1)], + ] + for i in 0.. { - let (k, v) = e - buf.write_string("[\{k}-\{v}]") - }) - inspect(buf, content="[2-two][1-one][3-three]") - buf.reset() - map - .iter() - .take(2) - .each(e => { - let (k, v) = e - buf.write_string("[\{k}-\{v}]") - }) - inspect(buf, content="[2-two][1-one]") + let array = map.iter().collect() + array.sort() + inspect( + array, + content=( + #|[(1, "one"), (2, "two"), (3, "three")] + ), + ) } ///| test "iter2" { - let buf = StringBuilder::new(size_hint=20) let map = @hashmap.of([(1, "one"), (2, "two"), (3, "three")]) - for k, v in map { - buf.write_string("[\{k}-\{v}]") - } - inspect(buf, content="[2-two][1-one][3-three]") + let array = map.iter2().to_array() + array.sort() + inspect( + array, + content=( + #|[(1, "one"), (2, "two"), (3, "three")] + ), + ) } ///| test "to_array" { let map = @hashmap.of([(1, "one"), (2, "two"), (3, "three")]) + let array = map.to_array() + array.sort() inspect( - map.to_array(), + array, content=( - #|[(2, "two"), (1, "one"), (3, "three")] + #|[(1, "one"), (2, "two"), (3, "three")] ), ) } @@ -327,10 +323,11 @@ test "get_nonexistent_key_with_psl" { ///| test "from_iter multiple elements iter" { - inspect( - @hashmap.from_iter([(1, 1), (2, 2), (3, 3)].iter()), - content="HashMap::of([(2, 2), (1, 1), (3, 3)])", - ) + let map = @hashmap.from_iter([(1, 1), (2, 2), (3, 3)].iter()) + guard map is { 1: 1, 2: 2, 3: 3, .. } else { + fail("Map is not expected: \{map}") + } + inspect(map.length(), content="3") } ///| @@ -370,14 +367,22 @@ test "@hashmap.contains/after_operations" { ///| test "@hashmap.from_array" { - let map = @hashmap.from_array([("a", 1), ("b", 2), ("c", 3)]) - @json.inspect(map, content={ "a": 1, "c": 3, "b": 2 }) + let array = [("a", 1), ("b", 2), ("c", 3)] + let map = @hashmap.from_array(array) + for k, v in map { + assert_eq(map.get(k), Some(v)) + } + assert_eq(map.length(), array.length()) } ///| test "@hashmap.from_array with int key" { - let map = @hashmap.from_array([(1, "one"), (2, "two"), (3, "three")]) - @json.inspect(map, content={ "2": "two", "1": "one", "3": "three" }) + let array = [(1, "one"), (2, "two"), (3, "three")] + let map = @hashmap.from_array(array) + for k, v in map { + assert_eq(map.get(k), Some(v)) + } + assert_eq(map.length(), array.length()) } ///| @@ -399,22 +404,12 @@ test "@hashmap.eq" { test "@hashmap.map" { let map = @hashmap.of([("a", 1), ("b", 2), ("c", 3)]) let v = map.map((k, v) => k + v.to_string()) - inspect( - v, - content=( - #|HashMap::of([("a", "a1"), ("c", "c3"), ("b", "b2")]) - ), - ) + verify_content(v, [("a", "a1"), ("b", "b2"), ("c", "c3")]) map["d"] = 10 map["e"] = 20 map.remove("c") let v = map.map((k, v) => k + v.to_string()) - inspect( - v, - content=( - #|HashMap::of([("e", "e20"), ("a", "a1"), ("b", "b2"), ("d", "d10")]) - ), - ) + verify_content(v, [("a", "a1"), ("b", "b2"), ("d", "d10"), ("e", "e20")]) let v : @hashmap.HashMap[String, String] = @hashmap.new().map((k, v) => k + v) inspect(v, content="HashMap::of([])") } @@ -423,22 +418,27 @@ test "@hashmap.map" { test "@hashmap.copy" { let map = @hashmap.of([("a", 1), ("b", 2), ("c", 3)]) let copy = map.copy() - inspect( - copy, - content=( - #|HashMap::of([("a", 1), ("c", 3), ("b", 2)]) - ), - ) + verify_content(copy, [("a", 1), ("b", 2), ("c", 3)]) map["d"] = 10 map["e"] = 20 map.remove("c") let copy = map.copy() - inspect( - copy, - content=( - #|HashMap::of([("e", 20), ("a", 1), ("b", 2), ("d", 10)]) - ), - ) + verify_content(copy, [("e", 20), ("a", 1), ("b", 2), ("d", 10)]) let copy : @hashmap.HashMap[String, String] = @hashmap.new().copy() inspect(copy, content="HashMap::of([])") } + +///| +fn[K : Hash + Eq + Show, V : Eq + Show] verify_content( + map : @hashmap.HashMap[K, V], + expected : Array[(K, V)], +) -> Unit raise { + for entry in expected { + let (k, v) = entry + assert_true( + map.contains_kv(k, v), + msg="Key \{k} with value \{v} not found in map \{map}", + ) + } + assert_eq(map.length(), expected.length()) +} diff --git a/hashmap/moon.pkg.json b/hashmap/moon.pkg.json index 0ef05bb97..b218a8238 100644 --- a/hashmap/moon.pkg.json +++ b/hashmap/moon.pkg.json @@ -7,5 +7,5 @@ "moonbitlang/core/quickcheck", "moonbitlang/core/int" ], - "test-import": ["moonbitlang/core/string", "moonbitlang/core/json"] + "test-import": ["moonbitlang/core/string"] } diff --git a/hashmap/utils.mbt b/hashmap/utils.mbt index df12a41fb..e426b5cd4 100644 --- a/hashmap/utils.mbt +++ b/hashmap/utils.mbt @@ -13,7 +13,7 @@ // limitations under the License. ///| -fn[K : Show, V : Show] debug_entries(self : HashMap[K, V]) -> String { +fn[K : Show, V : Show] _debug_entries(self : HashMap[K, V]) -> String { for s = "", i = 0; i < self.entries.length(); { let s = if i > 0 { s + "," } else { s } match self.entries[i] { @@ -160,10 +160,11 @@ pub fn[K : Hash + Eq, V] HashMap::from_iter( /// ```moonbit /// let map = @hashmap.of([(1, "one"), (2, "two")]) /// let arr = map.to_array() +/// arr.sort() /// inspect( /// arr, /// content=( -/// #|[(2, "two"), (1, "one")] +/// #|[(1, "one"), (2, "two")] /// ), /// ) /// ``` @@ -269,9 +270,12 @@ pub fn[K, V] is_empty(self : HashMap[K, V]) -> Bool { /// /// ```moonbit /// let map = @hashmap.of([(1, "one"), (2, "two")]) -/// let mut result = "" -/// map.each((k, v) => { result = result + "\{k}:\{v}," }) -/// inspect(result, content="2:two,1:one,") +/// let array = [] +/// map.each((k, v) => { array.push((k, v)) }) +/// array.sort() +/// inspect(array, content=( +/// #|[(1, "one"), (2, "two")] +/// )) /// ``` #locals(f) pub fn[K, V] each( @@ -289,6 +293,8 @@ pub fn[K, V] each( /// Iterates over all key-value pairs in the map with their index, applying the /// given function to each element. The index starts from 0 and only counts /// non-empty entries. +/// +/// Notice that the order of iteration is not guaranteed. /// /// Parameters: /// @@ -298,14 +304,6 @@ pub fn[K, V] each( /// * The key of the current entry /// * The value of the current entry /// -/// Example: -/// -/// ```moonbit -/// let map = @hashmap.of([("a", 1), ("b", 2), ("c", 3)]) -/// let mut result = 0 -/// map.eachi((i, k, _) => { if k == "b" { result = i } }) -/// // "b" is at index 1 -/// inspect(result, content="2") /// ``` #locals(f) pub fn[K, V] eachi( @@ -325,6 +323,8 @@ pub fn[K, V] eachi( ///| /// Provides string representation for hash maps. +/// +/// Notice that the order of key-value pairs in the output string is not guaranteed /// /// Parameters: /// @@ -335,12 +335,8 @@ pub fn[K, V] eachi( /// /// ```moonbit /// let map = @hashmap.of([(1, "one"), (2, "two")]) -/// inspect( -/// map, -/// content=( -/// #|HashMap::of([(2, "two"), (1, "one")]) -/// ), -/// ) +/// let array = map.to_array() +/// assert_eq(map.to_string(), "HashMap::of(\{array})") /// ``` pub impl[K : Show, V : Show] Show for HashMap[K, V] with output(self, logger) { logger.write_string("HashMap::of([") diff --git a/hashset/hashset.mbt b/hashset/hashset.mbt index f1b18e9e5..17ceabf24 100644 --- a/hashset/hashset.mbt +++ b/hashset/hashset.mbt @@ -416,7 +416,7 @@ fn calc_grow_threshold(capacity : Int) -> Int { } ///| -fn[K : Show] debug_entries(self : HashSet[K]) -> String { +fn[K : Show] _debug_entries(self : HashSet[K]) -> String { let mut s = "" for i in 0.. 0 { @@ -462,10 +462,10 @@ test "set" { m.add("c") m.add("d") inspect(m.size, content="7") - assert_eq( - m.debug_entries(), - "_,(0,a),(1,b),(2,c),(3,d),(3,bc),(4,cd),(4,abc),_,_,_,_,_,_,_,_", - ) + // assert_eq( + // m._debug_entries(), + // "_,(0,a),(1,b),(2,c),(3,d),(3,bc),(4,cd),(4,abc),_,_,_,_,_,_,_,_", + // ) } ///| @@ -483,10 +483,10 @@ test "remove" { m.add("abcdef" |> i) m.remove("ab" |> i) inspect(m.length(), content="5") - inspect( - m.debug_entries(), - content="_,(0,a),(0,bc),(1,cd),(1,abc),_,(0,abcdef),_", - ) + // inspect( + // m._debug_entries(), + // content="_,(0,a),(0,bc),(1,cd),(1,abc),_,(0,abcdef),_", + // ) } ///| @@ -501,7 +501,7 @@ test "remove_unexist_key" { m.add("abc" |> i) m.remove("d" |> i) inspect(m.length(), content="3") - inspect(m.debug_entries(), content="_,(0,a),(0,ab),(0,abc),_,_,_,_") + // inspect(m._debug_entries(), content="_,(0,a),(0,ab),(0,abc),_,_,_,_") } ///| @@ -527,10 +527,10 @@ test "grow" { m.add("Rescript" |> i) inspect(m.size, content="10") inspect(m.capacity, content="16") - assert_eq( - m.debug_entries(), - "_,(0,C),(0,Go),(0,C++),(0,Java),(0,Scala),(1,Julia),(2,Cobol),(2,Python),(2,Haskell),(2,Rescript),_,_,_,_,_", - ) + // assert_eq( + // m._debug_entries(), + // "_,(0,C),(0,Go),(0,C++),(0,Java),(0,Scala),(1,Julia),(2,Cobol),(2,Python),(2,Haskell),(2,Rescript),_,_,_,_,_", + // ) } ///| diff --git a/hashset/hashset_test.mbt b/hashset/hashset_test.mbt index e065b5d38..ff78b925b 100644 --- a/hashset/hashset_test.mbt +++ b/hashset/hashset_test.mbt @@ -67,8 +67,14 @@ test "copy" { ///| test "to_json" { + let data = [1, 2, 3] let set = @hashset.of([1, 2, 3]) - @json.inspect(set, content=[2, 1, 3]) + let json = set.to_json() + guard json is Array(array) else { fail("Expected JSON array") } + for item in data { + assert_true(array.contains(item.to_json())) + } + assert_eq(array.length(), 3) } ///| @@ -119,22 +125,32 @@ test "is_empty" { ///| test "iter" { - let m = @hashset.of(["a", "b", "c"]) - let mut sum = "" - m.each(k => sum += k) - inspect(sum, content="acb") + let m = @hashset.of(["a", "b", "c"]).iter().collect() + m.sort() + inspect( + m, + content=( + #|["a", "b", "c"] + ), + ) } ///| test "iteri" { let m = @hashset.of(["1", "2", "3"]) - let mut s = "" + let s = [] let mut sum = 0 m.eachi((i, k) => { - s += k sum += i + s.push(k) }) - inspect(s, content="132") + s.sort() + inspect( + s, + content=( + #|["1", "2", "3"] + ), + ) inspect(sum, content="3") } @@ -266,13 +282,14 @@ test "sub" { ///| test "iter" { - let buf = StringBuilder::new(size_hint=20) - let map = @hashset.of(["a", "b", "c"]) - map.iter().each(e => buf.write_string("[\{e}]")) - inspect(buf, content="[a][c][b]") - buf.reset() - map.iter().take(2).each(e => buf.write_string("[\{e}]")) - inspect(buf, content="[a][c]") + let array = @hashset.of(["a", "b", "c"]).iter().collect() + array.sort() + inspect( + array, + content=( + #|["a", "b", "c"] + ), + ) } ///| @@ -377,10 +394,9 @@ test "clear_and_reinsert" { ///| test "from_iter multiple elements iter" { - inspect( - @hashset.from_iter([1, 2, 3].iter()), - content="@hashset.of([2, 1, 3])", - ) + let array = @hashset.from_iter([1, 2, 3].iter()).to_array() + array.sort() + inspect(array, content="[1, 2, 3]") } ///| @@ -397,14 +413,31 @@ test "from_iter empty iter" { ///| test "hashset arbitrary" { let samples : Array[@hashset.HashSet[Int]] = @quickcheck.samples(20) - inspect( - samples[5:10], - content="[@hashset.of([]), @hashset.of([]), @hashset.of([0]), @hashset.of([0]), @hashset.of([0, 2, 1, 3])]", - ) - inspect( - samples[11:15], - content="[@hashset.of([0, -2, -1]), @hashset.of([0, -5, -2, 4, 8]), @hashset.of([0, 2, -1]), @hashset.of([0])]", - ) + let cases = [ + @hashset.of([]), + @hashset.of([]), + @hashset.of([]), + @hashset.of([0]), + @hashset.of([0]), + @hashset.of([]), + @hashset.of([]), + @hashset.of([0]), + @hashset.of([0]), + @hashset.of([0, 3, 1, 2]), + @hashset.of([0, 1, -2]), + @hashset.of([-2, 0, -1]), + @hashset.of([-5, 0, 8, 4, -2]), + @hashset.of([0, 2, -1]), + @hashset.of([0]), + @hashset.of([]), + @hashset.of([-2, 0, -3, -1]), + @hashset.of([-1, 0, 3, 1, -6, 2]), + @hashset.of([0]), + @hashset.of([-5, -1, 6, 0, 2, -2]), + ] + for i = 0; i < 20; i = i + 1 { + assert_true(samples[i].symmetric_difference(cases[i]).is_empty()) + } } ///| @@ -427,5 +460,5 @@ test "@hashset.to_array/multiple" { set.add(2) set.add(3) set.add(4) - inspect(set.to_array(), content="[2, 4, 1, 3]") + inspect(set.to_array()..sort().to_string(), content="[1, 2, 3, 4]") } diff --git a/immut/hashmap/HAMT.mbt b/immut/hashmap/HAMT.mbt index 3ddd735e3..ebd4db321 100644 --- a/immut/hashmap/HAMT.mbt +++ b/immut/hashmap/HAMT.mbt @@ -725,5 +725,8 @@ pub impl[K : Hash, V : Hash] Hash for HashMap[K, V] with hash_combine( self, hasher, ) { - hasher.combine(self.fold_with_key(init=0, (acc, k, v) => acc ^ (k, v).hash())) + hasher.combine( + self.fold_with_key(init=0, (acc, k, v) => acc ^ + Hasher::new()..combine((k, v)).finalize()), + ) } diff --git a/immut/hashmap/HAMT_test.mbt b/immut/hashmap/HAMT_test.mbt index 2ca6e2950..864610462 100644 --- a/immut/hashmap/HAMT_test.mbt +++ b/immut/hashmap/HAMT_test.mbt @@ -101,17 +101,27 @@ test "HAMT::values" { ///| test "HAMT::iter" { let data = @hashmap.of([(0, "a"), (2, "b"), (3, "d"), (5, "e"), (11111, "f")]) - let mut s = "" - data.iter().each(p => s += " \{p.0},\{p.1}") - inspect(s, content=" 0,a 5,e 3,d 11111,f 2,b") + let array = data.iter().collect() + array.sort() + inspect( + array, + content=( + #|[(0, "a"), (2, "b"), (3, "d"), (5, "e"), (11111, "f")] + ), + ) } ///| test "HAMT::iter2" { let data = @hashmap.of([(0, "a"), (2, "b"), (3, "d"), (5, "e"), (11111, "f")]) - let mut s = "" - data.iter2().each((k, v) => s += " \{k},\{v}") - inspect(s, content=" 0,a 5,e 3,d 11111,f 2,b") + let array = data.iter2().to_array() + array.sort() + inspect( + array, + content=( + #|[(0, "a"), (2, "b"), (3, "d"), (5, "e"), (11111, "f")] + ), + ) } ///| @@ -121,10 +131,8 @@ test "HAMT::to_string" { .add(3, 3) .add(0x0f_ff_ff_ff, 0x0f_ff_ff_ff) .add(42, 42) - inspect( - map, - content="@immut/hashmap.of([(3, 3), (42, 42), (1, 1), (268435455, 268435455)])", - ) + let array = map.to_array() + assert_eq(map.to_string(), "@immut/hashmap.of(\{array})") } ///| @@ -163,10 +171,11 @@ test "to_array" { ///| test "from_iter multiple elements iter" { - inspect( - @hashmap.from_iter([(1, 1), (2, 2), (3, 3)].iter()), - content="@immut/hashmap.of([(3, 3), (2, 2), (1, 1)])", - ) + let map = @hashmap.from_iter([(1, 1), (2, 2), (3, 3)].iter()) + guard map is { 1: 1, 2: 2, 3: 3, .. } else { + fail("Map is not as expected: \{map}") + } + inspect(map.length(), content="3") } ///| diff --git a/immut/hashset/HAMT_test.mbt b/immut/hashset/HAMT_test.mbt index d761a84a1..95336d0e2 100644 --- a/immut/hashset/HAMT_test.mbt +++ b/immut/hashset/HAMT_test.mbt @@ -12,6 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +///| +fn[T : Hash + Eq] verify_content( + set : @hashset.HashSet[T], + expected : Array[T], +) -> Unit raise { + for item in expected { + assert_true(set.contains(item)) + } + assert_true(set.length() == expected.length()) +} + ///| test "Set" { let map = loop (0, @hashset.new()) { @@ -57,15 +68,21 @@ test "@hashset.remove" { ///| test "@hashset.iter" { let data = @hashset.of(["a", "b", "d", "e", "f"]) - let mut s = "" - data.iter().each(k => s += " \{k}") - inspect(s, content=" e f b a d") + let array = data.iter().collect() + array.sort() + inspect( + array, + content=( + #|["a", "b", "d", "e", "f"] + ), + ) } ///| test "@hashset.to_string" { let set = @hashset.new().add(1).add(3).add(0x0f_ff_ff_ff).add(42) - inspect(set, content="@immut/hashset.of([3, 42, 1, 268435455])") + let content = set.iter().collect() + assert_eq(set.to_string(), "@immut/hashset.of(\{content})") } ///| @@ -127,10 +144,9 @@ test "@hashset.iter" { ///| test "from_iter multiple elements iter" { - inspect( - @hashset.from_iter([1, 2, 3].iter()), - content="@immut/hashset.of([3, 2, 1])", - ) + let data = [1, 2, 3] + let set = @hashset.from_iter(data.iter()) + verify_content(set, data) } ///| @@ -178,8 +194,9 @@ test "remove non-existent key from leaf" { ///| test "from_array - non-empty array" { - let set = @hashset.from_array([1, 2, 3]) - inspect(set, content="@immut/hashset.of([3, 2, 1])") + let data = [1, 2, 3] + let set = @hashset.from_array(data) + verify_content(set, data) } ///| @@ -327,12 +344,12 @@ test "@hashset.difference patch" { let set3 = @hashset.of([]) let set4 = @hashset.of([1, 2, 3, 4, 5, 6]) let leaf = @hashset.of([1]) - inspect(set1.difference(set2), content="@immut/hashset.of([2, 1])") - inspect(set1.difference(set3), content="@immut/hashset.of([3, 2, 4, 1])") - inspect(set3.difference(set1), content="@immut/hashset.of([])") - inspect(set2.difference(set4), content="@immut/hashset.of([])") - inspect(leaf.difference(set1), content="@immut/hashset.of([])") - inspect(set1.difference(leaf), content="@immut/hashset.of([3, 2, 4])") + verify_content(set1.difference(set2), [1, 2]) + verify_content(set1.difference(set3), [1, 2, 3, 4]) + verify_content(set3.difference(set1), []) + verify_content(set2.difference(set4), []) + verify_content(leaf.difference(set1), []) + verify_content(set1.difference(leaf), [2, 3, 4]) } ///| @@ -343,13 +360,13 @@ test "@hashset.intersection patch" { let set4 = @hashset.of([1, 2, 3, 4, 5, 6]) let set5 = @hashset.of([7, 8, 9, 10]) let leaf = @hashset.of([1]) - inspect(set1.intersection(set2), content="@immut/hashset.of([3, 4])") - inspect(set1.intersection(set3), content="@immut/hashset.of([])") - inspect(set3.intersection(set1), content="@immut/hashset.of([])") - inspect(set2.intersection(set4), content="@immut/hashset.of([5, 3, 6, 4])") - inspect(leaf.intersection(set1), content="@immut/hashset.of([1])") - inspect(set1.intersection(leaf), content="@immut/hashset.of([1])") - inspect(set5.intersection(set1), content="@immut/hashset.of([])") + verify_content(set1.intersection(set2), [3, 4]) + verify_content(set1.intersection(set3), []) + verify_content(set3.intersection(set1), []) + verify_content(set2.intersection(set4), [3, 4, 5, 6]) + verify_content(leaf.intersection(set1), [1]) + verify_content(set1.intersection(leaf), [1]) + verify_content(set5.intersection(set1), []) } ///| diff --git a/int/int_test.mbt b/int/int_test.mbt index fd532c4e1..e3bfcba50 100644 --- a/int/int_test.mbt +++ b/int/int_test.mbt @@ -26,18 +26,24 @@ test "overflow" { ///| test "hash" { - inspect((0).hash(), content="0") - inspect((1).hash(), content="69681622") - inspect((2).hash(), content="-236984087") - inspect((3).hash(), content="-1057966777") - inspect((4).hash(), content="-744398955") - inspect((5).hash(), content="-482482911") - inspect((6).hash(), content="-1506254959") - inspect((7).hash(), content="1327878809") - inspect((8).hash(), content="1120898975") - inspect((9).hash(), content="1567958539") - inspect((0x7fffffff).hash(), content="943411498") - inspect(Hash::hash(0x7fffffff + 1), content="963800214") + inspect(Hasher::new(seed=0)..combine(0).finalize(), content="148298089") + inspect(Hasher::new(seed=0)..combine(1).finalize(), content="-205818221") + inspect(Hasher::new(seed=0)..combine(2).finalize(), content="527729046") + inspect(Hasher::new(seed=0)..combine(3).finalize(), content="-2136036233") + inspect(Hasher::new(seed=0)..combine(4).finalize(), content="-43556126") + inspect(Hasher::new(seed=0)..combine(5).finalize(), content="-2146748363") + inspect(Hasher::new(seed=0)..combine(6).finalize(), content="-1455877728") + inspect(Hasher::new(seed=0)..combine(7).finalize(), content="-427046664") + inspect(Hasher::new(seed=0)..combine(8).finalize(), content="-1629857596") + inspect(Hasher::new(seed=0)..combine(9).finalize(), content="970764038") + inspect( + Hasher::new(seed=0)..combine(0x7fffffff).finalize(), + content="1225377669", + ) + inspect( + Hasher::new(seed=0)..combine(0x7fffffff + 1).finalize(), + content="1509677505", + ) } ///| diff --git a/int16/int16.mbt b/int16/int16.mbt index 4feb96301..b08c674f7 100644 --- a/int16/int16.mbt +++ b/int16/int16.mbt @@ -53,11 +53,6 @@ pub impl Compare for Int16 with compare(self, that) { self.to_int().compare(that.to_int()) } -///| -pub impl Hash for Int16 with hash(self) { - self.to_int() -} - ///| pub impl Hash for Int16 with hash_combine(self, hasher) { hasher.combine_int(self.to_int()) diff --git a/int16/int16_test.mbt b/int16/int16_test.mbt index d001ca711..658efc83f 100644 --- a/int16/int16_test.mbt +++ b/int16/int16_test.mbt @@ -174,13 +174,13 @@ test "Int16::compare" { ///| test "Int16::hash" { - inspect(Int16::hash(1), content="1") - inspect(Int16::hash(-1), content="-1") - inspect(Int16::hash(32767), content="32767") - inspect(Int16::hash(-32768), content="-32768") - inspect(Int16::hash(0), content="0") - inspect(Int16::hash(12345), content="12345") - inspect(Int16::hash(-12345), content="-12345") + inspect(Hasher::new(seed=0)..combine(1).finalize(), content="-205818221") + inspect(Hasher::new(seed=0)..combine(-1).finalize(), content="67608159") + inspect(Hasher::new(seed=0)..combine(32767).finalize(), content="929308064") + inspect(Hasher::new(seed=0)..combine(-32768).finalize(), content="-841328155") + inspect(Hasher::new(seed=0)..combine(0).finalize(), content="148298089") + inspect(Hasher::new(seed=0)..combine(12345).finalize(), content="-393624961") + inspect(Hasher::new(seed=0)..combine(-12345).finalize(), content="1221658118") } ///| diff --git a/int64/int64.mbt b/int64/int64.mbt index 78f3459e3..6c4b8547e 100644 --- a/int64/int64.mbt +++ b/int64/int64.mbt @@ -78,3 +78,8 @@ pub fn to_be_bytes(self : Int64) -> Bytes { pub fn to_le_bytes(self : Int64) -> Bytes { self.reinterpret_as_uint64().to_le_bytes() } + +///| +pub impl Hash for Int64 with hash_combine(self, hasher) { + hasher.combine_int64(self) +} diff --git a/int64/moon.pkg.json b/int64/moon.pkg.json index 09441e7ef..ef526d170 100644 --- a/int64/moon.pkg.json +++ b/int64/moon.pkg.json @@ -1,3 +1,4 @@ { - "import": ["moonbitlang/core/builtin", "moonbitlang/core/bytes", "moonbitlang/core/uint", "moonbitlang/core/uint64"] + "import": ["moonbitlang/core/builtin", "moonbitlang/core/uint64"], + "test-import": ["moonbitlang/core/bytes"] } diff --git a/int64/xxhash.mbt b/int64/xxhash.mbt deleted file mode 100644 index 1f4af684d..000000000 --- a/int64/xxhash.mbt +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2025 International Digital Economy Academy -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Not needed for 8 bytes data -// let gPRIME1 = 0x9E3779B1 - -///| -let gPRIME2 = 0x85EBCA77U - -///| -let gPRIME3 = 0xC2B2AE3DU - -///| -let gPRIME4 = 0x27D4EB2FU - -///| -let gPRIME5 = 0x165667B1U - -// https://github.com/Cyan4973/xxHash/blob/dev/doc/xxhash_spec.md#xxh32-algorithm-description -// For a more readable version, see bytes/xxhash.mbt - -///| -pub impl Hash for Int64 with hash(self) -> Int { - let mut input = self.reinterpret_as_uint64() - let seed = 0U - let mut acc = seed + gPRIME5 + 8 - let mut x = acc + input.to_uint() * gPRIME3 - let r = 17 - acc = ((x << r) | (x >> (32 - r))) * gPRIME4 - input = input >> 32 - x = acc + input.to_uint() * gPRIME3 - acc = ((x << r) | (x >> (32 - r))) * gPRIME4 - input = input >> 32 - acc = acc ^ (acc >> 15) - acc *= gPRIME2 - acc = acc ^ (acc >> 13) - acc *= gPRIME3 - acc = acc ^ (acc >> 16) - acc.reinterpret_as_int() -} - -///| -pub impl Hash for Int64 with hash_combine(self, hasher) { - hasher.combine_int64(self) -} - -///| -fn slow_hash(self : Int64) -> Int { - let self = self.reinterpret_as_uint64() - let b : Bytes = [ - (self & 0xFF).to_byte(), - ((self >> 8) & 0xFF).to_byte(), - ((self >> 16) & 0xFF).to_byte(), - ((self >> 24) & 0xFF).to_byte(), - ((self >> 32) & 0xFF).to_byte(), - ((self >> 40) & 0xFF).to_byte(), - ((self >> 48) & 0xFF).to_byte(), - ((self >> 56) & 0xFF).to_byte(), - ] - b.hash() -} - -///| -test "int64 hash" { - let i0 = 9_223_372_036_854_775_807L // INT64_MAX - let i1 = -9_223_372_036_854_775_808L // INT64_MIN - let i2 = gPRIME2.to_int64() - let i3 = gPRIME3.to_int64() - let i4 = gPRIME4.to_int64() - let i5 = gPRIME5.to_int64() - let i6 = 0L - inspect(i0.hash().to_string(), content=i0.slow_hash().to_string()) - inspect(i1.hash().to_string(), content=i1.slow_hash().to_string()) - inspect(i2.hash().to_string(), content=i2.slow_hash().to_string()) - inspect(i3.hash().to_string(), content=i3.slow_hash().to_string()) - inspect(i4.hash().to_string(), content=i4.slow_hash().to_string()) - inspect(i5.hash().to_string(), content=i5.slow_hash().to_string()) - inspect(i6.hash().to_string(), content=i6.slow_hash().to_string()) -} diff --git a/uint16/README.mbt.md b/uint16/README.mbt.md index 8bcfce43f..8d5ee2bf3 100644 --- a/uint16/README.mbt.md +++ b/uint16/README.mbt.md @@ -1,6 +1,8 @@ # `uint16` -The `moonbitlang/core/uint16` package provides functionality for working with 16-bit unsigned integers. This package includes constants, operators, and conversions for UInt16 values. +The `moonbitlang/core/uint16` package provides functionality for working with +16-bit unsigned integers. This package includes constants, operators, and +conversions for UInt16 values. ## Constants @@ -110,7 +112,9 @@ test "UInt16 default value" { // Hash support is available via .hash() let value : UInt16 = 42 - inspect(value.hash(), content="42") + // This may be random per process + let _ = value.hash() + } ``` diff --git a/uint16/uint16.mbt b/uint16/uint16.mbt index 4adba1486..e414352f8 100644 --- a/uint16/uint16.mbt +++ b/uint16/uint16.mbt @@ -53,11 +53,6 @@ pub impl Compare for UInt16 with compare(self, that) { self.to_int().compare(that.to_int()) } -///| -pub impl Hash for UInt16 with hash(self) { - self.to_int() -} - ///| pub impl Hash for UInt16 with hash_combine(self, hasher) { hasher.combine_int(self.to_int()) diff --git a/uint16/uint16_test.mbt b/uint16/uint16_test.mbt index 94d6b7d7e..6f90fd377 100644 --- a/uint16/uint16_test.mbt +++ b/uint16/uint16_test.mbt @@ -174,9 +174,9 @@ test "UInt16::compare" { ///| test "UInt16::hash" { - inspect(UInt16::hash(0), content="0") - inspect(UInt16::hash(123), content="123") - inspect(UInt16::hash(65535), content="65535") + inspect(Hasher::new(seed=0)..combine(0).finalize(), content="148298089") + inspect(Hasher::new(seed=0)..combine(123).finalize(), content="-868461090") + inspect(Hasher::new(seed=0)..combine(65535).finalize(), content="-888052495") } ///| @@ -361,7 +361,7 @@ test "UInt16::to_uint64" { ///| test "UInt16::hash_combine" { - let hasher = @builtin.Hasher::new() + let hasher = @builtin.Hasher::new(seed=0) let value : UInt16 = 42 value.hash_combine(hasher) inspect(hasher.finalize(), content="1161967057") diff --git a/uint64/README.mbt.md b/uint64/README.mbt.md index 971df5a5c..08522fa58 100644 --- a/uint64/README.mbt.md +++ b/uint64/README.mbt.md @@ -137,7 +137,7 @@ test "UInt64 default value" { // Hash support is available via .hash() let value : UInt64 = 42UL - inspect(value.hash(), content="-1962516083") + inspect(Hasher::new(seed=0)..combine(value).finalize(), content="-1962516083") } ``` diff --git a/unit/unit.mbt b/unit/unit.mbt index 530bf24b0..c222f5243 100644 --- a/unit/unit.mbt +++ b/unit/unit.mbt @@ -17,11 +17,6 @@ pub fn Unit::to_string(_ : Self) -> String { "()" } -///| -pub impl Hash for Unit with hash(_) -> Int { - 0 -} - ///| pub impl Hash for Unit with hash_combine(_, hasher) -> Unit { hasher.combine_unit() diff --git a/unit/unit_test.mbt b/unit/unit_test.mbt index f3b92c888..0d74c0f4d 100644 --- a/unit/unit_test.mbt +++ b/unit/unit_test.mbt @@ -43,7 +43,7 @@ test { ///| test { let unit = () - inspect(unit.hash(), content="0") + inspect(Hasher::new(seed=0)..combine(unit).finalize(), content="148298089") } ///|