c# - C# linq 从列表的顶部和底部删除 duplicates 并将 duplicates 保留在中间

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

例如,

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

删除顶部和底部的 duplicates 后的预期输出如下列表

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

请建议如何执行此逻辑并尝试 myList.Distinct() 无济于事,因为它也删除了中间的所有 duplicates 。

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

回答1

如果您只想从集合的顶部和底部删除 duplicates ,但希望在中间保持相同的 values :

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 的帮助下修剪集合:

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();

如果要删除所有看起来是 duplicates 的 values

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

你可以收集这些 values 然后过滤掉:

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();

https://dotnetfiddle.net/u6OFlB

回答2

要删除列表开头的 duplicates,您可以从 System.Linq 命名空间中的 .First().SkipWhile() 中受益:

var firstDuplicate = myList.First();

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

要删除列表末尾的 duplicates,如果存在 .SkipLastWhile(),您将从 .Last().SkipLastWhile() 中受益:

var lastDuplicate = myList.Last();

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

Paulo Morgado 在 https://weblogs.asp.net/paulomorgado/linq-implementing-the-skiplastwhile-operator 中建议了 .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() 实现根据谓词检查每个元素。如果某个元素的谓词不满足,则返回该元素。如果满足谓词(即在您的场景中:如果元素等于 lastDuplicate),则不会立即返回该元素,而是将其添加到缓冲区中。仅当后续元素不满足谓词时才返回缓冲区的内容。

几个例子:

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

将返回 { 1 }

缓冲区将从 { 9 }(索引 1 处的元素)累积到 { 9, 9, 9, 9 }(索引 1 到索引 4 的元素),并且不会返回。

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

将返回 { 1, 9, 9, 9, 9 }

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

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

将返回 { 9, 1 }

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

使用 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

正如 https://stackoverflow.com/users/2319407/dmitry-bychenko 所指出的,这种实现存在两个问题:

  1. 如果 myListnull 或为空 ({ }),并且抛出异常
    • 任何扩展方法都不能容忍在 null
    • .First() 上调用 (和 .Last()) 在空列表上调用时抛出异常
  2. 如果第一个元素不重复(即不等于第二个元素),则仍然会排除第一个元素;这不是预期的行为。同样,最后一个元素也将被排除,即使它没有重复。

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

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);

示例小提琴 https://dotnetfiddle.net/KoLrXP

回答3

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);

回答4

int topDuplicate = myList [0];
    myList .RemoveAll(x => x == topDuplicate);
    int bottomDuplicate = myList [myList .Count - 1];
    myList .RemoveAll(x => x == bottomDuplicate);

相似文章

随机推荐

最新文章