Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Myers algorithm in linear space #112

Merged
merged 14 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ build
/.idea
*.iml
out
.DS_Store
.DS_Store
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kotlin 2.0 introduces a new folder to store metadata.

.kotlin/
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ All credit for the implementation goes to original authors.

## Features

All features from version `4.10` of the original library are present, except for:
All features from version `4.12` of the original library are present, except for:

- fuzzy patches
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will port in another PR.

- unified diff, which heavily uses file read/write and therefore needs a more complicated rewrite
- diff-utils-jgit, which uses JVM-only jgit library
- diff-utils-jgit, which uses JVM-only JGit

Please refer to the original guides for more information.

Expand All @@ -29,8 +30,10 @@ Currently, artifacts for the following platforms are supported:
- WebAssembly (JS and WASI)
- Native

The supported Native targets are (following the Kotlin/Native [target support guidelines](https://kotlinlang.org/docs/native-target-support.html)):
The supported Native targets are (following the Kotlin/Native [target support guidelines][1]):

| Tier 1 | Tier 2 | Tier 3 |
|:---------|:---------|:---------|
| macosX64 | linuxX64 | mingwX64 |

[1]: https://kotlinlang.org/docs/native-target-support.html
38 changes: 26 additions & 12 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest

plugins {
kotlin("multiplatform")
Expand All @@ -31,12 +32,17 @@ kotlin {

jvm {
compilations.configureEach {
compilerOptions.configure {
// Minimum bytecode level is 52
jvmTarget = JvmTarget.JVM_1_8

// Output interfaces with default methods
freeCompilerArgs.add("-Xjvm-default=all")
compileTaskProvider.configure {
compilerOptions {
// Minimum bytecode level is 52
jvmTarget = JvmTarget.JVM_1_8

// Output interfaces with default methods
freeCompilerArgs.addAll(
"-Xjvm-default=all", // Output interfaces with default methods
"-Xno-param-assertions", // Remove Intrinsics.checkNotNullParameter
)
}
}
}

Expand All @@ -48,23 +54,31 @@ kotlin {
}

js {
browser()
nodejs()
val testConfig: (KotlinJsTest).() -> Unit = {
useMocha {
// Override default 2s timeout
timeout = "120s"
}
}

browser {
testTask(testConfig)
}

nodejs {
testTask(testConfig)
}
}

@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
nodejs()
applyBinaryen()
}

@OptIn(ExperimentalWasmDsl::class)
wasmWasi {
nodejs()

// Available since 2.0
// applyBinaryen()
}

linuxX64()
Expand Down
9 changes: 9 additions & 0 deletions detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,12 @@ formatting:
active: false
TrailingCommaOnDeclarationSite:
active: true
complexity:
ComplexCondition:
active: false
CyclomaticComplexMethod:
active: false
NestedBlockDepth:
active: false
LongParameterList:
functionThreshold: 7
897 changes: 502 additions & 395 deletions kotlin-js-store/yarn.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package io.github.petertrr.diffutils

import io.github.petertrr.diffutils.algorithm.DiffAlgorithm
import io.github.petertrr.diffutils.algorithm.DiffAlgorithmListener
import io.github.petertrr.diffutils.algorithm.DiffEqualizer
import io.github.petertrr.diffutils.algorithm.NoopAlgorithmListener
import io.github.petertrr.diffutils.algorithm.myers.MyersDiff
import io.github.petertrr.diffutils.patch.Patch
Expand Down Expand Up @@ -73,7 +74,7 @@ public fun diff(
public fun <T> diff(
source: List<T>,
target: List<T>,
equalizer: ((T, T) -> Boolean),
equalizer: DiffEqualizer<T>,
): Patch<T> =
diff(
source = source,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@
package io.github.petertrr.diffutils.algorithm

import io.github.petertrr.diffutils.patch.DeltaType
import kotlin.jvm.JvmField

public data class Change(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if making the class @JvmRecord instead would have similar effect

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@petertrr we are targeting Java 8, so it wouldn't make any difference unfortunately.
But anyway, with records the only difference is method naming, e.g. getX() > x(), so you'd still not expose fields directly.

val deltaType: DeltaType,
val startOriginal: Int,
val endOriginal: Int,
val startRevised: Int,
val endRevised: Int,
@JvmField val deltaType: DeltaType,
@JvmField val startOriginal: Int,
@JvmField val endOriginal: Int,
@JvmField val startRevised: Int,
@JvmField val endRevised: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2024 Peter Trifanov.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.petertrr.diffutils.algorithm

public fun interface DiffEqualizer<T> {
public fun test(one: T, two: T): Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2024 Peter Trifanov.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.petertrr.diffutils.algorithm

public class EqualsDiffEqualizer<T> : DiffEqualizer<T> {
override fun test(one: T, two: T): Boolean =
one == two
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2024 Peter Trifanov.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.petertrr.diffutils.algorithm

public class IgnoreWsStringDiffEqualizer : DiffEqualizer<String> {
private companion object {
private val ws = Regex("\\s+")
}

override fun test(one: String, two: String): Boolean =
ws.replace(one.trim(), " ") == ws.replace(two.trim(), " ")
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ package io.github.petertrr.diffutils.algorithm.myers
import io.github.petertrr.diffutils.algorithm.Change
import io.github.petertrr.diffutils.algorithm.DiffAlgorithm
import io.github.petertrr.diffutils.algorithm.DiffAlgorithmListener
import io.github.petertrr.diffutils.algorithm.DiffEqualizer
import io.github.petertrr.diffutils.algorithm.EqualsDiffEqualizer
import io.github.petertrr.diffutils.patch.DeltaType

/**
* A clean-room implementation of Eugene Myers greedy differencing algorithm.
*/
public class MyersDiff<T>(private val equalizer: (T, T) -> Boolean = { t1, t2 -> t1 == t2 }) : DiffAlgorithm<T> {
public class MyersDiff<T>(private val equalizer: DiffEqualizer<T> = EqualsDiffEqualizer()) : DiffAlgorithm<T> {
/**
* Returns an empty diff if we get an error while procession the difference.
*/
Expand Down Expand Up @@ -82,7 +84,7 @@ public class MyersDiff<T>(private val equalizer: (T, T) -> Boolean = { t1, t2 ->
var j = i - k
var node = PathNode(i, j, snake = false, bootstrap = false, prev = prev)

while (i < origSize && j < revSize && equalizer.invoke(orig[i], rev[j])) {
while (i < origSize && j < revSize && equalizer.test(orig[i], rev[j])) {
i++
j++
}
Expand Down
Loading
Loading