Эффективная обработка пар .NET - PullRequest
0 голосов
/ 22 декабря 2010

Следующий код позволяет вам обрабатывать все возможные пары объектов (где DoSomething (a, b) эквивалентен DoSomething (b, a), и вы не хотите делать то и другое, и вам никогда не нужно делать DoSomething (a , а)):

        void MyMethod (MyThing[] myArray)
        {
        for (int j = 0; j < (myArray.Length-1); ++j)
            {
            for (int k = j+1; k < myArray.Length; ++k)
                {
                DoSomething(myArray[j], myArray[k]);
                }
            }
        }

Я знаю, что это примерно n * n / 2 операций.

Существует ли одинаково эффективный способ обработки всех возможных пар, если вместо массива используется List<MyThing>? (В частности, решение, в котором мне не нужно переворачивать уже готовые элементы во внутреннем цикле). Может быть, использовать счетчики как-то?

Массивы бесполезны, потому что я заранее не знаю, сколько MyThings мне понадобится (это может быть 0, вероятно, никогда не будет больше 1000). Есть ли лучшая коллекция для этого конкретного использования, чем List<>? Мне не нужна сортировка, я только создаю новые коллекции, очищаю существующие коллекции, добавляю в коллекцию, перечисляю коллекцию или обрабатываю пары, как указано выше. Независимо от того, какую коллекцию я использую, мне может понадобиться от 100 до 10000 экземпляров, поэтому они не могут быть слишком дорогими для создания / хранения.

Допустим, DoSomething () выполняет обнаружение столкновений и реагирование на движущиеся объекты.

Ответы [ 4 ]

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

Есть ли столь же эффективный способ обработать все возможные пары, если Список используется вместо массив

A List<T> - это просто модная обертка вокруг T[], которая иногда изменяет размер, когда превышает объем вашего списка.

Ваш алгоритм уже оптимален. Если вы хотите использовать его с List<T>, просто измените объявление с void MyMethod (MyThing[] myArray) на void MyMethod (List<MyThing> myArray).

Есть ли лучшая коллекция для этого конкретное использование, чем список <>? Я не нужна сортировка, я только создаю новый коллекции, очистить существующие коллекции, добавить в коллекцию, перечислить коллекцию или процесс спаривания, как указано выше. Какую бы коллекцию Я использую, мне может понадобиться от 100 до 10000 их, поэтому они не могут быть слишком дорогими, чтобы создать / сохранить.

Сначала давайте попытаемся понять некоторые вещи о структурах данных:

Внутри List<T> хранится массив размера N; Когда вы добавляете элементы в массив, если вы превышаете размер внутреннего массива, то в List будет добавлен новый массив размером N * 2, скопируйте элементы и добавьте новый элемент. Изменение размера имеет наихудший случай O (n); однако удвоение массива при каждом изменении размера означает, что вам нужно добавить в два раза больше элементов, чем раньше, чтобы вызвать наихудший вариант поведения. У списков есть свойство, которое дает им амортизированных O (1) вставок, что означает, что вы можете выполнить n операций за O (n) время.

Обычно LinkedList имеет очень быструю вставку. Насколько мне известно, он не использует базовый массив, а скорее имеет набор узлов, содержащих указатели Next и Previous, которые указывают на смежные элементы в коллекции. С положительной стороны, вставка в наихудшем случае - это O (1), но иногда связанные списки могут иметь теоретически меньшую, чем оптимальная производительность, из-за плохого местоположения ссылки (то есть смежные элементы в списке не соседствуют в памяти).

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

Итак, с учетом сказанного, если вы действительно хотите получить коллекцию динамического размера с хорошей привязкой, но также поддерживает быструю вставку, то попробуйте VList . Он имеет оба свойства, которые вы ищете, и его очень легко написать:

public class VList<T> : IEnumerable<T>
{
    VListNode<T> RootNode;
    public int Count { get; private set; }

    public VList() : this(4) { }

    public VList(int size)
    {
        RootNode = new VListNode<T>(4, null);
    }

    public void Add(T element)
    {
        if (RootNode.Count == RootNode.MaxSize)
            RootNode = new VListNode<T>(RootNode.MaxSize * 2, RootNode);
        RootNode.Add(element);
        Count++;
    }

    public void Clear()
    {
        RootNode = new VListNode<T>(4, null);
    }

    public IEnumerator<T> GetEnumerator()
    {
        VListNode<T> node = RootNode;
        while (node != null)
        {
            foreach (T t in node)
                yield return t;
            node = node.Next;
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

public class VListNode<T> : IEnumerable<T>
{
    readonly T[] Elements;
    public VListNode<T> Next { get; private set; }
    public int Count { get; private set; }
    public int MaxSize { get; private set; }

    public VListNode(int size, VListNode<T> next)
    {
        MaxSize = size;
        Elements = new T[size];
        Next = next;
    }

    public void Add(T element)
    {
        Elements[Count] = element;
        Count++;
    }

    public IEnumerator<T> GetEnumerator()
    {
        // iterate in reverse to return elements in LIFO order.
        for (int i = Count - 1; i >= 0; i--)
            yield return Elements[i];
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

Простая реализация, описанная выше, должна поддерживать Add in O (1), сохраняя при этом хорошую ссылку.

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

Количество уникальных комбинаций из N предметов, взятых по 2 за раз, равно N! / (2! (N-2)!) = N (N-1) / 2 ~ = O (n ^ 2).Выполнение действия с каждой комбинацией двух элементов в списке не может быть достигнуто с меньшей сложностью.

Что касается используемой коллекции, список упадет прямо туда, где у вас был массив, с одним изменением;List использует свойство Count для определения количества элементов, а не Length.

Что касается более элегантного способа объединения As и B, Linq имеет некоторые преимущества:

var combinations = 
    from a in myThings.Reverse()
    from b in myThings.TakeWhile(x=>x!=a)
    select new {a,b};

foreach(var combo in combinations)
   DoSomething(combo.a, combo.b);

Этовсе еще будет работать медленнее, чем ваш первоначальный алгоритм, но я думаю, что он будет работать немного быстрее, чем cdhowie, потому что он будет проходить только через N дополнительных элементов (для создания перечислимого Reverse ()) вместо пропуска N (N-1)), как это делает внутренняя часть cdhowie.

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

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

Например, скажем, что DoSomething выглядит такthis:

void DoSomething(MyThing arg1, MyThing arg2)
{
    // Let's assume MyThing.Value is an int
    // and you want to print the product of both values
    Console.WriteLine(arg1.Value * arg2.Value);
}

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

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

List<T> будет работать просто отлично. Вы можете просто заменить MyThing[] в подписи вашего метода на List<MyThing>.

Если вы ищете решение, которое работает с перечислимыми, просто к черту:

void MyMethod<T>(IEnumerable<T> myThings, Action<T, T> action)
{
    int index = 0;
    foreach (var a in myThings)
        foreach (var b in myThings.Skip(++index))
            action(a, b);
}

Обратите внимание, что это будет немного медленнее, поскольку Skip будет фактически перебирать пропущенные элементы. Это может отменить любые преимущества, которые вы получите от кэширования памяти по мере приближения к концу списка, и, конечно, это приведет к потере времени на отбрасывание первых N элементов.

...