如何从每次运行的类似项目中选择最后一个值?

时间:2013-11-27 11:08:17

标签: c# .net linq ienumerable

我有一份清单。我想从每次类似元素的运行中获取最后一个值。

我的意思是什么?让我举一个简单的例子。给出单词列表

  

['golf','hip','hop','hotel','grass','world','wee']

相似度函数'以相同的字母开头',该函数将返回较短的列表

  

['golf','hotel','grass','wee']

为什么呢?原始列表包含1个G字,3个H字,1个G字和2个W字。该函数返回每次运行的最后一个单词。

我该怎么做?


假设的C#语法(实际上我正在使用客户对象,但我想分享一些你可以运行并自己测试的东西)

> var words = new List<string>{"golf", "hip", "hop", "hotel", "grass", "world", "wee"};
> words.LastDistinct(x => x[0])
["golf", "hotel", "grass", "wee"]

编辑:我试过了.GroupBy(x => x[0]).Select(g => g.Last()),但这给了''草', 'hotel','wee'] 我想要的东西。仔细阅读例子。


编辑。另一个例子。

  

['apples','armies','black','beer','bastion','cat','cart','able','art','bark']

这里有5次运行(A的运行,B的运行,C的运行,A的新的运行,B的运行) 。每次运行的最后一个词是:

  

['armies','bastion','cart','art','bark']

要理解的重要一点是每次运行都是独立的。不要在开始时将A的运行与A的运行混合在一起。

6 个答案:

答案 0 :(得分:1)

您可以使用此扩展程序,该扩展程序可以按相邻/连续元素进行分组:

public static IEnumerable<IGrouping<TKey, TSource>> GroupAdjacent<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector)
{
    TKey last = default(TKey);
    bool haveLast = false;
    List<TSource> list = new List<TSource>();
    foreach (TSource s in source)
    {
        TKey k = keySelector(s);
        if (haveLast)
        {
            if (!k.Equals(last))
            {
                yield return new GroupOfAdjacent<TSource, TKey>(list, last);
                list = new List<TSource>();
                list.Add(s);
                last = k;
            }
            else
            {
                list.Add(s);
                last = k;
            }
        }
        else
        {
            list.Add(s);
            last = k;
            haveLast = true;
        }
    }
    if (haveLast)
        yield return new GroupOfAdjacent<TSource, TKey>(list, last);
}

public class GroupOfAdjacent<TSource, TKey> : IEnumerable<TSource>, IGrouping<TKey, TSource>
{
    public TKey Key { get; set; }
    private List<TSource> GroupList { get; set; }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return ((System.Collections.Generic.IEnumerable<TSource>)this).GetEnumerator();
    }
    System.Collections.Generic.IEnumerator<TSource> System.Collections.Generic.IEnumerable<TSource>.GetEnumerator()
    {
        foreach (var s in GroupList)
            yield return s;
    }
    public GroupOfAdjacent(List<TSource> source, TKey key)
    {
        GroupList = source;
        Key = key;
    }
}

然后很容易:

var words = new List<string>{"golf", "hip", "hop", "hotel", "grass", "world", "wee"};
IEnumerable<string> lastWordOfConsecutiveFirstCharGroups = words
            .GroupAdjacent(str => str[0])
            .Select(g => g.Last());

输出:

string.Join(",", lastWordOfConsecutiveFirstCharGroups); // golf,hotel,grass,wee

您的其他样本:

words=new List<string>{"apples", "armies", "black", "beer", "bastion", "cat", "cart", "able", "art", "bark"};
lastWordOfConsecutiveFirstCharGroups = words
   .GroupAdjacent(str => str[0])
   .Select(g => g.Last());

输出:

string.Join(",", lastWordOfConsecutiveFirstCharGroups); // armies,bastion,cart,art,bark

Demonstration

答案 1 :(得分:1)

以老式的方式做这件事并不复杂:

Func<string, object> groupingFunction = s => s.Substring(0, 1);
IEnumerable<string> input = new List<string>() {"golf", "hip", "..." };

var output = new List<string>();

if (!input.Any())
{
    return output;
}

var lastItem = input.First();
var lastKey = groupingFunction(lastItem);
foreach (var currentItem in input.Skip(1))
{
    var currentKey = groupingFunction(str);
    if (!currentKey.Equals(lastKey))
    {
        output.Add(lastItem);
    }
    lastKey = currentKey;
    lastItem = currentItem;
}

output.Add(lastItem);

您还可以将其转换为Tim Schmelter has done的通用扩展方法;我已经采取了一些措施来有目的地概括代码(使用object作为密钥类型,IEnumerable<T>作为输入类型。

答案 2 :(得分:0)

您可以使用以下扩展方法在某些条件下将序列拆分为组(即子序列):

public static IEnumerable<IEnumerable<T>> Split<T, TKey>(
    this IEnumerable<T> source, Func<T, TKey> keySelector)
{
    var group = new List<T>();

    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
            yield break;
        else
        {
            TKey currentKey = keySelector(iterator.Current);
            var keyComparer = Comparer<TKey>.Default;
            group.Add(iterator.Current);

            while (iterator.MoveNext())
            {
                var key = keySelector(iterator.Current);
                if (keyComparer.Compare(currentKey, key) != 0)
                {
                    yield return group;
                    currentKey = key;
                    group = new List<T>();
                }

                group.Add(iterator.Current);
            }
        }
    }

    if (group.Any())
        yield return group;        
}

获得预期结果如下:

string[] words = { "golf", "hip", "hop", "hotel", "grass", "world", "wee" };

var result = words.Split(w => w[0])
                  .Select(g => g.Last());

结果:

golf
hotel
grass
wee

答案 3 :(得分:0)

试试这个算法

        var words = new List<string> { "golf", "hip", "hop", "hotel", "grass", "world", "wee" };
        var newList = new List<string>();
        int i = 0;
        while (i < words.Count - 1 && i <= words.Count)
        {
            if (words[i][0] != words[i+1][0])
            {
                newList.Add(words[i]);
                i++;
            }
            else
            {
                var j = i;
                while ( j < words.Count - 1 && words[j][0] == words[j + 1][0])
                {
                    j++;
                }
                newList.Add(words[j]);
                i = j+1;
            }
        }

答案 4 :(得分:0)

因为您的输入是List&lt;&gt;,所以我认为这应该对您有效,并且具有可接受的性能,特别是它非常简洁:

var result = words.Where((x, i) => i == words.Count - 1 || 
                                   words[i][0] != words[i + 1][0]);

如果您愿意,可以在结果上附加ToList()以获得List<string>

答案 5 :(得分:0)

我去了

/// <summary>
/// Given a list, return the last value from each run of similar items.
/// </summary>
public static IEnumerable<T> WithoutDuplicates<T>(this IEnumerable<T> source, Func<T, T, bool> similar)
{
    Contract.Requires(source != null);
    Contract.Requires(similar != null);
    Contract.Ensures(Contract.Result<IEnumerable<T>>().Count() <= source.Count(), "Result should be at most as long as original list");

    T last = default(T);
    bool first = true;
    foreach (var item in source)
    {
        if (!first && !similar(item, last))
            yield return last;

        last = item;
        first = false;
    }

    if (!first)
        yield return last;
}