С# linq удаляет дубликаты из верхняя и низ списка и сохраняет дубликаты в середина
Например,
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() не поможет, так как она также удаляет все дубликаты в середине.
** Обновлено: список не будет ПУСТОЙ, и независимо от дубликатов вверху и внизу первая и последняя записи должны быть удалены для расчета бизнес-логики. Если дубликаты найдены вверху или внизу, их также следует удалить. Список будет упорядочен по возрастанию перед выполнением операции удаления **
@ 41686d6564standsw.Палестина Обновил вопрос
Это спецификация, а не вопрос. Где у вас возникли проблемы с программированием?





int topDuplicate = myList [0];
myList .RemoveAll(x => x == topDuplicate);
int bottomDuplicate = myList [myList .Count - 1];
myList .RemoveAll(x => x == bottomDuplicate);
1. Ваш код выдает исключение на пустой список. 2. Ваш код удаляет первый и последний элементы, даже если они не дублируются, например. {1, 2, 3}: фактически {2} когда {1, 2, 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);
1. Ваш код выдает исключение на пустой список. 2. Ваш код удаляет первый и последний элементы, даже если они не дублируются, например. {1, 2, 3}: фактически {2} когда {1, 2, 3} ожидается.
Для удаления дубликатов в начале списка вы можете использовать .First() и .SkipWhile() в пространстве имен System.Linq:
var firstDuplicate = myList.First();
var listWithoutFirstDuplicate = myList
.SkipWhile(l => l == firstDuplicate)
.ToList();
Для удаления дубликатов в конец списка вам пригодятся .Last() и .SkipLastWhile(), существовал .SkipLastWhile():
var lastDuplicate = myList.Last();
var listWithoutLastDuplicate = myList
.SkipLastWhile(l => l == lastDuplicate)
.ToList();
Пауло Моргадо предложил реализовать .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;
}
}
}
Реализация Пауло .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
Как указал Дмитрий Быченко, в этой реализации есть две проблемы:
myList равно null или пусто ({ }), возникает исключение
null.First() (и .Last()) генерировать исключение при вызове из пустого спискаОдин из способов решения этих проблем — выполнять проверки перед тем, как отфильтровать фактические дубликаты. Если это обрабатывается в методе, это может быть реализовано следующим образом:
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);
Пример скрипки здесь.
Ваш последний код выдает исключение на пустой список (var myList = new List<int> {};) и отсекает первый и последний элементы, даже если они не дублирует: (var myList = new List<int> {1, 2, 3, 4, 5};, here I expected 1, 2, 3, 4, 5`, но получил 2, 3, 4
@DmitryBychenko Вы абсолютно правы; спасибо, что указали на это.
Обновлено (устранение проблем и добавление предлагаемой реализации для их решения).
Если вы хотите удалить дубликаты только из самого верха и низа коллекции, но хотите сохранить значения такой же посередине:
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();
Если вы хотите удалить все значения, которые оказались дубликатами
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();
Пожалуйста, рабочий пример
Если у вас есть одна или несколько единиц или девяток в середине, вы хотите сохранить или удалить их? Кроме того, что, если первый/последний элемент нет является дубликатом; вы хотите сохранить или удалить это?