C# linq 从列表的顶部和底部删除重复项并将重复项保留在中间

C# linq remove duplicates from the top and bottom of the list and keep the duplicates in the middle

C# linq 从列表的 topbottom 中删除重复项,并将重复项保留在 middle

例如

  var myArray = new[] { 
    1, 1, 2, 2, 3, 4, 5, 5, 9, 9 
  };
  
  List<int> myList = myArray.ToList();

预期输出 删除顶部和底部的重复项后低于列表

{ 2, 2, 3, 4, 5, 5 };

请告知如何执行此逻辑并尝试 myList.Distinct() 没有帮助,因为它也删除了 所有 中间的重复项。

**编辑:列表不会为空,无论顶部和底部是否重复,都应删除第一个和最后一个记录以计算业务逻辑。如果在顶部或底部发现重复项,也应将其删除。在执行删除操作之前,列表将按升序排列**

    int topDuplicate = myList [0];
    myList .RemoveAll(x => x == topDuplicate);
    int bottomDuplicate = myList [myList .Count - 1];
    myList .RemoveAll(x => x == bottomDuplicate);
var myList = new List<int> { 1, 1, 2, 2, 3, 4, 5, 5, 9, 9 };
    var topDublicate = myList.First();
    var lastDublicate = myList.Last();

    myList.RemoveAll(l => l == topDublicate);
    myList.RemoveAll(l => l == lastDublicate);

要删除列表开头的重复项,您可以使用 System.Linq 命名空间中的 .First().SkipWhile()

var firstDuplicate = myList.First();

var listWithoutFirstDuplicate = myList
    .SkipWhile(l => l == firstDuplicate)
    .ToList();

要删除列表 end 中的重复项,您将受益于 .Last().SkipLastWhile()had .SkipLastWhile() 存在:

var lastDuplicate = myList.Last();

var listWithoutLastDuplicate = myList
    .SkipLastWhile(l => l == lastDuplicate)
    .ToList();

Paulo Morgado 建议在 this blog post 中实施 .SkipLastWhile()。它看起来像这样:

public static IEnumerable<TSource> SkipLastWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    var buffer = new List<TSource>();

    foreach (var item in source)
    {
        if (predicate(item))
        {
            buffer.Add(item);
        }
        else
        {
            if (buffer.Count > 0)
            {
                foreach (var bufferedItem in buffer)
                {
                    yield return bufferedItem;
                }

                buffer.Clear();
            }

            yield return item;
        }
    }
}

Paulo 的 .SkipLastWhile() 实现根据谓词检查每个元素。如果某个元素的谓词 not 满足,则该元素是 returned。如果谓词 满足(即在您的场景中:如果元素等于 lastDuplicate),则该元素不会立即 returned,而是添加到缓冲区。如果后续元素 满足谓词,则仅 return 编辑缓冲区的内容。


举几个例子:

{ 1, 9, 9, 9, 9 }.SkipLastWhile(i => i == 9)

将 return { 1 }.
缓冲区将从 { 9 }(索引 1 处的元素)到 { 9, 9, 9, 9 }(从索引 1 到索引 4 的元素)构建,并且 而不是 returned.

{ 1, 9, 9, 9, 9, 1 }.SkipLastWhile(i => i == 1)

将 return { 1, 9, 9, 9, 9 }.
缓冲区将首先是 { 1 }(索引 0 处的元素),然后是 returned,并在找到 9 时清空(在索引 1 处)。当到达最后一个元素时,缓冲区将再次变为 { 1 };它不是 returned.

{ 9, 1, 9, 9, 9, 9 }.SkipLastWhile(i => i == 9)

将 return { 9, 1 }.
缓冲区将首先是 { 9 }(索引 0 处的元素),然后是 returned 并在找到 1 时清空(在索引 1 处)。当达到 9(在索引 2 处)时,缓冲区再次开始建立,从 { 9 } 开始。在最后一个元素处,缓冲区将为 { 9, 9, 9, 9 },其内容为 not returned.


使用 SkipLastWhile() 的这个实现,您可以获得过滤列表:

var myList = new List<int> { 1, 1, 9, 2, 2, 3, 9, 4, 5, 5, 1, 9, 9 };

var firstDuplicate = myList.First();
var lastDuplicate = myList.Last();

var myFilteredList = myList
    .SkipWhile(l => l == firstDuplicate)
    .SkipLastWhile(l => l == lastDuplicate)
    .ToList();

给定 myList 的输出如下:

9, 2, 2, 3, 9, 4, 5, 5, 1


正如Dmitry Bychenko所指出的,这个实现有两个问题:

  1. 如果 myListnull 或为空 ({ }),则抛出异常
    • none 的扩展方法可以容忍在 null
    • 上被调用
    • .First()(和.Last())在空列表上调用时抛出异常
  2. 如果第一个元素不重复(即不等于第二个元素),第一个元素将none排除;那不是预期的行为。类似地,最后一个元素也将被排除,即使它不是重复的。

解决这些问题的一种方法是在过滤掉实际重复项之前进行检查。如果用方法处理,可以这样实现:

public static List<int> GetFilteredList(List<int> list)
{
    // if list is null; return null
    if (list == null)
    {
        return null;
    }
    
    // If list is empty or contains only one element; return list as new list
    if (!list.Skip(1).Any())
    {
        return list.ToList();
    }
    
    var filtered = list.AsEnumerable();
    
    // Remove duplicates at beginning of list (if any)
    if (list.First() == list.Skip(1).First())
    {
        filtered = filtered.SkipWhile(l => l == list.First());
    }
    
    // Remove duplicates at end of list (if any)
    if (list.Last() == list.SkipLast(1).Last())
    {
        filtered = filtered.SkipLastWhile(l => l == list.Last());
    }
    
    return filtered.ToList();
}

可以这样调用:

var filteredList = GetFilteredList(myList);

示例 fiddle here.

如果您只想从集合的顶部和底部删除重复项,但又想在中间保持相同值:

1, 1, 2, 3, 1, 1, 8, 8, 9, 9, 4, 5, 9, 9 => 2, 3, 1, 1, 8, 8, 9, 9, 4, 5
                                                  ^  ^     ^  ^
                                                    preserved

您可以计算 leftright 边界,然后在 SkipTake 的帮助下 trim 集合:

      int[] myArray = new int[] { 
        ... 
      };

      ...

      int left = 0;

      for (int i = 1; i < myArray.Length; ++i)
        if (myArray[i - 1] == myArray[i])
          left = i + 1;
        else
          break;

      int right = myArray.Length - 1;

      for (int i = myArray.Length - 2; i >= 0; --i)
        if (myArray[i + 1] == myArray[i])
          right = i - 1;
        else
          break;

      List<int> myList = myArray
        .Skip(left)
        .Take(right - left + 1)
        .ToList();

如果您想删除所有看似重复的值

1, 1, 2, 3, 1, 1, 8, 8, 9, 9, 4, 5, 9, 9 => 2, 3, 8, 8, 4, 5

您可以收集这些值然后过滤掉:

      int[] myArray = new int[] { 
        ... 
      };

      ...

      HashSet<int> remove = new HashSet<int>();

      if (myArray.Length > 1) {
        if (myArray[0] == myArray[1])
          remove.Add(myArray[0]);
        if (myArray[myArray.Length - 1] == myArray[myArray.Length - 2])
          remove.Add(myArray[myArray.Length - 1]);
      }

      var myList = myArray
        .Where(item => !remove.Contains(item))
        .ToList();

拜托,fiddle