Как найти режим списка <double>?

У меня есть список:

List<double> final=new List<double>();
final.Add(1);
final.Add(2);
final.Add(3);

Какой метод я могу использовать для поиска режима этого списка? Кроме того, если есть два режима, функция вернет меньшую из двух.

Ответ 1

int? modeValue =
 final
 .GroupBy(x => x)
 .OrderByDescending(x => x.Count()).ThenBy(x => x.Key)
 .Select(x => (int?)x.Key)
 .FirstOrDefault();

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

Если список пуст, modeValue будет null.

Ответ 2

Ответ, заданный usr, кажется, что он выполнит трюк, но если вы хотите что-то не Linq, попробуйте следующее:

    public int? FindMode(List<int> sample)
    {
        if (sample == null || sample.Count == 0)
        {
            return null;
        }

        List<int> indices = new List<int>();
        sample.Sort();

        //Calculate the Discrete derivative of the sample and record the indices
        //where it is positive.
        for (int i = 0; i < sample.Count; i++)
        {
            int derivative;

            if (i == sample.Count - 1)
            {
                //This ensures that there is a positive derivative for the
                //last item in the sample. Without this, the mode could not
                //also be the largest value in the sample.
                derivative = int.MaxValue - sample[i];
            }
            else
            {
                derivative = sample[i + 1] - sample[i];
            }

            if (derivative > 0)
            {
                indices.Add(i + 1);
            }
        }

        int maxDerivative = 0, maxDerivativeIndex = -1;

        //Calculate the discrete derivative of the indices, recording its
        //maxima and index.
        for (int i = -1; i < indices.Count - 1; i++)
        {
            int derivative;

            if (i == -1)
            {
                derivative = indices[0];
            }
            else
            {
                derivative = indices[i + 1] - indices[i];
            }

            if (derivative > maxDerivative)
            {
                maxDerivative = derivative;
                maxDerivativeIndex = i + 1;
            }
        }

        //The mode is then the value of the sample indexed by the
        //index of the largest derivative.
        return sample[indices[maxDerivativeIndex] - 1];
    }

То, что я здесь сделал, по существу является реализацией алгоритма, описанного на странице Wikipedia в разделе "Режим образца". Обратите внимание, что, сначала отсортировав образец, это вернет меньший режим в многорежимном случае.

Кроме того, код Octave на странице Wikipedia предполагает индексирование на основе 1; потому что С# на основе 0, вы увидите, что я использовал indices.Add(i + 1) и maxDerivativeIndex = i + 1 для компенсации. По той же причине я также использовал indices[maxDerivativeIndex] - 1 для возврата к индексированию на основе 0 при возврате окончательного режима.


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

Вызов вышеуказанного метода:

int? mode = FindMode(new List<int>(new int[] { 1, 3, 6, 6, 6, 6, 7, 7, 12, 12, 17 }));

После этой начальной проверки и сортировки дискретная производная (т.е. список indices) будет выглядеть так в конце этого первого цикла:

[1, 2, 6, 8, 10, 11]

Затем мы вычисляем дискретную производную indices. По соображениям эффективности я не храню их в списке (в конце концов, нам нужен только самый большой из них), но они работают так:

[1, 1, 4, 2, 2, 1]

Следовательно, maxDerivative заканчивается как 4, а maxDerivativeIndex 2. Следовательно:

sample[indices[maxDerivativeIndex] - 1]
    -> sample[indices[2] - 1]
    -> sample[6 - 1]
    -> 6

Ответ 3

Альтернативное решение:

var counts = final
    .Distinct()
    .Select(o => new { Value = o, Count = final.Count(c => c == o) })
    .OrderByDescending(o => o.Count);

Это вернет коллекцию, указывающую, сколько раз каждое из значений появляется в списке, с самым популярным (средним) первым. Вы можете использовать это с помощью counts.FirstOrDefault();, но коллекция может быть более полезна, так как вы сможете увидеть, когда есть более одного режима!

Я нахожу GroupBy Запросы LINQ могут быть немного сложными для понимания, но это мое личное мнение.