Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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
}