集合操作,需要帮​​助从报表生成器

时间:2017-06-21 21:36:50

标签: c# linq optimization

我正在创建一个报表生成工具,该工具使用来自我们系统的不同来源的自定义数据类型。用户可以创建报告模式,并根据询问的内容,根据不同的索引键,时间,时间范围等关联数据。项目不在关系数据库中进行查询,它是来自RAM的集合中的纯C#代码。 / p>

我遇到了一个巨大的性能问题,我几天后就在查看我的代码,并且正在努力优化它。

我将代码最小化了一个简短的例子,以便将探查器指向有问题的算法,但实际版本在更多条件和使用日期时会更复杂。

简而言之,此函数返回满足条件的“值”子集,具体取决于从“索引行”中选择的值的键。

private List<LoadedDataSource> GetAssociatedValues(IReadOnlyCollection<List<LoadedDataSource>> indexRows, List<LoadedDataSource> values)
{
    var checkContainers = ((ValueColumn.LinkKeys & ReportLinkKeys.ContainerId) > 0 &&
                           values.Any(t => t.ContainerId.HasValue));

    var checkEnterpriseId = ((ValueColumn.LinkKeys & ReportLinkKeys.EnterpriseId) > 0 &&
                             values.Any(t => t.EnterpriseId.HasValue));

    var ret = new List<LoadedDataSource>();
    foreach (var value in values)
    {
        var valid = true;

        foreach (var index in indexRows)
        {
            // ContainerId
            var indexConservedSource = index.AsEnumerable();
            if (checkContainers && index.CheckContainer && value.ContainerId.HasValue)
            {
                indexConservedSource = indexConservedSource.Where(t => t.ContainerId.HasValue && t.ContainerId.Value == value.ContainerId.Value);
                if (!indexConservedSource.Any())
                {
                    valid = false;
                    break;
                }
            }

            //EnterpriseId
            if (checkEnterpriseId && index.CheckEnterpriseId && value.EnterpriseId.HasValue)
            {
                indexConservedSource = indexConservedSource.Where(t => t.EnterpriseId.HasValue && t.EnterpriseId.Value == value.EnterpriseId.Value);
                if (!indexConservedSource.Any())
                {
                    valid = false;
                    break;
                }
            }
        }

        if (valid)
            ret.Add(value);
    }

    return ret;
}

这适用于小样本,但只要我有数千个值,2-3个索引行也有几十个值,生成可能需要数小时。

正如您所看到的,我会在索引条件失败后立即尝试中断并传递给下一个值。

我可以在一个单独的“values.Where(####)。ToList()”中做所有事情,但这种情况变得很复杂。

我尝试在indexConservedSource周围生成IQueryable,但情况更糟。我尝试使用带有ConcurrentBag的Parallel.ForEach进行“ret”,而且速度也慢了。

还能做些什么?

1 个答案:

答案 0 :(得分:1)

原则上,你正在做的是计算两个序列的交集。你使用两个嵌套循环,因为时间是O(m * n),所以它很慢。您还有两个选择:

  1. 对两个序列进行排序并合并它们
  2. 将一个序列转换为哈希表并对其进行第二次测试
  3. 对于这种情况,第二种方法似乎更好。只需将这些索引列表转换为HashSet并对其进行测试。我添加了一些灵感代码:

    private List<LoadedDataSource> GetAssociatedValues(IReadOnlyCollection<List<LoadedDataSource>> indexRows, List<LoadedDataSource> values)
    {
        var ret = values;
    
        if ((ValueColumn.LinkKeys & ReportLinkKeys.ContainerId) > 0 &&
            ret.Any(t => t.ContainerId.HasValue))
        {
            var indexes = indexRows
                .Where(i => i.CheckContainer)
                .Select(i => new HashSet<int>(i
                    .Where(h => h.ContainerId.HasValue)
                    .Select(h => h.ContainerId.Value)))
                .ToList();
    
            ret = ret.Where(v => v.ContainerId == null 
                            || indexes.All(i => i.Contains(v.ContainerId)))
                     .ToList();
        }
    
        if ((ValueColumn.LinkKeys & ReportLinkKeys.EnterpriseId) > 0 &&
            ret.Any(t => t.EnterpriseId.HasValue))
        {
            var indexes = indexRows
                .Where(i => i.CheckEnterpriseId)
                .Select(i => new HashSet<int>(i
                    .Where(h => h.EnterpriseId.HasValue)
                    .Select(h => h.EnterpriseId.Value)))
                .ToList();
    
            ret = ret.Where(v => v.EnterpriseId == null 
                            || indexes.All(i => i.Contains(v.EnterpriseId)))
                     .ToList();
        }
    
        return ret;
    }