我可以缓存部分执行的LINQ查询吗?

时间:2012-04-20 22:57:03

标签: c# linq

我有以下代码:

IEnumerable<KeyValuePair<T, double>> items =
    sequence.Select(item => new KeyValuePair<T, double>(item, weight(item)));
if (items.Any(pair => pair.Value<0))
    throw new ArgumentException("Item weights cannot be less than zero.");

double sum = items.Sum(pair => pair.Value);
foreach (KeyValuePair<T, double> pair in items) {...}

weightFunc<T, double>

问题是我希望尽可能少地执行weight {{1}}。这意味着每件物品最多应执行一次。我可以通过将其保存到数组来实现这一点。但是,如果任何权重返回负值,我不想继续执行。

有没有办法在LINQ框架内轻松完成这项工作?

6 个答案:

答案 0 :(得分:15)

当然,这完全可行:

public static Func<A, double> ThrowIfNegative<A, double>(this Func<A, double> f)
{
    return a=>
    { 
      double r = f(a);  
      // if r is NaN then this will throw.
      if ( !(r >= 0.0) )
        throw new Exception(); 
      return r;
    };
}

public static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
    var d = new Dictionary<A, R>();
    return a=>
    {
        R r;
        if (!d.TryGetValue(a, out r))
        {
          r = f(a);
          d.Add(a, r);
        }
        return r;
    };
}

现在......

Func<T, double> weight = whatever;
weight = weight.ThrowIfNegative().Memoize();

你已经完成了。

答案 1 :(得分:2)

一种方法是将异常移到weight函数中,或至少通过执行以下操作来模拟这样做:

Func<T, double> weightWithCheck = i =>
    {
        double result = weight(i);
        if (result < 0)
        {
            throw new ArgumentException("Item weights cannot be less than zero.");
        }
        return result;
    };

IEnumerable<KeyValuePair<T, double>> items =
    sequence.Select(item => new KeyValuePair<T, double>(item, weightWithCheck(item)));

double sum = items.Sum(pair => pair.Value);

到目前为止,如果有例外情况,你应该拥有它。但是,在确定获得异常之前,您必须枚举items,但是一旦获得异常,就不会再次致电weight

答案 2 :(得分:0)

两个答案都很好(在哪里抛出异常,并记住该函数)。

但是你的真正问题是每次使用它时都会评估你的LINQ表达式,除非你强制它评估并存储为List(或类似的)。只需改变一下:

sequence.Select(item => new KeyValuePair<T, double>(item, weight(item)));

对此:

sequence.Select(item => new KeyValuePair<T, double>(item, weight(item))).ToList();

答案 3 :(得分:0)

你可以用foreach循环来做。以下是一种方法:

IEnumerable<KeyValuePair<T, double>> items = sequence
    .Select(item => new KeyValuePair<T, double>(item, weight(item)))
    .Select(kvp =>
    {
        if (kvp.Value < 0)
            throw new ArgumentException("Item weights cannot be less than zero.");
        else
            return kvp;
    }
    );

答案 4 :(得分:0)

不,LINQ框架中没有任何 IN 可以执行此操作,但您可以确定地编写自己的方法并从linq查询中调用它们(正如许多人已经展示的那样)。

就个人而言,我会ToList第一个查询或使用Eric的建议。

答案 5 :(得分:0)

除了其他答案建议的功能性备忘,您还可以对整个数据序列采用备忘:

var items =
    sequence.Select(item => new KeyValuePair<T, double>(item, weight(item))).Memoize();

(请注意在上面表达式的末尾调用Memoize()方法)

数据记忆的一个不错的特性是它代表了ToList()ToArray()方法的直接替代。

尽管功能齐全,但其中涉及很多:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

static class MemoizationExtensions
{
    /// <summary>
    /// Memoize all elements of a sequence, e.g. ensure that every element of a sequence is retrieved only once.
    /// </summary>
    /// <remarks>
    /// The resulting sequence is not thread safe.
    /// </remarks>
    /// <typeparam name="T">The type of the elements of source.</typeparam>
    /// <param name="source">The source sequence.</param>
    /// <returns>The sequence that fully replicates the source with all elements being memoized.</returns>
    public static IEnumerable<T> Memoize<T>(this IEnumerable<T> source) => Memoize(source, false);

    /// <summary>
    /// Memoize all elements of a sequence, e.g. ensure that every element of a sequence is retrieved only once.
    /// </summary>
    /// <typeparam name="T">The type of the elements of source.</typeparam>
    /// <param name="source">The source sequence.</param>
    /// <param name="isThreadSafe">Indicates whether resulting sequence is thread safe.</param>
    /// <returns>The sequence that fully replicates the source with all elements being memoized.</returns>
    public static IEnumerable<T> Memoize<T>(this IEnumerable<T> source, bool isThreadSafe)
    {
        switch (source)
        {
            case null:
                return null;

            case CachedEnumerable<T> existingCachedEnumerable:
                if (!isThreadSafe || existingCachedEnumerable is ThreadSafeCachedEnumerable<T>)
                {
                    // The source is already memoized with compatible parameters.
                    return existingCachedEnumerable;
                }
                break;

            case IList<T> _:
            case IReadOnlyList<T> _:
            case string _:
                // Given source types are intrinsically memoized by their nature.
                return source;
        }

        if (isThreadSafe)
            return new ThreadSafeCachedEnumerable<T>(source);
        else
            return new CachedEnumerable<T>(source);
    }

    class CachedEnumerable<T> : IEnumerable<T>, IReadOnlyList<T>
    {
        public CachedEnumerable(IEnumerable<T> source)
        {
            _Source = source;
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        IEnumerable<T> _Source;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        IEnumerator<T> _SourceEnumerator;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        protected readonly IList<T> Cache = new List<T>();

        public virtual int Count
        {
            get
            {
                while (_TryCacheElementNoLock()) ;
                return Cache.Count;
            }
        }

        bool _TryCacheElementNoLock()
        {
            if (_SourceEnumerator == null && _Source != null)
            {
                _SourceEnumerator = _Source.GetEnumerator();
                _Source = null;
            }

            if (_SourceEnumerator == null)
            {
                // Source enumerator already reached the end.
                return false;
            }
            else if (_SourceEnumerator.MoveNext())
            {
                Cache.Add(_SourceEnumerator.Current);
                return true;
            }
            else
            {
                // Source enumerator has reached the end, so it is no longer needed.
                _SourceEnumerator.Dispose();
                _SourceEnumerator = null;
                return false;
            }
        }

        public virtual T this[int index]
        {
            get
            {
                _EnsureItemIsCachedNoLock(index);
                return Cache[index];
            }
        }

        public IEnumerator<T> GetEnumerator() => new CachedEnumerator<T>(this);

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

        internal virtual bool EnsureItemIsCached(int index) => _EnsureItemIsCachedNoLock(index);

        bool _EnsureItemIsCachedNoLock(int index)
        {
            while (Cache.Count <= index)
            {
                if (!_TryCacheElementNoLock())
                    return false;
            }
            return true;
        }

        internal virtual T GetCacheItem(int index) => Cache[index];
    }

    sealed class ThreadSafeCachedEnumerable<T> : CachedEnumerable<T>
    {
        public ThreadSafeCachedEnumerable(IEnumerable<T> source) :
            base(source)
        {
        }

        public override int Count
        {
            get
            {
                lock (Cache)
                    return base.Count;
            }
        }

        public override T this[int index]
        {
            get
            {
                lock (Cache)
                    return base[index];
            }
        }

        internal override bool EnsureItemIsCached(int index)
        {
            lock (Cache)
                return base.EnsureItemIsCached(index);
        }

        internal override T GetCacheItem(int index)
        {
            lock (Cache)
                return base.GetCacheItem(index);
        }
    }

    sealed class CachedEnumerator<T> : IEnumerator<T>
    {
        CachedEnumerable<T> _CachedEnumerable;

        const int InitialIndex = -1;
        const int EofIndex = -2;

        int _Index = InitialIndex;

        public CachedEnumerator(CachedEnumerable<T> cachedEnumerable)
        {
            _CachedEnumerable = cachedEnumerable;
        }

        public T Current
        {
            get
            {
                var cachedEnumerable = _CachedEnumerable;
                if (cachedEnumerable == null)
                    throw new InvalidOperationException();

                var index = _Index;
                if (index < 0)
                    throw new InvalidOperationException();

                return cachedEnumerable.GetCacheItem(index);
            }
        }

        object IEnumerator.Current => Current;

        public void Dispose()
        {
            _CachedEnumerable = null;
        }

        public bool MoveNext()
        {
            var cachedEnumerable = _CachedEnumerable;
            if (cachedEnumerable == null)
            {
                // Disposed.
                return false;
            }

            if (_Index == EofIndex)
                return false;

            _Index++;
            if (!cachedEnumerable.EnsureItemIsCached(_Index))
            {
                _Index = EofIndex;
                return false;
            }
            else
            {
                return true;
            }
        }

        public void Reset()
        {
            _Index = InitialIndex;
        }
    }
}

更多信息和现成的NuGet软件包:https://github.com/gapotchenko/Gapotchenko.FX/tree/master/Source/Gapotchenko.FX.Linq#memoize