64 votes

Calculer la médiane en c#

J'ai besoin d'écrire une fonction qui accepte un tableau de décimales et qui trouve la médiane.

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

75voto

ShitalShah Points 2213

Il semble que les autres réponses utilisent le tri. Ce n'est pas optimal du point de vue des performances car il faut O(n logn) temps. Il est possible de calculer la médiane en O(n) temps à 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 qu'il faut trouver un élément K dans un ensemble tel que nous avons n éléments inférieurs ou égaux à K et le reste est supérieur ou égal à K. Ainsi, la statistique d'ordre 0 serait l'élément minimal dans l'ensemble (Remarque : certains ouvrages utilisent l'indice de 1 à N au lieu de 0 à N-1). La médiane est simplement (Count-1)/2 -statistique d'ordre.

Voici le code adopté à partir 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 du livre par une boucle itérative.
  2. Il élimine également la vérification supplémentaire inutile de la version originale lorsque le début==la fin.
  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, gardez à l'esprit qu'elle modifie l'ordre dans la liste.
  4. Les méthodes ci-dessus calculent la médiane ou n'importe quelle statistique d'ordre i en O(n) temps prévu . Si vous voulez O(n) temps dans le pire des cas alors il existe une technique pour utiliser la médiane de la médiane. Bien que cette technique améliore les performances dans le pire des cas, elle dégrade les performances dans le cas moyen, car la constante en O(n) est maintenant plus grande. Cependant, si vous calculez la médiane principalement sur de très grandes données, cela vaut la peine de l'examiner.
  5. La méthode NthOrderStatistics permet de passer un générateur de nombres aléatoires qui sera 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 présentent certains modèles, de sorte que le dernier élément ne sera 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 le nombre d'éléments est impair. C'est juste l'élément avec l'indice (Count-1)/2 dans un tableau trié. Mais lorsque vous avez un nombre pair d'éléments (Count-1)/2 n'est plus un nombre entier et vous avez deux médianes : La médiane inférieure Math.Floor((Count-1)/2) y Math.Ceiling((Count-1)/2) . Certains manuels utilisent la médiane inférieure comme "norme" tandis que d'autres proposent d'utiliser la moyenne des deux. Cette question devient particulièrement critique pour les ensembles de 2 éléments. Le code ci-dessus renvoie la médiane inférieure. Si vous voulez plutôt la moyenne de la médiane inférieure et supérieure, 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 ou simplement le tri direct.
  7. Pour .net 4.5+, vous pouvez ajouter MethodImplOptions.AggressiveInlining l'attribut Swap<T> pour des performances légèrement améliorées.

0 votes

@ShitalShah : re : 6, si je veux calculer la médiane avec la moyenne, au lieu de faire 2 appels à NthOrderStatistic, ne puis-je pas profiter du fait que la liste est mutée et sélectionner essentiellement l'élément suivant. Je ne sais pas si la méthode NthOrderStatistic finit par trier la liste de manière ascendante ou seulement une partie de celle-ci (en fonction des données de la liste en fin de compte).

1 votes

@costa - NthOrderStatistics n'a pas de garantie sur le tri d'une moitié. L'élément suivant n'est pas non plus garanti d'être l'élément suivant plus petit ou plus grand.

1 votes

C'est très pratique, merci ! J'ai mis à jour les méthodes pour utiliser les membres du corps de l'expression C# 6 et j'ai collé dans un gist, ainsi qu'un algorithme d'écart type -. gist.github.com/cchamberlain/478bf7a3411beb47abb6

44voto

Jason Jakob Points 439

Merci Rafe, cela prend en compte les problèmes que vos répondeurs ont signalés.

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;
    }

0 votes

Pourquoi la fonction est-elle statique ici ?

2 votes

@richieqianle : Parce que tout ce qui peut être statique devrait l'être. C'est plus efficace du point de vue de tableau des fonctions virtuelles .

0 votes

@abatishchev Une méthode n'est pas virtuelle par défaut en C# (contrairement à Java). Mais même si elle étaient La performance est une très mauvaise raison de rendre quelque chose statique ou non. Une meilleure raison - au moins dans cette réponse - pourrait être si la méthode est une sorte de méthode utilitaire, où aucune instance de la classe n'est nécessaire.

33voto

NePh Points 467

Math.NET est une bibliothèque opensource qui offre une méthode de calcul de la Médiane . Le paquet nuget s'appelle MathNet.Numerics .

L'utilisation est assez simple :

using MathNet.Numerics.Statistics;

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

28voto

Rafe Points 3297
decimal Median(decimal[] xs) {
  Array.Sort(xs);
  return xs[xs.Length / 2];
}

Ça devrait faire l'affaire.

-- EDIT --

Pour ceux qui veulent tout savoir, voici la solution complète, courte et pure (un tableau d'entrée non vide est supposé) :

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;
}

10 votes

C'est O(n log n) . Il est possible de trouver la médiane en O(n) temps. De plus, cette méthode ne retourne pas la médiane dans le cas où le tableau est de longueur égale (elle devrait être la moyenne des deux éléments du milieu après le tri du tableau).

5 votes

Bien sûr, mais la question ne mentionnait pas O(n) comme une exigence et, en ce qui concerne les cas pairs ou vides, ils ont été laissés comme un exercice pour l'étudiant.

6 votes

De plus, cela modifie le tableau que vous passez à la méthode, ce qui est tout simplement stupide.

24voto

Jason Points 125291

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

Non.

Il n'est pourtant pas difficile d'écrire le sien. L'algorithme naïf trie le tableau et choisit les éléments du milieu (ou la moyenne des deux éléments du milieu). 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.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X