/** * MIT License * * Copyright (c) 2017 zgqq * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package mah.common.search; import mah.common.util.StringUtils; import org.jetbrains.annotations.Contract; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Created by zgq on 16-11-24. */ public class SearcherImplV5 implements Searcher { private SearchResultComparator comparator; public SearcherImplV5() { this.comparator = new SearchResultComparator(); } private List<SearchResult> fuzzyMatch(List<? extends Searchable> data, String keyword, boolean smart) { List<SearchResult> searchResults = new ArrayList<>(); if (StringUtils.isEmpty(keyword)) { return searchResults; } for (Searchable dataRow : data) { int prority = 0; List<String> dataCells = dataRow.fetchData(); Map<Integer, String> matchedColumns = new HashMap<>(); Map<Integer, List<Integer>> matchedIndexs = new HashMap<>(); for (int k = 0; k < dataCells.size(); k++) { List<Integer> matchedIndex = new ArrayList<>(); List<Node> nodes = new ArrayList<>(); String columnData = dataCells.get(k); matchNodes(columnData, keyword, smart, 0, nodes); if (nodes.size() > 0) { for (int i = 0; i < nodes.size(); i++) { Node node = nodes.get(i); matchedIndex.addAll(node.realIndexs); prority += node.priority; } matchedColumns.put(k, columnData); matchedIndexs.put(k, matchedIndex); } } if (prority > 0) { SearchResult searchResult = new SearchResult(dataRow, prority); MatchedResult matchedResult = new MatchedResult(matchedColumns, matchedIndexs); searchResult.setMatchedResult(matchedResult); searchResults.add(searchResult); } } searchResults.sort(comparator); return searchResults; } @Override public List<SearchResult> smartFuzzyMatch(List<? extends Searchable> data, String keyword) { return fuzzyMatch(data, keyword, true); } static class Node { int priority; List<Integer> matchedIndexs; List<Integer> realIndexs; public Node(int prority, List<Integer> matchedIndexs) { this.priority = prority; this.matchedIndexs = matchedIndexs; } } protected boolean compare(char c, char c2, boolean smart) { if (smart) { if (Character.isUpperCase(c)) { return c == c2; } return c == Character.toLowerCase(c2); } return c == c2; } protected void matchNodes(String content, String keyword, boolean smart, int prevLen, List<Node> nodes) { if (StringUtils.isEmpty(content)) { return; } List<Node> matchedNode = matchNodes(content, keyword, 0); int size = matchedNode.size(); if (size > 0) { int end = 0; Node node = matchedNode.get(matchedNode.size() - 1); List<Integer> mi = node.matchedIndexs; if (mi.size() >= 0) { end = mi.get(mi.size() - 1); } Node propNode = selectOptimalNode(content, keyword, matchedNode); computeProp(propNode, prevLen); nodes.add(propNode); if (end >= content.length() - 1) { return; } matchNodes(content.substring(end + 1, content.length()), keyword, smart, prevLen + end + 1, nodes); } } private List<Node> matchNodes(String text, String key, int offset) { List<Node> matchedNodes = new ArrayList<>(3); List<Node> curNodes = new ArrayList<>(3); char keyFirstChar = key.charAt(0); mat: for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); List<Integer> indexs; if (compare(keyFirstChar, c, true)) { indexs = new ArrayList<>(); // add new node when encountering first char curNodes.add(new Node(0, indexs)); } int longestNodeIndex = getLongestNodeIndex(key, offset, curNodes, i, c); if (longestNodeIndex != -1) { // remove those nodes that are prior to longest node Node longestNode = curNodes.get(longestNodeIndex); for (int k = longestNodeIndex - 1; k >= 0; k--) { curNodes.remove(k); } // retrieving completes if there is only node List<Integer> mmi = longestNode.matchedIndexs; if (mmi.size() == key.length()) { matchedNodes.add(longestNode); if (curNodes.size() == 1) { break mat; } // remove matched node curNodes.remove(longestNode); } } } return matchedNodes; } private void computeProp(Node propNode, int prevLen) { List<Integer> matchedIndexs = propNode.matchedIndexs; List<Integer> realIndexs = new ArrayList(); for (Integer matchedIndex : matchedIndexs) { realIndexs.add(matchedIndex + prevLen); } propNode.realIndexs = realIndexs; } protected final Node computerProrityNode(String content, String key, Node node) { List<Integer> matchedIndexs = node.matchedIndexs; if (matchedIndexs.size() > 2) { int start = matchedIndexs.get(0); int end = matchedIndexs.get(matchedIndexs.size() - 1); String substring = content.substring(start, end + 1); matchedIndexs = new ArrayList<>(); int[] matchedInd = computeOptimalMatchedIndexs(substring, key); for (int index : matchedInd) { matchedIndexs.add(start + index); } node.matchedIndexs = matchedIndexs; } return node; } protected final Node selectOptimalNode(String content, String key, List<Node> matchedNodes) { int priority = 1; int ind = 0; for (int j = 0; j < matchedNodes.size(); j++) { Node node = computerProrityNode(content, key, matchedNodes.get(j)); List<Integer> matchedIndexs = node.matchedIndexs; int q = 0; int succ = 0; for (int i = matchedIndexs.size() - 1; i >= 1; i--) { int ind1 = matchedIndexs.get(i - 1); int ind2 = matchedIndexs.get(i); if ((ind2 - ind1) == 1) { ++succ; q += succ * 10; } else { succ = 0; } } if (q > priority) { ind = j; priority = q; } } Node node = matchedNodes.get(ind); node.priority = priority; return node; } private int getLongestNodeIndex(String key, int offset, List<Node> curNodes, int i, char c) { int longestNodeIndex = -1; int maxSize = -1; for (int j = 0; j < curNodes.size(); j++) { Node node = curNodes.get(j); List<Integer> matchedIndexs = node.matchedIndexs; int indexsSize = matchedIndexs.size(); if (indexsSize < key.length()) { // match next char of node char nextChar = key.charAt(indexsSize); if (compare(nextChar, c, true)) { matchedIndexs.add(offset + i); } } int size = matchedIndexs.size(); if (size >= maxSize) { maxSize = size; longestNodeIndex = j; } } return longestNodeIndex; } public final int[] computeOptimalMatchedIndexs(String str, String key) { if (key == null) { throw new NullPointerException("key could not be null"); } int[] pi = new int[key.length()]; pi[0] = 0; pi[key.length() - 1] = str.length() - 1; for (int i = 1; i < pi.length - 1; i++) { int prev = pi[i - 1]; char c = str.charAt(prev + 1); if (compare(key.charAt(i), c, true)) { pi[i] = prev + 1; continue; } else { int last = pi[i - 1]; int curOffset = last + 1; List<Node> nodes = matchNodes(str.substring(curOffset), key.substring(i), curOffset); Node node = nodes.get(0); List<Integer> matchedIndexs = node.matchedIndexs; Integer headIndex = matchedIndexs.get(0); pi[i] = headIndex; } } return pi; } }