Эффективно ли находить дубликаты в ограниченном наборе данных «многие ко многим»? - PullRequest
4 голосов
/ 28 апреля 2011

Я должен написать версию для большого количества операций нашего веб-приложения. позволяет делать на более ограниченной основе из пользовательского интерфейса. Желаемый Операция заключается в назначении объектов категории. Категория может иметь несколько объектов, но данный объект может быть только в одной категории.

Рабочий процесс для задачи:

1) Используя браузер, загружается файл следующей формы:

# ObjectID, CategoryID
Oid1, Cid1
Oid2, Cid1
Oid3, Cid2
Oid4, Cid2
[etc.]

Файл, скорее всего, будет содержать от десятков до сотен строк, но определенно может иметь тысячи строк.

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

2) Сервер получит файл, проанализирует его, предварительно обработает его и показать страницу что-то вроде:

723 objects to be assigned to 126 categories
142 objects not found
 42 categories not found

Do you want to continue?

[Yes]     [No]

3) Если пользователь нажмет кнопку Yes, сервер на самом деле делать работу.

Поскольку я не хочу анализировать файл на обоих этапах (2) и (3), как часть (2), мне нужно построить контейнер, который будет жить через запросы и держать полезное представление данных, которые позволят мне легко предоставить данные для заполнения страницы «предварительного просмотра» и позволит мне эффективно выполнять реальную работу. (Хотя, очевидно, у нас есть сессии, мы обычно сохраняет очень мало состояния сеанса в памяти.)

Существует существующий

assignObjectsToCategory(Set<ObjectId> objectIds, CategoryId categoryId)

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

Изначально все было в порядке, если файл "незаконно" указан несколько категорий для данного объекта - было бы хорошо назначить объект abitrarily к одной из категорий файла, связанного с ним с.

Итак, я изначально думал, что на шаге (2), когда я проходил через файл, который я бы собрал и поместил в контейнер перекрестных запросов Map<CategoryId, Set<ObjectId>> (в частности, HashMap для быстрого поиск и вставка), а затем, когда пришло время сделать работу, я мог просто итерации на карте и для каждого CategoryId вытащите Set<ObjectId> и передать их в assignObjectsToCategory().

Однако изменилось требование к обработке дубликатов ObjectId. И теперь они должны быть обработаны следующим образом:

  • Если ObjectId появляется несколько раз в файле и все время связано с тем же CategoryId, назначьте объект этой категории.
  • Если в файле несколько раз появляется ObjectId и связано с различными CategoryId с, считайте, что ошибку и упомяните об этом на странице предварительного просмотра.

Кажется, это испортило мою стратегию Map<CategoryId, Set<ObjectId>> так как он не обеспечивает хороший способ обнаружить, что ObjectId I только что прочитанное из файла уже связано с CategoryId.

Поэтому мой вопрос заключается в том, как наиболее эффективно обнаруживать и отслеживать эти дубликат ObjectId с?

То, что пришло в голову, это использовать как «прямую», так и «обратную» карты:

public CrossRequestContainer
{
    ...

    Map<CategoryId, Set<ObjectId>> objectsByCategory;  // HashMap
    Map<ObjectId, List<CategoryId>> categoriesByObject; // HashMap
    Set<ObjectId> illegalDuplicates;

    ...
}

Затем, после считывания каждой пары (ObjectId, CategoryId), будет попасть в обе карты. Как только файл был полностью прочитан, я мог сделать:

for (Map.Entry<ObjectId, List<CategoryId>> entry : categoriesByObject.entrySet()) {
    List<CategoryId> categories = entry.getValue();
    if (categories.size() > 1) {
        ObjectId object = entry.getKey();
        if (!all_categories_are_equal(categories)) {
            illegalDuplicates.add(object);
            // Since this is an "illegal" duplicate I need to remove it
            // from every category that it appeared with in the file.
            for (CategoryId category : categories) {
                objectsByCategory.get(category).remove(object);
            }
        }
    }
}

Когда этот цикл завершится, objectsByCategory больше не будет содержать «незаконных» дубликаты, и illegalDuplicates будет содержать все «незаконные» дубликаты для доложить по мере необходимости. Затем я могу перебрать objectsByCategory, получить Set<ObjectId> для каждой категории и позвонить assignObjectsToCategory(), чтобы выполнить назначения.

Но хотя я думаю, что это сработает, я беспокоюсь о сохранении данных дважды, особенно когда входной файл огромен. И я также обеспокоен тем, что мне не хватает чего-то ре: эффективности и это будет идти очень медленно.

Есть ли способы сделать это, которые не будут использовать двойную память, но все еще могут работать быстро?Я что-то упустил, что даже при двойном использовании памяти все равно будет много медленнее, чем я ожидаю?

1 Ответ

1 голос
/ 28 апреля 2011

Учитывая ограничения, которые вы дали, я не могу сделать это, используя намного меньше памяти.

Одна из возможных оптимизаций состоит в том, чтобы поддерживать только списки категорий для объектов, которые перечислены в нескольких категориях, а в остальном просто сопоставлять объект с категорией, то есть:

Map<CategoryId, Set<ObjectId>> objectsByCategory;  // HashMap
Map<ObjectId, CategoryId> categoryByObject; // HashMap
Map<ObjectId, Set<CategoryId>> illegalDuplicates;  // HashMap

Да, это добавляет еще один контейнер, но он будет содержать (надеюсь) только несколько записей; Кроме того, требования к памяти для карты categoryByObject уменьшены (исключая один список издержек на запись).

Логика, конечно, немного сложнее. Когда дубликат первоначально обнаружен, объект должен быть удален из карты categoryByObject и добавлен в карту invalidDuplicates. Перед добавлением какого-либо объекта в карту categoryByObject вам необходимо сначала проверить карту нелегальные дубликаты.

Наконец, вероятно, не повредит производительности построение карты objectsByCategory в отдельном цикле после построения двух других карт, и это немного упростит код.

...