| 37330 |
amit |
1 |
package com.smartdukaan.cron.offercircular;
|
|
|
2 |
|
|
|
3 |
import java.util.ArrayDeque;
|
|
|
4 |
import java.util.ArrayList;
|
|
|
5 |
import java.util.Deque;
|
|
|
6 |
import java.util.HashMap;
|
|
|
7 |
import java.util.List;
|
|
|
8 |
import java.util.Map;
|
|
|
9 |
|
|
|
10 |
/**
|
|
|
11 |
* Faithful port of Python's difflib.SequenceMatcher.ratio() (Ratcliff/Obershelp
|
|
|
12 |
* gestalt pattern matching): 2 * matched_characters / total_length.
|
|
|
13 |
*
|
|
|
14 |
* Needed because the product matcher's REVIEW vs CATALOG_GAP decision turns on a
|
|
|
15 |
* 0.75 similarity threshold. Any other similarity measure - Levenshtein, Jaro-Winkler,
|
|
|
16 |
* plain LCS - scores differently and would silently reclassify products, so this has
|
|
|
17 |
* to reproduce difflib rather than merely approximate it.
|
|
|
18 |
*
|
|
|
19 |
* difflib's autojunk heuristic only engages for sequences of length >= 200; product
|
|
|
20 |
* names are far shorter, so junk handling is deliberately omitted.
|
|
|
21 |
*/
|
|
|
22 |
public final class SequenceRatio {
|
|
|
23 |
|
|
|
24 |
private SequenceRatio() { }
|
|
|
25 |
|
|
|
26 |
public static double ratio(String a, String b) {
|
|
|
27 |
int total = a.length() + b.length();
|
|
|
28 |
if (total == 0) {
|
|
|
29 |
return 1.0;
|
|
|
30 |
}
|
|
|
31 |
// char -> ascending indices in b, mirroring difflib's b2j index
|
|
|
32 |
Map<Character, List<Integer>> b2j = new HashMap<>();
|
|
|
33 |
for (int j = 0; j < b.length(); j++) {
|
|
|
34 |
b2j.computeIfAbsent(b.charAt(j), k -> new ArrayList<>()).add(j);
|
|
|
35 |
}
|
|
|
36 |
return 2.0 * countMatches(a, b, b2j) / total;
|
|
|
37 |
}
|
|
|
38 |
|
|
|
39 |
/** Sum of difflib's matching block sizes, computed iteratively to avoid deep
|
|
|
40 |
* recursion on long strings. */
|
|
|
41 |
private static int countMatches(String a, String b, Map<Character, List<Integer>> b2j) {
|
|
|
42 |
int matches = 0;
|
|
|
43 |
Deque<int[]> queue = new ArrayDeque<>();
|
|
|
44 |
queue.push(new int[]{0, a.length(), 0, b.length()});
|
|
|
45 |
while (!queue.isEmpty()) {
|
|
|
46 |
int[] range = queue.pop();
|
|
|
47 |
int alo = range[0], ahi = range[1], blo = range[2], bhi = range[3];
|
|
|
48 |
int[] match = findLongestMatch(a, b, b2j, alo, ahi, blo, bhi);
|
|
|
49 |
int i = match[0], j = match[1], size = match[2];
|
|
|
50 |
if (size == 0) {
|
|
|
51 |
continue;
|
|
|
52 |
}
|
|
|
53 |
matches += size;
|
|
|
54 |
if (alo < i && blo < j) {
|
|
|
55 |
queue.push(new int[]{alo, i, blo, j});
|
|
|
56 |
}
|
|
|
57 |
if (i + size < ahi && j + size < bhi) {
|
|
|
58 |
queue.push(new int[]{i + size, ahi, j + size, bhi});
|
|
|
59 |
}
|
|
|
60 |
}
|
|
|
61 |
return matches;
|
|
|
62 |
}
|
|
|
63 |
|
|
|
64 |
/** @return {besti, bestj, bestsize} */
|
|
|
65 |
private static int[] findLongestMatch(String a, String b, Map<Character, List<Integer>> b2j,
|
|
|
66 |
int alo, int ahi, int blo, int bhi) {
|
|
|
67 |
int besti = alo, bestj = blo, bestsize = 0;
|
|
|
68 |
Map<Integer, Integer> j2len = new HashMap<>();
|
|
|
69 |
for (int i = alo; i < ahi; i++) {
|
|
|
70 |
Map<Integer, Integer> newJ2Len = new HashMap<>();
|
|
|
71 |
List<Integer> indices = b2j.get(a.charAt(i));
|
|
|
72 |
if (indices != null) {
|
|
|
73 |
for (int j : indices) {
|
|
|
74 |
if (j < blo) {
|
|
|
75 |
continue;
|
|
|
76 |
}
|
|
|
77 |
if (j >= bhi) {
|
|
|
78 |
break; // indices are ascending
|
|
|
79 |
}
|
|
|
80 |
int k = j2len.getOrDefault(j - 1, 0) + 1;
|
|
|
81 |
newJ2Len.put(j, k);
|
|
|
82 |
if (k > bestsize) {
|
|
|
83 |
besti = i - k + 1;
|
|
|
84 |
bestj = j - k + 1;
|
|
|
85 |
bestsize = k;
|
|
|
86 |
}
|
|
|
87 |
}
|
|
|
88 |
}
|
|
|
89 |
j2len = newJ2Len;
|
|
|
90 |
}
|
|
|
91 |
// grow the block outward over equal characters, as difflib does when there is
|
|
|
92 |
// no junk to skip
|
|
|
93 |
while (besti > alo && bestj > blo && a.charAt(besti - 1) == b.charAt(bestj - 1)) {
|
|
|
94 |
besti--;
|
|
|
95 |
bestj--;
|
|
|
96 |
bestsize++;
|
|
|
97 |
}
|
|
|
98 |
while (besti + bestsize < ahi && bestj + bestsize < bhi
|
|
|
99 |
&& a.charAt(besti + bestsize) == b.charAt(bestj + bestsize)) {
|
|
|
100 |
bestsize++;
|
|
|
101 |
}
|
|
|
102 |
return new int[]{besti, bestj, bestsize};
|
|
|
103 |
}
|
|
|
104 |
}
|