Используя LINQ для получения результатов другого набора LINQ

У меня есть оператор LINQ, который вытягивает вершину N рекордные идентификаторы от набора и затем другого запроса, который вытягивает все записи, которые имеют те идентификаторы. Это чувствует себя очень неуклюжим и неэффективным, и я задавался вопросом, мог ли быть более сжатый, LINQy способ получить те же результаты

var records = cache.Select(rec => rec.Id).Distinct().Take(n);

var results = cache.Where(rec => records.Contains(rec.Id));

К вашему сведению - будет несколько записей с тем же идентификатором, который является, почему существует Отличное () и почему я не могу использовать простое Взятие () во-первых.

Спасибо!

6
задан Josh 8 February 2010 в 23:44
поделиться

5 ответов

Как насчет чего-то подобного?

var results = cache.GroupBy(rec => rec.Id, rec => rec)
                   .Take(n)
                   .SelectMany(rec => rec);
4
ответ дан 17 December 2019 в 04:46
поделиться

То же самое, что и вы, но в одной строке и с помощью Join () вместо Contains ():

var results = cache
    .Select(rec => rec.Id)
    .Distinct()
    .Take(n)
    .ToList()
    .Join(cache, rec => rec, record => record.Id, (rec, record) => record);
1
ответ дан 17 December 2019 в 04:46
поделиться

Да, к сожалению, LINQ изначально не поддерживает, позволяя пользователю выбирать члена для получения отдельных записей. Поэтому я рекомендую создать для него свой собственный метод расширения:

/// <summary>
    /// Returns a list with the ability to specify key(s) to compare uniqueness on
    /// </summary>
    /// <typeparam name="T">Source type</typeparam>
    /// <param name="source">Source</param>
    /// <param name="keyPredicate">Predicate with key(s) to perform comparison on</param>
    /// <returns></returns>
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source,
                                             Func<T, object> keyPredicate)
    {
        return source.Distinct(new GenericComparer<T>(keyPredicate));
    }

А затем создать общий компаратор, который, как вы заметите, довольно общий.

   public class GenericComparer<T> : IEqualityComparer<T>
    {
        private Func<T, object> _uniqueCheckerMethod;

        public GenericComparer(Func<T, object> keyPredicate)
        {
            _uniqueCheckerMethod = keyPredicate;
        }

        #region IEqualityComparer<T> Members

        bool IEqualityComparer<T>.Equals(T x, T y)
        {
            return _uniqueCheckerMethod(x).Equals(_uniqueCheckerMethod(y));
        }

        int IEqualityComparer<T>.GetHashCode(T obj)
        {
            return _uniqueCheckerMethod(obj).GetHashCode();
        }

        #endregion
    }

Теперь просто объедините свой оператор LINQ в цепочку: var records = cache.Select (rec => rec.Id) .Distinct (). Take (n);

var results = cache.Distinct(rec => rec.Id).Take(n));

hth

{{1 }}
0
ответ дан 17 December 2019 в 04:46
поделиться

Единственный способ, который я могу придумать для этого в SQL, - это использовать подзапрос, так что, вероятно, также будет два запроса LINQ ...
Это "кажется" неэффективным ... не так ли? Возможно, вас беспокоит то, о чем не стоит беспокоиться. Вероятно, вы можете сделать это в одной строке, выполнив соединение, но будет ли это яснее / лучше / эффективнее - это другой вопрос.

Редактировать: Ответ Aaronaught о методе расширения можно заставить работать следующим образом:

    public static IEnumerable<T> TakeByDistinctKey<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keyFunc, int numKeys) {
    if(keyFunc == null) {
        throw new ArgumentNullException("keyFunc");
    }

    List<TKey> keys = new List<TKey>();
    foreach(T item in source) {
        TKey key = keyFunc(item);
        if(keys.Contains(key)) {
            // one if the first n keys, yield
            yield return item;
        } else if(keys.Count < numKeys) {
            // new key, but still one of the first n seen, yield
            keys.Add(key);
            yield return item;
        }
        // have enough distinct keys, just keep going to return all of the items with those keys
    }
}

Однако GroupBy / SelectMany выглядит лучше всего. Я бы пошел с этим.

0
ответ дан 17 December 2019 в 04:46
поделиться

Встроенного способа "Linqy" не существует (вы могли бы сгруппировать, но это было бы довольно неэффективно), но это не значит, что вы не можете сделать свой собственный способ:

public static IEnumerable<T> TakeDistinctByKey<T, TKey>(
    this IEnumerable<T> source,
    Func<T, TKey> keyFunc,
    int count)
{
    if (keyFunc == null)
        throw new ArgumentNullException("keyFunc");
    if (count <= 0)
        yield break;

    int currentCount = 0;
    TKey lastKey = default(TKey);
    bool isFirst = true;
    foreach (T item in source)
    {
        yield return item;
        TKey key = keyFunc(item);
        if (!isFirst && (key != lastKey))
            currentCount++;
        if (currentCount > count)
            yield break;
        isFirst = false;
        lastKey = key;
    }
}

Затем вы можете вызвать его следующим образом:

var items = cache.TakeDistinctByKey(rec => rec.Id, 20);

Если у вас есть составные ключи или что-то подобное, вы можете легко расширить метод выше, чтобы принимать IEqualityComparer в качестве аргумента.

Также обратите внимание, что это зависит от того, отсортированы ли элементы по ключу. Если это не так, вы можете либо изменить алгоритм выше, чтобы использовать HashSet вместо прямого подсчета и сравнения последнего элемента, либо вызвать его с помощью этого:

var items = cache.OrderBy(rec => rec.Id).TakeDistinctByKey(rec => rec.Id, 20);

Edit - Я также хотел бы отметить, что в SQL я бы использовал либо ROW_NUMBER запрос, либо рекурсивный CTE, в зависимости от требований к производительности - distinct+join не является наиболее эффективным методом. Если ваш кэш находится в отсортированном порядке (или если вы можете изменить его, чтобы он находился в отсортированном порядке), то приведенный выше метод будет самым дешевым как с точки зрения памяти, так и времени выполнения.

0
ответ дан 17 December 2019 в 04:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: