-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from teogor/feature/hashcode-builder
Introduce HashCodeBuilder Utility and Lazy HashCode Functions
- Loading branch information
Showing
8 changed files
with
265 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
public final class dev/teogor/crosslens/core/HashCodeBuilder { | ||
public fun <init> ()V | ||
public fun <init> (II)V | ||
public synthetic fun <init> (IIILkotlin/jvm/internal/DefaultConstructorMarker;)V | ||
public final fun append (Ljava/lang/Object;)Ldev/teogor/crosslens/core/HashCodeBuilder; | ||
public final fun build ()I | ||
} | ||
|
||
public final class dev/teogor/crosslens/core/HashCodeBuilderKt { | ||
public static final fun buildHashCode (IILkotlin/jvm/functions/Function1;)I | ||
public static synthetic fun buildHashCode$default (IILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I | ||
public static final fun buildLazyHashCode (Lkotlin/jvm/functions/Function1;)Lkotlin/Lazy; | ||
public static final fun lazyHashCode (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
115 changes: 115 additions & 0 deletions
115
crosslens-core/src/commonMain/kotlin/dev/teogor/crosslens/core/HashCodeBuilder.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* | ||
* Copyright 2024 Teogor (Teodor Grigor) | ||
* | ||
* 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 | ||
* | ||
* https://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. | ||
*/ | ||
|
||
package dev.teogor.crosslens.core | ||
|
||
import kotlin.contracts.ExperimentalContracts | ||
import kotlin.contracts.InvocationKind | ||
import kotlin.contracts.contract | ||
|
||
/** | ||
* A utility class for building hash codes using a custom algorithm. | ||
* | ||
* @property initialValue The initial value for the hash code computation. Defaults to `31`. | ||
* @property multiplier The multiplier used to calculate the hash code. Defaults to `31`. | ||
* | ||
* This class allows you to append values to a hash code and compute the final hash code using | ||
* a specified multiplier and initial value. | ||
* | ||
* @see [buildHashCode] for creating a hash code using a builder action. | ||
*/ | ||
public class HashCodeBuilder( | ||
private val initialValue: Int = 31, | ||
private val multiplier: Int = 31, | ||
) { | ||
private var hashCode = initialValue | ||
|
||
/** | ||
* Appends the hash code of the given value to the current hash code. | ||
* | ||
* @param value The value to append. If the value is an array, its deep hash code is used. | ||
* Null values are treated as `0`. If the hash code of the value is `0`, it is | ||
* not included. | ||
* @return The current instance of [HashCodeBuilder], for chaining. | ||
* | ||
* @see [buildHashCode] for an example of how to use this method. | ||
*/ | ||
public fun append(value: Any?): HashCodeBuilder = | ||
apply { | ||
val currentHashCode = | ||
when (value) { | ||
is Array<*> -> value.contentDeepHashCode() | ||
else -> value?.hashCode() ?: 0 | ||
} | ||
if (currentHashCode != 0) { | ||
hashCode = multiplier * hashCode + currentHashCode | ||
} | ||
} | ||
|
||
/** | ||
* Computes and returns the final hash code. | ||
* | ||
* @return The final hash code computed by the builder. | ||
* | ||
* @see [buildHashCode] for an example of how to use this method. | ||
*/ | ||
public fun build(): Int = hashCode | ||
} | ||
|
||
/** | ||
* Creates a hash code using a custom builder action. | ||
* | ||
* @param initialValue The initial value for the hash code computation. Defaults to `31`. | ||
* @param multiplier The multiplier used in hash code calculation. Defaults to `31`. | ||
* @param builderAction A lambda function to build the hash code using [HashCodeBuilder]. | ||
* | ||
* @return The computed hash code. | ||
* | ||
* @see [HashCodeBuilder] for details on how hash codes are computed. | ||
*/ | ||
@OptIn(ExperimentalContracts::class) | ||
public inline fun buildHashCode( | ||
initialValue: Int = 31, | ||
multiplier: Int = 31, | ||
builderAction: HashCodeBuilder.() -> Unit, | ||
): Int { | ||
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } | ||
return HashCodeBuilder(initialValue, multiplier).apply(builderAction).build() | ||
} | ||
|
||
/** | ||
* Creates a lazy hash code using an initializer function. | ||
* | ||
* @param initializer A function that returns the hash code value. | ||
* | ||
* @return A [Lazy] instance that computes the hash code when accessed. | ||
* | ||
* @see [buildLazyHashCode] for creating a lazy hash code using a [HashCodeBuilder]. | ||
*/ | ||
public fun lazyHashCode(initializer: () -> Int): Lazy<Int> = | ||
lazy(LazyThreadSafetyMode.PUBLICATION) { initializer() } | ||
|
||
/** | ||
* Creates a lazy hash code using a builder initializer function. | ||
* | ||
* @param initializer A lambda function to build the hash code using [HashCodeBuilder]. | ||
* | ||
* @return A [Lazy] instance that computes the hash code when accessed. | ||
* | ||
* @see [HashCodeBuilder] for details on hash code computation. | ||
*/ | ||
public inline fun buildLazyHashCode(crossinline initializer: HashCodeBuilder.() -> Unit): Lazy<Int> = | ||
lazyHashCode { HashCodeBuilder().apply(initializer).build() } |
17 changes: 0 additions & 17 deletions
17
crosslens-core/src/commonMain/kotlin/dev/teogor/crosslens/core/root.kt
This file was deleted.
Oops, something went wrong.
105 changes: 105 additions & 0 deletions
105
crosslens-core/src/commonTest/kotlin/dev/teogor/crosslens/core/HashCodeBuilderTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
* Copyright 2024 Teogor (Teodor Grigor) | ||
* | ||
* 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 | ||
* | ||
* https://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. | ||
*/ | ||
|
||
package dev.teogor.crosslens.core | ||
|
||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
|
||
class HashCodeBuilderTest { | ||
@Test | ||
fun initialHashCode_shouldBe31() { | ||
val builder = | ||
HashCodeBuilder() | ||
.build() | ||
assertEquals(31, builder) | ||
} | ||
|
||
@Test | ||
fun appendNullValue_shouldResultInInitialHashCode() { | ||
val hashCodeActual = | ||
buildHashCode { | ||
append(null) | ||
} | ||
val hashCodeExpected = 31 | ||
assertEquals( | ||
expected = hashCodeExpected, | ||
actual = hashCodeActual, | ||
message = "Null value should result in initial hash code (31)", | ||
) | ||
} | ||
|
||
@Test | ||
fun appendSingleValue_shouldCalculateHashCodeCorrectly() { | ||
val hashCodeActual = | ||
buildHashCode { | ||
append("test") | ||
} | ||
val hashCodeExpected = 31 * 31 + "test".hashCode() | ||
assertEquals( | ||
expected = hashCodeExpected, | ||
actual = hashCodeActual, | ||
message = "Hash code should be calculated using initial value and string's hash code", | ||
) | ||
} | ||
|
||
@Test | ||
fun appendMultipleValues_shouldCalculateHashCodeCorrectly() { | ||
val hashCodeActual = | ||
buildHashCode { | ||
append("test") | ||
append(123) | ||
append(45.67) | ||
} | ||
val hashCodeExpected = | ||
( | ||
(31 * 31 + "test".hashCode()) * 31 + 123.hashCode() | ||
) * 31 + 45.67.hashCode() | ||
assertEquals( | ||
expected = hashCodeExpected, | ||
actual = hashCodeActual, | ||
) | ||
} | ||
|
||
@Test | ||
fun appendSameValueMultipleTimes_shouldCalculateHashCodeCorrectly() { | ||
val hashCodeActual = | ||
buildHashCode { | ||
append("same") | ||
append("same") | ||
append("same") | ||
} | ||
val hashCodeExpected = | ||
( | ||
(31 * 31 + "same".hashCode()) * 31 + "same".hashCode() | ||
) * 31 + "same".hashCode() | ||
assertEquals( | ||
expected = hashCodeExpected, | ||
actual = hashCodeActual, | ||
) | ||
} | ||
|
||
@Test | ||
fun hashCodeConsistency_shouldBeMaintained() { | ||
val builder = | ||
HashCodeBuilder() | ||
.append("consistent") | ||
.append("value") | ||
val hashCode1 = builder.build() | ||
val hashCode2 = builder.build() | ||
assertEquals(hashCode1, hashCode2) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters