我在内存中收集了以下简化类的一个大集合:搜索的方式将是使用linq来处理类似以下对象的对象:

public class Product 
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public int CategoryId { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
}


当集合太大时,这需要进行一些处理,在我的情况下,每秒被称为数百次我想到的是引入一个新集合,该集合支持对不同属性进行索引:

var userProducts = products.Where(x => x.UserName == "SomeValue")


您可以像这个:

public class FastCollection<T> : IEnumerable<T>
{
    private IList<T> _items;
    private IList<Expression<Func<T, object>>> _lookups;
    private Dictionary<string, ILookup<object, T>> _indexes;

    public FastCollection(IList<T> data)
    {
        _items = data;
        _lookups = new List<Expression<Func<T, object>>>();
        _indexes = new Dictionary<string, ILookup<object, T>>();
    }

    public void AddIndex(Expression<Func<T, object>> property)
    {
        _lookups.Add(property);
        _indexes.Add(property.ToString(), _items.ToLookup(property.Compile()));
    }

    public void Add(T item)
    {
        _items.Add(item);
        RebuildIndexes();
    }

    public void Remove(T item)
    {
        _items.Remove(item);
        RebuildIndexes();
    }

    public void RebuildIndexes()
    {
        if (_lookups.Count > 0)
        {
            _indexes = new Dictionary<string, ILookup<object, T>>();
            foreach (var lookup in _lookups)
            {
                _indexes.Add(lookup.ToString(), _items.ToLookup(lookup.Compile()));
            }
        }
    }

    public IEnumerable<T> FindValue<TProperty>(Expression<Func<T, TProperty>> property, TProperty value)
    {
        var key = property.ToString();
        if(_indexes.ContainsKey(key))
        {
            return _indexes[key][value];
        }
        else
        {
            var c = property.Compile();
            return _items.Where(x => c(x).Equals(value));
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _items.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}


最后,您可以像这样搜索集合:在性能上有很大的不同。

我的问题是我做得对吗?我已经使用了委托和表达式使它尽可能通用,但是我感觉还有改进的空间!

评论

我不是很确定,但不是。在哪里可以使用某种哈希表来加快比较速度?可能是散列导致大量集合出现问题。

@ppumkin不,Where()不进行任何哈希处理。这样做是没有意义的,因为它必须为每个查询创建一个单独的哈希表。

#1 楼

使用ToString()在相等情况下比较表达式是否相等可能很简单,但是:


它要求您始终使用相同的参数名称,例如,它将x => x.Idproduct => product.Id视为不同
具有不同含义的表达式可以产生相同的字符串,例如(int i) => (float)i(int i) => (double)i都产生i => Convert(i)。因此,确保使用的表达式仅包含属性访问权限而不包含其他内容可能很有意义。

相反,您应该正确比较Expression。对我而言,每次更改后重建所有索引似乎很浪费。如果您经常更改集合,请考虑仅更改每个索引的相关部分。


在构造函数中设置但从未修改的字段应该是readonly


IList<T> data


如果您使用的是.Net 4.5,则可以在此处使用IReadOnlyList<T>


if (_lookups.Count > 0)


>这张支票几乎没有用。这样可以避免不必要地创建空字典,但是这样做非常便宜,因此我认为此处应优先考虑较短的代码。


您可以将整个RebuildIndexes()方法替换为一个ToDictionary()

_indexes = _lookups.ToDictionary(
    lookup => lookup.ToString(), lookup => _items.ToLookup(lookup.Compile()));



c(x).Equals(value)


c(x)返回null时,这将无法正常工作。您可能应该改用object.Equals(c(x), value)

#2 楼

当我使用Where针对列表测试您的收藏集时。 Where out在很大程度上执行收集。例如,有1,000个查询,每个查询从具有1,000,000个随机元素的集合中返回1,000条记录所需的时间少于1毫秒,而FindValue则需要15毫秒。迭代FastCollection可以更好地进行单个搜索。

对此进行了更多思考。看起来如此之快的原因之一是,您隐藏了多余的时间和资源,无法为要搜索的每个属性建立一个查找表。 DataTable而不是列表,并使用Select方法。当您考虑建立查询表的额外时间时,这可以非常快速地进行搜索,且时间可与您的班级相媲美。但是现在它已经准备好可以在DataGridView中显示或导出到xml文件的格式。一个警告是,数据作为DataRow的集合返回,这可能会麻烦一些,具体取决于您打算使用什么数据。

评论


\ $ \ begingroup \ $
这很奇怪,我的计时表明与您相同,迭代FindValue需要15毫秒,但是在我的计算机上,迭代Where()需要10毫秒。它仍然更快,但是差别并不大。
\ $ \ endgroup \ $
– svick
2014年2月4日在10:34

\ $ \ begingroup \ $
@svick我添加了测试代码,以防测试不完整。
\ $ \ endgroup \ $
–tinstaafl
2014年2月4日15:32

\ $ \ begingroup \ $
var temp = products.Where(x => x.UserName == searchindexes [i]);那就是您的问题所在,您可以调用Where(),但是绝不会迭代结果。但是由于Where()是惰性的,因此几乎没有任何作用,因此您的比较是不公平的。相反,您应该将对Where()的结果迭代与FindValue()的结果进行比较。
\ $ \ endgroup \ $
– svick
2014年2月4日在16:42



\ $ \ begingroup \ $
@svick是正确的。在不迭代结果之前,不会执行查询。如果将ToList()附加到Where和FindValue语句的末尾,它将执行查询,结果将是准确的。我确实经过了测试,FindValue的速度非常快。
\ $ \ endgroup \ $
– Atashbahar
2014年2月4日在22:41

\ $ \ begingroup \ $
@Atashbahar添加了更多想法供您考虑。
\ $ \ endgroup \ $
–tinstaafl
2014-2-5 18:54



#3 楼

FastCollection包含一个附加列表和一个列表字典,因此它的内存消耗开销可能会成为问题。

我建议您考虑使用一种数据结构来表示数据结构(产品)比列表更适合搜索。如果您使用树结构或平衡树结构,则会发现搜索成本为O(log n),其中n是树中元素的数量。

您可以轻松地用一个自定义的比较功能,从而具有非常快速的通用可搜索结构。

评论


\ $ \ begingroup \ $
这如何帮助OP“基于不同属性搜索产品”?数据必须位于可以由某个主键查询O(1)的集合中,以便每个属性都可以由提供该主键的其他集合表示。在性能方面,O(log n)查找的方向错误。
\ $ \ endgroup \ $
–ToolmakerSteve
18年11月23日在12:12