В Java (1.5 или более поздней), каков наилучший способ получить (любой) элемент из набора? - PullRequest
8 голосов
/ 05 декабря 2010

В приведенном ниже коде мне нужно было извлечь элемент, любой элемент из toSearch. Мне не удалось найти полезный метод в определении интерфейса Set, который бы возвращал только один (случайный, но не обязательно случайный) член набора. Итак, я использовал метод toArray () [0] (представлен в коде ниже).

private Set<Coordinate> floodFill(Value value, Coordinate coordinateStart)
{
    Set<Coordinate> result = new LinkedHashSet<Coordinate>();

    Set<Coordinate> toSearch = new LinkedHashSet<Coordinate>();
    toSearch.add(coordinateStart);
    while (toSearch.size() > 0)
    {
        Coordinate coordinate = (Coordinate)toSearch.toArray()[0];
        result.add(coordinate);
        toSearch.remove(coordinate);
        for (Coordinate coordinateAdjacent: getAdjacentCoordinates(coordinate))
        {
            if (this.query.getCoordinateValue(coordinateAdjacent) == value)
            {
                if (!result.contains(coordinateAdjacent))
                {
                    toSearch.add(coordinateAdjacent);
                }
            }
        }
    }

    return result;
}

Другой метод, который я рассмотрел, - заменить " (Coordinate) toSearch.toArray () [0] " на " toSearch.iterator (). Next () " , Какой метод, toArray () или iterator (), наиболее вероятно будет выполнен наиболее быстро с наименьшим влиянием GC (сборка мусора)?

Моя интуиция (после составления этого вопроса) заключается в том, что второй метод, использующий Итератор, будет и быстрее в исполнении, и снизит накладные расходы для GC. Учитывая, что я не знаю реализацию передаваемого набора (при условии, что HashSet или LinkedHashSet наиболее вероятны), сколько накладных расходов происходит в каждом из методов toArray () или iterator ()? Любая идея по этому вопросу будет принята с благодарностью.

Вопросы (повторяется сверху):

  1. Какой метод, toArray () или iterator (), наиболее вероятно выполнится наиболее быстро с наименьшим воздействием GC (Сборка мусора)?
  2. Учитывая, что я не знаю реализацию переданного набора (предполагая, что HashSet или LinkedHashSet наиболее вероятны), сколько накладных расходов происходит в каждом из методов toArray () и iterator ()?

Ответы [ 5 ]

9 голосов
/ 05 декабря 2010

toSearch.iterator().next() будет быстрее и менее ресурсоемким, поскольку не нужно копировать какие-либо данные, тогда как toArray будет выделять и копировать содержимое набора в массив.Это независимо от фактической реализации: toArray будет всегда придется копировать данные.

1 голос
/ 05 декабря 2010

Вот как я бы это реализовал:

private Set<Coordinate> floodFill(Value value, Coordinate start) {
    Set<Coordinate> result = new LinkedHashSet<Coordinate>();
    LinkedList<Coordinate> toSearch = new LinkedList<Coordinate>();
    toSearch.add(start);
    do {
        Coordinate coordinate = toSearch.removeFirst();
        if (result.add(coordinate)) {
            for (Coordinate ajacent: getAdjacentCoordinates(coordinate)) {
                if (this.query.getCoordinateValue(adjacent) == value) {
                    toSearch.add(adjacent);
                }
            }
        }
    } while (!toSearch.isEmpty());
    return result;
}

Примечания:

  1. Если подумать, структура данных toSearch не обязательно должна содержать уникальныеelements.
  2. Использование LinkedList для toSearch означает, что существует простой способ получить элемент и удалить его за один раз.
  3. Мы можем использовать тот факт, что Set.add(...)возвращает boolean, чтобы иметь количество поисков в наборе result ... по сравнению с использованием Set.contains().
  4. Было бы лучше использовать HashSet вместо LinkedHashSet для результатов... если вам не нужно знать порядок, в котором координаты были добавлены заливкой.
  5. Использование == для сравнения Value экземпляров может быть немного хитрым.
1 голос
/ 05 декабря 2010

Из того, что я вижу, вы делаете Поиск в ширину

Ниже приведен пример того, как это можно реализовать без использования toArray:

    private Set<Coordinate> floodFill(Value value, Coordinate coordinateStart) {
    final Set<Coordinate> visitedCoordinates = new LinkedHashSet<Coordinate>();
    final Deque<Coordinate> deque = new ArrayDeque<Coordinate>();

    deque.push(coordinateStart);

    while (!deque.isEmpty()) {
        final Coordinate currentVertex = deque.poll();
        visitedCoordinates.add(currentVertex);
        for (Coordinate coordinateAdjacent : getAdjacentCoordinates(currentVertex)) {
            if (this.query.getCoordinateValue(coordinateAdjacent) == value) {
                if (!visitedCoordinates.contains(coordinateAdjacent)) {
                    deque.add(coordinateAdjacent);
                }
            }
        }
    }

    return visitedCoordinates;
}

Замечания по реализации:

И теперь я обеспокоен тем, что реализация метода contains () в LinkedList может выполнять до полного сканирования содержимого перед возвратом ответа.

Вы правы относительно полного сканирования (он же линейный поиск). Тем не менее, в вашем случае можно иметь дополнительный набор для отслеживания уже посещенных вершин (кстати, на самом деле это ваш результат!), Который решит проблему с методом contains за O (1).

Приветствия

0 голосов
/ 07 декабря 2010

Хорошо, ниже моя последняя реализация, включающая обратную связь (в основном от Стивена, Кэмерона и Петро), которая включает в себя полное устранение конфликта toArray () [] - vs-interator (). Next (). И я прибавил в комментариях, чтобы более точно различать, что происходит и почему. И чтобы лучше понять, почему я конкретно реализовал оригинальный совет Петро «использовать набор отслеживания» (поддержанный Кэмероном). И сразу после фрагмента кода я сопоставлю его с другими предлагаемыми решениями.

private Set<Coordinate> floodFind3(Coordinate coordinate)
{
    Set<Coordinate> area = new LinkedHashSet<Coordinate>(); //includes only area of value (which is the same as at coordinate)

    area.add(coordinate);
    Value value = getCoordinateValue(coordinate); //value upon which to expand area
    Set<Coordinate> checked = new LinkedHashSet<Coordinate>(); //every coordinate evaluated regardless of value
    checked.add(coordinate);
    Queue<Coordinate> candidates = new LinkedList<Coordinate>(); //coordinates evaluated, were of value, and are queued to iterate through their adjacents
    candidates.add(nordinate);
    while (!candidates.isEmpty())
    {
        for (Nordinate coordinateAdjacent: this.query.getNordinates().getAdjacent(candidates.remove()).getOrthogonal())
        {
            if (checked.add(coordinateAdjacent)) //only expands containing value and !value
            {
                if (getCoordinateValue(coordinateAdjacent) == value)
                {
                    area.add(coordinateAdjacent); //only expands containing value
                    candidates.add(coordinateAdjacent); //expands and contracts containing value
                }
            }
        }
    }

    return area;
}

Я обновил метод несколькими значительными способами:

  1. Еще один параметр метода меньше: я удалил параметр, так как он был извлечен из поиска, и устранил возможную логическую проблему, когда начальная координата указывает на местоположение, содержащее значение!
  2. Три коллекции отслеживают поиск; область (Set), отмеченные (Set) и кандидаты (Queue). Комментарии кода разъясняют конкретное использование каждого. Использовал LinkedHashSet для надежной воспроизводимости при поиске ошибок и проблем с производительностью (/1448483/poryadok-iteratsii-hashset). После стабилизации я, скорее всего, вернусь к более быстрой реализации HashSet.
  3. Перед тестом «is value» изменился порядок проверки «проверена ли уже оценка», чтобы каждая координата посещалась только один раз. Это позволяет избежать повторного просмотра значения соседних координат более одного раза. Также включено умное двойное использование Стивеном метода Set add (). Это становится очень важным, поскольку область затопления становится более похожей на лабиринт (змеиный / паучий).
  4. Сохраняйте "==" для проверки значения, вызывая сравнение ссылок. Значение определено как Enum Java 1.5, и я не хотел зависеть от HotSpot, чтобы встроить вызов метода .equals () и сократить его до сравнения ссылок. Если бы ценность когда-либо изменилась с того, чтобы быть Enum, этот выбор мог бы вернуться, чтобы укусить меня. Tyvm Стивену за указание на это.

Решения Петро и Стефана посещают координаты, содержащие значение, только один раз, но требуют повторного пересмотра координат, содержащих! Value, более одного раза, что может привести к нескольким повторным проверкам выборок / значений для областей, состоящих из длинных лабиринтных туннелей. Хотя «длинные лабиринтоподобные туннели» можно считать патологическим случаем, это более типично для конкретной области, для которой мне нужен этот метод. И мое «второе» попытанное решение (которое имело низкую производительность, вызов LinkedList содержит ()) было сомнительным как реальный ответ ({кивок Стивену на этом).

Спасибо за ваши отзывы.

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

0 голосов
/ 05 декабря 2010

После ответа Петра я скопировал метод и переопределил его согласно его советам.Это выглядит так:

private Set<Coordinate> floodFind2(Value value, Coordinate coordinateStart)
{
    Set<Coordinate> result = new LinkedHashSet<Coordinate>();

    Queue<Coordinate> toSearch = new LinkedList<Coordinate>();
    toSearch.add(coordinateStart);
    while (!toSearch.isEmpty())
    {
        Coordinate coordinate = toSearch.remove();
        result.add(coordinate);
        for (Coordinate coordinateAdjacent: getAdjacentCoordinates(coordinate))
        {
            if (getCoordinateValue(coordinateAdjacent).equals(value))
            {
                if (!result.contains(coordinateAdjacent))
                {
                    if (!toSearch.contains(coordinateAdjacent))
                    {
                        toSearch.add(coordinateAdjacent);
                    }
                }
            }
        }
    }

    return result;
}

При переходе от Set to Queue мои вопросы эффективности перешли на новую условную проверку, которую мне пришлось добавить, " if (! ToSearch.contains (ordinateAdjacent)) ».Используя интерфейс Set, он молча мешал мне добавлять дубликаты.Используя интерфейс очереди, я должен убедиться, что я не добавляю дубликат.

И теперь я обеспокоен тем, что реализация метода contains () в LinkedList может выполнять до полного сканирования содержимого.перед возвратом ответа.Таким образом, сравнивая этот метод с тем, который я первоначально опубликовал, который, вероятно, будет более эффективным (прежде чем я проведу много времени, проводя эмпирическое тестирование)?

...