2012-05-22 1 views
3

Допустим, у меня есть перечислимую источник, который выглядит следующим образом:Удаление одного элемента из перечисляемого источника, когда элементы равны

IEnumerable<string> source = new [] { "first", "first", "first", "second" }; 

Я хочу, чтобы иметь возможность построить заявление LINQ, который будет возвращать этот :

"first", "first", "second" 

Обратите внимание, что только один из них не прошел. Меня не волнует, какой из них, потому что в моем случае все 3 «первые» считаются равными. Я пробовал source.Except(new [] { "first" }), но это удаляет все экземпляры.

+2

Просто чтобы быть ясно, вы хотите, чтобы удалить дубликаты, но только один экземпляр дублирующие? – Codeman

+0

Да, это так. До сих пор я только нашел вещи, которые удалят все. Я хотел бы сохранить это на IEnumerable без необходимости использовать возврат доходности, если это возможно. –

+0

Метод Distinct() удалит дубликаты для вас. http://msdn.microsoft.com/en-us/library/bb348436.aspx – Adam

ответ

8
source 
    .GroupBy(s => s) 
    .SelectMany(g => g.Skip(1).DefaultIfEmpty(g.First())) 

Для каждой группы, пропустить первый элемент группы и вернуть остаток - если это не будет возвращать ни ... в этом случае вернуть первый элемент группы.


source 
    .GroupBy(s => s) 
    .SelectMany(g => g.Take(1).Concat(g.Skip(2))) 

Для каждой группы, возьмите первый элемент, и принимать от третьего элемента на - всегда пропуская второй элемент.

+2

g.Single будет вызывать исключение. Замените на g.First(), и это будет совершенно элегантно (недопустимое исключение операции: последовательность содержит более одного элемента) –

+1

или * var res = источник \t .GroupBy (s => s) \t .SelectMany (g => (g.Count()> 1)? g.Skip (1): g) .ToList(); * –

+0

Вы также можете обернуть 'g.Single()' в 'Lazy', чтобы не оценивать его, если это не нужно, и чтобы избежать проблемы, о которой упоминалось в @ RaphaëlAlthaus. – Servy

0

Я не супер знакомы с LINQ, но вот общий поток вы можете использовать для этого:

магазин все уникальные предметы в новом списке B, т.е.:

A: {1, 1, 1, 2, 4, 4, 6} 

становится

B: {1, 2, 4, 6} 

Итерация B, удаляя его экземпляр в А, если она существует, а именно:

A: {1, 1, 1, 2, 4, 4, 6} 

становится

F: {1, 1, 2, 4, 6} 

Надеется, что это помогает!

1
IEnumerable<string> source = new [] { "first", "first", "first", "second" }; 

List<string> newSource = new List<string>(); 

var foo = source.GroupBy (s => s).Select (s => new KeyValuePair<string,int>(s.Key, (s.Count()>1)?s.Count()-1:s.Count())); 

foreach (var element in foo) 
{ 
    newSource.AddRange(Enumerable.Repeat(element.Key,element.Value)); 
} 

Очень быстрое усилие здесь. В принципе, это создаст второй список из оригинала с каждым отдельным ключом и количеством экземпляров, вычитает один, если их больше одного, а затем повторно отобразит список с нужным количеством элементов.

Не так изящно, как ответ Дэвида Б, но я уже написал это, хотя я могу также опубликовать его как еще один возможный ответ. Я уверен, что foreach может быть внесен в заявление Linq, но уже поздно, и мой мозг не работает!

3

Я вычислил заявление LINQ с одним слоем, чтобы сделать это. Для этого требуется отдельная переменная флага. Я реализовал ее в качестве метода расширения:

public static IEnumerable<T> ExceptOne<T>(this IEnumerable<T> enumerable, T element) 
{ 
    var i = 0; 

    return enumerable.Where(original => !EqualityComparer<T>.Default.Equals(original, element) || ++i > 1); 
} 

Я использовал Int в случае, если я позже хочу добавить параметр «numberToRemove» (изменить> 1 к> numberToRemove). YAGNI и все такое, но это было так же удобочитаемо, как и логическое.

6

Я думаю, что ответ Дэвида Б на самом деле близок, но он не удалит значение в случае, когда есть только одно значение, и это то, что я думаю, что оригинальный плакат искал.

Вот метод расширения, который удалит один экземпляр запрошенного элемента, даже если это последний экземпляр. Это отражает вызов LINQ Except(), но удаляет только первый экземпляр, а не все экземпляры.

public static IEnumerable<T> ExceptSingle<T>(this IEnumerable<T> source, T valueToRemove) 
    { 
     return source 
      .GroupBy(s => s) 
      .SelectMany(g => g.Key.Equals(valueToRemove) ? g.Skip(1) : g); 
    } 

Дано: {"one", "two", "three", "three", "three"}
Вызов source.ExceptSingle("three") приводит {"one", "two", "three", "three"}

Дано: {"one", "two", "three", "three"}
Его вызов source.ExceptSingle("three") приводит {"one", "two", "three"}

Дано: {"one", "two", "three"}
Его вызов source.ExceptSingle("three") приводит {"one", "two"}

Дано: {"one", "two", "three", "three"}
Вызов source.ExceptSingle("four") приводит {"one", "two", "three", "three"}

+0

Это действительно то, что я хочу. Я закончил тем, что использовал версию, которую я опубликовал, как еще один ответ на себя. Тем не менее, есть +1. –