Как я могу быстро определить, содержит ли список только дубликаты? - PullRequest
9 голосов
/ 15 ноября 2010

Есть несколько связанных вопросов, но я ищу решение, специфичное для моего случая. Существует массив (обычно) 14 целых чисел. Как я могу быстро определить, есть ли каждый int ровно дважды (то есть 7 пар)? Диапазон значений от 1 до 35. Основным аспектом здесь является производительность.

Для справки, это мое текущее решение. Он был написан так, чтобы напоминать спецификацию как можно точнее и без учета производительности, поэтому я уверен, что ее можно значительно улучшить:

var pairs = Array
    .GroupBy (x => x)
    .Where (x => x.Count () == 2)
    .Select (x => x.ToList ())
    .ToList ();
IsSevenPairs = pairs.Count == 7;

Использование Linq необязательно. Мне все равно, как, пока это быстро:)

Редактировать: Существует особый случай, когда int встречается 2n раза при n> 1. В этом случае проверка должна выполнить , т. Е. Должно быть 7 различных пар.

Редактировать: Результат Я протестировал решения Ani и Jon с крошечными модификациями и обнаружил, что во время нескольких тестов производительности в целевом приложении Ani имеет примерно вдвое большую пропускную способность Jon на моей машине (некоторые Core 2 Duo на Win7-64). Генерация массива целых уже занимает столько же времени, сколько и соответствующие проверки, поэтому я доволен результатом. Спасибо всем!

Ответы [ 6 ]

10 голосов
/ 15 ноября 2010

Ну, учитывая ваши точные требования, мы можем быть немного умнее. Примерно так:

public bool CheckForPairs(int[] array)
{
    // Early out for odd arrays.
    // Using "& 1" is microscopically faster than "% 2" :)
    if ((array.Length & 1) == 1)
    {
        return false;
    }

    int[] counts = new int[32];
    int singleCounts = 0;
    foreach (int item in array)
    {
        int incrementedCount = ++counts[item];
        // TODO: Benchmark to see if a switch is actually the best approach here
        switch (incrementedCount)
        {
            case 1:
                singleCounts++;
                break;
            case 2:
                singleCounts--;
                break;
            case 3:
                return false;
            default:
                throw new InvalidOperationException("Shouldn't happen");
        }
    }
    return singleCounts == 0;
}

По сути, это отслеживает, сколько непарных значений у вас все еще есть, и имеет «ранний выход», если он когда-либо найдет три типа.

(Я не знаю, будет ли это быстрее или медленнее, чем подход Ани, заключающийся в увеличении и последующей проверке на несопоставленные пары.)

6 голосов
/ 15 ноября 2010

Очевидно, что LINQ не предоставит здесь оптимальное решение, хотя я бы улучшил ваше текущее решение LINQ до:

// checks if sequence consists of items repeated exactly once
bool isSingleDupSeq = mySeq.GroupBy(num => num)
                           .All(group => group.Count() == 2);

// checks if every item comes with atleast 1 duplicate
bool isDupSeq = mySeq.GroupBy(num => num)
                     .All(group => group.Count() != 1);

Для конкретного случая, который вы упомянули (0 - 31), вот более быстрое решение на основе массива. Он не очень хорошо масштабируется при большом диапазоне возможных чисел (в этом случае используйте решение для хеширования).

// elements inited to zero because default(int) == 0
var timesSeenByNum = new int[32];

foreach (int num in myArray)
{
    if (++timesSeenByNum[num] == 3)
    {
        //quick-reject: number is seen thrice
        return false;
    }
}

foreach (int timesSeen in timesSeenByNum)
{
    if (timesSeen == 1)
    {
        // only rejection case not caught so far is
        // if a number is seen exactly once
        return false;
    }
}

// all good, a number is seen exactly twice or never
return true;   

РЕДАКТИРОВАТЬ: Исправлены ошибки, как указал Джон Скит. Я также должен отметить, что его алгоритм умнее и вероятно быстрее.

0 голосов
/ 15 ноября 2010

Я полагаю (никогда не измерял скорость), этот кодснипет может дать вам новую точку зрения:

int[] array = { 0, 1, 2, 3, 1, 1, 3, 5, 1, 2, 7, 31 }; // this is your sample array

uint[] powOf2 = {
    1, 2, 4, 8,
    16, 32, 64, 128,
    256, 512, 1024, 2048,
    4096, 8192, 16384, 32768,
    65536, 131072, 262144, 524288,
    1048576, 2097152, 4194304, 8388608,
    16777216, 33554432, 67108864, 134217728,
    268435456, 536870912, 1073741824, 2147483648
               };

uint now;
uint once = 0;
uint twice = 0;
uint more = 0;

for (int i = 0; i < array.Length; i++)
{
    now = powOf2[array[i]];

    more |= twice & now;
    twice ^= (once & now) & ~more;
    twice ^= more;
    once |= now;
}

Вы можете иметь удвоенные значения в переменной «double»; Конечно, это работает только для значений менее 32;

0 голосов
/ 15 ноября 2010

Если диапазон элементов от 0 до 31, вы можете хранить 32 однобитовых флага в uint32.Я бы посоветовал взять каждый элемент и вычислить mask = (1 элемент SHL) и посмотреть, что произойдет, если вы попробуете 'or'ing,' xor'ing или добавив значения маски.Посмотрите на результаты для действительных и недействительных случаев.Чтобы избежать переполнения, вы можете захотеть использовать uint64 для добавления (поскольку uint32 может переполниться, если есть два 31, четыре 30 или восемь 29).

0 голосов
/ 15 ноября 2010

Почти наверняка излишнее, когда у вас есть только 14-ишные пары и только 32-ишные возможные значения, но в общем случае вы можете сделать что-то вроде этого:

bool onlyPairs = yourArray.ContainsOnlyPairs();

// ...

public static class EnumerableExtensions
{
    public static bool ContainsOnlyPairs<T>(this IEnumerable<T> source)
    {
        var dict = new Dictionary<T, int>();

        foreach (T item in source)
        {
            int count;
            dict.TryGetValue(item, out count);

            if (count > 1)
                return false;

            dict[item] = count + 1;
        }

        return dict.All(kvp => kvp.Value == 2);
    }
}
0 голосов
/ 15 ноября 2010

Я бы создал массив из 32 целочисленных элементов, инициализированных нулем.Давайте назовем это «billy».

Для каждого элемента входного массива я бы увеличил billy [element] на 1.

В конце проверьте, содержит ли billy только 0 или 2.

...