package com.aallam.similarity

import kotlin.math.max
import kotlin.math.min

/**
 * The Jaro–Winkler distance metric is designed and best suited for short strings such as person names, and to detect
 * typos; it is (roughly) a variation of Damerau-Levenshtein, where the substitution of 2 close characters is considered
 * less important than the substitution of 2 characters that a far from each other.
 *
 * Jaro-Winkler was developed in the area of record linkage (duplicate detection) (Winkler, 1990).
 * It returns a value in the interval [0.0, 1.0].
 *
 * @param threshold The current value of the threshold used for adding the Winkler bonus. The default value is 0.7.
 *
 * [Jaro–Winkler distance](https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance)
 */
public class JaroWinkler(
    private val threshold: Double = 0.7
) {

    /**
     * Compute Jaro-Winkler similarity.
     *
     *
     * @param first the first string to compare.
     * @param second the second string to compare.
     * @return The Jaro-Winkler similarity in the range [0, 1]
     */
    public fun similarity(first: String, second: String): Double {
        if (first == second) return 1.0
        val (m, t, l, p) = matchStrings(first, second)
        if (m == 0f) return 0.0
        // Jaro similarity = 1/3 * (m/|s1| + m/|s2| + (m-t)/m)
        val sj = ((m / first.length) + (m / second.length) + ((m - t) / m)) / 3.0
        // Winkler similarity = Sj + P * L * (1 – Sj)
        return if (sj > threshold) sj + p * l * (1 - sj) else sj
    }

    /**
     * Return 1 - similarity.
     * @param first the first string to compare.
     * @param second the second string to compare.
     * @return 1 - similarity.
     */
    public fun distance(first: String, second: String): Double {
        return 1.0 - similarity(first, second)
    }

    /**
     * Matches two given strings and returns the count of matching characters, transpositions, the length of the
     * common prefix, and the scaling factor.
     *
     * @param first The first string to compare.
     * @param second The second string to compare.
     */
    private fun matchStrings(first: String, second: String): Match {
        val min = minOf(first, second)
        val max = maxOf(first, second)
        val (matchIndexes, matchFlags, matches) = computeStringMatch(max, min)
        val ms1 = CharArray(matches).apply { fill(min, matchIndexes) }
        val ms2 = CharArray(matches).apply { fill(max, matchFlags) }
        val transpositions = transpositions(ms1, ms2)
        val prefix = commonPrefix(min, max)
        val scaling = min(JW_SCALING_FACTOR, 1.0 / max.length)
        return Match(matches.toFloat(), transpositions, prefix, scaling)
    }

    /**
     * Computes the matching indexes and flags between two strings.
     *
     * @param max the longer string
     * @param min the shorter string
     */
    private fun computeStringMatch(max: String, min: String): StringMatchInfo {
        val range = max(max.length / 2 - 1, 0)
        val matchIndexes = IntArray(min.length) { -1 }
        val matchFlags = BooleanArray(max.length)
        var matches = 0
        for (i in min.indices) {
            val char = min[i]
            var xi = max(i - range, 0)
            val xn = min(i + range + 1, max.length)
            while (xi < xn) {
                if (!matchFlags[xi] && char == max[xi]) {
                    matchIndexes[i] = xi
                    matchFlags[xi] = true
                    matches++
                    break
                }
                xi++
            }
        }
        return StringMatchInfo(matchIndexes, matchFlags, matches)
    }

    /**
     * Fills this character array with the characters from [max] at the positions indicated by the corresponding value
     * in [flags].
     *
     * @param max the string from which to take the characters.
     * @param flags the boolean array indicating which characters in the max string should be included.
     */
    private fun CharArray.fill(max: String, flags: BooleanArray) {
        var si = 0
        for (i in max.indices) {
            if (flags[i]) {
                this[si] = max[i]
                si++
            }
        }
    }

    /**
     * Fills the character array with characters from the given string [min], based on the specified match [indexes].
     *
     * @param min the string containing the characters to be copied into the character array.
     * @param indexes the indexes indicating which characters to copy from `min`. Any index value of -1 will be ignored.
     */
    private fun CharArray.fill(min: String, indexes: IntArray) {
        var si = 0
        for (i in min.indices) {
            if (indexes[i] != -1) {
                this[si] = min[i]
                si++
            }
        }
    }

    /**
     * Calculates the length of the common prefix between two strings.
     *
     * @param min the shorter of the two strings being compared
     * @param max the longer of the two strings being compared
     */
    private fun commonPrefix(min: String, max: String): Int {
        var prefix = 0
        for (mi in min.indices) {
            if (min[mi] != max[mi]) break
            prefix++
        }
        return prefix
    }

    /**
     * Calculates the number of transpositions between two matched strings.
     *
     * @param ms1 the first matched string.
     * @param ms2 the second matched string.
     */
    private fun transpositions(ms1: CharArray, ms2: CharArray): Int {
        var transpositions = 0
        for (mi in ms1.indices) {
            if (ms1[mi] != ms2[mi]) {
                transpositions++
            }
        }
        return transpositions / 2
    }

    public companion object {

        /** The standard value for the scaling factor */
        private const val JW_SCALING_FACTOR = 0.1
    }
}

/**
 * Represents information about the matching indexes and flags between two strings,
 * along with the total number of matches found.
 *
 * @param matchIndexes matching indexes
 * @param matchFlags matching flags
 * @param matches total number of matches found
 */
internal data class StringMatchInfo(
    val matchIndexes: IntArray,
    val matchFlags: BooleanArray,
    val matches: Int
)

/**
 * Represents a set of parameters that describe the similarity between two strings.
 *
 * @param matches number of matching characters
 * @param transpositions number of transpositions
 * @param prefix length of common prefix
 * @param scaling scaling factor
 */
internal data class Match(
    val matches: Float,
    val transpositions: Int,
    val prefix: Int,
    val scaling: Double,
)

Compiled from "JaroWinkler.kt"
public final class com.aallam.similarity.JaroWinkler$Companion {
  private com.aallam.similarity.JaroWinkler$Companion();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public com.aallam.similarity.JaroWinkler$Companion(kotlin.jvm.internal.DefaultConstructorMarker);
    Code:
       0: aload_0
       1: invokespecial #12                 // Method "<init>":()V
       4: return
}

Compiled from "JaroWinkler.kt"
public final class com.aallam.similarity.JaroWinkler {
  public static final com.aallam.similarity.JaroWinkler$Companion Companion;

  private final double threshold;

  private static final double JW_SCALING_FACTOR;

  public com.aallam.similarity.JaroWinkler(double);
    Code:
       0: aload_0
       1: invokespecial #9                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: dload_1
       6: putfield      #13                 // Field threshold:D
       9: return

  public com.aallam.similarity.JaroWinkler(double, int, kotlin.jvm.internal.DefaultConstructorMarker);
    Code:
       0: iload_3
       1: iconst_1
       2: iand
       3: ifeq          10
       6: ldc2_w        #17                 // double 0.7d
       9: dstore_1
      10: aload_0
      11: dload_1
      12: invokespecial #20                 // Method "<init>":(D)V
      15: return

  public final double similarity(java.lang.String, java.lang.String);
    Code:
       0: aload_1
       1: ldc           #25                 // String first
       3: invokestatic  #31                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #33                 // String second
       9: invokestatic  #31                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_1
      13: aload_2
      14: invokestatic  #37                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      17: ifeq          22
      20: dconst_1
      21: dreturn
      22: aload_0
      23: aload_1
      24: aload_2
      25: invokespecial #41                 // Method matchStrings:(Ljava/lang/String;Ljava/lang/String;)Lcom/aallam/similarity/Match;
      28: astore_3
      29: aload_3
      30: invokevirtual #47                 // Method com/aallam/similarity/Match.component1:()F
      33: fstore        4
      35: aload_3
      36: invokevirtual #51                 // Method com/aallam/similarity/Match.component2:()I
      39: istore        5
      41: aload_3
      42: invokevirtual #54                 // Method com/aallam/similarity/Match.component3:()I
      45: istore        6
      47: aload_3
      48: invokevirtual #58                 // Method com/aallam/similarity/Match.component4:()D
      51: dstore        7
      53: fload         4
      55: fconst_0
      56: fcmpg
      57: ifne          64
      60: iconst_1
      61: goto          65
      64: iconst_0
      65: ifeq          70
      68: dconst_0
      69: dreturn
      70: fload         4
      72: aload_1
      73: invokevirtual #63                 // Method java/lang/String.length:()I
      76: i2f
      77: fdiv
      78: fload         4
      80: aload_2
      81: invokevirtual #63                 // Method java/lang/String.length:()I
      84: i2f
      85: fdiv
      86: fadd
      87: fload         4
      89: iload         5
      91: i2f
      92: fsub
      93: fload         4
      95: fdiv
      96: fadd
      97: f2d
      98: ldc2_w        #64                 // double 3.0d
     101: ddiv
     102: dstore        9
     104: dload         9
     106: aload_0
     107: getfield      #13                 // Field threshold:D
     110: dcmpl
     111: ifle          132
     114: dload         9
     116: dload         7
     118: iload         6
     120: i2d
     121: dmul
     122: iconst_1
     123: i2d
     124: dload         9
     126: dsub
     127: dmul
     128: dadd
     129: goto          134
     132: dload         9
     134: dreturn

  public final double distance(java.lang.String, java.lang.String);
    Code:
       0: aload_1
       1: ldc           #25                 // String first
       3: invokestatic  #31                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #33                 // String second
       9: invokestatic  #31                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: dconst_1
      13: aload_0
      14: aload_1
      15: aload_2
      16: invokevirtual #76                 // Method similarity:(Ljava/lang/String;Ljava/lang/String;)D
      19: dsub
      20: dreturn

  private final com.aallam.similarity.Match matchStrings(java.lang.String, java.lang.String);
    Code:
       0: aload_1
       1: checkcast     #78                 // class java/lang/Comparable
       4: aload_2
       5: checkcast     #78                 // class java/lang/Comparable
       8: invokestatic  #84                 // Method kotlin/comparisons/ComparisonsKt.minOf:(Ljava/lang/Comparable;Ljava/lang/Comparable;)Ljava/lang/Comparable;
      11: checkcast     #60                 // class java/lang/String
      14: astore_3
      15: aload_1
      16: checkcast     #78                 // class java/lang/Comparable
      19: aload_2
      20: checkcast     #78                 // class java/lang/Comparable
      23: invokestatic  #87                 // Method kotlin/comparisons/ComparisonsKt.maxOf:(Ljava/lang/Comparable;Ljava/lang/Comparable;)Ljava/lang/Comparable;
      26: checkcast     #60                 // class java/lang/String
      29: astore        4
      31: aload_0
      32: aload         4
      34: aload_3
      35: invokespecial #91                 // Method computeStringMatch:(Ljava/lang/String;Ljava/lang/String;)Lcom/aallam/similarity/StringMatchInfo;
      38: astore        5
      40: aload         5
      42: invokevirtual #96                 // Method com/aallam/similarity/StringMatchInfo.component1:()[I
      45: astore        6
      47: aload         5
      49: invokevirtual #99                 // Method com/aallam/similarity/StringMatchInfo.component2:()[Z
      52: astore        7
      54: aload         5
      56: invokevirtual #100                // Method com/aallam/similarity/StringMatchInfo.component3:()I
      59: istore        8
      61: iload         8
      63: newarray       char
      65: astore        10
      67: aload         10
      69: astore        11
      71: iconst_0
      72: istore        12
      74: aload_0
      75: aload         11
      77: aload_3
      78: aload         6
      80: invokespecial #104                // Method fill:([CLjava/lang/String;[I)V
      83: aload         10
      85: astore        9
      87: iload         8
      89: newarray       char
      91: astore        11
      93: aload         11
      95: astore        12
      97: iconst_0
      98: istore        13
     100: aload_0
     101: aload         12
     103: aload         4
     105: aload         7
     107: invokespecial #107                // Method fill:([CLjava/lang/String;[Z)V
     110: aload         11
     112: astore        10
     114: aload_0
     115: aload         9
     117: aload         10
     119: invokespecial #111                // Method transpositions:([C[C)I
     122: istore        11
     124: aload_0
     125: aload_3
     126: aload         4
     128: invokespecial #115                // Method commonPrefix:(Ljava/lang/String;Ljava/lang/String;)I
     131: istore        12
     133: ldc2_w        #116                // double 0.1d
     136: dconst_1
     137: aload         4
     139: invokevirtual #63                 // Method java/lang/String.length:()I
     142: i2d
     143: ddiv
     144: invokestatic  #123                // Method java/lang/Math.min:(DD)D
     147: dstore        13
     149: new           #43                 // class com/aallam/similarity/Match
     152: dup
     153: iload         8
     155: i2f
     156: iload         11
     158: iload         12
     160: dload         13
     162: invokespecial #126                // Method com/aallam/similarity/Match."<init>":(FIID)V
     165: areturn

  private final com.aallam.similarity.StringMatchInfo computeStringMatch(java.lang.String, java.lang.String);
    Code:
       0: aload_1
       1: invokevirtual #63                 // Method java/lang/String.length:()I
       4: iconst_2
       5: idiv
       6: iconst_1
       7: isub
       8: iconst_0
       9: invokestatic  #144                // Method java/lang/Math.max:(II)I
      12: istore_3
      13: iconst_0
      14: istore        5
      16: aload_2
      17: invokevirtual #63                 // Method java/lang/String.length:()I
      20: istore        6
      22: iload         6
      24: newarray       int
      26: astore        7
      28: iload         5
      30: iload         6
      32: if_icmpge     51
      35: iload         5
      37: istore        8
      39: aload         7
      41: iload         8
      43: iconst_m1
      44: iastore
      45: iinc          5, 1
      48: goto          28
      51: aload         7
      53: astore        4
      55: aload_1
      56: invokevirtual #63                 // Method java/lang/String.length:()I
      59: newarray       boolean
      61: astore        5
      63: iconst_0
      64: istore        6
      66: iconst_0
      67: istore        7
      69: aload_2
      70: checkcast     #146                // class java/lang/CharSequence
      73: invokeinterface #147,  1          // InterfaceMethod java/lang/CharSequence.length:()I
      78: istore        8
      80: iload         7
      82: iload         8
      84: if_icmpge     177
      87: aload_2
      88: iload         7
      90: invokevirtual #151                // Method java/lang/String.charAt:(I)C
      93: istore        9
      95: iload         7
      97: iload_3
      98: isub
      99: iconst_0
     100: invokestatic  #144                // Method java/lang/Math.max:(II)I
     103: istore        10
     105: iload         7
     107: iload_3
     108: iadd
     109: iconst_1
     110: iadd
     111: aload_1
     112: invokevirtual #63                 // Method java/lang/String.length:()I
     115: invokestatic  #153                // Method java/lang/Math.min:(II)I
     118: istore        11
     120: iload         10
     122: iload         11
     124: if_icmpge     171
     127: aload         5
     129: iload         10
     131: baload
     132: ifne          165
     135: iload         9
     137: aload_1
     138: iload         10
     140: invokevirtual #151                // Method java/lang/String.charAt:(I)C
     143: if_icmpne     165
     146: aload         4
     148: iload         7
     150: iload         10
     152: iastore
     153: aload         5
     155: iload         10
     157: iconst_1
     158: bastore
     159: iinc          6, 1
     162: goto          171
     165: iinc          10, 1
     168: goto          120
     171: iinc          7, 1
     174: goto          80
     177: new           #93                 // class com/aallam/similarity/StringMatchInfo
     180: dup
     181: aload         4
     183: aload         5
     185: iload         6
     187: invokespecial #156                // Method com/aallam/similarity/StringMatchInfo."<init>":([I[ZI)V
     190: areturn

  private final void fill(char[], java.lang.String, boolean[]);
    Code:
       0: iconst_0
       1: istore        4
       3: iconst_0
       4: istore        5
       6: aload_2
       7: checkcast     #146                // class java/lang/CharSequence
      10: invokeinterface #147,  1          // InterfaceMethod java/lang/CharSequence.length:()I
      15: istore        6
      17: iload         5
      19: iload         6
      21: if_icmpge     50
      24: aload_3
      25: iload         5
      27: baload
      28: ifeq          44
      31: aload_1
      32: iload         4
      34: aload_2
      35: iload         5
      37: invokevirtual #151                // Method java/lang/String.charAt:(I)C
      40: castore
      41: iinc          4, 1
      44: iinc          5, 1
      47: goto          17
      50: return

  private final void fill(char[], java.lang.String, int[]);
    Code:
       0: iconst_0
       1: istore        4
       3: iconst_0
       4: istore        5
       6: aload_2
       7: checkcast     #146                // class java/lang/CharSequence
      10: invokeinterface #147,  1          // InterfaceMethod java/lang/CharSequence.length:()I
      15: istore        6
      17: iload         5
      19: iload         6
      21: if_icmpge     51
      24: aload_3
      25: iload         5
      27: iaload
      28: iconst_m1
      29: if_icmpeq     45
      32: aload_1
      33: iload         4
      35: aload_2
      36: iload         5
      38: invokevirtual #151                // Method java/lang/String.charAt:(I)C
      41: castore
      42: iinc          4, 1
      45: iinc          5, 1
      48: goto          17
      51: return

  private final int commonPrefix(java.lang.String, java.lang.String);
    Code:
       0: iconst_0
       1: istore_3
       2: iconst_0
       3: istore        4
       5: aload_1
       6: checkcast     #146                // class java/lang/CharSequence
       9: invokeinterface #147,  1          // InterfaceMethod java/lang/CharSequence.length:()I
      14: istore        5
      16: iload         4
      18: iload         5
      20: if_icmpge     47
      23: aload_1
      24: iload         4
      26: invokevirtual #151                // Method java/lang/String.charAt:(I)C
      29: aload_2
      30: iload         4
      32: invokevirtual #151                // Method java/lang/String.charAt:(I)C
      35: if_icmpne     47
      38: iinc          3, 1
      41: iinc          4, 1
      44: goto          16
      47: iload_3
      48: ireturn

  private final int transpositions(char[], char[]);
    Code:
       0: iconst_0
       1: istore_3
       2: iconst_0
       3: istore        4
       5: aload_1
       6: arraylength
       7: istore        5
       9: iload         4
      11: iload         5
      13: if_icmpge     36
      16: aload_1
      17: iload         4
      19: caload
      20: aload_2
      21: iload         4
      23: caload
      24: if_icmpeq     30
      27: iinc          3, 1
      30: iinc          4, 1
      33: goto          9
      36: iload_3
      37: iconst_2
      38: idiv
      39: ireturn

  public com.aallam.similarity.JaroWinkler();
    Code:
       0: aload_0
       1: dconst_0
       2: iconst_1
       3: aconst_null
       4: invokespecial #171                // Method "<init>":(DILkotlin/jvm/internal/DefaultConstructorMarker;)V
       7: return

  static {};
    Code:
       0: new           #174                // class com/aallam/similarity/JaroWinkler$Companion
       3: dup
       4: aconst_null
       5: invokespecial #177                // Method com/aallam/similarity/JaroWinkler$Companion."<init>":(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
       8: putstatic     #181                // Field Companion:Lcom/aallam/similarity/JaroWinkler$Companion;
      11: return
}


package com.aallam.similarity

import kotlin.math.min

/**
 * Implementation of the Optimal String Alignment (sometimes called the restricted edit distance) variant
 * of the Damerau-Levenshtein distance.
 *
 * The difference between the two algorithms consists in that the Optimal String Alignment algorithm computes the number
 * of edit operations needed to make the strings equal under the condition that no substring is edited more than once,
 * whereas Damerau-Levenshtein presents no such restriction.
 */
public class OptimalStringAlignment {

    /**
     * Compute the distance between strings: the minimum number of operations needed to transform one string into the
     * other (insertion, deletion, substitution of a single character, or a transposition of two adjacent characters)
     * while no substring is edited more than once.
     *
     * @param first the first string to compare.
     * @param second the second string to compare.
     */
    public fun distance(first: String, second: String): Int {
        if (first == second) return 0
        val n = first.length
        val m = second.length
        if (n == 0) return m
        if (m == 0) return n

        // create the distance matrix H[0..first.length+1][0..second.length+1]
        val distanceMatrix = Array(n + 2) { IntArray(m + 2) }

        // initialize top row and leftmost column
        for (i in 0..n) distanceMatrix[i][0] = i
        for (j in 0..m) distanceMatrix[0][j] = j

        // fill the distance matrix
        for (i in 1..n) {
            for (j in 1..m) {
                val cost = if (first[i - 1] == second[j - 1]) 0 else 1
                val substitution = distanceMatrix[i - 1][j - 1] + cost
                val insertion = distanceMatrix[i][j - 1] + 1
                val deletion = distanceMatrix[i - 1][j] + 1
                distanceMatrix[i][j] = minOf(substitution, insertion, deletion)

                // transposition check
                if (i > 1 && j > 1 && first[i - 1] == second[j - 2] && first[i - 2] == second[j - 1]) {
                    distanceMatrix[i][j] = min(distanceMatrix[i][j], distanceMatrix[i - 2][j - 2] + cost)
                }
            }
        }

        return distanceMatrix[n][m]
    }
}

Compiled from "OptimalStringAlignment.kt"
public final class com.aallam.similarity.OptimalStringAlignment {
  public com.aallam.similarity.OptimalStringAlignment();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public final int distance(java.lang.String, java.lang.String);
    Code:
       0: aload_1
       1: ldc           #15                 // String first
       3: invokestatic  #21                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #23                 // String second
       9: invokestatic  #21                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_1
      13: aload_2
      14: invokestatic  #27                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      17: ifeq          22
      20: iconst_0
      21: ireturn
      22: aload_1
      23: invokevirtual #33                 // Method java/lang/String.length:()I
      26: istore_3
      27: aload_2
      28: invokevirtual #33                 // Method java/lang/String.length:()I
      31: istore        4
      33: iload_3
      34: ifne          40
      37: iload         4
      39: ireturn
      40: iload         4
      42: ifne          47
      45: iload_3
      46: ireturn
      47: iconst_0
      48: istore        6
      50: iload_3
      51: iconst_2
      52: iadd
      53: istore        7
      55: iload         7
      57: anewarray     #35                 // class "[I"
      60: astore        8
      62: iload         6
      64: iload         7
      66: if_icmpge     90
      69: iload         6
      71: istore        9
      73: aload         8
      75: iload         9
      77: iload         4
      79: iconst_2
      80: iadd
      81: newarray       int
      83: aastore
      84: iinc          6, 1
      87: goto          62
      90: aload         8
      92: astore        5
      94: iconst_0
      95: istore        6
      97: iload         6
      99: iload_3
     100: if_icmpgt     124
     103: aload         5
     105: iload         6
     107: aaload
     108: iconst_0
     109: iload         6
     111: iastore
     112: iload         6
     114: iload_3
     115: if_icmpeq     124
     118: iinc          6, 1
     121: goto          103
     124: iconst_0
     125: istore        6
     127: iload         6
     129: iload         4
     131: if_icmpgt     156
     134: aload         5
     136: iconst_0
     137: aaload
     138: iload         6
     140: iload         6
     142: iastore
     143: iload         6
     145: iload         4
     147: if_icmpeq     156
     150: iinc          6, 1
     153: goto          134
     156: iconst_1
     157: istore        6
     159: iload         6
     161: iload_3
     162: if_icmpgt     375
     165: iconst_1
     166: istore        7
     168: iload         7
     170: iload         4
     172: if_icmpgt     363
     175: aload_1
     176: iload         6
     178: iconst_1
     179: isub
     180: invokevirtual #39                 // Method java/lang/String.charAt:(I)C
     183: aload_2
     184: iload         7
     186: iconst_1
     187: isub
     188: invokevirtual #39                 // Method java/lang/String.charAt:(I)C
     191: if_icmpne     198
     194: iconst_0
     195: goto          199
     198: iconst_1
     199: istore        8
     201: aload         5
     203: iload         6
     205: iconst_1
     206: isub
     207: aaload
     208: iload         7
     210: iconst_1
     211: isub
     212: iaload
     213: iload         8
     215: iadd
     216: istore        9
     218: aload         5
     220: iload         6
     222: aaload
     223: iload         7
     225: iconst_1
     226: isub
     227: iaload
     228: iconst_1
     229: iadd
     230: istore        10
     232: aload         5
     234: iload         6
     236: iconst_1
     237: isub
     238: aaload
     239: iload         7
     241: iaload
     242: iconst_1
     243: iadd
     244: istore        11
     246: aload         5
     248: iload         6
     250: aaload
     251: iload         7
     253: iload         9
     255: iload         10
     257: iload         11
     259: invokestatic  #45                 // Method java/lang/Math.min:(II)I
     262: invokestatic  #45                 // Method java/lang/Math.min:(II)I
     265: iastore
     266: iload         6
     268: iconst_1
     269: if_icmple     350
     272: iload         7
     274: iconst_1
     275: if_icmple     350
     278: aload_1
     279: iload         6
     281: iconst_1
     282: isub
     283: invokevirtual #39                 // Method java/lang/String.charAt:(I)C
     286: aload_2
     287: iload         7
     289: iconst_2
     290: isub
     291: invokevirtual #39                 // Method java/lang/String.charAt:(I)C
     294: if_icmpne     350
     297: aload_1
     298: iload         6
     300: iconst_2
     301: isub
     302: invokevirtual #39                 // Method java/lang/String.charAt:(I)C
     305: aload_2
     306: iload         7
     308: iconst_1
     309: isub
     310: invokevirtual #39                 // Method java/lang/String.charAt:(I)C
     313: if_icmpne     350
     316: aload         5
     318: iload         6
     320: aaload
     321: iload         7
     323: aload         5
     325: iload         6
     327: aaload
     328: iload         7
     330: iaload
     331: aload         5
     333: iload         6
     335: iconst_2
     336: isub
     337: aaload
     338: iload         7
     340: iconst_2
     341: isub
     342: iaload
     343: iload         8
     345: iadd
     346: invokestatic  #45                 // Method java/lang/Math.min:(II)I
     349: iastore
     350: iload         7
     352: iload         4
     354: if_icmpeq     363
     357: iinc          7, 1
     360: goto          175
     363: iload         6
     365: iload_3
     366: if_icmpeq     375
     369: iinc          6, 1
     372: goto          165
     375: aload         5
     377: iload_3
     378: aaload
     379: iload         4
     381: iaload
     382: ireturn
}


package com.aallam.similarity

import com.aallam.similarity.internal.Shingle

/**
 * Sorensen-Dice coefficient, aka Sørensen index, Dice's coefficient or Czekanowski's binary (non-quantitative) index.
 *
 * The strings are first converted to boolean sets of k-shingles (sequences of k characters), then the similarity is
 * computed as 2 * |A inter B| / (|A| + |B|).
 *
 * Attention: Sorensen-Dice distance (and similarity) does not satisfy triangle inequality.
 *
 * [Sørensen–Dice coefficient](https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient)
 *
 * @param k length of k-shingles
 */
public class SorensenDice(public val k: Int = 3) {

    /**
     * Similarity is computed as 2 * |A ∩ B| / (|A| + |B|).
     *
     * @param first The first string to compare.
     * @param second The second string to compare.
     */
    public fun similarity(first: String, second: String): Double {
        if (first == second) return 1.0
        val profile1 = Shingle.profile(first, k)
        val profile2 = Shingle.profile(second, k)
        val intersect = profile1.keys.intersect(profile2.keys)
        return (2.0 * intersect.size) / (profile1.size + profile2.size)
    }

    /**
     * Returns 1 - similarity.
     *
     * @param first The first string to compare.
     * @param second The second string to compare.
     */
    public fun distance(first: String, second: String): Double {
        return 1.0 - similarity(first, second)
    }
}

Compiled from "SorensenDice.kt"
public final class com.aallam.similarity.SorensenDice {
  private final int k;

  public com.aallam.similarity.SorensenDice(int);
    Code:
       0: aload_0
       1: invokespecial #9                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iload_1
       6: putfield      #13                 // Field k:I
       9: return

  public com.aallam.similarity.SorensenDice(int, int, kotlin.jvm.internal.DefaultConstructorMarker);
    Code:
       0: iload_2
       1: iconst_1
       2: iand
       3: ifeq          8
       6: iconst_3
       7: istore_1
       8: aload_0
       9: iload_1
      10: invokespecial #18                 // Method "<init>":(I)V
      13: return

  public final int getK();
    Code:
       0: aload_0
       1: getfield      #13                 // Field k:I
       4: ireturn

  public final double similarity(java.lang.String, java.lang.String);
    Code:
       0: aload_1
       1: ldc           #25                 // String first
       3: invokestatic  #31                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #33                 // String second
       9: invokestatic  #31                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_1
      13: aload_2
      14: invokestatic  #37                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      17: ifeq          22
      20: dconst_1
      21: dreturn
      22: getstatic     #43                 // Field com/aallam/similarity/internal/Shingle.Companion:Lcom/aallam/similarity/internal/Shingle$Companion;
      25: aload_1
      26: checkcast     #45                 // class java/lang/CharSequence
      29: aload_0
      30: getfield      #13                 // Field k:I
      33: invokevirtual #51                 // Method com/aallam/similarity/internal/Shingle$Companion.profile:(Ljava/lang/CharSequence;I)Ljava/util/Map;
      36: astore_3
      37: getstatic     #43                 // Field com/aallam/similarity/internal/Shingle.Companion:Lcom/aallam/similarity/internal/Shingle$Companion;
      40: aload_2
      41: checkcast     #45                 // class java/lang/CharSequence
      44: aload_0
      45: getfield      #13                 // Field k:I
      48: invokevirtual #51                 // Method com/aallam/similarity/internal/Shingle$Companion.profile:(Ljava/lang/CharSequence;I)Ljava/util/Map;
      51: astore        4
      53: aload_3
      54: invokeinterface #57,  1           // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
      59: checkcast     #59                 // class java/lang/Iterable
      62: aload         4
      64: invokeinterface #57,  1           // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
      69: checkcast     #59                 // class java/lang/Iterable
      72: invokestatic  #65                 // Method kotlin/collections/CollectionsKt.intersect:(Ljava/lang/Iterable;Ljava/lang/Iterable;)Ljava/util/Set;
      75: astore        5
      77: ldc2_w        #66                 // double 2.0d
      80: aload         5
      82: invokeinterface #72,  1           // InterfaceMethod java/util/Set.size:()I
      87: i2d
      88: dmul
      89: aload_3
      90: invokeinterface #73,  1           // InterfaceMethod java/util/Map.size:()I
      95: aload         4
      97: invokeinterface #73,  1           // InterfaceMethod java/util/Map.size:()I
     102: iadd
     103: i2d
     104: ddiv
     105: dreturn

  public final double distance(java.lang.String, java.lang.String);
    Code:
       0: aload_1
       1: ldc           #25                 // String first
       3: invokestatic  #31                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #33                 // String second
       9: invokestatic  #31                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: dconst_1
      13: aload_0
      14: aload_1
      15: aload_2
      16: invokevirtual #81                 // Method similarity:(Ljava/lang/String;Ljava/lang/String;)D
      19: dsub
      20: dreturn

  public com.aallam.similarity.SorensenDice();
    Code:
       0: aload_0
       1: iconst_0
       2: iconst_1
       3: aconst_null
       4: invokespecial #83                 // Method "<init>":(IILkotlin/jvm/internal/DefaultConstructorMarker;)V
       7: return
}


package com.aallam.similarity

import kotlin.math.max

/**
 * The longest common subsequence (LCS) problem consists in finding the longest subsequence common to two (or more)
 * sequences. It differs from problems of finding common substrings: unlike substrings, subsequences are not required
 * to occupy consecutive positions within the original sequences.
 *
 * LCS distance is equivalent to Levenshtein distance, when only insertion and deletion is allowed (no substitution),
 * or when the cost of the substitution is the double of the cost of an insertion or deletion.
 */
public class LongestCommonSubsequence {

    /**
     * Return the LCS distance between strings [first] (length n) and [second] (length m),
     * computed as `|n| + |m| - 2 * |LCS(first, second)|`, with min = 0 max = n + m
     *
     * @param first the first string to compare.
     * @param second the second string to compare.
     */
    public fun distance(first: String, second: String): Int {
        if (first == second) return 0
        return first.length + second.length - 2 * length(first, second)
    }

    /**
     * Return the length of the longest common subsequence (LCS) between strings [first]
     * and [second].
     *
     * @param first the first string to compare.
     * @param second the second string to compare.
     */
    public fun length(first: String, second: String): Int {
        val n = first.length
        val m = second.length
        val x = first.toCharArray()
        val y = second.toCharArray()
        val c = Array(n + 1) { IntArray(m + 1) }
        for (i in 1..n) {
            for (j in 1..m) {
                if (x[i - 1] == y[j - 1]) {
                    c[i][j] = c[i - 1][j - 1] + 1
                } else {
                    c[i][j] = max(c[i][j - 1], c[i - 1][j])
                }
            }
        }
        return c[n][m]
    }
}

Compiled from "LongestCommonSubsequence.kt"
public final class com.aallam.similarity.LongestCommonSubsequence {
  public com.aallam.similarity.LongestCommonSubsequence();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public final int distance(java.lang.String, java.lang.String);
    Code:
       0: aload_1
       1: ldc           #15                 // String first
       3: invokestatic  #21                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #23                 // String second
       9: invokestatic  #21                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_1
      13: aload_2
      14: invokestatic  #27                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      17: ifeq          22
      20: iconst_0
      21: ireturn
      22: aload_1
      23: invokevirtual #33                 // Method java/lang/String.length:()I
      26: aload_2
      27: invokevirtual #33                 // Method java/lang/String.length:()I
      30: iadd
      31: iconst_2
      32: aload_0
      33: aload_1
      34: aload_2
      35: invokevirtual #35                 // Method length:(Ljava/lang/String;Ljava/lang/String;)I
      38: imul
      39: isub
      40: ireturn

  public final int length(java.lang.String, java.lang.String);
    Code:
       0: aload_1
       1: ldc           #15                 // String first
       3: invokestatic  #21                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #23                 // String second
       9: invokestatic  #21                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_1
      13: invokevirtual #33                 // Method java/lang/String.length:()I
      16: istore_3
      17: aload_2
      18: invokevirtual #33                 // Method java/lang/String.length:()I
      21: istore        4
      23: aload_1
      24: invokevirtual #40                 // Method java/lang/String.toCharArray:()[C
      27: dup
      28: ldc           #42                 // String toCharArray(...)
      30: invokestatic  #45                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      33: astore        5
      35: aload_2
      36: invokevirtual #40                 // Method java/lang/String.toCharArray:()[C
      39: dup
      40: ldc           #42                 // String toCharArray(...)
      42: invokestatic  #45                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      45: astore        6
      47: iconst_0
      48: istore        8
      50: iload_3
      51: iconst_1
      52: iadd
      53: istore        9
      55: iload         9
      57: anewarray     #47                 // class "[I"
      60: astore        10
      62: iload         8
      64: iload         9
      66: if_icmpge     90
      69: iload         8
      71: istore        11
      73: aload         10
      75: iload         11
      77: iload         4
      79: iconst_1
      80: iadd
      81: newarray       int
      83: aastore
      84: iinc          8, 1
      87: goto          62
      90: aload         10
      92: astore        7
      94: iconst_1
      95: istore        8
      97: iload         8
      99: iload_3
     100: if_icmpgt     211
     103: iconst_1
     104: istore        9
     106: iload         9
     108: iload         4
     110: if_icmpgt     199
     113: aload         5
     115: iload         8
     117: iconst_1
     118: isub
     119: caload
     120: aload         6
     122: iload         9
     124: iconst_1
     125: isub
     126: caload
     127: if_icmpne     155
     130: aload         7
     132: iload         8
     134: aaload
     135: iload         9
     137: aload         7
     139: iload         8
     141: iconst_1
     142: isub
     143: aaload
     144: iload         9
     146: iconst_1
     147: isub
     148: iaload
     149: iconst_1
     150: iadd
     151: iastore
     152: goto          186
     155: aload         7
     157: iload         8
     159: aaload
     160: iload         9
     162: aload         7
     164: iload         8
     166: aaload
     167: iload         9
     169: iconst_1
     170: isub
     171: iaload
     172: aload         7
     174: iload         8
     176: iconst_1
     177: isub
     178: aaload
     179: iload         9
     181: iaload
     182: invokestatic  #53                 // Method java/lang/Math.max:(II)I
     185: iastore
     186: iload         9
     188: iload         4
     190: if_icmpeq     199
     193: iinc          9, 1
     196: goto          113
     199: iload         8
     201: iload_3
     202: if_icmpeq     211
     205: iinc          8, 1
     208: goto          103
     211: aload         7
     213: iload_3
     214: aaload
     215: iload         4
     217: iaload
     218: ireturn
}


package com.aallam.similarity

import kotlin.math.max
import kotlin.math.min

/**
 * N-Gram Similarity as defined by Kondrak, "N-Gram Similarity and Distance", String Processing and Information
 * Retrieval, Lecture Notes in Computer Science Volume 3772, 2005, pp 115-126.
 *
 * The algorithm uses affixing with special character '\n' to increase the weight of first characters.
 * The normalization is achieved by dividing the total similarity score the original length of the longest word.
 *
 * [N-Gram Similarity and Distance](http://webdocs.cs.ualberta.ca/~kondrak/papers/spire05.pdf)
 *
 * @param n n-gram length.
 */
public class NGram(private val n: Int = 2) {

    /**
     * Compute n-gram distance, in the range `[0, 1]`.
     *
     * @param first the first string to compare.
     * @param second the second string to compare.
     */
    public fun distance(first: String, second: String): Double {
        if (first == second) return 0.0
        val sl = first.length
        val tl = second.length
        if (sl == 0 || tl == 0) return 1.0

        val special = '\n'
        var cost = 0
        if (sl < n || tl < n) {
            val ni = min(sl, tl)
            for (i in 0..ni) {
                if (first[i] == second[i]) cost++
            }
            return cost.toDouble() / max(sl, tl)
        }

        // construct `sa` with prefix
        val sa = CharArray(sl + n - 1) { i ->
            if (i < n - 1) special else first[i - n + 1]
        }

        var p = DoubleArray(sl + 1) { it.toDouble() } // 'previous' cost array, horizontally
        var d = DoubleArray(sl + 1) // cost array, horizontally
        var tj = CharArray(n) // jth n-gram of t
        for (j in 1..tl) {
            // construct tj n-gram
            if (j < n) {
                for (ti in 0..<n - j) {
                    tj[ti] = special // add prefix
                }
                for (ti in n - j..<n) {
                    tj[ti] = second[ti - (n - j)]
                }
            } else {
                tj = second.substring(j - n, j).toCharArray()
            }
            d[0] = j.toDouble()

            for (i in 1..sl) {
                cost = 0
                var tn = n
                // compare sa to tj
                for (ni in 0..<n) {
                    if (sa[i - 1 + ni] != tj[ni]) {
                        cost++
                    } else if (sa[i - 1 + ni] == special) {
                        tn-- // discount matches on prefix
                    }
                }
                val ec = cost.toDouble() / tn
                // minimum of cell to the left+1, to the top+1,
                // diagonally left and up +cost
                d[i] = minOf(d[i - 1] + 1, p[i] + 1, p[i - 1] + ec)
            }
            // copy current distance counts to 'previous row' distance counts
            val swap = p
            p = d
            d = swap
        }

        // our last action in the above loop was to switch d and p, so p now
        // actually has the most recent cost counts
        return p[sl] / max(tl, sl)
    }
}

Compiled from "NGram.kt"
public final class com.aallam.similarity.NGram {
  private final int n;

  public com.aallam.similarity.NGram(int);
    Code:
       0: aload_0
       1: invokespecial #9                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iload_1
       6: putfield      #13                 // Field n:I
       9: return

  public com.aallam.similarity.NGram(int, int, kotlin.jvm.internal.DefaultConstructorMarker);
    Code:
       0: iload_2
       1: iconst_1
       2: iand
       3: ifeq          8
       6: iconst_2
       7: istore_1
       8: aload_0
       9: iload_1
      10: invokespecial #18                 // Method "<init>":(I)V
      13: return

  public final double distance(java.lang.String, java.lang.String);
    Code:
       0: aload_1
       1: ldc           #23                 // String first
       3: invokestatic  #29                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #31                 // String second
       9: invokestatic  #29                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_1
      13: aload_2
      14: invokestatic  #35                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      17: ifeq          22
      20: dconst_0
      21: dreturn
      22: aload_1
      23: invokevirtual #41                 // Method java/lang/String.length:()I
      26: istore_3
      27: aload_2
      28: invokevirtual #41                 // Method java/lang/String.length:()I
      31: istore        4
      33: iload_3
      34: ifeq          42
      37: iload         4
      39: ifne          44
      42: dconst_1
      43: dreturn
      44: bipush        10
      46: istore        5
      48: iconst_0
      49: istore        6
      51: iload_3
      52: aload_0
      53: getfield      #13                 // Field n:I
      56: if_icmplt     68
      59: iload         4
      61: aload_0
      62: getfield      #13                 // Field n:I
      65: if_icmpge     129
      68: iload_3
      69: iload         4
      71: invokestatic  #47                 // Method java/lang/Math.min:(II)I
      74: istore        7
      76: iconst_0
      77: istore        8
      79: iload         8
      81: iload         7
      83: if_icmpgt     117
      86: aload_1
      87: iload         8
      89: invokevirtual #51                 // Method java/lang/String.charAt:(I)C
      92: aload_2
      93: iload         8
      95: invokevirtual #51                 // Method java/lang/String.charAt:(I)C
      98: if_icmpne     104
     101: iinc          6, 1
     104: iload         8
     106: iload         7
     108: if_icmpeq     117
     111: iinc          8, 1
     114: goto          86
     117: iload         6
     119: i2d
     120: iload_3
     121: iload         4
     123: invokestatic  #54                 // Method java/lang/Math.max:(II)I
     126: i2d
     127: ddiv
     128: dreturn
     129: iconst_0
     130: istore        8
     132: iload_3
     133: aload_0
     134: getfield      #13                 // Field n:I
     137: iadd
     138: iconst_1
     139: isub
     140: istore        9
     142: iload         9
     144: newarray       char
     146: astore        10
     148: iload         8
     150: iload         9
     152: if_icmpge     199
     155: iload         8
     157: istore        11
     159: aload         10
     161: iload         11
     163: iload         11
     165: aload_0
     166: getfield      #13                 // Field n:I
     169: iconst_1
     170: isub
     171: if_icmpge     179
     174: iload         5
     176: goto          192
     179: aload_1
     180: iload         11
     182: aload_0
     183: getfield      #13                 // Field n:I
     186: isub
     187: iconst_1
     188: iadd
     189: invokevirtual #51                 // Method java/lang/String.charAt:(I)C
     192: castore
     193: iinc          8, 1
     196: goto          148
     199: aload         10
     201: astore        7
     203: iconst_0
     204: istore        9
     206: iload_3
     207: iconst_1
     208: iadd
     209: istore        10
     211: iload         10
     213: newarray       double
     215: astore        11
     217: iload         9
     219: iload         10
     221: if_icmpge     242
     224: iload         9
     226: istore        12
     228: aload         11
     230: iload         12
     232: iload         12
     234: i2d
     235: dastore
     236: iinc          9, 1
     239: goto          217
     242: aload         11
     244: astore        8
     246: iload_3
     247: iconst_1
     248: iadd
     249: newarray       double
     251: astore        9
     253: aload_0
     254: getfield      #13                 // Field n:I
     257: newarray       char
     259: astore        10
     261: iconst_1
     262: istore        11
     264: iload         11
     266: iload         4
     268: if_icmpgt     563
     271: iload         11
     273: aload_0
     274: getfield      #13                 // Field n:I
     277: if_icmpge     359
     280: iconst_0
     281: istore        12
     283: aload_0
     284: getfield      #13                 // Field n:I
     287: iload         11
     289: isub
     290: istore        13
     292: iload         12
     294: iload         13
     296: if_icmpge     312
     299: aload         10
     301: iload         12
     303: iload         5
     305: castore
     306: iinc          12, 1
     309: goto          292
     312: aload_0
     313: getfield      #13                 // Field n:I
     316: iload         11
     318: isub
     319: istore        12
     321: aload_0
     322: getfield      #13                 // Field n:I
     325: istore        13
     327: iload         12
     329: iload         13
     331: if_icmpge     389
     334: aload         10
     336: iload         12
     338: aload_2
     339: iload         12
     341: aload_0
     342: getfield      #13                 // Field n:I
     345: iload         11
     347: isub
     348: isub
     349: invokevirtual #51                 // Method java/lang/String.charAt:(I)C
     352: castore
     353: iinc          12, 1
     356: goto          327
     359: aload_2
     360: iload         11
     362: aload_0
     363: getfield      #13                 // Field n:I
     366: isub
     367: iload         11
     369: invokevirtual #58                 // Method java/lang/String.substring:(II)Ljava/lang/String;
     372: dup
     373: ldc           #60                 // String substring(...)
     375: invokestatic  #63                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
     378: invokevirtual #67                 // Method java/lang/String.toCharArray:()[C
     381: dup
     382: ldc           #69                 // String toCharArray(...)
     384: invokestatic  #63                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
     387: astore        10
     389: aload         9
     391: iconst_0
     392: iload         11
     394: i2d
     395: dastore
     396: iconst_1
     397: istore        12
     399: iload         12
     401: iload_3
     402: if_icmpgt     538
     405: iconst_0
     406: istore        6
     408: aload_0
     409: getfield      #13                 // Field n:I
     412: istore        13
     414: iconst_0
     415: istore        14
     417: aload_0
     418: getfield      #13                 // Field n:I
     421: istore        15
     423: iload         14
     425: iload         15
     427: if_icmpge     478
     430: aload         7
     432: iload         12
     434: iconst_1
     435: isub
     436: iload         14
     438: iadd
     439: caload
     440: aload         10
     442: iload         14
     444: caload
     445: if_icmpeq     454
     448: iinc          6, 1
     451: goto          472
     454: aload         7
     456: iload         12
     458: iconst_1
     459: isub
     460: iload         14
     462: iadd
     463: caload
     464: iload         5
     466: if_icmpne     472
     469: iinc          13, -1
     472: iinc          14, 1
     475: goto          423
     478: iload         6
     480: i2d
     481: iload         13
     483: i2d
     484: ddiv
     485: dstore        14
     487: aload         9
     489: iload         12
     491: aload         9
     493: iload         12
     495: iconst_1
     496: isub
     497: daload
     498: iconst_1
     499: i2d
     500: dadd
     501: aload         8
     503: iload         12
     505: daload
     506: iconst_1
     507: i2d
     508: dadd
     509: aload         8
     511: iload         12
     513: iconst_1
     514: isub
     515: daload
     516: dload         14
     518: dadd
     519: invokestatic  #72                 // Method java/lang/Math.min:(DD)D
     522: invokestatic  #72                 // Method java/lang/Math.min:(DD)D
     525: dastore
     526: iload         12
     528: iload_3
     529: if_icmpeq     538
     532: iinc          12, 1
     535: goto          405
     538: aload         8
     540: astore        12
     542: aload         9
     544: astore        8
     546: aload         12
     548: astore        9
     550: iload         11
     552: iload         4
     554: if_icmpeq     563
     557: iinc          11, 1
     560: goto          271
     563: aload         8
     565: iload_3
     566: daload
     567: iload         4
     569: iload_3
     570: invokestatic  #54                 // Method java/lang/Math.max:(II)I
     573: i2d
     574: ddiv
     575: dreturn

  public com.aallam.similarity.NGram();
    Code:
       0: aload_0
       1: iconst_0
       2: iconst_1
       3: aconst_null
       4: invokespecial #96                 // Method "<init>":(IILkotlin/jvm/internal/DefaultConstructorMarker;)V
       7: return
}


package com.aallam.similarity

import com.aallam.similarity.internal.Profile
import com.aallam.similarity.internal.Shingle
import kotlin.math.abs

/**
 * Q-gram distance, as defined by Ukkonen in [Approximate string-matching with q-grams and maximal matches](http://www.sciencedirect.com/science/article/pii/0304397592901434).
 * The distance between two strings is defined as the L1 norm of the difference of their profiles (the number
 * of occurrences of each n-gram).
 *
 * Q-gram distance is a lower bound on Levenshtein distance, but can be computed in O(m+n), where Levenshtein
 * requires O(m.n).
 *
 * @param k length of k-shingles
 */
public class QGram(private val k: Int = 3) {

    /**
     * The distance between two strings is defined as the L1 norm of the
     * difference of their profiles (the number of occurence of each k-shingle).
     *
     * @param first the first string to compare.
     * @param second the second string to compare.
     */
    public fun distance(first: String, second: String): Int {
        if (first == second) return 0
        val p1 = Shingle.profile(first, k)
        val p2 = Shingle.profile(second, k)
        return distance(p1, p2)
    }

    /**
     * Compute QGram distance using precomputed profiles.
     */
    private fun distance(first: Profile, second: Profile): Int {
        var agg = 0
        for (key in (first.keys + second.keys)) {
            var v1 = 0
            var v2 = 0
            first[key]?.let { v1 = it }
            second[key]?.let { v2 = it }
            agg += abs(v1 - v2)
        }
        return agg
    }
}

Compiled from "QGram.kt"
public final class com.aallam.similarity.QGram {
  private final int k;

  public com.aallam.similarity.QGram(int);
    Code:
       0: aload_0
       1: invokespecial #9                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iload_1
       6: putfield      #13                 // Field k:I
       9: return

  public com.aallam.similarity.QGram(int, int, kotlin.jvm.internal.DefaultConstructorMarker);
    Code:
       0: iload_2
       1: iconst_1
       2: iand
       3: ifeq          8
       6: iconst_3
       7: istore_1
       8: aload_0
       9: iload_1
      10: invokespecial #18                 // Method "<init>":(I)V
      13: return

  public final int distance(java.lang.String, java.lang.String);
    Code:
       0: aload_1
       1: ldc           #23                 // String first
       3: invokestatic  #29                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #31                 // String second
       9: invokestatic  #29                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_1
      13: aload_2
      14: invokestatic  #35                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      17: ifeq          22
      20: iconst_0
      21: ireturn
      22: getstatic     #41                 // Field com/aallam/similarity/internal/Shingle.Companion:Lcom/aallam/similarity/internal/Shingle$Companion;
      25: aload_1
      26: checkcast     #43                 // class java/lang/CharSequence
      29: aload_0
      30: getfield      #13                 // Field k:I
      33: invokevirtual #49                 // Method com/aallam/similarity/internal/Shingle$Companion.profile:(Ljava/lang/CharSequence;I)Ljava/util/Map;
      36: astore_3
      37: getstatic     #41                 // Field com/aallam/similarity/internal/Shingle.Companion:Lcom/aallam/similarity/internal/Shingle$Companion;
      40: aload_2
      41: checkcast     #43                 // class java/lang/CharSequence
      44: aload_0
      45: getfield      #13                 // Field k:I
      48: invokevirtual #49                 // Method com/aallam/similarity/internal/Shingle$Companion.profile:(Ljava/lang/CharSequence;I)Ljava/util/Map;
      51: astore        4
      53: aload_0
      54: aload_3
      55: aload         4
      57: invokespecial #52                 // Method distance:(Ljava/util/Map;Ljava/util/Map;)I
      60: ireturn

  private final int distance(java.util.Map<java.lang.String, java.lang.Integer>, java.util.Map<java.lang.String, java.lang.Integer>);
    Code:
       0: iconst_0
       1: istore_3
       2: aload_1
       3: invokeinterface #63,  1           // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
       8: aload_2
       9: invokeinterface #63,  1           // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
      14: checkcast     #65                 // class java/lang/Iterable
      17: invokestatic  #71                 // Method kotlin/collections/SetsKt.plus:(Ljava/util/Set;Ljava/lang/Iterable;)Ljava/util/Set;
      20: invokeinterface #77,  1           // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;
      25: astore        4
      27: aload         4
      29: invokeinterface #83,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
      34: ifeq          139
      37: aload         4
      39: invokeinterface #87,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      44: checkcast     #89                 // class java/lang/String
      47: astore        5
      49: iconst_0
      50: istore        6
      52: iconst_0
      53: istore        7
      55: aload_1
      56: aload         5
      58: invokeinterface #93,  2           // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
      63: checkcast     #95                 // class java/lang/Integer
      66: dup
      67: ifnull        88
      70: checkcast     #97                 // class java/lang/Number
      73: invokevirtual #101                // Method java/lang/Number.intValue:()I
      76: istore        8
      78: iconst_0
      79: istore        9
      81: iload         8
      83: istore        6
      85: goto          90
      88: pop
      89: nop
      90: aload_2
      91: aload         5
      93: invokeinterface #93,  2           // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
      98: checkcast     #95                 // class java/lang/Integer
     101: dup
     102: ifnull        123
     105: checkcast     #97                 // class java/lang/Number
     108: invokevirtual #101                // Method java/lang/Number.intValue:()I
     111: istore        8
     113: iconst_0
     114: istore        9
     116: iload         8
     118: istore        7
     120: goto          125
     123: pop
     124: nop
     125: iload_3
     126: iload         6
     128: iload         7
     130: isub
     131: invokestatic  #107                // Method java/lang/Math.abs:(I)I
     134: iadd
     135: istore_3
     136: goto          27
     139: iload_3
     140: ireturn

  public com.aallam.similarity.QGram();
    Code:
       0: aload_0
       1: iconst_0
       2: iconst_1
       3: aconst_null
       4: invokespecial #116                // Method "<init>":(IILkotlin/jvm/internal/DefaultConstructorMarker;)V
       7: return
}


package com.aallam.similarity

import kotlin.math.min

/**
 * Implementation of Levenshtein that allows to define different weights for different character substitutions.
 *
 * @param weights the strategy to determine character operations weights.
 */
public class WeightedLevenshtein(
    private val weights: OperationsWeights
) {

    public fun distance(first: CharSequence, second: CharSequence, limit: Double = Double.MAX_VALUE): Double {
        if (first == second) return 0.0
        if (first.isEmpty()) return second.length.toDouble()
        if (second.isEmpty()) return first.length.toDouble()

        // initial costs is the edit distance from an empty string, which corresponds to the characters to insert.
        // the array size is : length + 1 (empty string)
        var cost = DoubleArray(first.length + 1)
        var newCost = DoubleArray(first.length + 1)

        first.forEachIndexed { i, char ->
            cost[i + 1] = cost[i] + weights.insertion(char)
        }

        for (i in 1..second.length) {

            // calculate new costs from the previous row.
            // the first element of the new row is the edit distance (deletes) to match empty string
            val secondChar = second[i - 1]
            val deletionCost = weights.deletion(secondChar)
            newCost[0] = cost[0] + deletionCost

            var minCost = newCost[0]

            // fill in the rest of the row
            for (j in 1..first.length) {
                // if it's the same char at the same position, no edit cost.
                val firstChar = first[j - 1]
                val edit = if (firstChar == secondChar) 0.0 else weights.substitution(firstChar, secondChar)
                val replace = cost[j - 1] + edit
                val insert = cost[j] + weights.insertion(secondChar)
                val delete = newCost[j - 1] + weights.deletion(firstChar)
                newCost[j] = minOf(insert, delete, replace)
                minCost = min(minCost, newCost[j])
            }

            if (minCost >= limit) return limit

            // flip references of current and previous row
            val swap = cost
            cost = newCost
            newCost = swap
        }

        return cost.last()
    }
}

/**
 * Used to indicate the cost of character operations (add, replace, delete).
 * The cost should always be in the range `[O, 1]`.
 *
 * Default implementation of all operations is `1.0`.
 *
 * Examples:
 * - In an OCR application, cost('o', 'a') could be 0.4.
 * - In a check-spelling application, cost('u', 'i') could be 0.4 because these are next to each other on the keyboard.
 */
public interface OperationsWeights {

    /**
     * Indicate the cost of substitution.
     * The cost in the range `[0, 1]`.
     *
     * @param first first character of the substitution.
     * @param second second character of the substitution.
     */
    public fun substitution(first: Char, second: Char): Double = 1.0

    /**
     * Get the cost to delete a given character.
     * The cost in the range `[0, 1]`.
     *
     * @param char the character being inserted.
     */
    public fun deletion(char: Char): Double = 1.0

    /**
     * Get the cost to insert a given character.
     * The cost in the range `[0, 1]`.
     *
     * @param char the character being inserted.
     */
    public fun insertion(char: Char): Double = 1.0
}

Compiled from "WeightedLevenshtein.kt"
public final class com.aallam.similarity.WeightedLevenshtein {
  private final com.aallam.similarity.OperationsWeights weights;

  public com.aallam.similarity.WeightedLevenshtein(com.aallam.similarity.OperationsWeights);
    Code:
       0: aload_1
       1: ldc           #9                  // String weights
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: invokespecial #18                 // Method java/lang/Object."<init>":()V
      10: aload_0
      11: aload_1
      12: putfield      #21                 // Field weights:Lcom/aallam/similarity/OperationsWeights;
      15: return

  public final double distance(java.lang.CharSequence, java.lang.CharSequence, double);
    Code:
       0: aload_1
       1: ldc           #27                 // String first
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #29                 // String second
       9: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_1
      13: aload_2
      14: invokestatic  #33                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      17: ifeq          22
      20: dconst_0
      21: dreturn
      22: aload_1
      23: invokeinterface #39,  1           // InterfaceMethod java/lang/CharSequence.length:()I
      28: ifne          35
      31: iconst_1
      32: goto          36
      35: iconst_0
      36: ifeq          47
      39: aload_2
      40: invokeinterface #39,  1           // InterfaceMethod java/lang/CharSequence.length:()I
      45: i2d
      46: dreturn
      47: aload_2
      48: invokeinterface #39,  1           // InterfaceMethod java/lang/CharSequence.length:()I
      53: ifne          60
      56: iconst_1
      57: goto          61
      60: iconst_0
      61: ifeq          72
      64: aload_1
      65: invokeinterface #39,  1           // InterfaceMethod java/lang/CharSequence.length:()I
      70: i2d
      71: dreturn
      72: aconst_null
      73: astore        5
      75: aload_1
      76: invokeinterface #39,  1           // InterfaceMethod java/lang/CharSequence.length:()I
      81: iconst_1
      82: iadd
      83: newarray       double
      85: astore        5
      87: aload_1
      88: invokeinterface #39,  1           // InterfaceMethod java/lang/CharSequence.length:()I
      93: iconst_1
      94: iadd
      95: newarray       double
      97: astore        6
      99: aload_1
     100: astore        7
     102: iconst_0
     103: istore        8
     105: iconst_0
     106: istore        9
     108: iconst_0
     109: istore        10
     111: iload         10
     113: aload         7
     115: invokeinterface #39,  1           // InterfaceMethod java/lang/CharSequence.length:()I
     120: if_icmpge     180
     123: aload         7
     125: iload         10
     127: invokeinterface #43,  2           // InterfaceMethod java/lang/CharSequence.charAt:(I)C
     132: istore        11
     134: iload         9
     136: iinc          9, 1
     139: iload         11
     141: istore        12
     143: istore        13
     145: iconst_0
     146: istore        14
     148: aload         5
     150: iload         13
     152: iconst_1
     153: iadd
     154: aload         5
     156: iload         13
     158: daload
     159: aload_0
     160: getfield      #21                 // Field weights:Lcom/aallam/similarity/OperationsWeights;
     163: iload         12
     165: invokeinterface #49,  2           // InterfaceMethod com/aallam/similarity/OperationsWeights.insertion:(C)D
     170: dadd
     171: dastore
     172: nop
     173: nop
     174: iinc          10, 1
     177: goto          111
     180: nop
     181: iconst_1
     182: istore        7
     184: aload_2
     185: invokeinterface #39,  1           // InterfaceMethod java/lang/CharSequence.length:()I
     190: istore        8
     192: iload         7
     194: iload         8
     196: if_icmpgt     425
     199: aload_2
     200: iload         7
     202: iconst_1
     203: isub
     204: invokeinterface #43,  2           // InterfaceMethod java/lang/CharSequence.charAt:(I)C
     209: istore        9
     211: aload_0
     212: getfield      #21                 // Field weights:Lcom/aallam/similarity/OperationsWeights;
     215: iload         9
     217: invokeinterface #52,  2           // InterfaceMethod com/aallam/similarity/OperationsWeights.deletion:(C)D
     222: dstore        10
     224: aload         6
     226: iconst_0
     227: aload         5
     229: iconst_0
     230: daload
     231: dload         10
     233: dadd
     234: dastore
     235: aload         6
     237: iconst_0
     238: daload
     239: dstore        12
     241: iconst_1
     242: istore        14
     244: aload_1
     245: invokeinterface #39,  1           // InterfaceMethod java/lang/CharSequence.length:()I
     250: istore        15
     252: iload         14
     254: iload         15
     256: if_icmpgt     391
     259: aload_1
     260: iload         14
     262: iconst_1
     263: isub
     264: invokeinterface #43,  2           // InterfaceMethod java/lang/CharSequence.charAt:(I)C
     269: istore        16
     271: iload         16
     273: iload         9
     275: if_icmpne     282
     278: dconst_0
     279: goto          295
     282: aload_0
     283: getfield      #21                 // Field weights:Lcom/aallam/similarity/OperationsWeights;
     286: iload         16
     288: iload         9
     290: invokeinterface #56,  3           // InterfaceMethod com/aallam/similarity/OperationsWeights.substitution:(CC)D
     295: dstore        17
     297: aload         5
     299: iload         14
     301: iconst_1
     302: isub
     303: daload
     304: dload         17
     306: dadd
     307: dstore        19
     309: aload         5
     311: iload         14
     313: daload
     314: aload_0
     315: getfield      #21                 // Field weights:Lcom/aallam/similarity/OperationsWeights;
     318: iload         9
     320: invokeinterface #49,  2           // InterfaceMethod com/aallam/similarity/OperationsWeights.insertion:(C)D
     325: dadd
     326: dstore        21
     328: aload         6
     330: iload         14
     332: iconst_1
     333: isub
     334: daload
     335: aload_0
     336: getfield      #21                 // Field weights:Lcom/aallam/similarity/OperationsWeights;
     339: iload         16
     341: invokeinterface #52,  2           // InterfaceMethod com/aallam/similarity/OperationsWeights.deletion:(C)D
     346: dadd
     347: dstore        23
     349: aload         6
     351: iload         14
     353: dload         21
     355: dload         23
     357: dload         19
     359: invokestatic  #62                 // Method java/lang/Math.min:(DD)D
     362: invokestatic  #62                 // Method java/lang/Math.min:(DD)D
     365: dastore
     366: dload         12
     368: aload         6
     370: iload         14
     372: daload
     373: invokestatic  #62                 // Method java/lang/Math.min:(DD)D
     376: dstore        12
     378: iload         14
     380: iload         15
     382: if_icmpeq     391
     385: iinc          14, 1
     388: goto          259
     391: dload         12
     393: dload_3
     394: dcmpl
     395: iflt          400
     398: dload_3
     399: dreturn
     400: aload         5
     402: astore        14
     404: aload         6
     406: astore        5
     408: aload         14
     410: astore        6
     412: iload         7
     414: iload         8
     416: if_icmpeq     425
     419: iinc          7, 1
     422: goto          199
     425: aload         5
     427: invokestatic  #68                 // Method kotlin/collections/ArraysKt.last:([D)D
     430: dreturn

  public static double distance$default(com.aallam.similarity.WeightedLevenshtein, java.lang.CharSequence, java.lang.CharSequence, double, int, java.lang.Object);
    Code:
       0: iload         5
       2: iconst_4
       3: iand
       4: ifeq          11
       7: ldc2_w        #98                 // double 1.7976931348623157E308d
      10: dstore_3
      11: aload_0
      12: aload_1
      13: aload_2
      14: dload_3
      15: invokevirtual #101                // Method distance:(Ljava/lang/CharSequence;Ljava/lang/CharSequence;D)D
      18: dreturn
}


package com.aallam.similarity

/**
 * Damerau-Levenshtein distance with transposition (*unrestricted Damerau-Levenshtein* distance).
 *
 * The distance is the minimum number of operations needed to transform one string into the other, where an operation
 * is defined as an insertion, deletion, or substitution of a single character, or a transposition of two adjacent
 * characters. It does respect triangle inequality, and is thus a metric distance.
 *
 * [Damerau–Levenshtein](https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance#Distance_with_adjacent_transpositions)
 */
public class DamerauLevenshtein {

    /**
     * Compute the distance between strings: the minimum number of operations needed to transform one string into the
     * other (insertion, deletion, substitution of a single character, or a transposition of two adjacent characters).
     *
     * @param first the first string to compare.
     * @param second the second string to compare.
     */
    public fun distance(first: CharSequence, second: CharSequence): Int {

        // infinite distance is the max possible distance
        val n = first.length
        val m = second.length
        val infinity = n + m

        // create and initialize the character array indices
        val charsRowIndex = mutableMapOf<Char, Int>()
        for (index in first.indices) charsRowIndex[first[index]] = 0
        for (char in second) charsRowIndex[char] = 0

        // create the distance matrix H[0 .. first.length+1][0 .. second.length+1]
        val distanceMatrix = Array(n + 2) { IntArray(m + 2) }

        // initialize the left edge
        for (i in first.indices) {
            distanceMatrix[i + 1][0] = infinity
            distanceMatrix[i + 1][1] = i
        }

        // initialize top edge
        for (j in second.indices) {
            distanceMatrix[0][j + 1] = infinity
            distanceMatrix[1][j + 1] = j
        }

        // fill in the distance matrix
        for (i in 1..n) {
            var lastMatch = 0

            for (j in 1..m) {
                val lastRowIndex = charsRowIndex.getValue(second[j - 1])
                val previousMatch = lastMatch
                val cost: Int
                if (first[i - 1] == second[j - 1]) {
                    cost = 0
                    lastMatch = j
                } else {
                    cost = 1
                }

                val substitution = distanceMatrix[i][j] + cost
                val insertion = distanceMatrix[i + 1][j] + 1
                val deletion = distanceMatrix[i][j + 1] + 1
                val transposition =
                    distanceMatrix[lastRowIndex][previousMatch] + (i - lastRowIndex - 1) + 1 + (j - previousMatch - 1)
                distanceMatrix[i + 1][j + 1] = minOf(substitution, insertion, deletion, transposition)
            }
            charsRowIndex[first[i - 1]] = i
        }

        return distanceMatrix[n + 1][m + 1]
    }
}

Compiled from "DamerauLevenshtein.kt"
public final class com.aallam.similarity.DamerauLevenshtein {
  public com.aallam.similarity.DamerauLevenshtein();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public final int distance(java.lang.CharSequence, java.lang.CharSequence);
    Code:
       0: aload_1
       1: ldc           #15                 // String first
       3: invokestatic  #21                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #23                 // String second
       9: invokestatic  #21                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_1
      13: invokeinterface #29,  1           // InterfaceMethod java/lang/CharSequence.length:()I
      18: istore_3
      19: aload_2
      20: invokeinterface #29,  1           // InterfaceMethod java/lang/CharSequence.length:()I
      25: istore        4
      27: iload_3
      28: iload         4
      30: iadd
      31: istore        5
      33: new           #31                 // class java/util/LinkedHashMap
      36: dup
      37: invokespecial #32                 // Method java/util/LinkedHashMap."<init>":()V
      40: checkcast     #34                 // class java/util/Map
      43: astore        6
      45: iconst_0
      46: istore        7
      48: aload_1
      49: invokeinterface #29,  1           // InterfaceMethod java/lang/CharSequence.length:()I
      54: istore        8
      56: iload         7
      58: iload         8
      60: if_icmpge     92
      63: aload         6
      65: aload_1
      66: iload         7
      68: invokeinterface #38,  2           // InterfaceMethod java/lang/CharSequence.charAt:(I)C
      73: invokestatic  #44                 // Method java/lang/Character.valueOf:(C)Ljava/lang/Character;
      76: iconst_0
      77: invokestatic  #49                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      80: invokeinterface #53,  3           // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      85: pop
      86: iinc          7, 1
      89: goto          56
      92: iconst_0
      93: istore        7
      95: iload         7
      97: aload_2
      98: invokeinterface #29,  1           // InterfaceMethod java/lang/CharSequence.length:()I
     103: if_icmpge     140
     106: aload_2
     107: iload         7
     109: invokeinterface #38,  2           // InterfaceMethod java/lang/CharSequence.charAt:(I)C
     114: istore        8
     116: iload         8
     118: invokestatic  #44                 // Method java/lang/Character.valueOf:(C)Ljava/lang/Character;
     121: aload         6
     123: swap
     124: iconst_0
     125: invokestatic  #49                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     128: invokeinterface #53,  3           // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
     133: pop
     134: iinc          7, 1
     137: goto          95
     140: iconst_0
     141: istore        8
     143: iload_3
     144: iconst_2
     145: iadd
     146: istore        9
     148: iload         9
     150: anewarray     #55                 // class "[I"
     153: astore        10
     155: iload         8
     157: iload         9
     159: if_icmpge     183
     162: iload         8
     164: istore        11
     166: aload         10
     168: iload         11
     170: iload         4
     172: iconst_2
     173: iadd
     174: newarray       int
     176: aastore
     177: iinc          8, 1
     180: goto          155
     183: aload         10
     185: astore        7
     187: iconst_0
     188: istore        8
     190: aload_1
     191: invokeinterface #29,  1           // InterfaceMethod java/lang/CharSequence.length:()I
     196: istore        9
     198: iload         8
     200: iload         9
     202: if_icmpge     233
     205: aload         7
     207: iload         8
     209: iconst_1
     210: iadd
     211: aaload
     212: iconst_0
     213: iload         5
     215: iastore
     216: aload         7
     218: iload         8
     220: iconst_1
     221: iadd
     222: aaload
     223: iconst_1
     224: iload         8
     226: iastore
     227: iinc          8, 1
     230: goto          198
     233: iconst_0
     234: istore        8
     236: aload_2
     237: invokeinterface #29,  1           // InterfaceMethod java/lang/CharSequence.length:()I
     242: istore        9
     244: iload         8
     246: iload         9
     248: if_icmpge     279
     251: aload         7
     253: iconst_0
     254: aaload
     255: iload         8
     257: iconst_1
     258: iadd
     259: iload         5
     261: iastore
     262: aload         7
     264: iconst_1
     265: aaload
     266: iload         8
     268: iconst_1
     269: iadd
     270: iload         8
     272: iastore
     273: iinc          8, 1
     276: goto          244
     279: iconst_1
     280: istore        8
     282: iload         8
     284: iload_3
     285: if_icmpgt     536
     288: iconst_0
     289: istore        9
     291: iconst_1
     292: istore        10
     294: iload         10
     296: iload         4
     298: if_icmpgt     494
     301: aload         6
     303: aload_2
     304: iload         10
     306: iconst_1
     307: isub
     308: invokeinterface #38,  2           // InterfaceMethod java/lang/CharSequence.charAt:(I)C
     313: invokestatic  #44                 // Method java/lang/Character.valueOf:(C)Ljava/lang/Character;
     316: invokestatic  #61                 // Method kotlin/collections/MapsKt.getValue:(Ljava/util/Map;Ljava/lang/Object;)Ljava/lang/Object;
     319: checkcast     #63                 // class java/lang/Number
     322: invokevirtual #66                 // Method java/lang/Number.intValue:()I
     325: istore        11
     327: iload         9
     329: istore        12
     331: iconst_0
     332: istore        13
     334: aload_1
     335: iload         8
     337: iconst_1
     338: isub
     339: invokeinterface #38,  2           // InterfaceMethod java/lang/CharSequence.charAt:(I)C
     344: aload_2
     345: iload         10
     347: iconst_1
     348: isub
     349: invokeinterface #38,  2           // InterfaceMethod java/lang/CharSequence.charAt:(I)C
     354: if_icmpne     367
     357: iconst_0
     358: istore        13
     360: iload         10
     362: istore        9
     364: goto          370
     367: iconst_1
     368: istore        13
     370: aload         7
     372: iload         8
     374: aaload
     375: iload         10
     377: iaload
     378: iload         13
     380: iadd
     381: istore        14
     383: aload         7
     385: iload         8
     387: iconst_1
     388: iadd
     389: aaload
     390: iload         10
     392: iaload
     393: iconst_1
     394: iadd
     395: istore        15
     397: aload         7
     399: iload         8
     401: aaload
     402: iload         10
     404: iconst_1
     405: iadd
     406: iaload
     407: iconst_1
     408: iadd
     409: istore        16
     411: aload         7
     413: iload         11
     415: aaload
     416: iload         12
     418: iaload
     419: iload         8
     421: iload         11
     423: isub
     424: iconst_1
     425: isub
     426: iadd
     427: iconst_1
     428: iadd
     429: iload         10
     431: iload         12
     433: isub
     434: iconst_1
     435: isub
     436: iadd
     437: istore        17
     439: aload         7
     441: iload         8
     443: iconst_1
     444: iadd
     445: aaload
     446: iload         10
     448: iconst_1
     449: iadd
     450: iload         14
     452: iconst_3
     453: newarray       int
     455: astore        18
     457: aload         18
     459: iconst_0
     460: iload         15
     462: iastore
     463: aload         18
     465: iconst_1
     466: iload         16
     468: iastore
     469: aload         18
     471: iconst_2
     472: iload         17
     474: iastore
     475: aload         18
     477: invokestatic  #72                 // Method kotlin/comparisons/ComparisonsKt.minOf:(I[I)I
     480: iastore
     481: iload         10
     483: iload         4
     485: if_icmpeq     494
     488: iinc          10, 1
     491: goto          301
     494: iload         8
     496: invokestatic  #49                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     499: astore        12
     501: aload         6
     503: aload_1
     504: iload         8
     506: iconst_1
     507: isub
     508: invokeinterface #38,  2           // InterfaceMethod java/lang/CharSequence.charAt:(I)C
     513: invokestatic  #44                 // Method java/lang/Character.valueOf:(C)Ljava/lang/Character;
     516: aload         12
     518: invokeinterface #53,  3           // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
     523: pop
     524: iload         8
     526: iload_3
     527: if_icmpeq     536
     530: iinc          8, 1
     533: goto          288
     536: aload         7
     538: iload_3
     539: iconst_1
     540: iadd
     541: aaload
     542: iload         4
     544: iconst_1
     545: iadd
     546: iaload
     547: ireturn
}


package com.aallam.similarity

import com.aallam.similarity.internal.Profile
import com.aallam.similarity.internal.Shingle
import kotlin.math.sqrt

/**
 * Implements Cosine Similarity between strings.
 *
 * The strings are first transformed in vectors of occurrences of k-shingles (sequences of [k] characters).
 * In this n-dimensional space, the similarity between the two strings is the cosine of their respective vectors.
 *
 * The cosine distance is computed as `1 - cosine similarity`.
 *
 * [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity)
 *
 * @param k length of k-shingles
 */
public class Cosine(private val k: Int = 3) {

    /**
     * Compute the cosine similarity between strings.
     *
     * It is computed as `V1.V2/(|V1|*|V2|)` where `V1` and `V2` are vector representation of [first] and [second].
     *
     * @param first first string to compare.
     * @param second second string to compare.
     * @return the cosine similarity in the range `[0, 1]`.
     */
    public fun similarity(first: CharSequence, second: CharSequence): Double {
        if (first == second) return 1.0
        if (first.length < k || second.length < k) return 0.0
        val p1 = Shingle.profile(first, k)
        val p2 = Shingle.profile(second, k)
        return (p1 dot p2) / (norm(p1) * norm(p2))
    }

    /** Dot product */
    private infix fun Profile.dot(profile: Profile): Int {
        val intersection = keys.intersect(profile.keys)
        return intersection.sumOf { getValue(it) * profile.getValue(it) }
    }

    /** Compute the norm L2 : sqrt(sum(v²)). */
    private fun norm(profile: Profile): Double {
        val sum = profile.values.sumOf { it * it }.toDouble()
        return sqrt(sum)
    }

    /**
     * Compute the cosine distance between two string.
     * Corresponds to 1.0 - similarity.
     *
     * @param first first string to compare.
     * @param second second string to compare.
     */
    public fun distance(first: CharSequence, second: CharSequence): Double {
        return 1.0 - similarity(first, second)
    }
}

Compiled from "Cosine.kt"
public final class com.aallam.similarity.Cosine {
  private final int k;

  public com.aallam.similarity.Cosine(int);
    Code:
       0: aload_0
       1: invokespecial #9                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iload_1
       6: putfield      #13                 // Field k:I
       9: return

  public com.aallam.similarity.Cosine(int, int, kotlin.jvm.internal.DefaultConstructorMarker);
    Code:
       0: iload_2
       1: iconst_1
       2: iand
       3: ifeq          8
       6: iconst_3
       7: istore_1
       8: aload_0
       9: iload_1
      10: invokespecial #18                 // Method "<init>":(I)V
      13: return

  public final double similarity(java.lang.CharSequence, java.lang.CharSequence);
    Code:
       0: aload_1
       1: ldc           #23                 // String first
       3: invokestatic  #29                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #31                 // String second
       9: invokestatic  #29                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_1
      13: aload_2
      14: invokestatic  #35                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      17: ifeq          22
      20: dconst_1
      21: dreturn
      22: aload_1
      23: invokeinterface #41,  1           // InterfaceMethod java/lang/CharSequence.length:()I
      28: aload_0
      29: getfield      #13                 // Field k:I
      32: if_icmplt     48
      35: aload_2
      36: invokeinterface #41,  1           // InterfaceMethod java/lang/CharSequence.length:()I
      41: aload_0
      42: getfield      #13                 // Field k:I
      45: if_icmpge     50
      48: dconst_0
      49: dreturn
      50: getstatic     #47                 // Field com/aallam/similarity/internal/Shingle.Companion:Lcom/aallam/similarity/internal/Shingle$Companion;
      53: aload_1
      54: aload_0
      55: getfield      #13                 // Field k:I
      58: invokevirtual #53                 // Method com/aallam/similarity/internal/Shingle$Companion.profile:(Ljava/lang/CharSequence;I)Ljava/util/Map;
      61: astore_3
      62: getstatic     #47                 // Field com/aallam/similarity/internal/Shingle.Companion:Lcom/aallam/similarity/internal/Shingle$Companion;
      65: aload_2
      66: aload_0
      67: getfield      #13                 // Field k:I
      70: invokevirtual #53                 // Method com/aallam/similarity/internal/Shingle$Companion.profile:(Ljava/lang/CharSequence;I)Ljava/util/Map;
      73: astore        4
      75: aload_0
      76: aload_3
      77: aload         4
      79: invokespecial #57                 // Method dot:(Ljava/util/Map;Ljava/util/Map;)I
      82: i2d
      83: aload_0
      84: aload_3
      85: invokespecial #61                 // Method norm:(Ljava/util/Map;)D
      88: aload_0
      89: aload         4
      91: invokespecial #61                 // Method norm:(Ljava/util/Map;)D
      94: dmul
      95: ddiv
      96: dreturn

  private final int dot(java.util.Map<java.lang.String, java.lang.Integer>, java.util.Map<java.lang.String, java.lang.Integer>);
    Code:
       0: aload_1
       1: invokeinterface #72,  1           // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
       6: checkcast     #74                 // class java/lang/Iterable
       9: aload_2
      10: invokeinterface #72,  1           // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
      15: checkcast     #74                 // class java/lang/Iterable
      18: invokestatic  #80                 // Method kotlin/collections/CollectionsKt.intersect:(Ljava/lang/Iterable;Ljava/lang/Iterable;)Ljava/util/Set;
      21: astore_3
      22: aload_3
      23: checkcast     #74                 // class java/lang/Iterable
      26: astore        4
      28: iconst_0
      29: istore        5
      31: aload         4
      33: invokeinterface #84,  1           // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
      38: astore        6
      40: aload         6
      42: invokeinterface #90,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
      47: ifeq          110
      50: aload         6
      52: invokeinterface #94,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      57: astore        7
      59: iload         5
      61: aload         7
      63: checkcast     #96                 // class java/lang/String
      66: astore        8
      68: istore        10
      70: iconst_0
      71: istore        9
      73: aload_1
      74: aload         8
      76: invokestatic  #102                // Method kotlin/collections/MapsKt.getValue:(Ljava/util/Map;Ljava/lang/Object;)Ljava/lang/Object;
      79: checkcast     #104                // class java/lang/Number
      82: invokevirtual #107                // Method java/lang/Number.intValue:()I
      85: aload_2
      86: aload         8
      88: invokestatic  #102                // Method kotlin/collections/MapsKt.getValue:(Ljava/util/Map;Ljava/lang/Object;)Ljava/lang/Object;
      91: checkcast     #104                // class java/lang/Number
      94: invokevirtual #107                // Method java/lang/Number.intValue:()I
      97: imul
      98: istore        11
     100: iload         10
     102: iload         11
     104: iadd
     105: istore        5
     107: goto          40
     110: iload         5
     112: ireturn

  private final double norm(java.util.Map<java.lang.String, java.lang.Integer>);
    Code:
       0: aload_1
       1: invokeinterface #120,  1          // InterfaceMethod java/util/Map.values:()Ljava/util/Collection;
       6: checkcast     #74                 // class java/lang/Iterable
       9: astore        4
      11: iconst_0
      12: istore        5
      14: aload         4
      16: invokeinterface #84,  1           // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
      21: astore        6
      23: aload         6
      25: invokeinterface #90,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
      30: ifeq          76
      33: aload         6
      35: invokeinterface #94,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      40: astore        7
      42: iload         5
      44: aload         7
      46: checkcast     #104                // class java/lang/Number
      49: invokevirtual #107                // Method java/lang/Number.intValue:()I
      52: istore        8
      54: istore        10
      56: iconst_0
      57: istore        9
      59: iload         8
      61: iload         8
      63: imul
      64: istore        11
      66: iload         10
      68: iload         11
      70: iadd
      71: istore        5
      73: goto          23
      76: iload         5
      78: i2d
      79: dstore_2
      80: dload_2
      81: invokestatic  #126                // Method java/lang/Math.sqrt:(D)D
      84: dreturn

  public final double distance(java.lang.CharSequence, java.lang.CharSequence);
    Code:
       0: aload_1
       1: ldc           #23                 // String first
       3: invokestatic  #29                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #31                 // String second
       9: invokestatic  #29                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: dconst_1
      13: aload_0
      14: aload_1
      15: aload_2
      16: invokevirtual #132                // Method similarity:(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)D
      19: dsub
      20: dreturn

  public com.aallam.similarity.Cosine();
    Code:
       0: aload_0
       1: iconst_0
       2: iconst_1
       3: aconst_null
       4: invokespecial #134                // Method "<init>":(IILkotlin/jvm/internal/DefaultConstructorMarker;)V
       7: return
}