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
您可以计算 left
和 right
边界,然后在 Skip
和 Take
的帮助下修剪集合:
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();
回答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 所指出的,这种实现存在两个问题:
- 如果
myList
为null
或为空 ({ }
),并且抛出异常- 任何扩展方法都不能容忍在
null
.First()
上调用 (和.Last()
) 在空列表上调用时抛出异常
- 任何扩展方法都不能容忍在
- 如果第一个元素不重复(即不等于第二个元素),则仍然会排除第一个元素;这不是预期的行为。同样,最后一个元素也将被排除,即使它没有重复。
解决这些问题的一种方法是在过滤掉实际的 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);
回答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);