7 votes

Filtrage de deux tableaux pour éviter les valeurs Inf/NaN

J'ai deux tableaux de doubles de la même taille, contenant les valeurs X et Y de certains graphiques.

Je dois créer une sorte de protection contre les valeurs INF/NaN. Je dois trouver toutes les paires de valeurs (X, Y) pour lesquelles X et Y ne sont ni INF ni NaN.

Si j'ai un tableau, je peux le faire en utilisant des lambdas :

var filteredValues = someValues.Where(d=> !(double.IsNaN(d) || double.IsInfinity(d))).ToList();

Maintenant, pour deux tableaux, j'utilise la boucle suivante :

List<double> filteredX=new List<double>();
List<double> filteredY=new List<double>();

for(int i=0;i<XValues.Count;i++)
{
   if(!double.IsNan(XValues[i]) &&
         !double.IsInfinity(XValues[i]) &&
         !double.IsNan(YValues[i]) &&
         !double.IsInfinity(YValues[i]) )
     {
       filteredX.Add(XValues[i]);
       filteredY.Add(YValues[i]);
     }
}

Existe-t-il un moyen de filtrer deux tableaux en même temps en utilisant LINQ/lambdas, comme cela a été fait pour le tableau unique ?

Malheureusement, je ne peux utiliser que .NET 3.5.

7voto

Jon Skeet Points 692016

Légère correction pour la réponse originale de Mark :

var filteredValues = XValues.Zip(YValues, (x,y) => new { x, y })
        .Where(p => !(double.IsNan(p.x) || double.IsNan(p.y) || 
                      double.IsInfinity(p.x) || double.IsInfinity(p.y)))
        .ToList();

Sinon, vous pourriez vouloir le rendre un peu plus soigné :

Func<double, bool> valid = z => !double.IsNan(z) && !double.IsInfinity(z);
var filteredValues = XValues.Zip(YValues, (x,y) => new { x, y })
        .Where(p => valid(p.x) && valid(p.y))
        .ToList();

Si vous avez ensuite besoin des résultats en deux listes, vous pouvez le faire :

var filteredX = filteredValues.Select(p => p.x).ToList();
var filteredY = filteredValues.Select(p => p.y).ToList();

2voto

Mark Rushakoff Points 97350

C# 4.0 introduit le Enumerable.Zip méthode d'extension pour accomplir l'itération sur les énumérables "en parallèle" comme vous le décrivez.

Je ne l'ai pas utilisé moi-même, mais ça devrait être quelque chose comme :

var filteredValues = 
   XValues.Zip(YValues, (x,y) => new { X = x, Y = y})
        .Where( o =>
          !(double.IsNan(o.X) || double.IsNan(o.Y) || double.IsInfinity(o.X) || double.IsInfinity(o.Y)))
            .ToList();

(Désolé pour l'indentation bizarre, je voulais que ce soit plus lisible sur SO)

2voto

Dan Tao Points 60518

OK, donc vous ne pouvez pas utiliser .NET 4.0 et donc ne pouvez pas utiliser l'extension Zip.

Ou peut vous ?

public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
    this IEnumerable<TFirst> first,
    IEnumerable<TSecond> second,
    Func<TFirst, TSecond, TResult> resultSelector)
{
    using (var eFirst = first.GetEnumerator())
    using (var eSecond = second.GetEnumerator())
    {
        while (eFirst.MoveNext() && eSecond.MoveNext())
            yield return resultSelector(eFirst.Current, eSecond.Current);
    }
}

Voyez par vous-même :)

static void Main(string[] args)
{
    var x = new double[] { 0.0, 1.0, 2.0, double.NaN, 4.0, 5.0 };
    var y = new double[] { 0.5, 1.5, double.PositiveInfinity, 3.5, 4.5, 5.5 };

    // note: using KeyValuePair<double, double> --
    // you could just as easily use your own custom type
    // (probably a simple struct)
    var zipped = x.Zip(y, (a, b) => new KeyValuePair<double, double>(a, b))
        .Where(kvp => IsValid(kvp.Key) && IsValid(kvp.Value))
        .ToList();

    foreach (var z in zipped)
        Console.WriteLine("X: {0}, Y: {1}", z.Key, z.Value);
}

static bool IsValid(double value)
{
    return !double.IsNaN(value) && !double.IsInfinity(value);
}

Sortie :

X: 0, Y: 0.5
X: 1, Y: 1.5
X: 4, Y: 4.5
X: 5, Y: 5.5

0voto

Rubys Points 1527

Vous pouvez essayer de le faire :

doubleArray1.Zip(doubleArray2, (x, y) => Tuple.Create(x, y))
            .Where(tup => !double.IsNaN(tup.Item1) &&
            !double.IsNaN(tup.Item2) &&
            !double.IsInfinity(tup.Item1) &&
            !double.IsInfinity(tup.Item1));

Vous pourriez également créer une méthode permettant de filtrer et de zipper en même temps, l'avantage majeur étant que vous n'êtes pas limité à C# 4 :

public static IEnumerable<Tuple<TOne, TTwo>> DualWhere<TOne, TTwo>(this IEnumerable<TOne> one, IEnumerable<TTwo> two, Func<TOne, TTwo, bool> predicate)
{
    var oneEnumerator = one.GetEnumerator();
    var twoEnumerator = two.GetEnumerator();
    while (oneEnumerator.MoveNext() && twoEnumerator.MoveNext())
    {
        if (predicate(oneEnumerator.Current, twoEnumerator.Current))
            yield return Tuple.Create(oneEnumerator.Current, twoEnumerator.Current);
    }
    oneEnumerator.Dispose();
    twoEnumerator.Dispose();
}

Edit : Ce dernier devrait fonctionner avec C# 3.5.

0voto

Anthony Pegram Points 58528

Voici une solution qui fonctionnera en C# 3 et .NET 3.5

List<double> list1 = new List<double>() { 1.2, 3.8, double.NaN, 17.8 };
List<double> list2 = new List<double>() { 9.4, double.PositiveInfinity, 10.4, 26.2 };

var query = from x in list1.Select((item, idx) => new { item, idx })
            where !double.IsNaN(x.item) && !double.IsInfinity(x.item)
            join y in list2.Select((item, idx) => new { item, idx })
            on x.idx equals y.idx
            where !double.IsNaN(y.item) && !double.IsInfinity(y.item)
            select new { X = x.item, Y = y.item };

L'itération sur la requête produirait des paires contenant 1,2 & 9,4 et 17,8 & 26,2. Les deux paires du milieu sont éliminées car l'une contient NaN et l'autre l'infini.

foreach (var pair in query)
{
    Console.WriteLine("{0}\t{1}", pair.X, pair.Y);
}

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