如何从列表中删除空字符串,然后再从列表中删除重复的值。

92

假设我有一个来自表格的某列值列表,如何删除空字符串和重复值。请参见以下代码:

List<string> dtList = dtReportsList.AsEnumerable().Select(dr => dr.Field<string>("column1")).ToList();

这是我刚刚编写的代码,但Amiram的代码更加优雅,所以我会选择那个答案,这是我实现的方式:

DataTable dtReportsList = someclass.GetReportsList();

        if (dtReportsList.Rows.Count > 0)
       { 
           List<string> dtList = dtReportsList.AsEnumerable().Select(dr => dr.Field<string>("column1")).ToList();
           dtList.RemoveAll(x=>x == "");
           dtList = dtList.Distinct().ToList();         

           rcboModule.DataSource = dtList;
           rcboModule.DataBind();               
           rcboModule.Items.Insert(0, new RadComboBoxItem("All", "All"));
       }

了解RemoveAll()会改变dtList;每个被删除的元素都会强制List在使用的底层数组中重新排列高索引处的元素。像Amiram在他的Where方法中所做的那样,跳过它们会更快。 - KeithS
4个回答

247
dtList = dtList.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct().ToList();

我认为空字符串和空格类似于null。如果不是这样,您可以使用 IsNullOrEmpty(允许空格),或者 s != null


只有一件事;使用Distinct()进行去重相对低效,因为该方法必须假定最坏情况。 - KeithS
@KeithS 我们对这些数据有哪些断言,Distinct不知道,从而使其能够被优化? - Servy
我们可以对列表进行排序,然后断言它已经排序完成,这样去重算法就是线性的了;请参考我的答案。 - KeithS

13

为了简化Amiram Korach的解决方案:

dtList.RemoveAll(s => string.IsNullOrWhiteSpace(s))

不需要使用Distinct()或ToList()


5
为了简化@Bojan的解决方案:dtList.RemoveAll(string.IsNullOrWhiteSpace) :D - Teneko

10

Amiram的回答是正确的,但Distinct()的实现是一个N2操作;对于列表中的每个项目,算法将其与所有已处理的元素进行比较,如果它是唯一的,则返回它,否则忽略它。我们可以做得更好。

排序后的列表可以在线性时间内去重;如果当前元素等于前一个元素,则忽略它,否则返回它。排序是NlogN,所以即使需要对集合进行排序,我们也会获得一些好处:

public static IEnumerable<T> SortAndDedupe<T>(this IEnumerable<T> input)
{
   var toDedupe = input.OrderBy(x=>x);

   T prev;
   foreach(var element in toDedupe)
   {
      if(element == prev) continue;

      yield return element;
      prev = element;      
   }
}

//Usage
dtList  = dtList.Where(s => !string.IsNullOrWhitespace(s)).SortAndDedupe().ToList();

这会返回相同的元素,只是它们被排序了。


太好了。如果我没记错的话,通过迭代元素,您实际上正在执行排序。您能想到一种使您的方法“懒惰”的方法吗? - Amiram Korach
不幸的是,大多数排序算法需要对整个集合进行排序;最后一个元素可能是需要返回的第一个元素。因此,必须评估输入的所有元素才能产生输出的第一个元素。我所知道的唯一可以在找到其输出的下一个元素后中断的排序算法是选择排序的变体,在这种情况下,我们回到了起点。 - KeithS
此外,在我们的情况下,整个操作的结果是一个列表,需要“急切”的执行开始。如果我们想将其作为IEnumerable处理并推迟执行,可以将函数的主要部分放入实现IEnumerable的隐藏迭代器类中。 - KeithS
3
“Distinct”使用哈希,应该比O(N^2)更接近O(N)。来源 - Risky Martin
哎呀,它确实是这样的;System.Linq.Set 是 Distinct 使用的内部哈希表实现,假设您的项的 GetHashCode() 实现是有效的并且生成均匀分布的哈希值(默认实现会这样做),那么访问时间接近 O(1)。然而,哈希表确实存在内存问题;.NET 的基本实现使用两个数组,一个是 int 数组,另一个是链接项数组,每个数组的大小最好等于集合中的项数,最坏情况下是其两倍。 - KeithS

3

Amiram Korach的解决方案确实很整洁。为了多样性,这里提供另一种选择。

var count = dtList.Count;
// Perform a reverse tracking.
for (var i = count - 1; i > -1; i--)
{
    if (dtList[i]==string.Empty) dtList.RemoveAt(i);
}
// Keep only the unique list items.
dtList = dtList.Distinct().ToList();

4
虽然这样做可以实现相同的结果,但是Where子句更快,因为它不需要改变输入集合的内容。当从列表中删除元素时,你可以最小化必须执行的“移位”数量,但是Where子句不会从输入中删除任何元素;它只是跳过不匹配的元素。 - KeithS

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