【问题标题】:Optimisation time process Linq request优化时间处理 Linq 请求
【发布时间】:2020-07-30 14:24:49
【问题描述】:

我在下面有这个请求,我想知道是否有办法优化它的时间处理?

var relevantQuantities2 = AllOrders
                                .GroupBy(item => new {item.CreatedTime, item.Quantity})
                                .Where(group => group.Any(item => item.Tag == "Name1") &&
                                                group.Any(item => item.Tag == "Name2"))
                                .OrderByDescending(item => item.Key.CreatedTime).Take(1)
                                .Select(g => g.Key.Quantity).FirstOrDefault();

【问题讨论】:

  • group.Any(item => item.Tag == "Name1") 实际上应该是一个 OR 条件......你不需要 FirstOrDefault() 因为你只用 Take(1) 记录了一条记录
  • @Rahul "你不需要 FirstOrDefault(),因为你用 Take(1) 只记录了 1 条记录" 虽然 IEnumerable 只会迭代 1 项,该项目仍需要提取。
  • 尝试在 GroupBy 之前跟随 where 可能会加快时间:AllOrders.Where(item => (item.Tag == "Name1") || (item.Tag == "Name2"))
  • @Rahul, Johnathan Barclay, jdweng 我还有一个可以更改标签的类,所以我必须使用 AND 而不是 OR。我试试你的提议 jdweng :)
  • var relatedQuantities1 = AllOrders .Where(item => (item.Tag == "Name1") && (item.Tag == "Name2")) .GroupBy(item => new {item. CreatedTime, item.Quantity}) .OrderByDescending(item => item.Key.CreatedTime).Take(1) .Select(g => g.Key.Quantity).FirstOrDefault();

标签: c# linq optimization time


【解决方案1】:

嗯,这也取决于您的数据是什么样的。 在一组值上表现良好的东西,在另一组值下可能表现不佳。

如果您想提高算法的性能:

隔离您验证的数据集,以便您准确知道应该获取哪些数据,然后测量返回结果所需的时间。

让相同的算法在不同的数据集上运行,或者让数据集尽可能地模仿您的生产数据值。 (相同是最好的,但生产数据通常不是静态的,值得尝试预测一年或 5 年或 10 年的数据值)

并且使用当前算法的各种版本来验证实际结果数据以及您的版本花费在计算所述响应上的时间。

现在比较你的个人集合的结果时间,有没有一个版本是最快的?然后利用这个时间(如果没有的话)更深入地了解您的数据以及为什么会这样。

【讨论】:

  • 你好 Mortens,我在 Quantconnect 工作,网络 IDLE Quant 平台。我无法控制 API 和 DataSet 的构建方式。
  • @Sinatr 好吧,你看,答案就在那里:“你如何优化你的算法”这些是分步说明。但在回答中,我也在问他应该问自己的问题。我可以把它放在 cmets 中,但没有足够的空间,从技术上讲,我确实回答了。您声称没有答案,但是我无法帮助您。
  • @guerinremi 创建一个“假”数据集。通过将您从 API 接收到的数据集复制到本地数据集,您可以根据命令重新创建。 (就像您在测试中反序列化和序列化的 JSON 对象)然后尝试在该数据集上运行不同版本的算法
  • @guerinremi 或者,设置一个测试,将垃圾邮件从生产系统中剔除:) 我建议您创建一个本地数据集进行测试。然后尝试改变算法,计算每个版本的算法处理它需要多长时间。尝试减少或增加数据集大小,看看这是否会影响您算法的各个版本。
  • @Mortens Bork,这样做是为了破解平台。数据不是免费的,投射或复制它不符合我的兴趣......但是如果你想尝试测试漏洞并将其告诉 quantco 团队,你可以,我想他们会感激的!
【解决方案2】:

你所谓的item实际上是Order

首先,您将具有相同{CreatedTime, Quantity} 组合的Orders 分组:

AllOrders.GroupBy(order => new {order.CreatedTime, order.Quantity})

结果是一系列组。每个组都有一个 Key {CreatedTime, Quantity};每个组都是具有此 Key 的 Orders 序列。

然后您过滤您的订单组。您只想保留那些至少有一个标签等于“Name1”的订单和至少一个标签等于“Name2”的订单的订单组

.Where(group => group.Any(order => order.Tag == "Name1")
             && group.Any(order => order.Tag == "Name2"))

所以你拿了小组的第一个订单。它有 Tag=="Name1" 吗?不,拿小组的第二名。它是否有 Tag=="Name1"。没有。等等,直到您在 20 号订单上找到标签

然后你从第一个订单重新开始。它有 Tag=="Name2" 吗?没有。等等。直到你在 15 号订单上找到标签。

问题是,在搜索“Name1”时,您已经发现第一个订单没有“Name2”。您还看到了具有 Name2 的 15th Order,但您正在寻找 Name1。

结果是你必须枚举你的序列两次。

您想要做的是记住您看到了 Name2,直到您也看到 Name1。

创建一个类似Any 的扩展方法,它检查两个谓词,并在找到与谓词1 匹配的元素以及与谓词2 匹配的元素时立即返回true:

public static bool Any<Tsource>(this IEnumerable<Tsource> source,
       Func<Tsource, bool> predicate1,
       Func<Tsource, bool> predicate2)
{
    // TODO: exception if source or predicates null
    bool predicate1Met = false;
    bool pedicate2Met = false;

    using (var enumerator = source.GetEnumerator())
    {
        // enumerate every element, and check for every element if the predicates are met
        // don't check on predicates that are already met
        // stop as soon as both predicates are met.
        while (enumerator.MoveNext() && !predicate1Met && !predicate2Met)
        {
            // There is a next element in the source. predicates not met yet
            // check the predicates on this element
            // but only if the predicates are not met yet
            predicate1met = predicate1met || predicate1(enumerator.Current);
            predicate2met = predicate2met || predicate2(enumerator.Current);
        }
    }

    // if here, all elements enumerated, or both predicates met
    return predicate1met && predicate2met;
}

所以现在你可以优化你的Where

.Where(group => group.Any(item => item.Tag == "Name1",    // predicate 1
                          item => item.Tag == "Name2"))   // predicate 2

在您按 CreatedTime 的降序排列所有组的位置之后。然后你扔掉大部分这些有序组:

.OrderByDescending(item => item.Key.CreatedTime)
.Take(1)
.Select(g => g.Key.Quantity)
.FirstOrDefault();

只找到 CreatedTime 最大的组不是更好吗?这样您就不必多次枚举该组。您需要某种Max or default 方法,您可以在其中决定要最大化哪个键。

再次:让我们创建一个类似于 LINQ 的扩展方法。就像我们在 LINQ 中看到的那样,我们创建了带有和不带有比较器的重载。

public static TSource MaxOrDefault<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TSource, TResult> resultSelector)
{
    // call the Max method with a null comparer
    return MaxOrDefault(source, keySelector, resultSelector, null);
}

比较器的重载:

public static TSource MaxOrDefault<TSource, TKey>(
    this IEnumerable<TSource> source,
    this Func<TSource, TKey> keySelector,
    Func<TSource, TResult> resultSelector,
    IComparer<TKey> comparer)
{
    // TODO: exception if input null, allow null comparer
    if (comparer == null) comparer = Comparer<TKey>.Default();

    // enumerate the sequence
    var enumerator = source.GetEnumerator();
    if (enumerator.MoveNext())
    {
        // the source has at least one element. For now this is the max:
        TSource max = enumerator.Current;
        TKey maxKey = keySelector(max);

        // are there larger elements in source? let's enumerate the rest
        while (enumerator.MoveNext())
        {
            TKey currentKey = keySelector(enumerator.Current);
            if (comparer.Compare(currentKey, maxKey) > 0)
            {
                // we found a larger element:
                max = enumerator.Current;
                maxKey = currentKey;
            }
        }

        // all elements are enumerated. Create the result
        TResult result = resultSelector(max);
        return result;
    }
    else
    {
        // the source is empty; return default:
        return default(TResult);
    }
}

这将替换 `OrderBy(...).Take().Select(...).FirstOrDefault 为:

.MaxOrDefault(group => group.Key.CreatedTime,   // keySelector: get the max value
              group => group.Key.Quantity)      // resultSelector: when found use this

所以你的完整 LINQ 将是:

var result = AllOrders
    .GroupBy(order => new {order.CreatedTime, order.Quantity})
    .Where(group => group.Any(item => item.Tag == "Name1",
                              item => item.Tag == "Name2"))
    .MaxOrDefault(group => group.Key.CreatedTime,
                  group => group.Key.Quantity);

那么你将枚举你的序列多少次:

  • 一次组团
  • 每组最多一次,直到找到标签
  • 剩余组:只有组的键,而不是组中的命令:一次

因此,如果您的 1000 个订单将分为 10 个组,您需要检查每个组大约一半才能找到标签。可能会枚举一个组中没有标签的所有订单,但话又说回来:你不会再使用它们了。

最后,从剩余的组中,您只枚举键一次。

好消息是,Any with two predicatesMaxOrDefault with predicate 是相当有用的方法,您可能会在其他情况下使用它们。特别是 MaxOrDefault 是我在类似于 FirstOrDefault 的情况下经常使用的。标准 Max 不适用于空序列。

进一步优化

我认为您可以通过将所有内容放在一个扩展方法中来进一步优化它。但是,此方法不可重复使用。但是你可以在只枚举你的序列一次的同时做到这一点。

public int ToMaxNewestOrder(this IEnumerable<Order> orders)
{
    // create a Dictionary with key `{CreatedTime, Quantity}`.
    // Values are the booleans: name1Found, name2Found
    var dictionary = new Dictionary<Tuple<DateTime, int>, Tuple<bool, bool>();

    foreach (var order in orders)
    {
        bool tag1Mach = order.Tag == "Name1");
        bool tag2Mach = order.Tag == "Name2");
        var orderKey = Tuple.Create(order.CreatedTime, order.Quantity);

        if (dictionary.TryGetValue(orderKey, out Tuple<bool, bool, int> value)
        {
            // the key is in the dictionary. Update the tags
            value.Item1 = value.Item1 || tag1Match);
            value.Item2 = value.Item1 || tag2Match);
        }
        else
        {
            // this key not in dictionary yet
            dictionary.Add(orderKey, Tuple.Create(tag1Match, tag2Match));
        }
    }

    // all order processed. We only need to enumerate the KeyValues from the dictionary
    // to keep the ones with boot tags matching
    // remember the maximum value for quantity

    int maxQuantity = 0;
    foreach(var keyValuePair in dictionary)
    {
        if (keyValuePair.Value.Item1     // any of the Tags matches name1 
        &&  keyValuePair.Value.Item2     // any of the Tags matches name2
        &&  keyValuePair.Key.Quantity > maxQuantity)
        {
            maxQuantity = quantity;
        }
    }
    return maxQuantity;
}

现在您确定您只列举了两次序列:一次是所有订单,一次是所有组。如果每个订单都有不同的 CreatedTime 和 Quantity,那么每个订单都将在自己的组中。最大值是您将 Orders 枚举两次。

【讨论】:

  • 你好,Harald,我会花几个小时在我的脑海中打印出你感激地发布的内容,非常感谢!!!
  • 您好 Harald,您知道有一个网站解释了如何在请求 linq 中逐步处理信息吗?或者编程更全球化?我没有成功将您的代码集成到我的算法中,我认为我需要更深入地理解逻辑。
  • 如何手动枚举示例:GetEnumerator / MoveNext / Current:请参阅this page 上使用 GetEnumerator() 的 c# 程序。要创建具有收益率返回的可枚举,请参阅this example。大多数 LINQ 方法使用 yield return 或 GetEnumerator / MoveNext 进行迭代。见source code Enumerable 另见All standard LING operators
  • 要创建自己的 LINQ 方法,请使用扩展方法。见extension methods demystified
猜你喜欢
  • 1970-01-01
  • 2020-10-17
  • 2021-12-09
  • 2011-01-24
  • 1970-01-01
  • 1970-01-01
  • 2018-08-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多