如何使用LINQ重命名列表中的重复项

4
我需要一个独特值的列表。但是,列表中可能存在重复的值。 如果发生这种情况,我必须重新命名该值,但是已经改名的值也可能在列表中出现。
是否可能使用LINQ查询来重新命名值,以便不需要子查询?
示例1: 之前: "one", "one", "two", "two", "three" 之后: "one", "one_", "two", "two_", "three"
示例2: 之前: "one", "one", "one_" 之后: "one", "one_", "one__"
第3个“one”有两个下划线,因为第2个“one”被重新命名为“one_”。
非常感谢您提供想法...

1
LINQ 不适合修改集合,但适合查询它们。我会选择 Alxandr 的建议。 - Jens
@Jens:这并不要求修改TSource,只是查询派生项。虽然我同意这个建议。 - H H
3个回答

12

我认为这不应该仅使用linq查询来完成。如果我是你,我会使用HashSet并创建一个函数。类似这样:

IEnumerable<String> GetUnique(IEnumerable<String> list) {
    HashSet<String> itms = new HashSet<String>();
    foreach(string itm in list) {
         string itr = itm;
         while(itms.Contains(itr)) {
             itr = itr + "_";
         }
         itms.Add(itr);
         yield return itr;
    }
}

[编辑]

这可以被制作成一个扩展方法,这样你就可以像这样调用它:myList.GetUnique();(或类似的方式)

[编辑2]

修复了迭代器变量被更改的错误。


正要发布类似的内容! 我唯一的建议是将其制作为扩展方法,这样调用会更容易,并在构建要插入的候选字符串时使用StringBuilder以避免大量低效的字符串连接。 - BishopRook
我曾考虑使用StringBuilder,但后来想到通常你可能只会在列表末尾添加1或2个“_”;在这种情况下,我认为使用StringBuilder的收益并不大,而且在while循环中调用ToString的成本也不高...嗯,我只是觉得这样做不值得。 - Alxandr
@Alxandr:同意,因为每次查找都需要字符串,所以使用连接符(+)更有效率。 - James Michael Hare
@StriplingWarrior:等等,什么?你的意思是如果我这样做 string a = "a"; string b = "b"; string c = a + b; C#(在幕后)会为连接创建一个 StringBuilder 吗?那不是浪费内存和其他东西吗? - Alxandr
@Alxandr:我改正了。只有在连接四个或更多字符串时才会创建StringBuilder - StriplingWarrior
显示剩余2条评论

2
我会创建一个新的扩展方法,代码如下:

public static IEnumerable<string> Uniquifier(this IEnumerable<string> values)
{
    if (values == null) throw new ArgumentNullException("values");

    var unique = new HashSet<string>();

    foreach(var item in values)
    {
        var newItem = item;

        while(unique.Contains(newItem))
        {
            newItem += '_';
        }

        unique.Add(newItem);

        yield return newItem;
    }
}

这将会接收任何字符串序列,并创建一个哈希集 - 非常快速,O(1) - 的值。如果该值已经存在,则添加下划线并重试。一旦找到唯一的值,就返回它。

这是第一个能够成功编译并运行的答案。 - docmanhattan

1
使用扩展方法:
public static class EnumerableExtensions
{
    public static IEnumerable<string> Uniquify(this IEnumerable<string> enumerable, string suffix)
    {
        HashSet<string> prevItems = new HashSet<string>();
        foreach(var item in enumerable)
        {
            var temp = item;
            while(prevItems.Contains(temp))
            {
                temp += suffix;
            }
            prevItems.Add(temp);
            yield return temp;
        }
    }
}

用法:

var test1 = new[] {"one","one","two","two","three"};
Console.WriteLine(String.Join(",",test1.Uniquify("_")));

实时示例:http://rextester.com/rundotnet?code=BYFVK87508

编辑:现在使用while循环支持以前不支持的所有情况,如下面的评论所述。


你应该使用 HashSet 替换列表。速度会快得多。 - Alxandr
错误的算法。使用“one”,“one_”,“one”进行测试。 - H H
如果有多个相同字符串值的出现,则无法正常工作。 - docmanhattan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接