【问题标题】:Having a List<string> where rows can be the same value, how to add a int to make each row unique?有一个 List<string> ,其中行可以是相同的值,如何添加一个 int 以使每一行唯一?
【发布时间】:2012-09-25 13:56:51
【问题描述】:

我有一个列表,当多个项目具有相同名称时,我想在其中添加一个内部计数器。

var myList = new List<string>();

myList.Add("a");
myList.Add("b");
myList.Add("b");
myList.Add("c");

我希望结果是 a01 b01 b02 c01 在一些花哨的 LINQ 东西之后。

有什么好主意吗?

【问题讨论】:

  • 商品是否保证井然有序?这会发生吗:a, b, b, c, a?如果是,计数器的行为应该是什么?
  • 顺序不重要,您的示例应该返回 a1,b1,b2,c1,a2 编辑:对不起,误解了,如果有问题,我可以通过排序来排序。
  • 这不是问题,只是你可以做不同的事情。考虑到您的第一个描述,我添加了一个答案,在排序列表的情况下,该代码可以稍微简化。

标签: linq c#-4.0 lambda


【解决方案1】:

不是说这很好,但它是(主要)Linq 解决方案:

var indexed =   from index in myList.Aggregate(
                    new 
                    { 
                        Counters = new Dictionary<string, int>(), 
                        Items = new List<string>() 
                    }, 
                    (acc, cur) => 
                    { 
                        if (!acc.Counters.ContainsKey(cur))
                            acc.Counters.Add(cur, 0);
                        acc.Counters[cur] = acc.Counters[cur] + 1;
                        acc.Items.Add(cur + acc.Counters[cur]);
                        return acc;
                    }).Items
                select index;

累加部分非常难看,但它在 Linq 计算中完成了这项工作。

编辑

如果初始列表已经排序,则此表达式更简洁(但可能效率低下,您必须查看列表中有多少项):

var indexed =   from index in myList.Aggregate(
                    new 
                    { 
                        Counter = 0, 
                        Key     = (string)null,
                        Items   = Enumerable.Empty<string>() 
                    }, 
                    (acc, cur) => 
                    {
                        var counter = acc.Key != cur ? 1 : acc.Counter+1;
                        return new 
                        { 
                            Counter = counter,
                            Key     = cur,
                            Items   = acc.Items.Concat(
                                        Enumerable.Repeat(cur + counter, 1))
                        };
                    }).Items
                select index;

【讨论】:

    【解决方案2】:

    如果你想保留顺序,在 OOTB LINQ 中没有非常好的方法可以做到这一点,但你可以敲出类似的东西

    public static IEnumerable<TResult> SelectWithUniquifier<TSource, TResult>(
        this IEnumerable<TSource> source, Func<TSource, int, TResult> uniquifier)
    {
         Dictionary<TSource, int> counter = new Dictionary<TSource, int>();
         foreach(TSource s in source)
         {
             int thisIndex = counter.ContainsKey(s) ? counter[s] : 0;
             counter[s] = thisIndex + 1;
             yield return uniquifier(s, thisIndex);
         }
    }
    

    只是有一个更好的名字。

    对于您的示例,您将拥有

    var result = myList.SelectWithUniquifier((s, i) => s + (i+1).ToString("00"));
    

    因为你得到的索引是从零开始的。

    【讨论】:

    • 您可以使用IEqualityComparer&lt;TSource&gt; 参数进一步扩展它,以便您可以使用例如StringComparer.OrdinalIgnoreCase.
    • 无法弄清楚这个例子,不过会更深入地研究它!看起来很时尚..
    【解决方案3】:

    查看其他答案以了解一些花哨(而且相当混乱)的 LINQ 解决方案。如果您不一定需要使用 LINQ:

    var myList = new List<string> { "a", "b", "c", "b" };
    
    var counter = new ConcurrentDictionary<string, int>();
    for (int i = 0; i < myList.Count; i++)
    {
        var currVal = myList[i];
        counter.AddOrUpdate(currVal, 1, (value, count) => count + 1);
        myList[i] = currVal + counter[currVal].ToString("00");
    }
    

    ConcurrentDictionary 不需要,您可以手动执行“添加或更新”操作,具体取决于您对速度与代码清晰度的重视程度。无论哪种方式,在我看来,这是一种更具可读性和可维护性的方式来做你想做的事情。不要害怕循环。 :)

    当然,这可以作为扩展方法,或某些实用程序类的静态方法等来完成。

    【讨论】:

    • 不幸的是,这将返回 a1, b2, c1 而不是 a1, b1, b2, c1
    • 没有? :) 你试过代码吗?我得到a01,b01,c01,b02。我没有使用ConcurrentDictionary 作为结果。这只是一个计数器。
    • 你是完全正确的,我的错误 - 我尝试的时候有点快!
    • @AndersHolmström 我同意你的观点,在这种情况下 Linq 看起来不太好 :) 但我认为展示解决简单问题的方法仍然很重要,因为有一天你可能需要更多复杂的场景,您可能必须组合计算,因此循环可能不是一种选择
    • @Wasp 我同意,并没有试图批评任何 LINQ 答案。我不能做得更好。 :) 一般来说,编写/理解这样的东西也是一个很好的 LINQ 练习。但我也认为人们可以对 LINQ 视而不见,并尝试通过它来做所有事情,只是因为它感觉更“现代”。
    【解决方案4】:

    另一种更简单的方法:

      var result = myList.GroupBy(x => x)
                .SelectMany(g => g.Select((x, i) => x + (i + 1).ToString("00")));
    

    【讨论】:

    • 这确实应该是答案。 :)
    【解决方案5】:
    var res =
        myList
            .GroupBy(item => item)
            .Select(item => String.Format("{0}{1}", item.Key, item.Count()));
    

    【讨论】:

    • 这将返回 a1, b2, c1 而不是 a1, b1, b2, c1
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-03
    • 1970-01-01
    • 2020-11-05
    • 2022-09-28
    • 1970-01-01
    • 2017-09-17
    • 1970-01-01
    相关资源
    最近更新 更多