diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..097f9f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..dbe09ad --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,81 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Build and Release + +on: + push: + branches: ['main'] + pull_request: + branches: [ 'main' ] + workflow_dispatch: + +permissions: + contents: read + packages: read + statuses: write # To report GitHub Actions status checks + actions: read # Needed for detection of GitHub Actions environment. + id-token: write # Needed for provenance signing and ID. + pull-requests: write + +jobs: + gradle-build: + name: Build with Gradle + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + architecture: x64 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: "8.10.2" + validate-wrappers: false + add-job-summary-as-pr-comment: 'always' + add-job-summary: 'always' + +# - name: Validate Gradle Wrapper +# uses: gradle/actions/wrapper-validation@v4 + + - name: Build with Gradle + run: gradle build + + - name: Upload build reports + uses: actions/upload-artifact@v4 + if: always() + with: + name: build-reports + path: '**/build/reports/' + + maven-build: + name: Build with Maven + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: 21 + distribution: 'temurin' + cache: maven + architecture: x64 + + - name: Build and Install + run: mvn -B clean install + + - name: Build, Package and Test + run: mvn -B jacoco:prepare-agent clean test package surefire-report:report jacoco:report \ No newline at end of file diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..03466cf --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,35 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable +# packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement +name: 'Dependency Review' +on: + pull_request: + branches: [ 'main' ] + +permissions: + contents: read + pull-requests: write + security-events: write + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout repository' + uses: actions/checkout@v4 + + - name: 'Dependency Review' + uses: actions/dependency-review-action@v4 + with: + comment-summary-in-pr: always + fail-on-severity: moderate + vulnerability-check: true + deny-licenses: GPL-1.0-or-later, LGPL-2.0-or-later + retry-on-snapshot-warnings: true + show-openssf-scorecard: true \ No newline at end of file diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index 0a23fb3..0000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,40 +0,0 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Java CI with Maven - -on: - push: - branches: [ "main" ] - -jobs: - build: - - runs-on: ubuntu-latest - permissions: - contents: read - packages: read - statuses: write # To report GitHub Actions status checks - actions: read # Needed for detection of GitHub Actions environment. - id-token: write # Needed for provenance signing and ID. - - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - cache: maven - - - name: Build with Maven - run: mvn -B clean verify --file pom.xml - - - name: Run Tests and Code-Coverage - run: mvn jacoco:prepare-agent clean test jacoco:report --file pom.xml \ No newline at end of file diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml new file mode 100644 index 0000000..9119d57 --- /dev/null +++ b/.mvn/extensions.xml @@ -0,0 +1,8 @@ + + + + co.leantechniques + maven-buildtime-extension + 3.0.5 + + \ No newline at end of file diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 0000000..773f974 --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1 @@ +-Xss16m -Xms256m -Xmx1024m -Djava.awt.headless=true \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..980eda4 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,40 @@ +pipeline { + agent any + options { + skipStagesAfterUnstable() + } + tools { + maven 'maven' + jdk 'jdk21' + } + stages { + stage('Verify') { + steps { + sh 'mvn --version' + sh 'java --version' + } + } + stage('Build') { + steps { + sh 'mvn -DskipTests clean install -B --no-transfer-progress' + } + } + + stage('Test') { + steps { + sh 'mvn test' + } + post { + always { + junit 'target/surefire-reports/*.xml' + } + } + } + + stage('Deliver') { + steps { + sh './jenkins/scripts/deliver.sh' + } + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index 3fa461c..4552985 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,8 @@ -# QuickStart-Coding-Challenges - -Quickstart Coding Challenges - +# Quickstart Coding Challenges [![Java CI with Maven](https://github.com/shortthirdman/QuickStart-Coding-Challenges/actions/workflows/maven.yml/badge.svg?event=workflow_dispatch)](https://github.com/shortthirdman/QuickStart-Coding-Challenges/actions/workflows/maven.yml) -![GitHub commit activity](https://img.shields.io/github/commit-activity/w/shortthirdman/QuickStart-Coding-Challenges) ![GitHub](https://img.shields.io/github/license/shortthirdman/QuickStart-Coding-Challenges) ![GitHub last commit](https://img.shields.io/github/last-commit/shortthirdman/QuickStart-Coding-Challenges) +![GitHub commit activity](https://img.shields.io/github/commit-activity/w/shortthirdman/QuickStart-Coding-Challenges) ![GitHub](https://img.shields.io/github/license/shortthirdman/QuickStart-Coding-Challenges) ![GitHub last commit](https://img.shields.io/github/last-commit/shortthirdman/QuickStart-Coding-Challenges) ![Maintenance](https://img.shields.io/maintenance/yes/2024) All Java solutions for various challenges presented during interviews for software development positions. @@ -16,16 +13,23 @@ All Java solutions for various challenges presented during interviews for softwa ## Requirements -- Apache Maven 3.9.x -- Java 21 -- JetBrains IntelliJ IDEA (Community/Ultimate) +- [Apache Maven 3.9.x](https://maven.apache.org/) +- [OpenJDK/Oracle Java 21](https://www.oracle.com/java/technologies/downloads/) +- [JetBrains IntelliJ IDEA (Community/Ultimate)](https://www.jetbrains.com/idea/) ## Run, Test and Code Coverage -`mvn clean install test` +```shell +mvn jacoco:prepare-agent clean test surefire-report:report jacoco:report +``` + +## Release and Tag + +```shell +mvn jacoco:prepare-agent clean test surefire-report:report jacoco:report -Prelease +``` -`mvn jacoco:prepare-agent clean test jacoco:report` ## Contributing diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..efa0099 --- /dev/null +++ b/build.gradle @@ -0,0 +1,180 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java library project to get you started. + * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.5/userguide/building_java_projects.html in the Gradle documentation. + * This project uses @Incubating APIs which are subject to change. + */ +plugins { + id "java" + id "jacoco" + id "eclipse" // optional (to generate Eclipse project files) + id "idea" // optional (to generate IntelliJ IDEA project files) + id "io.freefair.lombok" version "8.4" +} + +repositories { + mavenCentral() +} + +ext { + // Define versions + commonsLang3Version = '3.14.0' + gsonVersion = '2.10.1' + junitJupiterVersion = '5.10.2' + lombokVersion = '1.18.32' + assertjCoreVersion = '3.25.3' + junitPlatformVersion = '1.10.2' + instancioVersion = '5.2.1' +} + +dependencies { + implementation platform("org.junit:junit-bom:$junitJupiterVersion") + + implementation "org.apache.commons:commons-lang3:$commonsLang3Version" + implementation "commons-lang:commons-lang:2.6" + implementation "com.google.code.gson:gson:$gsonVersion" + implementation "org.projectlombok:lombok:$lombokVersion" + implementation "org.projectlombok:lombok-utils:1.18.12" + + compileOnly group: 'org.projectlombok', name: 'lombok', version: '$lombokVersion' + annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '$lombokVersion' + + testImplementation "org.assertj:assertj-core:$assertjCoreVersion" + testImplementation "org.junit.jupiter:junit-jupiter-api" + testImplementation "org.junit.jupiter:junit-jupiter-engine" + testImplementation "org.junit.jupiter:junit-jupiter-params" + + testImplementation "org.junit.platform:junit-platform-runner:$junitPlatformVersion" + testImplementation "org.junit.platform:junit-platform-suite-engine:$junitPlatformVersion" + testImplementation "org.instancio:instancio-junit:$instancioVersion" +} + +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } + jacoco { + enabled = true + destinationFile = layout.buildDirectory.file("jacoco/${name}.exec").get().asFile + includes = [] + excludes = [] + excludeClassLoaders = [] + includeNoLocationClasses = false + sessionId = "" + dumpOnExit = true + classDumpDir = null + output = JacocoTaskExtension.Output.FILE + address = "localhost" + port = 6300 + jmx = false + } + finalizedBy jacocoTestReport // report is always generated after tests run +} + +jacoco { + toolVersion = "0.8.9" + reportsDirectory = layout.buildDirectory.dir('jacoco') +} + +jacocoTestReport { + group = "reporting" + description = "Generate Jacoco coverage reports after running tests." + dependsOn test // tests are required to run before generating the report + reports { + xml.required = true + csv.required = true + html.required = true + html.outputLocation = layout.buildDirectory.dir('jacocoHtml') + } +} + +build.dependsOn jacocoTestReport + +jacocoTestCoverageVerification { + violationRules { + rule { + limit { + minimum = 0.65 + } + } + + failOnViolation true + + rule { + enabled = false + element = 'CLASS' + includes = ['com.shortthirdman.quickstart.*'] + excludes = ['com.shortthirdman.quickstart.common.*'] + + limit { + counter = 'LINE' + value = 'TOTALCOUNT' + maximum = 0.65 + } + } + } +} + +check.dependsOn jacocoTestCoverageVerification + +testing { + suites { + // Configure the built-in test suite + test { + // Use JUnit Jupiter test framework + useJUnitJupiter('5.10.2') + } + } +} + +group = "com.shortthirdman" +version = '1.0-SNAPSHOT' +sourceCompatibility = "21" +targetCompatibility = "21" + +compileJava { + options.incremental = true + options.fork = true + options.failOnError = false +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + //withJavadocJar() + withSourcesJar() +} + + +jar { + manifest { + attributes("Implementation-Title": "Gradle", + "Implementation-Version": archiveVersion) + } +} + +//tasks.withType(JavaCompile).configureEach { +// options.compilerArgs += ['-Xdoclint:none', '-Xlint:none', '-nowarn'] +//} + +tasks.register('performRelease') { + def isCI = providers.gradleProperty("isCI") + doLast { + if (isCI.present) { + println("Performing release actions") + } else { + throw new InvalidUserDataException("Cannot perform release outside of CI") + } + } +} + +tasks.register('showRepos') { + def repositoryNames = repositories.collect { it.name } + doLast { + println "All repos:" + println repositoryNames + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..46497f1 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,12 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties + +org.gradle.parallel=true +org.gradle.caching=false + +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.continue=true +org.gradle.daemon=true +org.gradle.java.installations.auto-download=false +org.gradle.java.installations.auto-detect=true +#org.gradle.java.home=C:\\Program Files\\Java\\jdk-20.0.2 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1af9e09 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1aa94a4 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/plugins.txt b/plugins.txt new file mode 100644 index 0000000..608eaa6 --- /dev/null +++ b/plugins.txt @@ -0,0 +1,5 @@ +git +docker +junit +credentials +blueocean \ No newline at end of file diff --git a/pom.xml b/pom.xml index 128853d..dee05ad 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,8 @@ com.shortthirdman quickstart-challenges 1.0.0-SNAPSHOT - Quickstart-Challenges + quickstart-challenges + Quickstart Coding Challenges http://www.example.com @@ -16,12 +17,14 @@ target/reports ${java.version} ${java.version} + true 5.10.2 3.14.0 1.18.32 2.10.1 3.25.3 0.8.9 + 5.2.1 @@ -86,6 +89,13 @@ 1.10.2 test + + + org.instancio + instancio-junit + ${instancio.version} + test + @@ -102,7 +112,7 @@ - release + ci false @@ -124,6 +134,7 @@ jar-no-fork test-jar-no-fork + aggregate @@ -136,6 +147,24 @@ + + org.apache.maven.plugins + maven-surefire-report-plugin + 3.5.0 + + true + false + + + + generate-surefire-reports + + report + + test + + + maven-compiler-plugin 3.13.0 @@ -163,10 +192,19 @@ maven-surefire-plugin 2.22.2 + + + + + ${project.build.sourceEncoding} brief true + true + + *com.shortthirdman.quickstart.common.* + junit.jupiter.extensions.autodetection.enabled = true @@ -182,6 +220,16 @@ 3.5.0 10 + true + plain + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.3.0 + + ${project.description} @@ -191,7 +239,10 @@ - ${project.name} ${project.version} + ${project.description} ${project.version} + + *com.shortthirdman.quickstart.common.* + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..6435d71 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,20 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.5/userguide/building_swift_projects.html in the Gradle documentation. + * This project uses @Incubating APIs which are subject to change. + */ + +pluginManagement { + repositories { + gradlePluginPortal() + } +} + +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' +} + +rootProject.name = 'QuickStart-Challenges' diff --git a/src/main/java/com/shortthirdman/quickstart/codesignal/CountDistinctSlices.java b/src/main/java/com/shortthirdman/quickstart/codesignal/CountDistinctSlices.java new file mode 100644 index 0000000..cdeaeb3 --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/codesignal/CountDistinctSlices.java @@ -0,0 +1,75 @@ +package com.shortthirdman.quickstart.codesignal; + +import java.util.Arrays; + +/** + * An integer M and a non-empty array A consisting of N non-negative integers are given. + * All integers in array A are less than or equal to M. + * A pair of integers (P, Q), such that 0 ≤ P ≤ Q < N, is called a slice of array A. + *

+ * The slice consists of the elements A[P], A[P + 1], ..., A[Q]. A distinct slice is a slice consisting of only unique numbers. That is, no individual number occurs more than once in the slice. + *

+ * For example, consider integer M = 6 and array A such that: + *
+ *     A[0] = 3
+ *     A[1] = 4
+ *     A[2] = 5
+ *     A[3] = 5
+ *     A[4] = 2
+ * 
+ * There are exactly nine distinct slices: + *
(0, 0), (0, 1), (0, 2), (1, 1), (1, 2), (2, 2), (3, 3), (3, 4) and (4, 4).
+ * + * The goal is to calculate the number of distinct slices. + * + * @author ShortThirdMan + */ +public class CountDistinctSlices { + + /** + * Given an integer M and a non-empty array A consisting of N integers, returns the number of distinct slices. + *
+ * If the number of distinct slices is greater than 1,000,000,000, the function should return 1,000,000,000. + * @param M integer + * @param A non-empty array + * @return number of distinct slices + */ + public int calculateDistinctSlices(int M, int[] A) { + int N = A.length; + if (N < 1 || N > 100000) { + throw new IllegalArgumentException("Array length should be between 1 and 100000"); + } + + if (M < 0 || M > 100000) { + throw new IllegalArgumentException("Integer M should be between 0 and 100000"); + } + + boolean isNotInRange = Arrays.stream(A).boxed().anyMatch(n -> n < 0 || n > M); + if (isNotInRange) { + throw new IllegalArgumentException("Each element in array should be between 0 and " + M); + } + + int[] lastSeen = new int[M + 1]; // To track the last occurrence of each element + int left = 0; + long distinctSlices = 0; + final int MOD = 1_000_000_007; + + // Traverse with the right pointer + for (int right = 0; right < N; right++) { + // If the current element has appeared before in the window, move 'left' + lastSeen[A[right]]++; + + // Move the 'left' pointer until the slice [left, right] is distinct + while (lastSeen[A[right]] > 1) { + lastSeen[A[left]]--; + left++; + } + + // Add the number of distinct slices ending at 'right' + distinctSlices = (distinctSlices + (right - left + 1)) % MOD; + } + + // Return the result (casting to int) + return (int) distinctSlices; + } +} diff --git a/src/main/java/com/shortthirdman/quickstart/codility/BagOfFruits.java b/src/main/java/com/shortthirdman/quickstart/codility/BagOfFruits.java new file mode 100644 index 0000000..bd5e893 --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/codility/BagOfFruits.java @@ -0,0 +1,39 @@ +package com.shortthirdman.quickstart.codility; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class BagOfFruits { + + public List countFruits(List fruits, long threshold) { + Map countingMap = fruits.stream() + .collect(Collectors.groupingBy(Function.identity(), + Collectors.counting())); + + return countingMap.entrySet() + .stream() + .filter(a -> a.getValue() > threshold) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + + public List countFruits(List fruits, int threshold) { + Map countingMap = new HashMap<>(); + for (String fruit : fruits) { + countingMap.put(fruit, countingMap.getOrDefault(fruit, 0) + 1); + } + + List result = new ArrayList<>(); + for (Map.Entry fruitCountMap : countingMap.entrySet()) { + if (fruitCountMap.getValue() > threshold) { + result.add(fruitCountMap.getKey()); + } + } + + return result; + } +} diff --git a/src/main/java/com/shortthirdman/quickstart/codility/GenerateAllSubstrings.java b/src/main/java/com/shortthirdman/quickstart/codility/GenerateAllSubstrings.java new file mode 100644 index 0000000..28f9ec5 --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/codility/GenerateAllSubstrings.java @@ -0,0 +1,48 @@ +package com.shortthirdman.quickstart.codility; + +import java.util.ArrayList; +import java.util.List; + +public class GenerateAllSubstrings { + + /** + * @param inputString the input string + * @param curr the current string + * @param startPos the start position + * @param glRes the resultant list + * @return List of substrings + */ + public List generateSubStrings(String inputString, String curr, int startPos, List glRes) { + if (startPos >= inputString.length()) { + List currList = new ArrayList<>(); + currList.add(curr); + return currList; + } + + List subStringChars = generateSubStrings(inputString, "" + inputString.charAt(startPos), startPos + 1, glRes); + + List result = new ArrayList<>(); + result.add("" + inputString.charAt(startPos)); + + if (startPos < inputString.length() - 1) { + for (String sub : subStringChars) { + result.add(inputString.charAt(startPos) + sub); + } + } + + glRes.addAll(result); + return result; + } + +// public void generateSubstrings(String inputString, int startPos, List res){ +// if (startPos >= inputString.length()) { +// return; +// } +// +// for (int j = startPos; j < inputString.length(); j++) { +// res.add(inputString.substring(startPos, j + 1)); +// } +// +// generateSubstrings(inputString, startPos + 1, res); +// } +} diff --git a/src/main/java/com/shortthirdman/quickstart/common/Employee.java b/src/main/java/com/shortthirdman/quickstart/common/Employee.java index f9bd37d..f7bed89 100644 --- a/src/main/java/com/shortthirdman/quickstart/common/Employee.java +++ b/src/main/java/com/shortthirdman/quickstart/common/Employee.java @@ -4,11 +4,9 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import lombok.ToString; @Data @Builder -@ToString @NoArgsConstructor @AllArgsConstructor public class Employee { diff --git a/src/main/java/com/shortthirdman/quickstart/common/EmployeeRecord.java b/src/main/java/com/shortthirdman/quickstart/common/EmployeeRecord.java new file mode 100644 index 0000000..cbaef34 --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/common/EmployeeRecord.java @@ -0,0 +1,16 @@ +package com.shortthirdman.quickstart.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EmployeeRecord { + + private int id; + private String name; +} diff --git a/src/main/java/com/shortthirdman/quickstart/common/Pair.java b/src/main/java/com/shortthirdman/quickstart/common/Pair.java new file mode 100644 index 0000000..bd1e515 --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/common/Pair.java @@ -0,0 +1,17 @@ +package com.shortthirdman.quickstart.common; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Pair { + + private F first; + private S second; + + public Pair(F first, S second) { + this.first = first; + this.second = second; + } +} diff --git a/src/main/java/com/shortthirdman/quickstart/common/Transaction.java b/src/main/java/com/shortthirdman/quickstart/common/Transaction.java new file mode 100644 index 0000000..ae6cf5d --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/common/Transaction.java @@ -0,0 +1,16 @@ +package com.shortthirdman.quickstart.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Transaction { + + private String date; + private long amount; +} diff --git a/src/main/java/com/shortthirdman/quickstart/common/TreeNode.java b/src/main/java/com/shortthirdman/quickstart/common/TreeNode.java new file mode 100644 index 0000000..9672177 --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/common/TreeNode.java @@ -0,0 +1,26 @@ +package com.shortthirdman.quickstart.common; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TreeNode { + + public int value; + public TreeNode left; + public TreeNode right; + + public TreeNode() { + } + + public TreeNode(int value) { + this.value = value; + } + + public TreeNode(int value, TreeNode left, TreeNode right) { + this.value = value; + this.left = left; + this.right = right; + } +} diff --git a/src/main/java/com/shortthirdman/quickstart/hackerrank/BinaryTree.java b/src/main/java/com/shortthirdman/quickstart/hackerrank/BinaryTree.java new file mode 100644 index 0000000..7d8d00c --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/hackerrank/BinaryTree.java @@ -0,0 +1,196 @@ +package com.shortthirdman.quickstart.hackerrank; + +import com.shortthirdman.quickstart.common.TreeNode; + +import java.util.*; + +/** + * Binary Tree + * @author ShortThirdMan + */ +public class BinaryTree { + + /** + * Given a binary tree, find its minimum depth. + *

+ * The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node. + * A leaf is a node with no children. + * + * @param root the node with its leaves + * @return the minimum depth + * @see Minimum Depth of Binary Tree + */ + public int minDepth(TreeNode root) { + + if (root == null) { + return 0; // Base case: if the tree is empty + } + + // If the node is a leaf node + if (root.getLeft() == null && root.getRight() == null) { + return 1; // Count this node + } + + // If the left child is null, we only consider the right child + if (root.getLeft() == null) { + return 1 + minDepth(root.getRight()); + } + + // If the right child is null, we only consider the left child + if (root.getRight() == null) { + return 1 + minDepth(root.getLeft()); + } + + // If both children are present, return the minimum depth of both subtrees + return 1 + Math.min(minDepth(root.getLeft()), minDepth(root.getRight())); + } + + /** + * Given the root of a binary tree, return its maximum depth. + *

+ * A binary tree's maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node. + * + * @param root the node with its leaves + * @return the maximum depth + * @see Minimum Depth of Binary Tree + */ + public int maxDepth(TreeNode root) { + if (root == null) { + return 0; + } + + // Calculate depth recursively for left and right subtrees + return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; + } + + /** + * Given a binary tree, determine if it is height-balanced. + * + * @param root the node with its leaves + * @return true if height is balanced, false otherwise + * @see Balanced Binary Tree + */ + public boolean isBalanced(TreeNode root) { + if (root == null) { + return true; // An empty tree is balanced + } + + // Stack to simulate recursion + Deque stack = new ArrayDeque<>(); + // Map to store the height of each node + Map heights = new HashMap<>(); + + stack.push(root); + + while (!stack.isEmpty()) { + TreeNode node = stack.peek(); + + // If the node has not been processed yet, push its children onto the stack + if (!heights.containsKey(node)) { + if (node.right != null) { + stack.push(node.right); + } + if (node.left != null) { + stack.push(node.left); + } + // Mark the node as processed by adding it to the heights map + heights.put(node, 0); + } else { + // If the node has been processed, calculate its height + stack.pop(); // Remove the node from the stack + + int leftHeight = heights.getOrDefault(node.left, 0); + int rightHeight = heights.getOrDefault(node.right, 0); + + // Check if the current node is balanced + if (Math.abs(leftHeight - rightHeight) > 1) { + return false; // Tree is unbalanced + } + + // Calculate the height of the current node + int height = Math.max(leftHeight, rightHeight) + 1; + heights.put(node, height); + } + } + + return true; // If all nodes are balanced + } + + /** + * @param root the root tree node + * @return the depth of the tree + */ + public int minimumDepth(TreeNode root) { + if (root == null) return 0; + + Queue queue = new LinkedList<>(); + queue.add(root); + int depth = 1; // Start with depth 1 since we start from the root + + while (!queue.isEmpty()) { + int levelSize = queue.size(); + + for (int i = 0; i < levelSize; i++) { + TreeNode node = queue.poll(); + + // Check for null node (just in case) + if (node == null) continue; + + // Check if we have reached a leaf node + if (node.getLeft() == null && node.getRight() == null) { + return depth; // Return the current depth when we find the first leaf + } + + // Add children to the queue + if (node.left != null) { + queue.add(node.left); + } + if (node.right != null) { + queue.add(node.right); + } + } + depth++; // Increase the depth for the next level + } + + return depth; // In case of an empty tree, though not reached due to checks + } + + /** + * Given the root of a binary tree, check whether it is a mirror of itself (i.e., symmetric around its center). + * + * @param root the node with its leaves + * @return true if tree is symmetric, otherwise false + * @see Symmetric Tree + */ + public boolean isSymmetric(TreeNode root) { + if (root == null) { + return true; // An empty tree is symmetric + } + + Stack stack = new Stack<>(); + stack.push(root.left); + stack.push(root.right); + + while (!stack.isEmpty()) { + TreeNode right = stack.pop(); + TreeNode left = stack.pop(); + + // Both nodes are null, continue checking + if (left == null && right == null) { + continue; + } + + // If one is null or their values don't match, it's not symmetric + if (left == null || right == null || left.value != right.value) { + return false; + } + + // Push children in the order that ensures symmetry + stack.push(left.left); + stack.push(right.right); + stack.push(left.right); + stack.push(right.left); + } + return true; // If we finish checking without issues, it's symmetric + } +} diff --git a/src/main/java/com/shortthirdman/quickstart/hackerrank/DominantStringAnalyzer.java b/src/main/java/com/shortthirdman/quickstart/hackerrank/DominantStringAnalyzer.java new file mode 100644 index 0000000..ba0576e --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/hackerrank/DominantStringAnalyzer.java @@ -0,0 +1,55 @@ +package com.shortthirdman.quickstart.hackerrank; + +import java.util.Arrays; +import java.util.stream.IntStream; + +/** + * A team of data analysts at Amazon is working to identify data patterns. + * During their analysis, they found a category of string they call dominant string: + *

+ * - It has an even length. + *

+ * - The string contains at least one character with a frequency that matches exactly half of the length of the string. + *

+ * Given a string s of length n, determine the number of its distinct substrings that are dominant strings. + * + * @author ShortThirdMan + */ +public class DominantStringAnalyzer { + + /** + * @param s the input text + * @return the count or number of dominant string + */ + public long getDominantStringCount(String s) { + long count = 0; + int n = s.length(); + var lowercaseText = s.toLowerCase(); + + count = IntStream.range(2, n + 1) + .filter(len -> len % 2 == 0) // Only consider even lengths + .mapToLong(len -> + IntStream.range(0, n - len + 1) + .mapToObj(i -> lowercaseText.substring(i, i + len)) + .filter(substring -> isDominant(substring, len)) + .count() + ).sum(); + + return count; + } + + private boolean isDominant(String substring, int len) { + int[] frequency = new int[10]; + substring.chars() + .forEach(c -> { + // Ensure the character is between 'a' and 'z' before updating frequency + if (c >= 'a' && c <= 'z') { + frequency[c - 'a']++; + } + }); + + int halfLength = len / 2; + return Arrays.stream(frequency) + .anyMatch(f -> f == halfLength); + } +} diff --git a/src/main/java/com/shortthirdman/quickstart/hackerrank/OptimizingBoxWeights.java b/src/main/java/com/shortthirdman/quickstart/hackerrank/OptimizingBoxWeights.java new file mode 100644 index 0000000..5fb20d6 --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/hackerrank/OptimizingBoxWeights.java @@ -0,0 +1,68 @@ +package com.shortthirdman.quickstart.hackerrank; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Stack; + +/** + * + * An Amazon Fulfillment Associate has a set of items that need to be packed into two boxes. + * Given an integer array of the item weights (arr) to be packed, divide the item weights into two subsets, + * A and B, for packing into the associated boxes, while respecting the following conditions: + *
    + *
  • The intersection of A and B is null.
  • + *
  • The union A and B is equal to the original array.
  • + *
  • The number of elements in subset A is minimal.
  • + *
  • The sum of A's weights is greater than the sum of B's weights.
  • + *
+ * + * Return the subset A in increasing order where the sum of A's weights is greater than the sum of B's weights. + * If more than one subset A exists, return the one with the maximal total weight. + * + * @author ShortThirdMan + */ +public class OptimizingBoxWeights { + + /** + * @param weights the input array + * @return the list of + */ + public List minimalHeaviestSetA(List weights) { + Collections.sort(weights); + Stack stack = new Stack<>(); + int totalRemainingSum = 0; + + for (Integer integer : weights) { + stack.push(integer); + totalRemainingSum += integer; + } + + List setA = new ArrayList<>(); + int setASum = 0; + while (!stack.isEmpty()) { + int next = stack.pop(); + setA.addFirst(next); + setASum+= next; + totalRemainingSum-= next; + + // Stop When Sum of List A is greater than remaining + if (setASum > totalRemainingSum) { + while (!stack.isEmpty() && setASum - setA.getFirst() + stack.peek() > totalRemainingSum + setA.getFirst() - stack.peek()) { + int lastItem = setA.getFirst(); + Integer newMin = stack.pop(); + setA.removeFirst(); + setA.addFirst(newMin); + + setASum += (newMin - lastItem); + totalRemainingSum += (lastItem - newMin); + } + break; + } + } + + Collections.sort(setA); + + return setA; + } +} diff --git a/src/main/java/com/shortthirdman/quickstart/hackerrank/ReversePolishNotation.java b/src/main/java/com/shortthirdman/quickstart/hackerrank/ReversePolishNotation.java new file mode 100644 index 0000000..24417d3 --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/hackerrank/ReversePolishNotation.java @@ -0,0 +1,82 @@ +package com.shortthirdman.quickstart.hackerrank; + +import java.util.Stack; + +/** + * Evaluate the value of an arithmetic expression in Reverse Polish Notation. + * Valid operators are +, -, *, /. Each operand may be an integer or another expression. + * + * @author ShortThirdMan + */ +public class ReversePolishNotation { + + public int evaluateRPN(String[] tokens) { + int returnValue = 0; + String operators = "+-*/"; + + Stack stack = new Stack<>(); + for (String t : tokens) { + if (!operators.contains(t)) { //push to stack if it is a number + stack.push(t); + } else { + // pop numbers from stack if it is an operator + int a = Integer.parseInt(stack.pop()); + int b = Integer.parseInt(stack.pop()); + switch (t) { + case "+": + stack.push(String.valueOf(a + b)); + break; + case "-": + stack.push(String.valueOf(b - a)); + break; + case "*": + stack.push(String.valueOf(a * b)); + break; + case "/": + stack.push(String.valueOf(b / a)); + break; + } + } + } + + returnValue = Integer.parseInt(stack.pop()); + + return returnValue; + } + + public int computeRPN(String[] tokens) { + Stack stack = new Stack<>(); + String operators = "+-*/"; + + for (String token : tokens) { + if (!operators.contains(token)) { + stack.push(Integer.parseInt(token)); // Push numbers to the stack + } else { + // Pop the last two numbers for the operation + int b = stack.pop(); + int a = stack.pop(); + switch (token) { + case "+": + stack.push(a + b); + break; + case "-": + stack.push(a - b); + break; + case "*": + stack.push(a * b); + break; + case "/": + if (b == 0) { + throw new ArithmeticException("Division by zero"); + } + stack.push(a / b); + break; + default: + throw new IllegalArgumentException("Invalid operator: " + token); + } + } + } + + return stack.pop(); // Return the final result + } +} diff --git a/src/main/java/com/shortthirdman/quickstart/hackerrank/StoneWall.java b/src/main/java/com/shortthirdman/quickstart/hackerrank/StoneWall.java new file mode 100644 index 0000000..c94e210 --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/hackerrank/StoneWall.java @@ -0,0 +1,37 @@ +package com.shortthirdman.quickstart.hackerrank; + +/** + * You are going to build a stone wall. The wall should be straight and N meters long, and its thickness should be constant; + * however, it should have different heights in different places. The height of the wall is specified by an array H of N positive integers. + * H[i] is the height of the wall from i to i+1 meters to the right of its left end. + *
+ * In particular, H[0] is the height of the wall's left end and H[N−1] is the height of the wall's right end. + * The wall should be built of cuboid stone blocks (that is, all sides of such blocks are rectangular). + * + * @author ShortThirdMan + */ +public class StoneWall { + + /** + * Compute the minimum number of blocks needed to build the wall. + * @param H array of positive integers specifying height of wall + * @return Minimum number of blocks needed to build + */ + public int coverManhattanSkyline(int[] H) { + int N = H.length; + int totalBlocks = 0; + + if (N < 1 || N > 100000) { + throw new IllegalArgumentException("Number of height-blocks should be in range [1,100000]"); + } + + for (int height : H) { + if (height <= 0) { + throw new IllegalArgumentException("Height values must be positive"); + } + totalBlocks += height; + } + + return totalBlocks; + } +} diff --git a/src/main/java/com/shortthirdman/quickstart/hackerrank/StreamApiFactory.java b/src/main/java/com/shortthirdman/quickstart/hackerrank/StreamApiFactory.java new file mode 100644 index 0000000..cb757ee --- /dev/null +++ b/src/main/java/com/shortthirdman/quickstart/hackerrank/StreamApiFactory.java @@ -0,0 +1,165 @@ +package com.shortthirdman.quickstart.hackerrank; + +import com.shortthirdman.quickstart.common.EmployeeRecord; +import com.shortthirdman.quickstart.common.Transaction; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class StreamApiFactory { + + public String concatenateEvenLengthWords(List words) { + String result = null; + if (words == null || words.isEmpty()) { + throw new IllegalArgumentException("words cannot be null or empty"); + } + + result = words.stream() + .filter(x -> x.length() % 2 == 0) + .limit(2) + .collect(Collectors.joining()); + return result; + } + + /** + * @param sentence the sentence or group of words + * @return map of character frequency + */ + public Map countCharacterFrequency(String sentence) { + Map charFreq = null; + + if (sentence == null || sentence.isEmpty()) { + throw new IllegalArgumentException("sentence cannot be null or empty"); + } + + charFreq = Arrays.stream(sentence.split("")).collect( + Collectors.groupingBy( + Function.identity(), // or x -> x + Collectors.counting() + ) + ); + + return charFreq; + } + + /** + * @param transactions the list of transactions + * @return + */ + public Map getDailyTransactionSums(List transactions) { + Map dailyTotals = null; + + if (transactions == null || transactions.isEmpty()) { + throw new IllegalArgumentException("transactions cannot be null or empty"); + } + + dailyTotals = transactions.stream() + .collect(Collectors.groupingBy( + Transaction::getDate, + TreeMap::new, + Collectors.summingLong(Transaction::getAmount) + )); + + return dailyTotals; + } + + /** + * @param employees the list of employees with identifier and name + * @return + */ + public Map> toSortedMapWithDuplicates(List employees) { + Map> employeeMap = null; + + if (employees == null || employees.isEmpty()) { + throw new IllegalArgumentException("employees cannot be null or empty"); + } + + employeeMap = employees.stream() + .collect(Collectors.groupingBy( + EmployeeRecord::getId, // Key extractor + TreeMap::new, // Use TreeMap to maintain sorted order + Collectors.toList() // Collect values into a list + )); + + return employeeMap; + } + + public Map countStringsByFirstCharacter(List words) { + Map frequency = null; + + if (words == null || words.isEmpty()) { + throw new IllegalArgumentException("words cannot be null or empty"); + } + + frequency = words.stream() + .collect(Collectors.groupingBy( + str -> str.charAt(0), + Collectors.counting() + )); + + return frequency; + } + + /** + * Given two arrays of integers, merge them, sort them, and then + * filter out any numbers greater than a specified threshold. + * + * @param arr1 the first input array + * @param arr2 the second input array + * @param threshold the threshold value + * @return the list of concatenated array of integers + */ + public List mergeSortAndFilterByThreshold(int[] arr1, int[] arr2, int threshold) { + Stream intStream = null; + if (arr1 == null || arr2 == null || arr1.length == 0 || arr2.length == 0) { + throw new IllegalArgumentException("arr1 and arr2 cannot be null or empty"); + } + + intStream = IntStream.concat(Arrays.stream(arr1), Arrays.stream(arr2)) + .boxed() + .sorted(Comparator.naturalOrder()) + .filter(x -> x > threshold); + + return intStream.collect(Collectors.toList()); + } + + /** + * @param numbers the list of numbers + * @return + */ + public Map> partitionByPrime(List numbers) { + Map> partitioned = null; + if (numbers == null || numbers.isEmpty()) { + throw new IllegalArgumentException("numbers cannot be null or empty"); + } + + partitioned = numbers.stream() + .collect(Collectors.partitioningBy(this::isPrime)); + + return partitioned; + } + + private boolean isPrime(int number) { + return number > 1 && IntStream.range(2, (int) Math.sqrt(number) + 1).noneMatch(i -> number % i == 0); + } + + /** + * Count the total number of distinct words (case-insensitive) across multiple sentences. + * @param sentences the input list of group of words + * @return + */ + public long countTotalUniqueWords(List sentences) { + if (sentences == null || sentences.isEmpty()) { + throw new IllegalArgumentException("sentences cannot be null or empty"); + } + var uniqueWords = sentences.stream() + .map(x -> x.toLowerCase().split(" ")) + .flatMap(Arrays::stream) + .distinct() + .count(); + return uniqueWords; + } +} diff --git a/src/main/java/com/shortthirdman/quickstart/leetcode/MinimumBinaryFlips.java b/src/main/java/com/shortthirdman/quickstart/leetcode/MinimumBinaryFlips.java index c43e3ea..1eb0378 100644 --- a/src/main/java/com/shortthirdman/quickstart/leetcode/MinimumBinaryFlips.java +++ b/src/main/java/com/shortthirdman/quickstart/leetcode/MinimumBinaryFlips.java @@ -1,5 +1,7 @@ package com.shortthirdman.quickstart.leetcode; +import java.util.Objects; + /** * A password string, pwd, consists of binary characters (0s and 1s). A cyber-security expert is trying to determine the * minimum number of changes required to make the password secure. @@ -18,9 +20,13 @@ public class MinimumBinaryFlips { * @param text the binary string * @return the minimum number of flips to make the division possible */ - public Integer getMinFlips(String text) { + public int getMinFlips(String text) { int minFlips = Integer.MAX_VALUE; + if (Objects.isNull(text)) { + throw new NullPointerException("Input text password can not be null"); + } + int len = text.length(); int flipsToMakeAllZeros = flipsToMakeAllSame(text, '0'); @@ -41,11 +47,11 @@ public Integer getMinFlips(String text) { minFlips = Math.min(minFlips, Math.min(flipsToZeroPart1 + flipsToOnePart2, flipsToOnePart1 + flipsToZeroPart2)); } - return Integer.valueOf(minFlips); + return minFlips; } - public int flipsToMakeAllSame(String str, char targetChar) { + private int flipsToMakeAllSame(String str, char targetChar) { int flips = 0; for (char c : str.toCharArray()) { if (c != targetChar) { diff --git a/src/test/java/com/shortthirdman/quickstart/codesignal/CountDistinctSlicesTest.java b/src/test/java/com/shortthirdman/quickstart/codesignal/CountDistinctSlicesTest.java new file mode 100644 index 0000000..cd76039 --- /dev/null +++ b/src/test/java/com/shortthirdman/quickstart/codesignal/CountDistinctSlicesTest.java @@ -0,0 +1,133 @@ +package com.shortthirdman.quickstart.codesignal; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CountDistinctSlicesTest { + + CountDistinctSlices app; + + @BeforeEach + void setUp() { + app = new CountDistinctSlices(); + } + + // Testing the case with a small array where all elements are distinct + @Test + void testDistinctElements() { + int M = 5; + int[] A = {1, 2, 3}; + int result = app.calculateDistinctSlices(M, A); + assertEquals(6, result); // The 6 distinct slices are: (0,0), (0,1), (0,2), (1,1), (1,2), (2,2) + } + + // Testing the case where there are duplicate elements in the array + @Test + void testWithDuplicates() { + int M = 5; + int[] A = {1, 2, 2, 3}; + int result = app.calculateDistinctSlices(M, A); + assertNotEquals(7, result); // The distinct slices are: (0,0), (0,1), (0,2), (1,1), (1,2), (2,2), (3,3) + } + + // Edge case with the smallest possible array of length 1 + @Test + void testSingleElementArray() { + int M = 1; + int[] A = {0}; + int result = app.calculateDistinctSlices(M, A); + assertEquals(1, result); // Only one slice: (0,0) + } + + // Case with all elements being the same + @Test + void testAllSameElements() { + int M = 3; + int[] A = {2, 2, 2, 2}; + + int result = app.calculateDistinctSlices(M, A); + assertEquals(4, result); // The distinct slices are: (0,0), (1,1), (2,2), (3,3) + } + + // Edge case with an empty array (this should never happen due to validation) + @Test + void testEmptyArray() { + int M = 0; + int[] A = {}; + assertThrows(IllegalArgumentException.class, () -> app.calculateDistinctSlices(M, A)); + } + + // Testing with a large input size (e.g., 100,000 elements) + @Test + void testLargeInput() { + int M = 100000; + int[] A = new int[100000]; + for (int i = 0; i < 100000; i++) { + A[i] = i % 1000; // Repeating elements in a controlled pattern + } + + int result = app.calculateDistinctSlices(M, A); + // Expected result is not easy to calculate by hand, but the test ensures it runs within time limits + assertTrue(result >= 0); // Just check that result is non-negative + } + + // Edge case with maximum possible value for M + @Test + void testMaximumM() { + int M = 100000; + int[] A = {100000, 100000, 100000}; + + int result = app.calculateDistinctSlices(M, A); + assertEquals(3, result); // Only slices (0,0), (1,1), and (2,2) are distinct + } + + // Testing the method with a single element at maximum value + @Test + void testSingleMaxElement() { + int M = 100000; + int[] A = {100000}; + + int result = app.calculateDistinctSlices(M, A); + assertEquals(1, result); // Only one slice (0,0) + } + + // Testing for large range where M is much greater than the values in A + @Test + void testLargeMWithSmallerArray() { + int M = 100000; + int[] A = {1, 2, 3, 4}; + + int result = app.calculateDistinctSlices(M, A); + assertEquals(10, result); // The distinct slices are: (0,0), (0,1), (0,2), (0,3), (1,1), (1,2), (1,3), (2,2), (2,3), (3,3) + } + + // Testing an array where the elements are in decreasing order + @Test + void testDecreasingOrder() { + int M = 5; + int[] A = {5, 4, 3, 2, 1}; + + int result = app.calculateDistinctSlices(M, A); + assertEquals(15, result); // The distinct slices are: (0,0), (0,1), (0,2), (0,3), (0,4), (1,1), (1,2), (1,3), (1,4), (2,2), (2,3), (2,4), (3,3), (3,4), (4,4) + } + + // Testing invalid input for array elements exceeding the upper bound M + @Test + void testInvalidElementExceedingM() { + int M = 5; + int[] A = {1, 2, 6}; // Element 6 exceeds M + + assertThrows(IllegalArgumentException.class, () -> app.calculateDistinctSlices(M, A)); + } + + // Testing invalid input for array length being zero + @Test + void testInvalidArrayLength() { + int M = 5; + int[] A = {}; // Empty array + + assertThrows(IllegalArgumentException.class, () -> app.calculateDistinctSlices(M, A)); + } +} \ No newline at end of file diff --git a/src/test/java/com/shortthirdman/quickstart/codility/BagOfFruitsTest.java b/src/test/java/com/shortthirdman/quickstart/codility/BagOfFruitsTest.java new file mode 100644 index 0000000..017e97f --- /dev/null +++ b/src/test/java/com/shortthirdman/quickstart/codility/BagOfFruitsTest.java @@ -0,0 +1,46 @@ +package com.shortthirdman.quickstart.codility; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BagOfFruitsTest { + + BagOfFruits app; + + @BeforeEach + void setUp() { + app = new BagOfFruits(); + } + + @AfterEach + void tearDown() { + } + + @Test + void countFruits() { + List fruits = List.of("apple", + "apple", "apple", "apple", "apple", "apple", + "grapes", "grapes", "kiwi", "kiwi", "kiwi", "kiwi", "kiwi"); + List expected = List.of("apple", "kiwi"); + long threshold = 2; + List actual = app.countFruits(fruits, threshold); + assertEquals(expected, actual); + } + + @Test + void testCountFruits() { + List fruits = List.of("apple", + "apple","apple","apple", "apple", "apple", + "grapes", "grapes", + "kiwi","kiwi", "kiwi", "kiwi", "kiwi"); + List expected = List.of("apple", "kiwi"); + int threshold = 2; + List actual = app.countFruits(fruits, threshold); + assertEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/com/shortthirdman/quickstart/codility/GenerateAllSubstringsTest.java b/src/test/java/com/shortthirdman/quickstart/codility/GenerateAllSubstringsTest.java new file mode 100644 index 0000000..ff43a90 --- /dev/null +++ b/src/test/java/com/shortthirdman/quickstart/codility/GenerateAllSubstringsTest.java @@ -0,0 +1,22 @@ +package com.shortthirdman.quickstart.codility; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class GenerateAllSubstringsTest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void generateSubStrings() { + } +} \ No newline at end of file diff --git a/src/test/java/com/shortthirdman/quickstart/hackerrank/BinaryTreeTest.java b/src/test/java/com/shortthirdman/quickstart/hackerrank/BinaryTreeTest.java new file mode 100644 index 0000000..97312be --- /dev/null +++ b/src/test/java/com/shortthirdman/quickstart/hackerrank/BinaryTreeTest.java @@ -0,0 +1,518 @@ +package com.shortthirdman.quickstart.hackerrank; + +import com.shortthirdman.quickstart.common.TreeNode; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BinaryTreeTest { + + BinaryTree tree; + + @BeforeEach + void setUp() { + tree = new BinaryTree(); + } + + @Test + void minDepth_SingleLeaf() { + TreeNode root = new TreeNode(1); + assertEquals(1, tree.minDepth(root), "Expected min depth for single node to be 1."); + } + + @Test + void minDepth_TwoLevels() { + TreeNode root = new TreeNode(1, new TreeNode(2), null); + assertEquals(2, tree.minDepth(root), "Expected min depth for single child tree to be 2."); + } + + @Test + void minDepth_ThreeLevels() { + TreeNode root = new TreeNode(1, new TreeNode(2, new TreeNode(3), null), new TreeNode(4)); + assertEquals(2, tree.minDepth(root), "Expected min depth for balanced tree with three levels to be 2."); + } + + @Test + void minDepth_BalancedTree() { + TreeNode root = new TreeNode(1, new TreeNode(2), new TreeNode(3)); + assertEquals(2, tree.minDepth(root), "Expected min depth for balanced tree to be 2."); + } + + // Negative test cases + @Test + void minDepth_EmptyTree() { + TreeNode root = null; + assertEquals(0, tree.minDepth(root), "Expected min depth for null tree to be 0."); + } + + // Edge cases + @Test + void minDepth_LeftHeavyTree() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.left.left = new TreeNode(3); + assertEquals(3, tree.minDepth(root)); + } + + @Test + void minDepth_RightHeavyTree() { + TreeNode root = new TreeNode(1); + root.right = new TreeNode(2); + root.right.right = new TreeNode(3); + assertEquals(3, tree.minDepth(root)); + } + + // Parameterized test cases + static Object[][] provideMinDepthCases() { + return new Object[][] { + {null, 0}, // empty tree + {new TreeNode(1), 1}, // single node + {new TreeNode(1, new TreeNode(2), null), 2}, // left child only + {new TreeNode(1, null, new TreeNode(3)), 2}, // right child only + {new TreeNode(1, new TreeNode(2), new TreeNode(3)), 2}, // balanced tree + {new TreeNode(1, new TreeNode(2, new TreeNode(4), null), new TreeNode(3)), 2} // left heavy + }; + } + + @ParameterizedTest + @MethodSource("provideMinDepthCases") + void minDepth_Parameterized(TreeNode root, int expectedDepth) { + assertEquals(expectedDepth, tree.minDepth(root)); + } + + // Test for a large balanced tree + @Test + void minDepth_LargeBalancedTree() { + TreeNode root = createBalancedTree(1, 17); // height of 17, so depth should be 17 + assertEquals(17, tree.minDepth(root)); + } + + // Test for a large skewed tree (left-heavy) + @Test + void minDepth_LargeLeftHeavyTree() { + TreeNode root = createLeftHeavyTree(100000); // 100000 nodes deep + assertEquals(100000, tree.minimumDepth(root)); + } + + // Helper method to create a balanced binary tree + private TreeNode createBalancedTree(int value, int height) { + if (height <= 0) return null; + TreeNode node = new TreeNode(value); + node.left = createBalancedTree(value, height - 1); + node.right = createBalancedTree(value, height - 1); + return node; + } + + // Helper method to create a left-heavy tree + private TreeNode createLeftHeavyTree(int nodes) { + if (nodes <= 0) return null; + TreeNode root = new TreeNode(1); + TreeNode current = root; + for (int i = 2; i <= nodes; i++) { + current.left = new TreeNode(i); + current = current.left; + } + return root; + } + + @Test + public void minDepth_ComplexTree() { + // Test case for a more complex tree + TreeNode root = new TreeNode(1, + new TreeNode(2, + new TreeNode(4, + new TreeNode(7), + null), + new TreeNode(5)), + new TreeNode(3)); + assertNotEquals(3, tree.minDepth(root), "Expected min depth for complex tree not to be 3."); // Shortest path to leaf 3 + assertEquals(2, tree.minDepth(root), "Expected min depth for complex tree to be 2."); + } + + @Test + public void maxDepth_NullTree() { + // Test case for an empty tree + assertEquals(0, tree.maxDepth(null), "Expected max depth for null tree to be 0."); + } + + @Test + public void maxDepth_LeafNode() { + // Test case for a single leaf node + TreeNode root = new TreeNode(1); + assertEquals(1, tree.maxDepth(root), "Expected max depth for single node to be 1."); + } + + @Test + public void maxDepth_SingleChildTree() { + // Test case where the tree has one child + TreeNode root = new TreeNode(1, new TreeNode(2), null); + assertEquals(2, tree.maxDepth(root), "Expected max depth for single child tree to be 2."); + } + + @Test + public void maxDepth_BalancedTree() { + // Test case for a balanced tree + TreeNode root = new TreeNode(1, + new TreeNode(2, + new TreeNode(4), + new TreeNode(5)), + new TreeNode(3)); + assertEquals(3, tree.maxDepth(root), "Expected max depth for balanced tree to be 3."); + } + + @Test + public void maxDepth_ComplexTree() { + // Test case for a more complex tree + TreeNode root = new TreeNode(1, + new TreeNode(2, + new TreeNode(4, + new TreeNode(7), + null), + new TreeNode(5)), + new TreeNode(3)); + assertEquals(4, tree.maxDepth(root), "Expected max depth for complex tree to be 4."); + } + + @Test + public void maxDepth_OnlyLeftChildren() { + // Test case for a tree with only left children + TreeNode root = new TreeNode(1, + new TreeNode(2, + new TreeNode(3, + new TreeNode(4), + null), + null), + null); + assertEquals(4, tree.maxDepth(root), "Expected max depth for left-only tree to be 4."); + } + + @Test + public void maxDepth_OnlyRightChildren() { + // Test case for a tree with only right children + TreeNode root = new TreeNode(1, + null, + new TreeNode(2, + null, + new TreeNode(3, + null, + new TreeNode(4)))); + assertEquals(4, tree.maxDepth(root), "Expected max depth for right-only tree to be 4."); + } + + @ParameterizedTest + @MethodSource("provideTreesForParameterizedTest") + public void maxDepth_Parameterized(TreeNode root, int expectedDepth) { + assertEquals(expectedDepth, tree.maxDepth(root), "Expected max depth does not match."); + } + + private static Object[][] provideTreesForParameterizedTest() { + return new Object[][] { + { null, 0 }, // Empty tree + { new TreeNode(1), 1 }, // Single node + { new TreeNode(1, new TreeNode(2), null), 2 }, // One left child + { new TreeNode(1, null, new TreeNode(2)), 2 }, // One right child + { new TreeNode(1, + new TreeNode(2, new TreeNode(3), null), + new TreeNode(4)), 3 }, // Mixed tree + { new TreeNode(1, + new TreeNode(2, + new TreeNode(3, + new TreeNode(4), + null), + null), + null), 4 } // All left children + }; + } + + @Test + void isSymmetric_testSymmetricTree() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.right = new TreeNode(2); + root.left.left = new TreeNode(3); + root.left.right = new TreeNode(4); + root.right.left = new TreeNode(4); + root.right.right = new TreeNode(3); + + assertTrue(tree.isSymmetric(root), "The tree should be symmetric."); + } + + @Test + void isSymmetric_testAsymmetricTreeDifferentValues() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.right = new TreeNode(2); + root.left.left = new TreeNode(3); + root.left.right = new TreeNode(5); + root.right.left = new TreeNode(4); + root.right.right = new TreeNode(3); + + assertFalse(tree.isSymmetric(root), "The tree should not be symmetric."); + } + + @Test + void isSymmetric_testAsymmetricTreeNulls() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.right = new TreeNode(2); + root.left.right = new TreeNode(3); + root.right.right = new TreeNode(3); + + assertFalse(tree.isSymmetric(root), "The tree should not be symmetric."); + } + + @Test + void isSymmetric_testSingleNodeTree() { + TreeNode root = new TreeNode(1); + + assertTrue(tree.isSymmetric(root), "A single node tree should be symmetric."); + } + + @Test + void isSymmetric_testTwoLevelTreeSymmetric() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.right = new TreeNode(2); + + assertTrue(tree.isSymmetric(root), "The two-level tree should be symmetric."); + } + + @Test + void isSymmetric_testTwoLevelTreeAsymmetric() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.right = new TreeNode(3); + + assertFalse(tree.isSymmetric(root), "The two-level tree should not be symmetric."); + } + + @Test + void isSymmetric_testEmptyTree() { + assertTrue(tree.isSymmetric(null), "An empty tree should be symmetric."); + } + + @Test + void isSymmetric_testLargeSymmetricTree() { + TreeNode root = generateBalancedSymmetricTree(1, 4); // Height 4 + assertTrue(tree.isSymmetric(root), "A large balanced tree should be symmetric."); + } + + @Test + void isSymmetric_testLargeAsymmetricTree() { + TreeNode root = generateBalancedAsymmetricTree(1, 4); // Height 4 + assertFalse(tree.isSymmetric(root), "A large unbalanced tree should not be symmetric."); + } + + // Helper method to create a balanced symmetric tree + private TreeNode generateBalancedSymmetricTree(int value, int height) { + if (height <= 0) { + return null; + } + TreeNode node = new TreeNode(value); + node.left = generateBalancedSymmetricTree(value + 1, height - 1); + node.right = generateBalancedSymmetricTree(value + 1, height - 1); + return node; + } + + // Helper method to create a balanced asymmetric tree + private TreeNode generateBalancedAsymmetricTree(int value, int height) { + if (height <= 0) { + return null; + } + TreeNode node = new TreeNode(value); + node.left = generateBalancedAsymmetricTree(value + 1, height - 1); + node.right = new TreeNode(value + 1, generateBalancedAsymmetricTree(value + 1, height - 1), null); + return node; + } + + @Test + void isSymmetric_testTreeWithNegativeValues() { + TreeNode root = new TreeNode(0); + root.left = new TreeNode(-1); + root.right = new TreeNode(-1); + root.left.left = new TreeNode(-2); + root.left.right = new TreeNode(-3); + root.right.left = new TreeNode(-3); + root.right.right = new TreeNode(-2); + + assertTrue(tree.isSymmetric(root), "The tree with negative values should be symmetric."); + } + + @Test + void isSymmetric_testTreeWithNegativeAndPositiveValues() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(-2); + root.right = new TreeNode(-2); + root.left.left = new TreeNode(-3); + root.left.right = new TreeNode(-4); + root.right.left = new TreeNode(-4); + root.right.right = new TreeNode(-3); + + assertTrue(tree.isSymmetric(root), "The tree with mixed values should be symmetric."); + } + + @Test + void isBalanced_testBalancedTree() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.right = new TreeNode(2); + root.left.left = new TreeNode(3); + root.left.right = new TreeNode(4); + root.right.left = new TreeNode(4); + root.right.right = new TreeNode(3); + + assertTrue(tree.isBalanced(root), "The tree should be balanced."); + } + + @Test + void isBalanced_testUnbalancedTree() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.left.left = new TreeNode(3); + root.left.left.left = new TreeNode(4); + + assertFalse(tree.isBalanced(root), "The tree should not be balanced."); + } + + @Test + void isBalanced_testAsymmetricTree() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.left.left = new TreeNode(3); + root.left.left.left = new TreeNode(4); + + assertFalse(tree.isBalanced(root), "The tree should not be balanced."); + } + + @Test + void isBalanced_testEmptyTree() { + assertTrue(tree.isBalanced(null), "An empty tree should be considered balanced."); + } + + @Test + void isBalanced_testSingleNodeTree() { + TreeNode root = new TreeNode(1); + assertTrue(tree.isBalanced(root), "A single node tree should be balanced."); + } + + @Test + void isBalanced_testTwoLevelBalancedTree() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.right = new TreeNode(2); + assertTrue(tree.isBalanced(root), "A two-level tree should be balanced."); + } + + @Test + void isBalanced_testTwoLevelAsymmetricTree() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.right = new TreeNode(3); + root.right.right = new TreeNode(4); + + assertTrue(tree.isBalanced(root), "The two-level tree should be balanced."); + } + + @Test + void isBalanced_testUnbalancedTwoLevelTree() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.left.left = new TreeNode(3); + + assertFalse(tree.isBalanced(root), "The two-level tree should not be balanced."); + } + + @Test + void isBalanced_testLargeBalancedTree() { + TreeNode root = generateBalancedTree(1, 5); // Height 5 + assertTrue(tree.isBalanced(root), "A large balanced tree should be balanced."); + } + + @Test + void isBalanced_testLargeAsymmetricTree() { + TreeNode root = generateAsymmetricTree(1, 5); // Height 5 + assertFalse(tree.isBalanced(root), "A large unbalanced tree should not be balanced."); + } + + // Helper method to create a balanced tree + private TreeNode generateBalancedTree(int value, int height) { + if (height <= 0) { + return null; + } + TreeNode node = new TreeNode(value); + node.left = generateBalancedTree(value + 1, height - 1); + node.right = generateBalancedTree(value + 1, height - 1); + return node; + } + + // Helper method to create an asymmetric tree + private TreeNode generateAsymmetricTree(int value, int height) { + if (height <= 0) { + return null; + } + TreeNode node = new TreeNode(value); + node.left = generateAsymmetricTree(value + 1, height - 1); + node.right = new TreeNode(value + 1); + return node; + } + + @Test + void isBalanced_testTreeWithNegativeValues() { + TreeNode root = new TreeNode(0); + root.left = new TreeNode(-1); + root.right = new TreeNode(-1); + root.left.left = new TreeNode(-2); + root.left.right = new TreeNode(-3); + root.right.left = new TreeNode(-3); + root.right.right = new TreeNode(-2); + + assertTrue(tree.isBalanced(root), "The tree with negative values should be balanced."); + } + + @Test + void isBalanced_testTreeWithNegativeAndPositiveValues() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(-2); + root.right = new TreeNode(-2); + root.left.left = new TreeNode(-3); + root.left.right = new TreeNode(-4); + root.right.left = new TreeNode(-4); + root.right.right = new TreeNode(-3); + + assertTrue(tree.isBalanced(root), "The tree with mixed values should be balanced."); + } + + @Test + void isBalanced_testExample1() { + TreeNode root = new TreeNode(1); + root.left = new TreeNode(2); + root.right = new TreeNode(2); + root.left.left = new TreeNode(3); + root.left.right = new TreeNode(3); + root.left.left.left = new TreeNode(4); + root.left.left.right = new TreeNode(4); + + assertFalse(tree.isBalanced(root), "The tree should not be balanced."); + } + + @Test + void isBalanced_testExample2() { + assertTrue(tree.isBalanced(null), "An empty tree should be considered balanced."); + } + + @Test + void isBalanced_testExample3() { + TreeNode root = new TreeNode(3); + root.left = new TreeNode(9); + root.right = new TreeNode(20); + root.right.left = new TreeNode(15); + root.right.right = new TreeNode(7); + + assertTrue(tree.isBalanced(root), "The tree should be balanced."); + } +} \ No newline at end of file diff --git a/src/test/java/com/shortthirdman/quickstart/hackerrank/DominantStringAnalyzerTest.java b/src/test/java/com/shortthirdman/quickstart/hackerrank/DominantStringAnalyzerTest.java new file mode 100644 index 0000000..bf181b6 --- /dev/null +++ b/src/test/java/com/shortthirdman/quickstart/hackerrank/DominantStringAnalyzerTest.java @@ -0,0 +1,88 @@ +package com.shortthirdman.quickstart.hackerrank; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +class DominantStringAnalyzerTest { + + DominantStringAnalyzer app; + + @BeforeEach + void setUp() { + app = new DominantStringAnalyzer(); + } + + @Test + void testEmptyString() { + assertEquals(0, app.getDominantStringCount("")); + } + + @Test + void testSingleCharacter() { + assertEquals(0, app.getDominantStringCount("a")); + } + + @Test + void testTwoSameCharacters() { + assertEquals(0, app.getDominantStringCount("aa")); + } + + @Test + void testTwoDifferentCharacters() { + assertEquals(1, app.getDominantStringCount("ab")); + } + + @Test + void testFourSameCharacters() { + assertEquals(0, app.getDominantStringCount("aaaa")); + } + + @Test + void testTwoPairsDominantSubstrings() { + assertEquals(2, app.getDominantStringCount("aabb")); + } + + @Test + void testMixedCaseInput() { + assertEquals(2, app.getDominantStringCount("AaBb")); + } + + @Test + void testNonLetterCharactersIncluded() { + assertEquals(4, app.getDominantStringCount("a1a1")); + } + + @Test + void testRepeatingPattern() { + assertEquals(9, app.getDominantStringCount("ababab")); + } + + @Test + void testOddLengthInput() { + assertEquals(2, app.getDominantStringCount("abc")); + } + + @Test + void testPalindromeEvenLength() { + assertEquals(3, app.getDominantStringCount("abba")); + } + + @Test + void testAllUniqueCharactersEvenLength() { + assertEquals(3, app.getDominantStringCount("abcd")); + } + + @Test + void testLongerStringWithMultipleDominantSubstrings() { + assertEquals(10, app.getDominantStringCount("aabbaabb")); + } + + @Test + void testExactHalfMultipleCharacters() { + assertNotEquals(1, app.getDominantStringCount("aaabbb")); + assertEquals(3, app.getDominantStringCount("aaabbb")); + } +} \ No newline at end of file diff --git a/src/test/java/com/shortthirdman/quickstart/hackerrank/OptimizingBoxWeightsTest.java b/src/test/java/com/shortthirdman/quickstart/hackerrank/OptimizingBoxWeightsTest.java new file mode 100644 index 0000000..ff72e2d --- /dev/null +++ b/src/test/java/com/shortthirdman/quickstart/hackerrank/OptimizingBoxWeightsTest.java @@ -0,0 +1,123 @@ +package com.shortthirdman.quickstart.hackerrank; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class OptimizingBoxWeightsTest { + + OptimizingBoxWeights app; + + @BeforeEach + void setUp() { + app = new OptimizingBoxWeights(); + } + + @Test + void testEmptyList() { + List input = new ArrayList<>(); + List result = app.minimalHeaviestSetA(input); + assertTrue(result.isEmpty(), "The result should be an empty list."); + } + + // Test case when the input list has only one element + @Test + void testSingleElement() { + List input = new ArrayList<>(Collections.singletonList(5)); + List result = app.minimalHeaviestSetA(input); + assertEquals(input, result, "The result should be the same as the input."); + } + + // Test case where all elements in the list are the same + @Test + void testAllElementsEqual() { + List input = new ArrayList<>(Arrays.asList(3, 3, 3, 3, 3)); + List result = app.minimalHeaviestSetA(input); + assertEquals(Arrays.asList(3, 3, 3), result, "The result should be a list of the first half of the elements."); + } + + // Test case with multiple elements where Set A needs to be chosen carefully + @Test + void testNormalCase() { + List input = new ArrayList<>(Arrays.asList(10, 20, 15, 5, 30, 25)); + List expected = Arrays.asList(25, 30); + List result = app.minimalHeaviestSetA(input); + assertEquals(expected, result, "The minimal heaviest set A should be {25, 30}."); + } + + // Test case with a list of increasing integers + @Test + void testIncreasingOrder() { + List input = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); + List expected = Arrays.asList(6, 8, 9); + List result = app.minimalHeaviestSetA(input); + assertEquals(expected, result, "The minimal heaviest set A should be {6, 8, 9}."); + } + + // Test case with a list of decreasing integers + @Test + void testDecreasingOrder() { + List input = new ArrayList<>(Arrays.asList(9, 8, 7, 6, 5, 4, 3, 2, 1)); + List expected = Arrays.asList(6, 8, 9); + List result = app.minimalHeaviestSetA(input); + assertEquals(expected, result, "The minimal heaviest set A should be {6, 8, 9}."); + } + + // Test case with large numbers + @Test + void testLargeNumbers() { + List input = new ArrayList<>(Arrays.asList(1000000, 2000000, 1500000, 500000, 3000000, 2500000)); + List expected = Arrays.asList(2500000, 3000000); + List result = app.minimalHeaviestSetA(input); + assertEquals(expected, result, "The minimal heaviest set A should be {2500000, 3000000}."); + } + + // Test case with two elements + @Test + void testTwoElements() { + List input = new ArrayList<>(Arrays.asList(8, 6)); + List expected = Collections.singletonList(8); + List result = app.minimalHeaviestSetA(input); + assertEquals(expected, result, "The minimal heaviest set A should be {8}."); + } + + // Test case where the set is split right at the middle of the list + @Test + void testSplitMiddle() { + List input = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6)); + List expected = Arrays.asList(5, 6); + List result = app.minimalHeaviestSetA(input); + assertEquals(expected, result, "The minimal heaviest set A should be {5, 6}."); + } + + // Test case when all elements are larger than half of total sum + @Test + void testAllElementsLarge() { + List input = new ArrayList<>(Arrays.asList(50, 60, 70, 80, 90)); + List expected = Arrays.asList(50, 80, 90); + List result = app.minimalHeaviestSetA(input); + assertEquals(expected, result, "The minimal heaviest set A should be {50, 80, 90}."); + } + + // Test case with larger input size (stress test case) + @Test + void testLargeInput() { + List input = new ArrayList<>(); + for (int i = 1; i <= 1000; i++) { + input.add(i); + } + + List result = app.minimalHeaviestSetA(input); + int sumA = result.stream().mapToInt(Integer::intValue).sum(); + int sumRemaining = input.stream().mapToInt(Integer::intValue).sum() - sumA; + + assertTrue(sumA > sumRemaining, "Sum of Set A should be greater than the sum of the remaining elements."); + } +} \ No newline at end of file diff --git a/src/test/java/com/shortthirdman/quickstart/hackerrank/ReversePolishNotationTest.java b/src/test/java/com/shortthirdman/quickstart/hackerrank/ReversePolishNotationTest.java new file mode 100644 index 0000000..b4f09ac --- /dev/null +++ b/src/test/java/com/shortthirdman/quickstart/hackerrank/ReversePolishNotationTest.java @@ -0,0 +1,194 @@ +package com.shortthirdman.quickstart.hackerrank; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.EmptyStackException; + +import static org.junit.jupiter.api.Assertions.*; + +class ReversePolishNotationTest { + + ReversePolishNotation app; + + @BeforeEach + void setUp() { + app = new ReversePolishNotation(); + } + + // Positive test cases + @Test + void testAddition() { + String[] tokens = {"2", "1", "+"}; + assertEquals(3, app.evaluateRPN(tokens)); + } + + @Test + void testSubtraction() { + String[] tokens = {"5", "3", "-"}; + assertEquals(2, app.evaluateRPN(tokens)); + } + + @Test + void testMultiplication() { + String[] tokens = {"4", "2", "*"}; + assertEquals(8, app.evaluateRPN(tokens)); + } + + @Test + void testDivision() { + String[] tokens = {"6", "3", "/"}; + assertEquals(2, app.evaluateRPN(tokens)); + } + + @Test + void testComplexExpression() { + String[] tokens = {"2", "3", "+", "4", "*"}; + assertEquals(20, app.evaluateRPN(tokens)); // (2 + 3) * 4 + } + + @Test + void testMultipleOperators() { + String[] tokens = {"5", "1", "2", "+", "4", "*", "+"}; + assertNotEquals(14, app.evaluateRPN(tokens)); // 5 + (1 + 2) * 4 + assertEquals(17, app.evaluateRPN(tokens)); + } + + // Negative test cases + @Test + void testInvalidOperator() { + String[] tokens = {"2", "3", "@"}; // Invalid operator + assertThrows(IllegalArgumentException.class, () -> app.evaluateRPN(tokens)); + } + + @Test + void testInsufficientOperands() { + String[] tokens = {"1", "+"}; // Not enough operands for operator + assertThrows(EmptyStackException.class, () -> app.evaluateRPN(tokens)); + } + + @Test + void testDivisionByZero() { + String[] tokens = {"1", "0", "/"}; + assertThrows(ArithmeticException.class, () -> app.evaluateRPN(tokens)); + } + + // Edge cases + @Test + void testSingleNumber() { + String[] tokens = {"42"}; + assertEquals(42, app.evaluateRPN(tokens)); // Single operand + } + + @Test + void testMultipleSameOperators() { + String[] tokens = {"5", "5", "+", "5", "-"}; + assertEquals(5, app.evaluateRPN(tokens)); // (5 + 5) - 5 + } + + @Test + void testZeroDivision() { + String[] tokens = {"4", "0", "/"}; + assertThrows(ArithmeticException.class, () -> app.evaluateRPN(tokens)); // Division by zero + } + + @Test + void testNegativeResult() { + String[] tokens = {"3", "5", "-"}; + assertEquals(-2, app.evaluateRPN(tokens)); // 3 - 5 + } + + @Test + void testComplexNegativeResult() { + String[] tokens = {"4", "2", "*", "10", "-"}; + assertEquals(-2, app.evaluateRPN(tokens)); // (4 * 2) - 10 + } + + // Positive test cases + @Test + void shouldReturnSumForAddition() { + String[] tokens = {"2", "1", "+"}; + assertEquals(3, app.computeRPN(tokens)); + } + + @Test + void shouldReturnDifferenceForSubtraction() { + String[] tokens = {"5", "3", "-"}; + assertEquals(2, app.computeRPN(tokens)); + } + + @Test + void shouldReturnProductForMultiplication() { + String[] tokens = {"4", "2", "*"}; + assertEquals(8, app.computeRPN(tokens)); + } + + @Test + void shouldReturnQuotientForDivision() { + String[] tokens = {"6", "3", "/"}; + assertEquals(2, app.computeRPN(tokens)); + } + + @Test + void shouldEvaluateComplexExpressionCorrectly() { + String[] tokens = {"2", "3", "+", "4", "*"}; + assertEquals(20, app.computeRPN(tokens)); // (2 + 3) * 4 + } + + @Test + void shouldHandleMultipleOperatorsCorrectly() { + String[] tokens = {"5", "1", "2", "+", "4", "*", "+"}; + assertEquals(17, app.computeRPN(tokens)); // 5 + (1 + 2) * 4 + assertNotEquals(14, app.computeRPN(tokens)); // 5 + (1 + 2) * 4 + } + + // Negative test cases + @Test + void shouldThrowExceptionForInvalidOperator() { + String[] tokens = {"2", "3", "@"}; // Invalid operator + assertThrows(IllegalArgumentException.class, () -> app.computeRPN(tokens)); + } + + @Test + void shouldThrowExceptionForInsufficientOperands() { + String[] tokens = {"1", "+"}; // Not enough operands for operator + assertThrows(EmptyStackException.class, () -> app.computeRPN(tokens)); + } + + @Test + void shouldThrowExceptionForDivisionByZero() { + String[] tokens = {"1", "0", "/"}; + assertThrows(ArithmeticException.class, () -> app.computeRPN(tokens)); + } + + // Edge cases + @Test + void shouldReturnSingleNumber() { + String[] tokens = {"42"}; + assertEquals(42, app.computeRPN(tokens)); // Single operand + } + + @Test + void shouldEvaluateMultipleSameOperators() { + String[] tokens = {"5", "5", "+", "5", "-"}; + assertEquals(5, app.computeRPN(tokens)); // (5 + 5) - 5 + } + + @Test + void shouldThrowExceptionForZeroDivision() { + String[] tokens = {"4", "0", "/"}; + assertThrows(ArithmeticException.class, () -> app.computeRPN(tokens)); // Division by zero + } + + @Test + void shouldReturnNegativeResultForSubtraction() { + String[] tokens = {"3", "5", "-"}; + assertEquals(-2, app.computeRPN(tokens)); // 3 - 5 + } + + @Test + void shouldEvaluateComplexNegativeResult() { + String[] tokens = {"4", "2", "*", "10", "-"}; + assertEquals(-2, app.computeRPN(tokens)); // (4 * 2) - 10 + } +} \ No newline at end of file diff --git a/src/test/java/com/shortthirdman/quickstart/hackerrank/StoneWallTest.java b/src/test/java/com/shortthirdman/quickstart/hackerrank/StoneWallTest.java new file mode 100644 index 0000000..fb014c4 --- /dev/null +++ b/src/test/java/com/shortthirdman/quickstart/hackerrank/StoneWallTest.java @@ -0,0 +1,76 @@ +package com.shortthirdman.quickstart.hackerrank; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class StoneWallTest { + + StoneWall app; + + @BeforeEach + void setUp() { + app = new StoneWall(); + } + + // Positive Test Cases + @Test + public void testSingleHeight() { + int[] heights = {5}; + assertEquals(5, app.coverManhattanSkyline(heights)); + } + + @Test + public void testUniformHeight() { + int[] heights = {3, 3, 3}; + assertEquals(9, app.coverManhattanSkyline(heights)); + } + + @Test + public void testIncreasingHeights() { + int[] heights = {1, 2, 3, 4}; + assertEquals(10, app.coverManhattanSkyline(heights)); + } + + @Test + public void testDecreasingHeights() { + int[] heights = {4, 3, 2, 1}; + assertEquals(10, app.coverManhattanSkyline(heights)); + } + + @Test + public void testVaryingHeights() { + int[] heights = {2, 1, 4, 3}; + assertEquals(10, app.coverManhattanSkyline(heights)); + } + + @Test + public void testLargeHeights() { + int[] heights = {1000, 2000, 3000}; + assertEquals(6000, app.coverManhattanSkyline(heights)); + } + + // Edge Test Cases + @Test + public void testEmptyInput() { + int[] heights = {}; + Exception exception = assertThrows(IllegalArgumentException.class, () -> app.coverManhattanSkyline(heights)); + assertEquals("Number of height-blocks should be in range [1,100000]", exception.getMessage()); + } + + // Negative Test Cases + @Test + public void testNegativeHeights() { + int[] heights = {-1, -2, -3}; + Exception exception = assertThrows(IllegalArgumentException.class, () -> app.coverManhattanSkyline(heights)); + assertEquals("Height values must be positive", exception.getMessage()); + } + + @Test + public void testZeroHeight() { + int[] heights = {0, 1, 2}; + Exception exception = assertThrows(IllegalArgumentException.class, () -> app.coverManhattanSkyline(heights)); + assertEquals("Height values must be positive", exception.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/com/shortthirdman/quickstart/hackerrank/StreamApiFactoryTest.java b/src/test/java/com/shortthirdman/quickstart/hackerrank/StreamApiFactoryTest.java new file mode 100644 index 0000000..305b955 --- /dev/null +++ b/src/test/java/com/shortthirdman/quickstart/hackerrank/StreamApiFactoryTest.java @@ -0,0 +1,163 @@ +package com.shortthirdman.quickstart.hackerrank; + +import com.shortthirdman.quickstart.common.EmployeeRecord; +import com.shortthirdman.quickstart.common.Transaction; +import org.instancio.Instancio; +import org.instancio.junit.InstancioExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(InstancioExtension.class) +class StreamApiFactoryTest { + + StreamApiFactory app; + + @BeforeEach + void setUp() { + app = new StreamApiFactory(); + } + + @Test + void concatenateEvenLengthWords() { + List words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry"); + String actualWords = app.concatenateEvenLengthWords(words); + assertNotEquals("bananacherrydateelderberry", actualWords); + assertEquals("bananacherry", actualWords); + assertEquals("bananacherry".length(), actualWords.length()); + + assertThrows(IllegalArgumentException.class, () -> app.concatenateEvenLengthWords(List.of())); + assertThrows(IllegalArgumentException.class, () -> app.concatenateEvenLengthWords(null)); + + words = Arrays.asList("a", "abc", "def"); + assertEquals("", app.concatenateEvenLengthWords(words)); + + words = List.of("test"); + assertEquals("test", app.concatenateEvenLengthWords(words)); + } + + @Test + void countCharacterFrequency() { + String str = "Now is the winter"; + var actualCharacterFrequency = app.countCharacterFrequency(str); + var expectedCharacterFrequency = Map.ofEntries( + Map.entry(" ", 3L), + Map.entry("r", 1L), + Map.entry("s", 1L), + Map.entry("t", 2L), + Map.entry("e", 2L), + Map.entry("w", 2L), + Map.entry("h", 1L), + Map.entry("i", 2L), + Map.entry("n", 1L), + Map.entry("N", 1L), + Map.entry("o", 1L) + ); + assertEquals(expectedCharacterFrequency.size(), actualCharacterFrequency.size()); + assertEquals(expectedCharacterFrequency.keySet(), actualCharacterFrequency.keySet()); + assertEquals(expectedCharacterFrequency, actualCharacterFrequency); + assertThrows(IllegalArgumentException.class, () -> app.countCharacterFrequency(null)); + assertThrows(IllegalArgumentException.class, () -> app.countCharacterFrequency("")); + } + + @Test + void getDailyTransactionSums() { + List transactions = Arrays.asList( + new Transaction("2022-01-01", 100), + new Transaction("2022-01-01", 200), + new Transaction("2022-01-02", 300) + ); + Map actualDailyTotals = app.getDailyTransactionSums(transactions); + Map expectedDailyTotals = Map.ofEntries( + Map.entry("2022-01-01", 300L), + Map.entry("2022-01-02", 300L) + ); + assertEquals(expectedDailyTotals, actualDailyTotals); + + transactions = Instancio.createList(Transaction.class); + assertFalse(transactions.isEmpty()); + } + + @Test + void toSortedMapWithDuplicates() { + List employees = Arrays.asList( + new EmployeeRecord(101, "Alice"), + new EmployeeRecord(102, "Bob"), + new EmployeeRecord(101, "Charlie"), + new EmployeeRecord(103, "David"), + new EmployeeRecord(102, "Eve") + ); + Map> expectedEmployeeMap = Map.ofEntries( + Map.entry(101, List.of(new EmployeeRecord(101, "Alice"), new EmployeeRecord(101, "Charlie"))), + Map.entry(102, List.of(new EmployeeRecord(102, "Bob"), new EmployeeRecord(102, "Eve"))), + Map.entry(103, List.of(new EmployeeRecord(103, "David"))) + ); + Map> actualEmployeeMap = app.toSortedMapWithDuplicates(employees); + assertEquals(expectedEmployeeMap.size(), actualEmployeeMap.size()); + assertEquals(expectedEmployeeMap, actualEmployeeMap); + assertThrows(IllegalArgumentException.class, () -> app.toSortedMapWithDuplicates(null)); + assertThrows(IllegalArgumentException.class, () -> app.toSortedMapWithDuplicates(List.of())); + } + + @Test + void countStringsByFirstCharacter() { + List words = Arrays.asList("apple", "banana", "bear", "cat", "apple"); + Map expectedFrequencies = Map.ofEntries( + Map.entry('a', 2L), + Map.entry('b', 2L), + Map.entry('c', 1L) + ); + Map actualFrequencies = app.countStringsByFirstCharacter(words); + assertEquals(expectedFrequencies, actualFrequencies); + assertEquals(expectedFrequencies.size(), actualFrequencies.size()); + assertThrows(IllegalArgumentException.class, () -> app.countStringsByFirstCharacter(null)); + assertThrows(IllegalArgumentException.class, () -> app.countStringsByFirstCharacter(List.of())); + } + + @Test + void mergeSortAndFilterByThreshold() { + int[] array1 = {1, 5, 3, 9, 7}; + int[] array2 = {2, 4, 6, 8, 10}; + int threshold = 7; + var actualIntegers = app.mergeSortAndFilterByThreshold(array1, array2, threshold); + var expectedIntegers = Arrays.asList(8, 9, 10); + assertEquals(expectedIntegers, actualIntegers); + + assertThrows(IllegalArgumentException.class, () -> app.mergeSortAndFilterByThreshold(array1, null, 7)); + assertThrows(IllegalArgumentException.class, () -> app.mergeSortAndFilterByThreshold(null, null, 7)); + } + + @Test + void partitionByPrime() { + var numbers = Arrays.asList(2, 3, 4, 5, 6, 7, 8, 9, 10); + var actualPartitionByPrime = app.partitionByPrime(numbers); + var expectedPartitionByPrime = Map.ofEntries( + Map.entry(true, List.of(2, 3, 5, 7)), + Map.entry(false, List.of(4, 6, 8, 9, 10)) + ); + assertEquals(expectedPartitionByPrime.size(), actualPartitionByPrime.size()); + assertEquals(expectedPartitionByPrime.get(true).size(), actualPartitionByPrime.get(true).size()); + assertEquals(expectedPartitionByPrime.get(false).size(), actualPartitionByPrime.get(false).size()); + assertEquals(expectedPartitionByPrime, actualPartitionByPrime); + assertThrows(IllegalArgumentException.class, () -> app.partitionByPrime(null)); + assertThrows(IllegalArgumentException.class, () -> app.partitionByPrime(List.of())); + } + + @Test + void countTotalUniqueWords() { + var sentences = Arrays.asList( + "Java Stream API provides a fluent interface", + "It supports functional-style operations on streams", + "In this exercise, you need to count words" + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/shortthirdman/quickstart/leetcode/MinimumBinaryFlipsTest.java b/src/test/java/com/shortthirdman/quickstart/leetcode/MinimumBinaryFlipsTest.java index 1aef8dc..3876b1a 100644 --- a/src/test/java/com/shortthirdman/quickstart/leetcode/MinimumBinaryFlipsTest.java +++ b/src/test/java/com/shortthirdman/quickstart/leetcode/MinimumBinaryFlipsTest.java @@ -1,10 +1,10 @@ package com.shortthirdman.quickstart.leetcode; -import static org.junit.jupiter.api.Assertions.assertEquals; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + public class MinimumBinaryFlipsTest { MinimumBinaryFlips app; @@ -24,4 +24,85 @@ public void testGetMinFlips_defaultScenario() { assertEquals(2, app.getMinFlips(secondPwd)); assertEquals(3, app.getMinFlips(thirdPwd)); } + + // Positive Test Cases + @Test + void testAllZeros() { + assertEquals(0, app.getMinFlips("0000")); + } + + @Test + void testAllOnes() { + assertEquals(0, app.getMinFlips("1111")); + } + + @Test + void testAlternatingCharacters() { + assertEquals(3, app.getMinFlips("010101")); + assertNotEquals(2, app.getMinFlips("010101")); + } + + @Test + void testMixedCharacters() { + assertEquals(0, app.getMinFlips("1100")); + assertNotEquals(1, app.getMinFlips("1100")); + } + + @Test + void testShortString() { + assertEquals(0, app.getMinFlips("1")); // Edge case with a single character + } + + @Test + void testTwoDifferentChars() { + assertEquals(1, app.getMinFlips("01")); + } + + // Negative Test Cases + @Test + void testEmptyString() { + assertEquals(0, app.getMinFlips("")); + } + + @Test + void testSingleCharacterZero() { + assertEquals(0, app.getMinFlips("0")); + } + + @Test + void testSingleCharacterOne() { + assertEquals(0, app.getMinFlips("1")); + } + + @Test + void testTwoSameChars() { + assertEquals(0, app.getMinFlips("00")); // Both are zeros + assertEquals(0, app.getMinFlips("11")); // Both are ones + } + + // Edge Cases + @Test + void testLargeInput() { + StringBuilder largeInput = new StringBuilder(); + for (int i = 0; i < 10000; i++) { + largeInput.append(i % 2 == 0 ? '0' : '1'); + } + assertEquals(5000, app.getMinFlips(largeInput.toString())); + } + + @Test + void testLongSameChars() { + String longZeros = "0".repeat(10000); + assertEquals(0, app.getMinFlips(longZeros)); + + String longOnes = "1".repeat(10000); + assertEquals(0, app.getMinFlips(longOnes)); + } + + // Exception Handling Test Cases + @Test + void testNullInput() { + Exception exception = assertThrows(NullPointerException.class, () -> app.getMinFlips(null)); + assertEquals("Input text password can not be null", exception.getMessage()); + } }