2 votes

Requête LINQ sur les séries chronologiques

Je dispose d'un référentiel central pour les journaux des appareils IoT. Ainsi, lorsque les journaux arrivent, ils sont horodatés. Le problème que je souhaite résoudre est le suivant : au cours d'une période donnée, le même appareil peut envoyer plusieurs journaux concernant son interaction avec un catalyseur spécifique. Je veux considérer cet ensemble de journaux comme un seul événement et non comme 5 journaux disparates. Je veux compter le nombre d'interactions et non le nombre de journaux.

Ensemble de données

public class Data
{
    public Guid DeviceId {get; set;}
    public DateTime StartTime { get; set; }
    public DateTime EndDateTime { get; set; }
    public int Id { get; set; }
    public int Direction { get; set;}
}

Data d1 = new Data();// imagine it's populated
Data d2 = new Data();// imagine it's populated

Je cherche une requête LINQ qui produirait quelque chose du genre

If ((d1.DeviceId == d2.DeviceId )  && (d1.Id == d2.Id) && (d1.Direction == d2.Direction) && (d1.StartTime - d2.StartTime < 15 minutes ))  

Si je sais que le même appareil IoT interagit avec le même identifiant (catalyseur) et que la direction est la même, et que tous ces enregistrements se produisent dans un laps de temps de 15 minutes, on peut supposer qu'ils correspondent au même événement catalyseur.

Je ne contrôle pas la création du journal, donc... non, je ne peux pas mettre à jour les données pour y inclure "quelque chose" qui indiquerait la relation.

Données par demande... rien d'extraordinaire. Je suis sûr que la plupart des gens pensent que j'ai plus de 30 propriétés et que je ne fournis que celle qui est affectée par le calcul, mais il s'agit d'un simple ensemble de possibilités.

class SampleData
{
    public List<Data> GetSampleData()
    {
        Guid device1 = Guid.NewGuid();

        List<Data> dataList = new List<Data>();

        Data  data1 = new Data();
        data1.DeviceId = device1;
        data1.Id = 555;
        data1.Direction = 1; 
        data1.StartTime = new DateTime(2010, 8, 18, 16, 32, 0);
        data1.EndDateTime = new DateTime(2010, 8, 18, 16, 32, 30);
        dataList.Add(data1);

        //so this data point should be excluded in the final result
        Data data2 = new Data();
        data1.DeviceId = device1;
        data1.Id = 555;
        data1.Direction = 1;
        data1.StartTime = new DateTime(2010, 8, 18, 16, 32, 32);
        data1.EndDateTime = new DateTime(2010, 8, 18, 16, 33, 30);
        dataList.Add(data2);

        //Should be included because ID is different
        Data data3 = new Data();
        data1.DeviceId = device1;
        data1.Id = 600;
        data1.Direction = 1;
        data1.StartTime = new DateTime(2010, 8, 18, 16, 32, 2);
        data1.EndDateTime = new DateTime(2010, 8, 18, 16, 32, 35);
        dataList.Add(data3);

        //exclude due to time
        Data data4 = new Data();
        data1.DeviceId = device1;
        data1.Id = 600;
        data1.Direction = 1;
        data1.StartTime = new DateTime(2010, 8, 18, 16, 32, 37);
        data1.EndDateTime = new DateTime(2010, 8, 18, 16, 33, 40);
        dataList.Add(data4);

        //include because time > 15 minutes 
        Data data5 = new Data();
        data1.DeviceId = device1;
        data1.Id = 600;
        data1.Direction = 1;
        data1.StartTime = new DateTime(2010, 8, 18, 16, 58, 42);
        data1.EndDateTime = new DateTime(2010, 8, 18, 16, 58, 50);
        dataList.Add(data5);

        return dataList;
    }

2voto

NetMage Points 163

Cela s'est avéré plus complexe que je ne l'espérais.

J'ai utilisé une méthode d'extension LINQ personnalisée que j'ai appelée ScanPair qui est une variante de mon Scan qui est une version de l'opérateur de balayage APL (qui ressemble à Aggregate mais renvoie les résultats intermédiaires). ScanPair renvoie les résultats intermédiaires de l'opération avec chaque valeur originale. Je pense que je dois réfléchir à la manière de rendre toutes ces méthodes plus générales, car le modèle est utilisé par un tas d'autres méthodes d'extension que j'ai pour regrouper par diverses conditions (par exemple, séquentiel, exécuté, alors que le test est vrai ou faux).

public static class IEnumerableExt {
    public static IEnumerable<(TKey Key, T Value)> ScanPair<T, TKey>(this IEnumerable<T> src, Func<T, TKey> seedFn, Func<(TKey Key, T Value), T, TKey> combineFn) {
        using (var srce = src.GetEnumerator()) {
            if (srce.MoveNext()) {
                var seed = (seedFn(srce.Current), srce.Current);

                while (srce.MoveNext()) {
                    yield return seed;
                    seed = (combineFn(seed, srce.Current), srce.Current);
                }
                yield return seed;
            }
        }
    }
}

Vous pouvez maintenant utiliser un tuple comme résultat intermédiaire pour suivre l'horodatage initial et le numéro de groupe, et passer au résultat suivant (horodatage, numéro de groupe) lorsque l'intervalle dépasse 15 minutes. Si vous commencez par regrouper les interactions, puis comptez les groupes de moins de 15 minutes par interaction, vous obtenez la réponse :

var ans = interactionLogs.GroupBy(il => new { il.DeviceId, il.Id, il.Direction })
            .Select(ilg => new {
                ilg.Key,
                Count = ilg.OrderBy(il => il.Timestamp)
                           .ScanPair(il => (firstTimestamp: il.Timestamp, groupNum: 1), (kvp, cur) => (cur.Timestamp - kvp.Key.firstTimestamp).TotalMinutes <= 15 ? kvp.Key : (cur.Timestamp, kvp.Key.groupNum + 1))
                           .GroupBy(ilkvp => ilkvp.Key.groupNum, ilkvp => ilkvp.Value)
                           .Count()
            });

Voici une partie d'un échantillon de résultats intermédiaires provenant de ScanPair - le résultat effectif est un ValueTuple avec deux champs, où le Key est le résultat intermédiaire (qui est le ValueTuple de firstTimestamp , groupNum ) et Value est l'élément source (log) correspondant. L'utilisation de la fonction "seeded version" place le premier élément source dans la fonction "seed" pour commencer le processus.

Key_firstTimestamp  Key_groupNum    Timestamp
7:58 PM                 1           7:58 PM
7:58 PM                 1           8:08 PM
7:58 PM                 1           8:12 PM
8:15 PM                 2           8:15 PM
8:15 PM                 2           8:20 PM

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