diff --git a/.github/workflows/github-packages-publish.yml b/.github/workflows/github-packages-publish.yml index e93a4af..6601e5d 100644 --- a/.github/workflows/github-packages-publish.yml +++ b/.github/workflows/github-packages-publish.yml @@ -8,9 +8,9 @@ name: Publish Packages to GitHub Packages with Gradle on: - push: - branches: - - main + release: + types: + - published jobs: build: @@ -51,19 +51,17 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: '17' - distribution: 'corretto' - server-id: github # Value of the distributionManagement/repository/id field of the pom.xml - settings-path: ${{ github.workspace }} # location for the settings.xml file + java-version: "17" + distribution: "corretto" - name: Setup Gradle - uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 + uses: gradle/actions/setup-gradle@v4 - name: Grant Execution Authority to Gradlew run: chmod +x ./gradlew - name: Build with Gradle - run: ./gradlew build + run: ./gradlew build -PartefactVersion=${{ github.event.release.tag_name }} # Overwrite artefactVersion - name: Publish to Maven Central run: ./gradlew publish diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/BranchUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/BranchUtil.java index c6ad92e..0d339d6 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/BranchUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/BranchUtil.java @@ -17,7 +17,6 @@ package com.onixbyte.devkit.utils; -import java.util.Arrays; import java.util.Objects; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -89,62 +88,50 @@ private BranchUtil(boolean result) { * Creates a {@code BranchUtil} instance to evaluate a logical OR operation on the provided * boolean expressions. * - * @param booleans the boolean expressions to be evaluated - * @param the type of the result to be handled by the methods + * @param values the boolean expressions to be evaluated + * @param the type of the result to be handled by the methods * @return a {@code BranchUtil} instance representing the result of the logical OR operation */ - public static BranchUtil or(Boolean... booleans) { - var result = Arrays.stream(booleans) - .filter(Objects::nonNull) - .anyMatch(Boolean::booleanValue); - return new BranchUtil<>(result); + public static BranchUtil or(Boolean... values) { + return new BranchUtil<>(BoolUtil.or(values)); } /** * Creates a {@code BranchUtil} instance to evaluate a logical AND operation on the provided * boolean expressions. * - * @param booleans the boolean expressions to be evaluated - * @param the type of the result to be handled by the methods + * @param values the boolean expressions to be evaluated + * @param the type of the result to be handled by the methods * @return a {@code BranchUtil} instance representing the result of the logical AND operation */ - public static BranchUtil and(Boolean... booleans) { - var result = Arrays.stream(booleans) - .filter(Objects::nonNull) - .allMatch(Boolean::booleanValue); - return new BranchUtil<>(result); + public static BranchUtil and(Boolean... values) { + return new BranchUtil<>(BoolUtil.and(values)); } /** * Creates a {@code BranchUtil} instance to evaluate a logical OR operation on the provided * boolean suppliers. * - * @param booleanSuppliers the boolean suppliers to be evaluated - * @param the type of the result to be handled by the methods + * @param valueSuppliers the boolean suppliers to be evaluated + * @param the type of the result to be handled by the methods * @return a {@code BranchUtil} instance representing the result of the * logical OR operation */ - public static BranchUtil or(BooleanSupplier... booleanSuppliers) { - var result = Arrays.stream(booleanSuppliers) - .filter(Objects::nonNull) - .anyMatch(BooleanSupplier::getAsBoolean); - return new BranchUtil<>(result); + public static BranchUtil or(BooleanSupplier... valueSuppliers) { + return new BranchUtil<>(BoolUtil.or(valueSuppliers)); } /** * Creates a {@code BranchUtil} instance to evaluate a logical AND operation on the provided * boolean suppliers. * - * @param booleanSuppliers the boolean suppliers to be evaluated - * @param the type of the result to be handled by the methods + * @param valueSuppliers the boolean suppliers to be evaluated + * @param the type of the result to be handled by the methods * @return a {@code BranchUtil} instance representing the result of the * logical AND operation */ - public static BranchUtil and(BooleanSupplier... booleanSuppliers) { - var result = Arrays.stream(booleanSuppliers) - .filter(Objects::nonNull) - .allMatch(BooleanSupplier::getAsBoolean); - return new BranchUtil<>(result); + public static BranchUtil and(BooleanSupplier... valueSuppliers) { + return new BranchUtil<>(BoolUtil.and(valueSuppliers)); } /** diff --git a/gradle.properties b/gradle.properties index ecf33a6..435e1d8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,18 +15,17 @@ # limitations under the License. # -jacksonVersion=2.17.2 +jacksonVersion=2.18.0 javaJwtVersion=4.4.0 -jjwtVersion=0.12.6 -junitVersion=5.10.2 -logbackVersion=1.5.4 -lombokVersion=1.18.30 -slf4jVersion=2.0.9 -springVersion=6.1.3 -springBootVersion=3.2.3 +junitVersion=5.11.2 +logbackVersion=1.5.10 +lombokVersion=1.18.34 +slf4jVersion=2.0.16 +springVersion=6.1.13 +springBootVersion=3.3.4 buildGroupId=com.onixbyte -buildVersion=1.6.4 +buildVersion=1.6.5 projectUrl=https://onixbyte.com/JDevKit projectGithubUrl=https://github.com/OnixByte/JDevKit licenseName=The Apache License, Version 2.0 diff --git a/num4j/build.gradle.kts b/num4j/build.gradle.kts new file mode 100644 index 0000000..d95c6ac --- /dev/null +++ b/num4j/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("java") +} + +group = "com.onixbyte" +version = "unspecified" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ChainedCalcUtil.java b/num4j/src/main/java/com/onixbyte/nums/ChainedCalcUtil.java similarity index 99% rename from devkit-utils/src/main/java/com/onixbyte/devkit/utils/ChainedCalcUtil.java rename to num4j/src/main/java/com/onixbyte/nums/ChainedCalcUtil.java index 11bcc47..27242c9 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ChainedCalcUtil.java +++ b/num4j/src/main/java/com/onixbyte/nums/ChainedCalcUtil.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package com.onixbyte.devkit.utils; +package com.onixbyte.nums; import lombok.Getter; @@ -87,7 +87,7 @@ * * @author sunzsh * @version 1.1.0 - * @see java.math.BigDecimal + * @see BigDecimal * @since 1.0.0 */ @Getter diff --git a/num4j/src/main/java/com/onixbyte/nums/PercentileCalculator.java b/num4j/src/main/java/com/onixbyte/nums/PercentileCalculator.java new file mode 100644 index 0000000..0992f24 --- /dev/null +++ b/num4j/src/main/java/com/onixbyte/nums/PercentileCalculator.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 OnixByte. + * + * 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. + */ +package com.onixbyte.nums; + +import com.onixbyte.nums.model.QuartileBounds; + +import java.util.List; + +/** + * A utility class that provides methods for calculating percentiles and interquartile range (IQR) bounds + * for a dataset. + *

+ * This class contains static methods to: + *

    + *
  • Calculate a specified percentile from a list of double values using linear interpolation.
  • + *
  • Calculate interquartile bounds (Q1, Q3) and the corresponding lower and upper bounds, + * which can be used to identify outliers in the dataset.
  • + *
+ *

+ * This class is final, meaning it cannot be subclassed, and it only contains static methods, + * so instances of the class cannot be created. + *

+ *

Example usage:

+ *
+ * {@code
+ * List data = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);
+ * Double percentileValue = PercentileCalculator.calculatePercentile(data, 50.0);  // Calculates median
+ * QuartileBounds bounds = PercentileCalculator.calculatePercentileBounds(data);   // Calculates IQR bounds
+ * }
+ * 
+ * + * @author zihluwang + * @version 1.6.5 + * @since 1.6.5 + */ +public final class PercentileCalculator { + + /** + * Calculates the specified percentile from a list of values. + *

+ * This method takes a list of double values and calculates the given percentile using linear interpolation between + * the two closest ranks. The list is first sorted in ascending order, and the specified percentile is + * then calculated. + * + * @param values a list of {@code Double} values from which the percentile is calculated. + * @param percentile a {@code Double} representing the percentile to be calculated (e.g., 50.0 for the median) + * @return a {@code Double} value representing the calculated percentile + */ + public static Double calculatePercentile(List values, Double percentile) { + var sorted = values.stream().sorted().toList(); + if (sorted.isEmpty()) { + throw new IllegalArgumentException("Unable to sort an empty list."); + } + + var rank = percentile / 100. * (sorted.size() - 1); + var lowerIndex = (int) Math.floor(rank); + var upperIndex = (int) Math.ceil(rank); + var weight = rank - lowerIndex; + + return sorted.get(lowerIndex) * (1 - weight) + sorted.get(upperIndex) * weight; + } + + /** + * Calculates the interquartile range (IQR) and the corresponding lower and upper bounds + * based on the first (Q1) and third (Q3) quartiles of a dataset. + *

+ * This method takes a list of double values, calculates the first quartile (Q1), + * the third quartile (Q3), and the interquartile range (IQR). Using the IQR, it computes + * the lower and upper bounds, which can be used to detect outliers in the dataset. + * The lower bound is defined as {@code Q1 - 1.5 * IQR}, and the upper bound is defined as + * {@code Q3 + 1.5 * IQR}. + * + * @param data a list of {@code Double} values for which the quartile bounds will be calculated + * @return a {@code QuartileBounds} object containing the calculated lower and upper bounds + */ + public static QuartileBounds calculatePercentileBounds(List data) { + var sorted = data.stream().sorted().toList(); + var Q1 = calculatePercentile(sorted, 25.); + var Q3 = calculatePercentile(sorted, 75.); + + var IQR = Q3 - Q1; + + var lowerBound = Q1 - 1.5 * IQR; + var upperBound = Q3 + 1.5 * IQR; + + return QuartileBounds.builder() + .upperBound(upperBound) + .lowerBound(lowerBound) + .build(); + } + +} diff --git a/num4j/src/main/java/com/onixbyte/nums/model/QuartileBounds.java b/num4j/src/main/java/com/onixbyte/nums/model/QuartileBounds.java new file mode 100644 index 0000000..3936e61 --- /dev/null +++ b/num4j/src/main/java/com/onixbyte/nums/model/QuartileBounds.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2024-2024 OnixByte. + * + * 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. + */ + +package com.onixbyte.nums.model; + +/** + * A record representing the quartile bounds of a dataset. + *

+ * This class encapsulates the lower and upper bounds of a dataset, which are typically used for detecting outliers in + * the data. The bounds are calculated based on the interquartile range (IQR) of the dataset. Values below the lower + * bound or above the upper bound may be considered outliers. + *

+ * Quartile bounds consist of: + *

    + *
  • {@code lowerBound} - The lower bound of the dataset, typically {@code Q1 - 1.5 * IQR}.
  • + *
  • {@code upperBound} - The upper bound of the dataset, typically {@code Q3 + 1.5 * IQR}.
  • + *
+ *

+ *

+ * Example usage: + *

+ * QuartileBounds bounds = QuartileBounds.builder()
+ *     .lowerBound(1.5)
+ *     .upperBound(7.5)
+ *     .build();
+ * 
+ * + * @param upperBound the upper bound of the dataset + * @param lowerBound the lower bound of the dataset + * @author zihluwang + * @version 1.6.5 + * @since 1.6.5 + */ +public record QuartileBounds( + Double upperBound, + Double lowerBound +) { + + /** + * Creates a new {@link Builder} instance for building a {@code QuartileBounds} object. + *

+ * The {@link Builder} pattern is used to construct the {@code QuartileBounds} object with optional values for the + * upper and lower bounds. + *

+ * + * @return a new instance of the {@link Builder} class + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder class for constructing instances of the {@code QuartileBounds} record. + *

+ * The {@link Builder} pattern allows for the step-by-step construction of a {@code QuartileBounds} object, + * providing a flexible way to set values for the lower and upper bounds. Once the builder has the required values, + * the {@link #build()} method creates and returns a new {@code QuartileBounds} object. + *

+ *

+ * Example usage: + *

+     * {@code
+     * QuartileBounds bounds = QuartileBounds.builder()
+     *     .lowerBound(1.5)
+     *     .upperBound(7.5)
+     *     .build();
+     * }
+     * 
+ */ + public static class Builder { + private Double upperBound; + private Double lowerBound; + + /** + * Private constructor for {@code Builder}, ensuring it can only be instantiated through the + * {@link QuartileBounds#builder()} method. + */ + private Builder() { + } + + /** + * Sets the upper bound for the {@code QuartileBounds}. + * + * @param upperBound the upper bound of the dataset + * @return the current {@code Builder} instance, for method chaining + */ + public Builder upperBound(Double upperBound) { + this.upperBound = upperBound; + return this; + } + + /** + * Sets the lower bound for the {@code QuartileBounds}. + * + * @param lowerBound the lower bound of the dataset + * @return the current {@code Builder} instance, for method chaining + */ + public Builder lowerBound(Double lowerBound) { + this.lowerBound = lowerBound; + return this; + } + + /** + * Builds and returns a new {@code QuartileBounds} instance with the specified upper and lower bounds. + * + * @return a new {@code QuartileBounds} object containing the specified bounds + */ + public QuartileBounds build() { + return new QuartileBounds(upperBound, lowerBound); + } + } + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index d39293c..b840c05 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,3 +28,4 @@ include( "simple-jwt-spring-boot-starter", "property-guard-spring-boot-starter" ) +include("num4j")