Linq - Главное значение от каждой группы

Вот обертка для неуниверсального типа Systems.Collections.Specialized.OrderedDictionary.

Этот тип будет возвращать последовательности ключей / значений / пар в порядке вставки , во многом как хэши Ruby 2.0.

Он не требует магии C # 6, соответствует IDictionary<TKey,TValue> (что также означает, что доступ к неназначенному ключу вызывает исключение) и должен быть сериализуемым.

Ему присваивается имя IndexedDictionary в примечании к ответу Адриана.

YMMV.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;

/// <summary>
/// A dictionary that maintains insertion ordering of keys.
/// 
/// This is useful for emitting JSON where it is preferable to keep the key ordering
/// for various human-friendlier reasons.
/// 
/// There is no support to manually re-order keys or to access keys
/// by index without using Keys/Values or the Enumerator (eg).
/// </summary>
[Serializable]
public sealed class IndexedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    // Non-generic version only in .NET 4.5
    private readonly OrderedDictionary _backing = new OrderedDictionary();

    private IEnumerable<KeyValuePair<TKey, TValue>> KeyValuePairs
    {
        get
        {
            return _backing.OfType<DictionaryEntry>()
                .Select(e => new KeyValuePair<TKey, TValue>((TKey)e.Key, (TValue)e.Value));
        }
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return KeyValuePairs.GetEnumerator();
    }

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

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        _backing[item.Key] = item.Value;
    }

    public void Clear()
    {
        _backing.Clear();
    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        return _backing.Contains(item.Key);
    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        KeyValuePairs.ToList().CopyTo(array, arrayIndex);
    }

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        TValue value;
        if (TryGetValue(item.Key, out value)
            && Equals(value, item.Value))
        {
            Remove(item.Key);
            return true;
        }
        return false;
    }

    public int Count
    {
        get { return _backing.Count; }
    }

    public bool IsReadOnly
    {
        get { return _backing.IsReadOnly; }
    }

    public bool ContainsKey(TKey key)
    {
        return _backing.Contains(key);
    }

    public void Add(TKey key, TValue value)
    {
        _backing.Add(key, value);
    }

    public bool Remove(TKey key)
    {
        var result = _backing.Contains(key);
        if (result) {
            _backing.Remove(key);
        }
        return result;
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        object foundValue;
        if ((foundValue = _backing[key]) != null
            || _backing.Contains(key))
        {
            // Either found with a non-null value, or contained value is null.
            value = (TValue)foundValue;
            return true;
        }
        value = default(TValue);
        return false;
    }

    public TValue this[TKey key]
    {
        get
        {
            TValue value;
            if (TryGetValue(key, out value))
                return value;
            throw new KeyNotFoundException();
        }
        set { _backing[key] = value; }
    }

    public ICollection<TKey> Keys
    {
        get { return _backing.Keys.OfType<TKey>().ToList(); }
    }

    public ICollection<TValue> Values
    {
        get { return _backing.Values.OfType<TValue>().ToList(); }
    }
}
38
задан Bridge 3 August 2012 в 07:41
поделиться

5 ответов

Мой ответ похож на ответ Юрия, но с использованием MaxBy из MoreLINQ , что не требует, чтобы сравнение производилось с помощью целых чисел:

var query = from player in players
            group player by player.TeamName into team
            select team.MaxBy(p => p.PlayerScore);

foreach (Player player in query)
{
    Console.WriteLine("{0}: {1} ({2})",
        player.TeamName,
        player.PlayerName,
        player.PlayerScore);
}

Обратите внимание, что я изменил имя типа с "Team" на "Player" поскольку я считаю, что это имеет больше смысла - ты не

28
ответ дан 27 November 2019 в 03:32
поделиться

Реализация, предложенная The Lame Duck, великолепна, но требует двух проходов O (n) над сгруппированным набором, чтобы вычислить Макс. Было бы полезно рассчитать MaxScore один раз, а затем использовать его повторно. Здесь пригодится SelectMany (ключевое слово let в C #). Вот оптимизированный запрос:

var x = from t in teams 
        group t by t.TeamName into groupedT 
        let maxScore = groupedT.Max(gt => gt.PlayerScore)
        select new 
        { 
           TeamName = groupedT.Key,
           MaxScore = maxScore, 
           MaxPlayer = groupedT.First(gt2 => gt2.PlayerScore == maxScore).PlayerName 
        };
0
ответ дан 27 November 2019 в 03:32
поделиться

Я предлагаю вам сначала реализовать метод расширения класса IEnumerbale под названием Top Например:

IEnumerable<T,T1> Top(this IEnumerable<T> target, Func<T1> keySelector, int topCount)
{
    return target.OrderBy(i => keySelector(i)).Take(topCount);
}

Затем вы можете написать:

team.GroupBy (team => team.TeamName) .Top (team => team.PlayerScore, 1).

Для этого могут быть внесены небольшие изменения компилировать.

-1
ответ дан 27 November 2019 в 03:32
поделиться

Следующий код получает желаемое значение:

foreach (Team team in teams
    .GroupBy(t => t.TeamName)
    .Select(ig => ig.MaxValue(t => t.PlayerScore)))
{
    Console.WriteLine(team.TeamName + " " + 
        team.PlayerName + " " + 
        team.PlayerScore);
}

Для него требуется следующее расширение, которое я написал ранее сегодня:

public static T MaxValue<T>(this IEnumerable<T> e, Func<T, int> f)
{
    if (e == null) throw new ArgumentException();
    using(var en = e.GetEnumerator())
    {
        if (!en.MoveNext()) throw new ArgumentException();
        int max = f(en.Current);
        T maxValue = en.Current;
        int possible = int.MaxValue;
        while (en.MoveNext())
        {
            possible = f(en.Current);
            if (max < possible)
            {
                max = possible;
                maxValue = en.Current;
            }
        }
        return maxValue;
    }
}

Следующий код получает ответ без расширения, но работает немного медленнее:

foreach (Team team in teams
    .GroupBy(t => t.TeamName)
    .Select(ig => ig.OrderByDescending(t => t.PlayerScore).First()))
{
    Console.WriteLine(team.TeamName + " " + 
        team.PlayerName + " " + 
        team.PlayerScore);
}
26
ответ дан 27 November 2019 в 03:32
поделиться

Для этого вам потребуется для группировки по названию команды, затем выберите максимальное количество очков.

Единственная сложная часть - получить соответствующего игрока, но это не так уж плохо. Просто выберите игрока с максимальным количеством очков. Грубо говоря, если возможно, что более чем один игрок имеет одинаковые результаты, сделайте это с помощью функции First (), как показано ниже, а не функции Single ().

var x =
    from t in teams
    group t by t.TeamName into groupedT
    select new
    {
        TeamName = groupedT.Key,
        MaxScore = groupedT.Max(gt => gt.PlayerScore),
        MaxPlayer = groupedT.First(gt2 => gt2.PlayerScore == 
                    groupedT.Max(gt => gt.PlayerScore)).PlayerName
    };

К вашему сведению - я запускал этот код для ваших данных, и он сработал (после того, как я исправил это,

13
ответ дан 27 November 2019 в 03:32
поделиться
Другие вопросы по тегам:

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