Это мое первое сообщение здесь, надеюсь, что я не буду слишком отчаянным с моим вопросом.
У меня есть рабочая задача, которая включает в себя сравнение двух больших наборов имен, чтобы увидеть, существует ли между ними соответствие (независимо от порядка слов в именах).
Я пробовал как обычный, более простой подход, так и один с помощью Regex.
Стандартный подход:
public static boolean isMatch(String terroristName, String clientName) {
String[] terroristArray = terroristName.split(" ");
String[] clientArray = clientName.split(" ");
int size = clientArray.length;
int ctrl = 0;
boolean alreadyFound = false;
for (String client : clientArray) {
for (String terrorist : terroristArray) {
//if already found a match, stop comparing with rest of the words from terrorist name
if (!alreadyFound)
if (client.compareTo(terrorist) == 0) {
alreadyFound = true;
ctrl++;
break;
}
}
alreadyFound = false;
if (ctrl == 0 && !alreadyFound) {
//if first word of client is not found in whole terrorist name
//then exit loop, no match possible
break;
}
}
if (ctrl == size)
return true;
else
return false;
}
Regex подход:
public static boolean isRegexMatch(String terroristName, String clientName) {
boolean result = false;
String[] clientNameArray = clientName.split(" ");
String myPattern = "^";
//build pattern using client name
for (String cname : clientNameArray) {
myPattern += "(?=.*\\b" + cname + "\\b)";
}
myPattern += ".*$";
Pattern pattern = Pattern.compile(myPattern);
Matcher matcher = pattern.matcher(terroristName);
// check all occurance
while (matcher.find()) {
result = true;
}
return result;
}
Петля, сравнивающая 2 списка имен:
for (Person terrorist : terrorists) {
System.setOut(matchPrintStream);
for (Person client : clients) {
if (Util.isRegexMatch(terrorist.getNoDuplicatesName(), client.getName())) {
System.out.println(client.getId() + ";" + client.getName() + ";" + terrorist.getId() + ";" +
terrorist.getName());
}
}
}
Эти два набора имеют следующие размеры:
Продолжительность обоих методов довольно медленная:
ps -ef | grep 42810
42810 41919 99 17:47 pts/0 00:52:23 java -Xms1024M -Xmx1024M -classpath ojdbc6.jar:TerroristBuster.jar ro.btrl.mis.mihai.Main
К моменту превышения времени выполнения 00:52:23 он обработал около 170 записей, а это значит, что для завершения потребуется несколько дней. Я знаю, что он имеет большую сложность, не зная, как его снизить. Как вы думаете, может быть, что-то другое, кроме List? Я полагал, что это будет наиболее быстрое использование foreach с момента случайного доступа.
Может ли этот код быть улучшен/изменен каким-либо образом, чтобы улучшить время выполнения, или я просто занимаюсь слишком большим набором данных?
Если вы можете использовать Java 8, это должно быть очень легко распараллеливаться.
Во-первых, у вас не так много клиентов, поэтому препроцесс:
final Collection<Collection<String>> processedClients = clients.parallelStream().
map(c -> c.split("\\s+")).
map(Arrays::asList).
collect(toList());
Это занимает каждое имя клиента, разбивает его на части и затем использует оболочку asList
чтобы превратить его в List
. Это делается параллельно, поэтому должно быть быстро.
Затем нам нужно зациклиться на всех террористах:
terrorists.parallelStream().
map(t -> t.split("\\s+")).
map(t -> Stream.of(t).collect(toSet())).
forEach(t -> {
processedClients.parallelStream().forEach(c -> {
if (t.containsAll(c)) {
System.out.println("Match found t:" + t + ", c:" + c);
}
});
});
Здесь для каждого террориста мы разделяем их имя, но на этот раз мы превращаем его в Set
потому что Set
имеет O(1)
contains()
- это означает, что проверка того, содержится ли целое имя клиента во всем террористическом имени, займет время пропорционально размеру имени клиента.
Затем мы используем forEach
для forEach
террористов и другого forEach
чтобы зацикливаться на клиентах, мы проверяем имя террористов Set
containsAll
имя клиента.
Опять это происходит параллельно.
Теоретически это не займет много времени. Для хранения обработанных имен клиентов в памяти может потребоваться бит ОЗУ, но это не должно быть слишком много - около 1 ГБ.
РЕДАКТИРОВАТЬ
Вот переписать более раннюю версию (1.7, но если вы удалите нотацию алмаза, она должна работать на 1.5)
Сначала вам нужны два класса обработки, они представлены отдельным рабочим потокам:
final class NameProcessor implements Callable<Collection<String>> {
private final String name;
public NameProcessor(final String name) {
this.name = name;
}
@Override
public Collection<String> call() throws Exception {
return Arrays.asList(name.split("\\s+"));
}
}
final class TerroristProcessor implements Runnable {
private final String name;
public TerroristProcessor(final String name) {
this.name = name;
}
@Override
public void run() {
final Set<String> splitName = new HashSet<>(Arrays.asList(name.split("\\s+")));
for (final Collection<String> client : proccessedClients) {
if (splitName.containsAll(client)) {
System.out.println("Match found t:" + name + ", c:" + client);
}
}
}
}
Теперь вам нужно ExecutorService
и ExecutorCompletionService
:
final ExecutorService es = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
final ExecutorCompletionService<Collection<String>> cs = new ExecutorCompletionService<>(es);
Теперь вам сначала нужно обработать своих клиентов, как и раньше:
for (final String name : clients) {
cs.submit(new NameProcessor(name));
}
final Collection<Collection<String>> proccessedClients = new LinkedList<>();
for (int i = 0; i < clients.size(); ++i) {
try {
proccessedClients.add(cs.take().get());
} catch (InterruptedException ex) {
return;
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
А затем обработайте террористов:
final Collection<Future<?>> futures = new LinkedList<>();
for (final String terrorist : terrorists) {
futures.add(es.submit(new TerroristProcessor(terrorist)));
}
es.shutdown();
es.awaitTermination(1, TimeUnit.DAYS);
for (final Future<?> f : futures) {
try {
f.get();
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
Цикл над фьючерсами - проверка ошибок обработки.
РЕДАКТИРОВАТЬ
OP хочет обрабатывать пользовательские объекты, а не коллекции String
.
Я бы предположил, что у вас есть какой-то класс Person
например:
final class Person {
private final int id;
private final String name;
//constructor
//getters and setters
}
Затем вы можете просто создать класс-оболочку следующим образом:
final class PersonWrapper {
private final Person person;
private final Collection<String> processedName;
//constructor
//getters and setters
}
И создайте класс результата следующим образом:
final class ProblemClient {
private final Person client;
private final Person terrorist;
//constructor
//getters and setters
}
И просто перепишите код соответствующим образом:
final class NameProcessor implements Callable<PersonWrapper> {
private final Person person;
public NameProcessor(final Person person) {
this.person = person;
}
@Override
public PersonWrapper call() throws Exception {
return new PersonWrapper(person, Arrays.asList(person.getName().split("\\s+")));
}
}
final ExecutorService es = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
final ExecutorCompletionService<PersonWrapper> cs = new ExecutorCompletionService<>(es);
for (final Person client : clients) {
cs.submit(new NameProcessor(client));
}
final Collection<PersonWrapper> proccessedClients = new LinkedList<>();
for (int i = 0; i < clients.size(); ++i) {
try {
proccessedClients.add(cs.take().get());
} catch (InterruptedException ex) {
return;
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
final class TerroristProcessor implements Runnable {
private final Person person;
private final Collection<ProblemClient> results;
public TerroristProcessor(final Person person, final Collection<ProblemClient> results) {
this.person = person;
this.results = results;
}
@Override
public void run() {
final Set<String> splitName = new HashSet<>(Arrays.asList(person.getName().split("\\s+")));
for (final PersonWrapper client : proccessedClients) {
if (splitName.containsAll(client.getProcessedName())) {
results.add(new ProblemClient(client.getPerson(), person));
}
}
}
}
final Collection<ProblemClient> results = new ConcurrentLinkedQueue<>();
final Collection<Future<?>> futures = new LinkedList<>();
for (final Person terrorist : terrorists) {
futures.add(es.submit(new TerroristProcessor(terrorist, results)));
}
es.shutdown();
es.awaitTermination(1, TimeUnit.DAYS);
for (final Future<?> f : futures) {
try {
f.get();
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
//process results
for (final ProblemClient problemClient : results) {
//whatever.
}
Как я уже сказал, это может быть информативным, чтобы понять, что исход предварительной обработки террористов в первую очередь, а затем зацикливание на клиентах тоже:
final class TerroristPreprocessor implements Callable<PersonWrapper> {
private final Person person;
public TerroristPreprocessor(final Person person) {
this.person = person;
}
@Override
public PersonWrapper call() throws Exception {
final Set<String> splitName = new HashSet<>(Arrays.asList(person.getName().split("\\s+")));
return new PersonWrapper(person, splitName);
}
}
final ExecutorService es = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
final ExecutorCompletionService<PersonWrapper> cs = new ExecutorCompletionService<>(es);
for (final Person terrorist : terrorists) {
cs.submit(new TerroristPreprocessor(terrorist));
}
final Collection<PersonWrapper> proccessedTerrorists = new LinkedList<>();
for (int i = 0; i < terrorists.size(); ++i) {
try {
proccessedTerrorists.add(cs.take().get());
} catch (InterruptedException ex) {
return;
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
final class ProblemClientFinder implements Runnable {
private final Person client;
private final Collection<ProblemClient> results;
public ProblemClientFinder(final Person client, final Collection<ProblemClient> results) {
this.client = client;
this.results = results;
}
@Override
public void run() {
final Collection<String> splitName = Arrays.asList(client.getName().split("\\s+"));
for (final PersonWrapper terrorist : proccessedTerrorists) {
if (terrorist.getProcessedName().containsAll(splitName)) {
results.add(new ProblemClient(client, terrorist.getPerson()));
}
}
}
}
final Collection<ProblemClient> results = new ConcurrentLinkedQueue<>();
final Collection<Future<?>> futures = new LinkedList<>();
for (final Person client : clients) {
futures.add(es.submit(new ProblemClientFinder(client, results)));
}
es.shutdown();
es.awaitTermination(1, TimeUnit.DAYS);
for (final Future<?> f : futures) {
try {
f.get();
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
//process results
for (final ProblemClient problemClient : results) {
//whatever.
}