web-dev-qa-db-fra.com

Calculer la médiane en c #

Je dois écrire une fonction qui acceptera un tableau de nombres décimaux et qui trouvera la médiane.

Existe-t-il une fonction dans la bibliothèque mathématique .net?

43
WingMan20-10

Existe-t-il une fonction dans la bibliothèque mathématique .net?

Non.

Ce n'est pas difficile d'écrire votre propre bien. L'algorithme naïf trie le tableau et sélectionne les éléments du milieu (ou la moyenne des deux). Cependant, cet algorithme est O(n log n) alors qu'il est possible de résoudre ce problème en O(n) temps. Vous voulez regarder algorithmes de sélection pour obtenir un tel algorithme.

17
jason

On dirait que les autres réponses utilisent le tri. Ce n’est pas optimal du point de vue des performances car cela prend O(n logn) temps. Il est possible de calculer la médiane dans le temps O(n) à la place. La version généralisée de ce problème est connue sous le nom de "statistique d'ordre n", ce qui signifie trouver un élément K dans un ensemble tel que nous ayons n éléments inférieurs ou égaux à K et que le reste soit plus grand ou égal à K. Ainsi, la statistique d'ordre 0 serait minimale. élément de l'ensemble (Remarque: Certaines publications utilisent les indices de 1 à N au lieu de 0 à N-1). La médiane est simplement une statistique (Count-1)/2- à commander.

Ci-dessous, le code repris de Introduction aux algorithmes par Cormen et al, 3e édition .

/// <summary>
/// Partitions the given list around a pivot element such that all elements on left of pivot are <= pivot
/// and the ones at thr right are > pivot. This method can be used for sorting, N-order statistics such as
/// as median finding algorithms.
/// Pivot is selected ranodmly if random number generator is supplied else its selected as last element in the list.
/// Reference: Introduction to Algorithms 3rd Edition, Corman et al, pp 171
/// </summary>
private static int Partition<T>(this IList<T> list, int start, int end, Random rnd = null) where T : IComparable<T>
{
    if (rnd != null)
        list.Swap(end, rnd.Next(start, end+1));

    var pivot = list[end];
    var lastLow = start - 1;
    for (var i = start; i < end; i++)
    {
        if (list[i].CompareTo(pivot) <= 0)
            list.Swap(i, ++lastLow);
    }
    list.Swap(end, ++lastLow);
    return lastLow;
}

/// <summary>
/// Returns Nth smallest element from the list. Here n starts from 0 so that n=0 returns minimum, n=1 returns 2nd smallest element etc.
/// Note: specified list would be mutated in the process.
/// Reference: Introduction to Algorithms 3rd Edition, Corman et al, pp 216
/// </summary>
public static T NthOrderStatistic<T>(this IList<T> list, int n, Random rnd = null) where T : IComparable<T>
{
    return NthOrderStatistic(list, n, 0, list.Count - 1, rnd);
}
private static T NthOrderStatistic<T>(this IList<T> list, int n, int start, int end, Random rnd) where T : IComparable<T>
{
    while (true)
    {
        var pivotIndex = list.Partition(start, end, rnd);
        if (pivotIndex == n)
            return list[pivotIndex];

        if (n < pivotIndex)
            end = pivotIndex - 1;
        else
            start = pivotIndex + 1;
    }
}

public static void Swap<T>(this IList<T> list, int i, int j)
{
    if (i==j)   //This check is not required but Partition function may make many calls so its for perf reason
        return;
    var temp = list[i];
    list[i] = list[j];
    list[j] = temp;
}

/// <summary>
/// Note: specified list would be mutated in the process.
/// </summary>
public static T Median<T>(this IList<T> list) where T : IComparable<T>
{
    return list.NthOrderStatistic((list.Count - 1)/2);
}

public static double Median<T>(this IEnumerable<T> sequence, Func<T, double> getValue)
{
    var list = sequence.Select(getValue).ToList();
    var mid = (list.Count - 1) / 2;
    return list.NthOrderStatistic(mid);
}

Quelques notes:

  1. Ce code remplace le code récursif de la version originale de book dans la boucle itérative.
  2. Il élimine également les vérifications inutiles de la version originale lorsque start == end.
  3. J'ai fourni deux versions de Median, une qui accepte IEnumerable et crée ensuite une liste. Si vous utilisez la version qui accepte IList, n'oubliez pas qu'elle modifie l'ordre dans la liste.
  4. Les méthodes ci-dessus calculent la médiane ou toute statistique d'ordre i dans O(n)temps attendu. Si vous voulez O(n)pire affaire temps, il existe une technique permettant d'utiliser la médiane de la médiane. Bien que cela améliorerait les performances dans les cas les plus défavorables, cela dégrade le cas moyen car constant dans O(n) est maintenant plus grand. Cependant, si vous calculez la médiane principalement sur des données très volumineuses, il est intéressant de regarder.
  5. La méthode NthOrderStatistics permet de passer un générateur de nombre aléatoire qui serait ensuite utilisé pour choisir un pivot aléatoire lors de la partition. Cela n'est généralement pas nécessaire, sauf si vous savez que vos données contiennent certains modèles afin que le dernier élément ne soit pas suffisamment aléatoire ou si votre code est exposé à l'extérieur pour une exploitation ciblée. 
  6. La définition de la médiane est claire si vous avez un nombre impair d'éléments. C'est juste l'élément avec index (Count-1)/2 dans un tableau trié. Mais lorsque vous même nombre d'élément (Count-1)/2 n'est plus un entier et que vous avez deux médianes: Médiane inférieure Math.Floor((Count-1)/2) et Math.Ceiling((Count-1)/2). Certains manuels utilisent la médiane inférieure comme "standard" alors que d'autres proposent d'en utiliser une moyenne de deux. Cette question devient particulièrement critique pour un ensemble de 2 éléments. Le code ci-dessus renvoie la médiane inférieure. Si vous voulez plutôt moyenne et basse, vous devez appeler le code ci-dessus deux fois. Dans ce cas, assurez-vous de mesurer les performances de vos données pour décider si vous devez utiliser le code ci-dessus simplement du tri simple.
  7. Pour .net 4.5+, vous pouvez ajouter l'attribut MethodImplOptions.AggressiveInlining à la méthode Swap<T> pour améliorer légèrement les performances.
52
Shital Shah

Merci Rafe, cela prend en compte les problèmes signalés par vos réplicateurs.

public static double GetMedian(double[] sourceNumbers) {
        //Framework 2.0 version of this method. there is an easier way in F4        
        if (sourceNumbers == null || sourceNumbers.Length == 0)
            throw new System.Exception("Median of empty array not defined.");

        //make sure the list is sorted, but use a new array
        double[] sortedPNumbers = (double[])sourceNumbers.Clone();
        Array.Sort(sortedPNumbers);

        //get the median
        int size = sortedPNumbers.Length;
        int mid = size / 2;
        double median = (size % 2 != 0) ? (double)sortedPNumbers[mid] : ((double)sortedPNumbers[mid] + (double)sortedPNumbers[mid - 1]) / 2;
        return median;
    }
33
Jason Jakob
decimal Median(decimal[] xs) {
  Array.Sort(xs);
  return xs[xs.Length / 2];
}

Devrait faire l'affaire.

-- MODIFIER --

Pour ceux qui veulent la version complète, voici la solution complète, courte et pure (on suppose un tableau d'entrée non vide):

decimal Median(decimal[] xs) {
  var ys = xs.OrderBy(x => x).ToList();
  double mid = (ys.Count - 1) / 2.0;
  return (ys[(int)(mid)] + ys[(int)(mid + 0.5)]) / 2;
}
17
Rafe

Math.NET est une bibliothèque opensource qui offre une méthode de calcul de la médiane . Le paquet de nugets est appelé MathNet.Numerics .

L'utilisation est assez simple:

using MathNet.Numerics.Statistics;

IEnumerable<double> data;
double median = data.Median();
9
NePh

Voici une version générique de la réponse de Jason

    /// <summary>
    /// Gets the median value from an array
    /// </summary>
    /// <typeparam name="T">The array type</typeparam>
    /// <param name="sourceArray">The source array</param>
    /// <param name="cloneArray">If it doesn't matter if the source array is sorted, you can pass false to improve performance</param>
    /// <returns></returns>
    public static T GetMedian<T>(T[] sourceArray, bool cloneArray = true) where T : IComparable<T>
    {
        //Framework 2.0 version of this method. there is an easier way in F4        
        if (sourceArray == null || sourceArray.Length == 0)
            throw new ArgumentException("Median of empty array not defined.");

        //make sure the list is sorted, but use a new array
        T[] sortedArray = cloneArray ? (T[])sourceArray.Clone() : sortedArray = sourceArray;
        Array.Sort(sortedArray);

        //get the median
        int size = sortedArray.Length;
        int mid = size / 2;
        if (size % 2 != 0)
            return sortedArray[mid];

        dynamic value1 = sortedArray[mid];
        dynamic value2 = sortedArray[mid - 1];
        return (sortedArray[mid] + value2) * 0.5;
    }
3
Will Calderwood

Voici l’implémentation non sécurisée la plus rapide, Même algorithme que précédemment, tirée de cette source

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static unsafe void SwapElements(int* p, int* q)
    {
        int temp = *p;
        *p = *q;
        *q = temp;
    }

    public static unsafe int Median(int[] arr, int n)
    {
        int middle, ll, hh;

        int low = 0; int high = n - 1; int median = (low + high) / 2;
        fixed (int* arrptr = arr)
        {
            for (;;)
            {
                if (high <= low)
                    return arr[median];

                if (high == low + 1)
                {
                    if (arr[low] > arr[high])
                        SwapElements(arrptr + low, arrptr + high);
                    return arr[median];
                }

                middle = (low + high) / 2;
                if (arr[middle] > arr[high])
                    SwapElements(arrptr + middle, arrptr + high);

                if (arr[low] > arr[high])
                    SwapElements(arrptr + low, arrptr + high);

                if (arr[middle] > arr[low])
                    SwapElements(arrptr + middle, arrptr + low);

                SwapElements(arrptr + middle, arrptr + low + 1);

                ll = low + 1;
                hh = high;
                for (;;)
                {
                    do ll++; while (arr[low] > arr[ll]);
                    do hh--; while (arr[hh] > arr[low]);

                    if (hh < ll)
                        break;

                    SwapElements(arrptr + ll, arrptr + hh);
                }

                SwapElements(arrptr + low, arrptr + hh);

                if (hh <= median)
                    low = ll;
                if (hh >= median)
                    high = hh - 1;
            }
        }
    }
1
eladm

Dans le futur. Je pense que c'est aussi simple que possible.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Median
{
    class Program
    {
        static void Main(string[] args)
        {
            var mediaValue = 0.0;
            var items = new[] { 1, 2, 3, 4,5 };
            var getLengthItems = items.Length;
            Array.Sort(items);
            if (getLengthItems % 2 == 0)
            {
                var firstValue = items[(items.Length / 2) - 1];
                var secondValue = items[(items.Length / 2)];
                mediaValue = (firstValue + secondValue) / 2.0;
            }
            if (getLengthItems % 2 == 1)
            {
                mediaValue = items[(items.Length / 2)];
            }
            Console.WriteLine(mediaValue);
            Console.WriteLine("Enter to Exit!");
            Console.ReadKey();
        }
    }
}
1
Krishneil

La bibliothèque NMath de CenterSpace fournit une fonction: 

double[] values = new double[arraySize];
double median = NMathFunctions.Median(values);

Vous pouvez éventuellement utiliser NaNMedian (si votre tableau peut contenir des valeurs NULL), mais vous devrez le convertir en vecteur

double median = NMathFunctions.NaNMedian(new DoubleVector(values));

La bibliothèque NMath de CenterSpace n'est pas gratuite, mais de nombreuses universités ont des licences

1
soxfan04

Le code ci-dessous fonctionne: mais de manière peu efficace. :(

static void Main(String[] args) {
        int n = Convert.ToInt32(Console.ReadLine());            
        int[] medList = new int[n];

        for (int x = 0; x < n; x++)
            medList[x] = int.Parse(Console.ReadLine());

        //sort the input array:
        //Array.Sort(medList);            
        for (int x = 0; x < n; x++)
        {
            double[] newArr = new double[x + 1];
            for (int y = 0; y <= x; y++)
                newArr[y] = medList[y];

            Array.Sort(newArr);
            int curInd = x + 1;
            if (curInd % 2 == 0) //even
            {
                int mid = (x / 2) <= 0 ? 0 : (newArr.Length / 2);
                if (mid > 1) mid--;
                double median = (newArr[mid] + newArr[mid+1]) / 2;
                Console.WriteLine("{0:F1}", median);
            }
            else //odd
            {
                int mid = (x / 2) <= 0 ? 0 : (newArr.Length / 2);
                double median = newArr[mid];
                Console.WriteLine("{0:F1}", median);
            }
        }

}
0
Prabakar Veer

Mes 5 centimes (parce que cela semble plus simple/plus simple et suffisant pour les listes courtes):

public static T Median<T>(this IEnumerable<T> items)
{
    var i = (int)Math.Ceiling((double)(items.Count() - 1) / 2);
    if (i >= 0)
    {
        var values = items.ToList();
        values.Sort();
        return values[i];
    }

    return default(T);
}

P.S. en utilisant "médiane supérieure" comme décrit par ShitalShah.

0
mike

J'ai un histogramme avec la variable: groupe
Voici comment je calcule ma médiane: 

int[] group = new int[nbr]; 

// -- Fill the group with values---

// sum all data in median
int median = 0;
for (int i =0;i<nbr;i++) median += group[i];

// then divide by 2 
median = median / 2;

// find 50% first part 
for (int i = 0; i < nbr; i++)
{
   median -= group[i];
   if (median <= 0)
   {
      median = i;
      break;
   }
}

la médiane est l'indice de groupe de la médiane 

0
Dominique Mathieu