#java #string #data-structures #string-matching
#java #строка #структуры данных #сопоставление строк
Вопрос:
У меня есть функция, которая позволяет мне находить соответствие между неполным элементом и хотя бы одним элементом в наборе. Примером неполного элемента является 22.2.X.13, в котором есть элемент (определенный с помощью X ), который может принимать любое значение.
Цель этой функции — найти хотя бы один элемент в наборе элементов, который имеет 22 в первой позиции, 2 на второй и 13 на четвертой.
Например, если мы рассмотрим множество:
{
20.8.31.13,
32.3.29.13,
24.2.12.13,
19.2.37.13,
22.2.22.13,
27.17.22.13,
26.22.32.13,
22.3.22.13,
20.19.12.13,
17.4.37.13,
31.8.34.13
}
Выходные данные функции возвращают True, поскольку есть элементы 22.2.22.13
, которые соответствуют 22.2.X.13
.
Моя функция сравнивает каждую пару элементов, таких как строки, и каждый элемент элементов как целое число:
public boolean containsElement(String element) {
StringTokenizer strow = null, st = null;
boolean check = true;
String nextrow = "", next = "";
for(String row : setOfElements) {
strow = new StringTokenizer(row, ".");
st = new StringTokenizer(element, ".");
check = true;
while(st.hasMoreTokens()) {
next = st.nextToken();
if(!strow.hasMoreTokens()) {
break;
}
nextrow = strow.nextToken();
if(next.compareTo("X") != 0) {
int x = Integer.parseInt(next);
int y = Integer.parseInt(nextrow);
if(x != y) {
check = false;
break;
}
}
}
if(check) return true;
}
return false;
Однако это дорогостоящая операция, особенно если размер строки увеличивается. Можете ли вы предложить мне другую стратегию или структуру данных для быстрого выполнения этой операции?
Мое решение тесно связано со строками. Однако мы можем рассмотреть другие типы элементов (например, массив, список, узел дерева и т. Д.)
Спасибо всем за ваши ответы. Я перепробовал почти все функции, и бенч:
myFunction: 0ms
hasMatch: 2ms
Stream API: 5ms
isIPMatch; 2ms
Я думаю, что основная проблема регулярного выражения — это время для создания шаблона и сопоставления строк.
Комментарии:
1. Кажется, это идеальное приложение для регулярного выражения.
2. Замените каждый
X
элемент в шаблоне наd
, затем используйте регулярные выражения для поиска совпадающих записей. Однако, хотя это намного проще (и, вероятно, немного быстрее), это на самом деле не уменьшает сложность проблемы. Вместо этого вы можете рассмотреть возможность использования вложенной карты для хранения записей, например{22: {2: {22: {...}}, 3: {...}}, ...}
3. @tobias_k Спасибо за ваши ответы. Проблема с использованием карты заключается в «пропуске» элементов, которые соответствуют «X».
4. Разница во времени слишком велика, чтобы рассматривать один подход намного лучше, чем другие — шум может легко объяснить разницу в несколько мс между двумя запусками. Повторное сопоставление нескольких миллионов IP-адресов даст более точные результаты.
Ответ №1:
Вы хотите использовать регулярное выражение, созданное именно для таких задач. Посмотрите демонстрацию.
22.2.d .13
Java 8 и выше
Вы можете использовать Stream API начиная с Java 8, чтобы найти хотя бы один, соответствующий регулярному выражению, используя Pattern
Matcher
классы и:
Set<String> set = ... // the set of Strings (can be any collection)
Pattern pattern = Pattern.compile("22\.2\.\d \.13"); // compiled Pattern
boolean matches = set.stream() // Stream<String>
.map(pattern::matcher) // Stream<Matcher>
.anyMatch(Matcher::matches); // true if at least one matches
Java 7 и ниже
Способ аналогичен Stream API: цикл короткого замыкания для каждого с break
инструкцией на случай, если совпадение найдено.
boolean matches = false;
Pattern pattern = Pattern.compile("22\.2\.\d \.13");
for (String str: set) {
Matcher matcher = pattern.matcher(str);
if (matcher.matches()) {
matches = true;
break;
}
}
Ответ №2:
Вы можете решить эту проблему, подойдя к проблеме на основе регулярных выражений, как предложил Николас Хараламбидис ( 1), или вы можете сделать это по-другому. Чтобы избежать излишеств с другим ответом, я сосредоточусь здесь на альтернативном подходе, используя метод split .
public boolean isIPMatch(String pattern[], String input[]) {
if ((pattern == null) || (input == null) || (pattern.length <> input.length)) return false; //edge cases
for (int index = 0; index < pattern.length; index ) {
if ((!pattern[index].equals("X")) amp;amp; (!pattern[index].equals(input[index]))) return false; //difference
}
return true; //everything matched
}
И вы можете вызвать описанный выше метод в своем цикле после преобразования элементов для сравнения в String
массивы с помощью split
.
Ответ №3:
Для строк регулярные выражения решают задачу намного лучше:
private boolean hasMatch(String[] haystack, String partial) {
String patternString = partial.replace("X", "[0-9] ").replace(".", "\.");
// "22.2.X.13" becomes "22\.2\.[0-9] \.13"
Pattern p = Pattern.compile(patternString);
for (String s : haystack) {
if (p.matcher(s).matches()) return true;
}
return false;
}
Для других типов объектов это зависит от их структуры.
- Если есть какой-то порядок, вы могли бы рассмотреть возможность реализации ваших элементов
Comparable
, а затем вы можете поместить их в aTreeSet
(или как ключи в aTreeMap
), которые всегда будут отсортированы. Таким образом, вы можете сравнивать только с элементами, которые могут совпадать:mySortedSet.subSet(fromElement, toElement)
возвращает только элементы между этими двумя. - Если порядка нет, вам просто нужно будет сравнить все элементы с вашим «шаблоном».
Обратите внимание, что строки сопоставимы, но их порядок сортировки по умолчанию игнорирует специальную семантику ваших .
-разделителей. Итак, с некоторой осторожностью вы можете реализовать подход, основанный на наборе деревьев, чтобы сделать поиск лучше линейного.
Ответ №4:
В других ответах уже обсуждалось использование регулярного выражения путем преобразования, например 22.2.X.13
, в 22.2.d .13
(не забудьте также экранировать .
или они означают «что угодно»). Но, хотя это, безусловно, будет проще и, вероятно, также намного быстрее, это не снижает общую сложность. Вам все равно придется проверять каждый элемент в наборе.
Вместо этого вы можете попытаться преобразовать свой набор IP-адресов во вложенный Map
в этой форме:
{20: {8: {31: {13: null}}, 19: {12: {13: null}}}, 22: {2: {...}, 3: {...}}, ...}
(Конечно, вы должны создать эту структуру только один раз, а не для каждого поискового запроса.)
Затем вы можете написать рекурсивную функцию match
, которая работает примерно следующим образом (псевдокод):
boolean match(ip: String, map: Map<String, Map<...>>) {
if (ip.empty) return true // done
first, rest = ip.splitfirst
if (first == "X") {
return map.values().any(submap -> match(rest, submap))
} else {
return first in map amp;amp; match(rest, map[first])
}
}
Это должно уменьшить сложность с O (n) до O (log n); чем больше, тем чаще вам приходится разветвляться, но не более O (n) для X.X.X.123
( X.X.X.X
снова тривиально). Для небольших наборов регулярное выражение все равно может быть быстрее, поскольку оно имеет меньшие накладные расходы, но для больших наборов это должно быть быстрее.