Частично совпадающие строки в случае List.contains(String)

У меня есть List<String>

List<String> list = new ArrayList<String>();
list.add("ABCD");
list.add("EFGH");
list.add("IJ KL");
list.add("M NOP");
list.add("UVW X");

если я делаю list.contains("EFGH"), он возвращает true. Могу ли я получить правду в случае list.contains("IJ")? Я имею в виду, могу ли я частично сопоставлять строки, чтобы найти, существуют ли они в списке?

У меня есть список из 15000 строк. И я должен проверить около 10000 строк, если они существуют в списке. Что может быть другим (более быстрым) способом сделать это?

Спасибо.

Ответы

Ответ 1

Если предложение Roadrunner-EX этого недостаточно, я считаю, что вы ищете алгоритм Кнута-Морриса-Пратта.

Сложность времени:

  • Временная сложность алгоритма таблицы - O (n), время предварительной обработки
  • Сложность времени алгоритма поиска: O (k)

Таким образом, сложность общего алгоритма равна O (n + k).

  • n = Размер списка
  • k = длина шаблона, который вы ищете

Обычная грубая сила будет иметь временную сложность O (nm)

Кроме того, алгоритм KMP будет выполнять такую ​​же сложность O (k) для поиска в одной строке поиска, с другой стороны, всегда будет O (km) для подхода грубой силы.

Ответ 2

Возможно, вы хотите поместить каждую группу String в HashSet, а по фрагменту, я имею в виду, не добавляйте "IJ KL", а добавляйте отдельно "IJ" и "KL". Если вам нужны как список, так и возможности поиска, вам может потребоваться сохранить две коллекции.

Ответ 3

В качестве второго ответа, перечитывая свой вопрос, вы также можете наследовать от интерфейса List, специализировать его только для Strings и переопределить метод contains().

public class PartialStringList extends ArrayList<String>
{
    public boolean contains(Object o)
    {
        if(!(o instanceof String))
        {
            return false;
        }
        String s = (String)o;
        Iterator<String> iter = iterator();
        while(iter.hasNext())
        {
            String iStr = iter.next();
            if (iStr.contain(s))
            {
                return true;
            }
        }
        return false;
    }
}

Судя по вашим более ранним комментариям, возможно, это не та скорость, которую вы ищете, но похоже ли это на то, о чем вы просите?

Ответ 4

Вы можете выполнить итерацию по списку, а затем вызвать contains() для каждой строки.

public boolean listContainsString(List<string> list. String checkStr)
{
    Iterator<String> iter = list.iterator();
    while(iter.hasNext())
    {
        String s = iter.next();
        if (s.contain(checkStr))
        {
            return true;
        }
    }
    return false;
}

Что-то вроде этого должно работать, я думаю.

Ответ 5

Как насчет:

java.util.List<String> list = new java.util.ArrayList<String>();
list.add("ABCD");
list.add("EFGH");
list.add("IJ KL");
list.add("M NOP");
list.add("UVW X");
java.util.regex.Pattern p = java.util.regex.Pattern.compile("IJ");
java.util.regex.Matcher m = p.matcher("");
for(String s : list)
{
    m.reset(s);
    if(m.find()) System.out.println("Partially Matched");
}

Ответ 6

Вот некоторый код, который использует регулярное выражение для быстрого вызова внутреннего цикла, если ни одна из тестовых строк не найдена в целевой строке.

public static void main(String[] args) throws Exception {
    List<String> haystack = Arrays.asList(new String[] { "ABCD", "EFGH", "IJ KL", "M NOP", "UVW X" });
    List<String> needles = Arrays.asList(new String[] { "IJ", "NOP" });

    // To cut down on iterations, create one big regex to check the whole haystack
    StringBuilder sb = new StringBuilder();
    sb.append(".*(");
    for (String needle : needles) {
        sb.append(needle).append('|');
    }
    sb.replace(sb.length() - 1, sb.length(), ").*");
    String regex = sb.toString();

    for (String target : haystack) {
        if (!target.matches(regex)) {
            System.out.println("Skipping " + target);
            continue;
        }

        for (String needle : needles) {
            if (target.contains(needle)) {
                System.out.println(target + " contains " + needle);
            }
        }
    }
}

Вывод:

Skipping ABCD
Skipping EFGH
IJ KL contains IJ
M NOP contains NOP
Skipping UVW X

Если вы действительно хотите получить симпатичный, вы можете использовать двоичный поиск, чтобы определить, какие сегменты целевого списка совпадают, но это может и не стоить.

Это зависит от того, насколько вероятно, что вы найдете хит. Низкие ставки попадут в хороший результат. Высокие коэффициенты удара будут работать не намного лучше, чем простая версия вложенного цикла. рассмотрите инвертирование петель, если некоторые иглы попали во многие мишени, а другие не ударили.

Все о прерывании пути поиска как можно скорее.

Ответ 7

Для начала, для любви к Богу, используйте набор (например, HashSet), а не список. Выполнение a contains() в списке - O (n), но на множестве O (1). Это очень небольшое исправление, которое поможет вам сэкономить массу времени.

Теперь вставьте свои элементы по одному, включая разбиение их на слова. Например:

java.util.Set<String> set = new java.util.HashSet<String>();
set.add("ABCD");
set.add("IJ");
set.add("IJ KL");

если вы хотите частичное совпадение слов в середине строки (не только начинается с), добавьте:

set.add("KL");

Посмотрите String.split(), чтобы быстро разбить текст на основе пробелов.

Теперь, когда вы ищете, вы можете сделать:

boolean isItThere = set.contains("IJ");

Тада! Очень простой поиск O (1). Это будет очень быстро.

ПРИМЕЧАНИЕ. Предполагая, что 10 000 записей по 10 символов каждый со средним значением 2 слова на запись, означает, что мы используем < 200 КБ памяти (10k * 10 * 2 = 200k). Если ваши размеры строк растут, или количество слов растет, это может ускользнуть из-под контроля. Затем вы должны изучить что-то вроде Lucene.

Ответ 8

Да, вы можете! Сортировка.

То, что вы ищете, часто называют нечетким поиском или приближенным сопоставлением строк, и есть несколько решений этой проблемы.

С FuzzyWuzzy lib, например, вы можете присвоить всем своим строкам оценку, основанную на том, насколько они похожи на конкретный поисковый запрос. Фактические значения кажутся целыми процентами от числа символов, соответствующих по отношению к длине строки поиска.

После вызова FuzzySearch.extractAll вам решать, какой минимальный балл будет для строки считаться совпадением.

Есть и другие похожие библиотеки, которые стоит проверить, например google-diff-match-patch или API сходства текста Apache Commons и т.д.

Если вам нужно что-то действительно сверхмощное, лучшим вариантом будет Lucene (как также упоминается Райан Шиллингтон)

Ответ 9

Вы можете использовать IterableUtils из Apache Commons Collections.

List<String> list = new ArrayList<String>();
list.add("ABCD");
list.add("EFGH");
list.add("IJ KL");
list.add("M NOP");
list.add("UVW X");

boolean hasString = IterableUtils.contains(list, "IJ", new Equator<String>() {
    @Override
    public boolean equate(String o1, String o2) {
        return o2.contains(o1);
    }

    @Override
    public int hash(String o) {
        return o.hashCode();
    }
});

System.out.println(hasString); // true