diff --git a/build.gradle.kts b/build.gradle.kts index 2cfb061..7b962de 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "1.3.21" + kotlin("jvm") version Config.Versions.kotlin } group = "norangebit" @@ -16,6 +16,7 @@ dependencies { implementation(kotlin("stdlib-jdk8")) implementation(Config.Libs.arrowCore) implementation(Config.Libs.koin) + implementation(Config.Libs.gson) testImplementation(Config.Libs.junit) testImplementation(Config.Libs.kluent) diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index b8ca9df..c8f6bcc 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -30,8 +30,9 @@ object Config { val koin = "1.0.2" val junit = "5.1.0" val spek = "2.0.1" - val kotlin = "1.3.21." + val kotlin = "1.3.21" val mockk = "1.9.2" + val gson = "2.8.5" } object Libs { @@ -43,5 +44,6 @@ object Config { val kotlinReflect = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlin}" val spekRunner = "org.spekframework.spek2:spek-runner-junit5:${Versions.spek}" val mockk = "io.mockk:mockk:${Versions.mockk}" + val gson = "com.google.code.gson:gson:${Versions.gson}" } } \ No newline at end of file diff --git a/src/main/kotlin/it/norangeb/algorithms/exercises/BranchAndMerge.kt b/src/main/kotlin/it/norangeb/algorithms/exercises/BranchAndMerge.kt index 4c49c9d..c1cd5ff 100644 --- a/src/main/kotlin/it/norangeb/algorithms/exercises/BranchAndMerge.kt +++ b/src/main/kotlin/it/norangeb/algorithms/exercises/BranchAndMerge.kt @@ -6,7 +6,7 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights - * to use, copy, modify, tryMerge, publish, distribute, sublicense, and/or sell + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * @@ -25,13 +25,20 @@ package it.norangeb.algorithms.exercises +import com.google.gson.Gson import java.io.File import java.io.PrintStream import java.util.Scanner -object BranchAndMerge { - private const val TMP_FILE_PATH_1 = "/tmp/file1" - private const val TMP_FILE_PATH_2 = "/tmp/file2" +class BranchAndMerge( + private val typeClass: Class, + var compare: (obj1: T, obj2: T) -> Int +) { + companion object { + private const val TMP_FILE_PATH_1 = "/tmp/file1" + private const val TMP_FILE_PATH_2 = "/tmp/file2" + } + private val GSON = Gson() fun run(source: File) { tryBranch(source) @@ -45,7 +52,7 @@ object BranchAndMerge { private fun isSorted(): Boolean { val sc1 = Scanner(File(TMP_FILE_PATH_1)) val sc2 = Scanner(File(TMP_FILE_PATH_2)) - return !sc1.hasNextInt() || !sc2.hasNextInt() + return !sc1.hasNextLine() || !sc2.hasNextLine() } private fun tryBranch(fileToSort: File) { @@ -70,16 +77,16 @@ object BranchAndMerge { destination2: PrintStream ) { var destination = destination1 - var current: Int - var last: Int? = null + var current: T + var last: T? = null - while (source.hasNextInt()) { - current = source.nextInt() + while (source.hasNextLine()) { + current = fromJson(source.nextLine()) if (isLess(current, last)) destination = switchDestination( destination, destination1, destination2 ) - destination.println(current) + destination.println(toJson(current)) last = current } } @@ -103,23 +110,25 @@ object BranchAndMerge { private fun merge(source1: Scanner, source2: Scanner, destination: PrintStream) { var source = source1 - var current: Int - var cache: Int? = null + var current: T + var cache: T? = null - while (source1.hasNextInt() && source2.hasNextInt()) { + while (source1.hasNextLine() && source2.hasNextLine()) { if (cache == null) - cache = switchSource(source, source1, source2).nextInt() + cache = fromJson( + switchSource(source, source1, source2).nextLine() + ) - current = source.nextInt() + current = fromJson(source.nextLine()) if (!isLess(current, cache)) { source = switchSource(source, source1, source2) - current = cache.also { cache = current } + current = cache!!.also { cache = current } } - destination.println(current) + destination.println(toJson(current)) } - destination.println(cache) + destination.println(toJson(cache!!)) mergeTail(source1, source2, destination) } @@ -129,18 +138,18 @@ object BranchAndMerge { source2: Scanner, destination: PrintStream ) { - while (source1.hasNextInt()) - destination.println(source1.nextInt()) + while (source1.hasNextLine()) + destination.println(source1.nextLine()) - while (source2.hasNextInt()) - destination.println(source2.nextInt()) + while (source2.hasNextLine()) + destination.println(source2.nextLine()) } - private fun isLess(current: Int, last: Int?): Boolean { - if (last == null) + private fun isLess(first: T, second: T?): Boolean { + if (second == null) return false - return current < last + return compare(first, second) < 0 } private fun switchDestination( @@ -160,4 +169,8 @@ object BranchAndMerge { source1 -> source2 else -> source1 } + + private fun fromJson(json: String) = GSON.fromJson(json, typeClass) + + private fun toJson(obj: T) = GSON.toJson(obj) } \ No newline at end of file diff --git a/src/test/kotlin/it/norangeb/algorithms/exercises/BranchAndMergeTest.kt b/src/test/kotlin/it/norangeb/algorithms/exercises/BranchAndMergeTest.kt index 529907b..3f048ad 100644 --- a/src/test/kotlin/it/norangeb/algorithms/exercises/BranchAndMergeTest.kt +++ b/src/test/kotlin/it/norangeb/algorithms/exercises/BranchAndMergeTest.kt @@ -25,6 +25,7 @@ package it.norangeb.algorithms.exercises +import com.google.gson.Gson import io.mockk.every import io.mockk.mockk import org.amshove.kluent.`should equal` @@ -43,22 +44,30 @@ class BranchAndMergeTest { .find { it.name == "branch" } .also { it?.isAccessible = true } - val list1 = arrayListOf() - val list2 = arrayListOf() + val list1 = arrayListOf() + val list2 = arrayListOf() val source = mockk() val destination1 = mockk() val destination2 = mockk() - every { source.hasNextInt() } returnsMany + every { source.hasNextLine() } returnsMany listOf(true, true, true, true, true, false) - every { source.nextInt() } returnsMany listOf(3, 1, 4, 2, 0) - every { destination1.println(any() as Int) } answers { list1.add(arg(0)) } - every { destination2.println(any() as Int) } answers { list2.add(arg(0)) } + every { source.nextLine() } returnsMany + listOf(3, 1, 4, 2, 0).map { it.toString() } + every { destination1.println(any() as String) } answers { list1.add(arg(0)) } + every { destination2.println(any() as String) } answers { list2.add(arg(0)) } - branch?.call(BranchAndMerge, source, destination1, destination2) + branch?.call( + BranchAndMerge(Int::class.java, this::defaultComparator), + source, + destination1, + destination2 + ) - list1.toIntArray() `should equal` intArrayOf(3, 2) - list2.toIntArray() `should equal` intArrayOf(1, 4, 0) + list1.map { it.toInt() } + .toIntArray() `should equal` intArrayOf(3, 2) + list2.map { it.toInt() } + .toIntArray() `should equal` intArrayOf(1, 4, 0) } @Test @@ -67,21 +76,29 @@ class BranchAndMergeTest { .find { it.name == "branch" } .also { it?.isAccessible = true } - val list1 = arrayListOf() - val list2 = arrayListOf() + val list1 = arrayListOf() + val list2 = arrayListOf() val source = mockk() val destination1 = mockk() val destination2 = mockk() - every { source.hasNextInt() } returnsMany + every { source.hasNextLine() } returnsMany listOf(true, true, true, true, true, false) - every { source.nextInt() } returnsMany listOf(1, 2, 3, 10, 12) - every { destination1.println(any() as Int) } answers { list1.add(arg(0)) } + every { source.nextLine() } returnsMany + listOf(1, 2, 3, 10, 12).map { it.toString() } + every { destination1.println(any() as String) } answers { list1.add(arg(0)) } - branch?.call(BranchAndMerge, source, destination1, destination2) + branch?.call( + BranchAndMerge(Int::class.java, this::defaultComparator), + source, + destination1, + destination2 + ) - list1.toIntArray() `should equal` intArrayOf(1, 2, 3, 10, 12) - list2.toIntArray() `should equal` intArrayOf() + list1.map { it.toInt() } + .toIntArray() `should equal` intArrayOf(1, 2, 3, 10, 12) + list2.map { it.toInt() } + .toIntArray() `should equal` intArrayOf() } @Test @@ -90,23 +107,33 @@ class BranchAndMergeTest { .find { it.name == "merge" } .also { it?.isAccessible = true } - val list = arrayListOf() + val list = arrayListOf() val source1 = mockk() val source2 = mockk() val destination = mockk() - every { source1.hasNextInt() } returnsMany + every { source1.hasNextLine() } returnsMany listOf(true, true, true, false) - every { source1.nextInt() } returnsMany listOf(3, 2) - every { source2.hasNextInt() } returnsMany + every { source1.nextLine() } returnsMany + listOf(3, 2).map { it.toString() } + every { source2.hasNextLine() } returnsMany listOf(true, true, true, true, false) - every { source2.nextInt() } returnsMany listOf(1, 4, 0) - every { destination.println(any() as Int) } answers { list.add(arg(0)) } - every { destination.println(any() as Int?) } answers { list.add(arg(0)) } + every { source2.nextLine() } returnsMany + listOf(1, 4, 0).map { it.toString() } + every { destination.println(any() as String) } + .answers { list.add(arg(0)) } + every { destination.println(any() as String?) } + .answers { list.add(arg(0)) } - merge?.call(BranchAndMerge, source1, source2, destination) + merge?.call( + BranchAndMerge(Int::class.java, this::defaultComparator), + source1, + source2, + destination + ) - list.toIntArray() `should equal` intArrayOf(1, 3, 2, 4, 0) + list.map { it.toInt() } + .toIntArray() `should equal` intArrayOf(1, 3, 2, 4, 0) } @Test @@ -119,10 +146,16 @@ class BranchAndMergeTest { val source2 = mockk() val result1 = switchSource?.call( - BranchAndMerge, source1, source1, source2 + BranchAndMerge(Int::class.java, this::defaultComparator), + source1, + source1, + source2 ) val result2 = switchSource?.call( - BranchAndMerge, source2, source1, source2 + BranchAndMerge(Int::class.java, this::defaultComparator), + source2, + source1, + source2 ) result1 `should equal` source2 @@ -139,10 +172,16 @@ class BranchAndMergeTest { val destination2 = mockk() val result1 = switchDestination?.call( - BranchAndMerge, destination1, destination1, destination2 + BranchAndMerge(Int::class.java, this::defaultComparator), + destination1, + destination1, + destination2 ) val result2 = switchDestination?.call( - BranchAndMerge, destination2, destination1, destination2 + BranchAndMerge(Int::class.java, this::defaultComparator), + destination2, + destination1, + destination2 ) result1 `should equal` destination2 @@ -155,20 +194,24 @@ class BranchAndMergeTest { .find { it.name == "mergeTail" } .also { it?.isAccessible = true } - val list = arrayListOf() + val list = arrayListOf() val source1 = mockk() val source2 = mockk() val destination = mockk() - every { source1.hasNextInt() } returns false - every { source2.hasNextInt() } returns false - every { destination.println(any() as Int) } answers { list.add(arg(0)) } + every { source1.hasNextLine() } returns false + every { source2.hasNextLine() } returns false + every { destination.println(any() as String) } answers { list.add(arg(0)) } mergeTail?.call( - BranchAndMerge, source1, source2, destination + BranchAndMerge(Int::class.java, this::defaultComparator), + source1, + source2, + destination ) - list.toIntArray() `should equal` intArrayOf() + list.map { it.toInt() } + .toIntArray() `should equal` intArrayOf() } @Test @@ -177,21 +220,27 @@ class BranchAndMergeTest { .find { it.name == "mergeTail" } .also { it?.isAccessible = true } - val list = arrayListOf() + val list = arrayListOf() val source1 = mockk() val source2 = mockk() val destination = mockk() - every { source1.hasNextInt() } returns false - every { source2.hasNextInt() } returnsMany listOf(true, true, false) - every { source2.nextInt() } returnsMany listOf(3, 7) - every { destination.println(any() as Int) } answers { list.add(arg(0)) } + every { source1.hasNextLine() } returns false + every { source2.hasNextLine() } returnsMany listOf(true, true, false) + every { source2.nextLine() } returnsMany + listOf(3, 7).map { it.toString() } + every { destination.println(any() as String) } + .answers { list.add(arg(0)) } mergeTail?.call( - BranchAndMerge, source1, source2, destination + BranchAndMerge(Int::class.java, this::defaultComparator), + source1, + source2, + destination ) - list.toIntArray() `should equal` intArrayOf(3, 7) + list.map { it.toInt() } + .toIntArray() `should equal` intArrayOf(3, 7) } @Test @@ -200,48 +249,108 @@ class BranchAndMergeTest { .find { it.name == "mergeTail" } .also { it?.isAccessible = true } - val list = arrayListOf() + val list = arrayListOf() val source1 = mockk() val source2 = mockk() val destination = mockk() - every { source1.hasNextInt() } returnsMany listOf(true, false) - every { source1.nextInt() } returns 1 - every { source2.hasNextInt() } returns false - every { destination.println(any() as Int) } answers { list.add(arg(0)) } + every { source1.hasNextLine() } returnsMany listOf(true, false) + every { source1.nextLine() } returns "1" + every { source2.hasNextLine() } returns false + every { destination.println(any() as String) } + .answers { list.add(arg(0)) } mergeTail?.call( - BranchAndMerge, source1, source2, destination + BranchAndMerge(Int::class.java, this::defaultComparator), + source1, + source2, + destination ) - list.toIntArray() `should equal` intArrayOf(1) + list.map { it.toInt() } + .toIntArray() `should equal` intArrayOf(1) } @Test fun sortAlreadySortedTest() { - val path = "/tmp/test" - val ps = PrintStream(path) + val filePath = "/tmp/test" + val ps = PrintStream(filePath) val testSet = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 14, 16, 21, 34, 45) testSet.forEach { ps.println(it) } ps.close() - BranchAndMerge.run(File(path)) + BranchAndMerge(Int::class.java, this::defaultComparator) + .run(File(filePath)) - File(path).readLines().map { it.toInt() } `should equal` testSet + File(filePath).readLines() + .map { it.toInt() } `should equal` testSet } @Test fun sortTest() { - val path = "/tmp/test" - val ps = PrintStream(path) + val filePath = "/tmp/test" + val ps = PrintStream(filePath) val testSet = listOf(6, 2, 39, 4, 5, 11, 7, 99, 9, 10, 3, 16, 1, 45, 44) testSet.forEach { ps.println(it) } ps.close() - BranchAndMerge.run(File(path)) + BranchAndMerge(Int::class.java, this::defaultComparator) + .run(File(filePath)) - File(path).readLines().map { it.toInt() } `should equal` testSet.sorted() + File(filePath).readLines() + .map { it.toInt() } `should equal` testSet.sorted() + } + + @Test + fun reverseSortTest() { + val filePath = "/tmp/test" + val ps = PrintStream(filePath) + val testSet = listOf(6, 2, 39, 4, 5, 11, 7, 99, 9, 10, 3, 16, 1, 45, 44) + + testSet.forEach { ps.println(it) } + ps.close() + + BranchAndMerge(Int::class.java) { it1, it2 -> it2.compareTo(it1) } + .run(File(filePath)) + + File(filePath).readLines() + .map { it.toInt() } `should equal` testSet.sorted().reversed() + } + + data class Person(val firstName: String, val lastName: String, val age: Int) + + @Test + fun sortPersonsByAge() { + val filePath = "/tmp/test" + val ps = PrintStream(filePath) + val gson = Gson() + val testSet = listOf( + Person("Name1", "Surname1", 25), + Person("Name2", "Surname2", 15), + Person("Name3", "Surname3", 39), + Person("Name4", "Surname4", 10), + Person("Name5", "Surname5", 4) + ) + + testSet.map { gson.toJson(it) } + .forEach { ps.println(it) } + ps.close() + + val sortByAge = { p1: Person, p2: Person -> + p1.age.compareTo(p2.age) + } + + BranchAndMerge(Person::class.java, sortByAge) + .run(File(filePath)) + + File(filePath).readLines() + .map { gson.fromJson(it, Person::class.java) } `should equal` + testSet.sortedBy { it.age } + } + + private fun defaultComparator(obj1: T, obj2: T): Int { + return (obj1 as Comparable).compareTo(obj2) } } \ No newline at end of file