Pergunta

Eu tenho um List <> de objetos em C # e eu preciso de uma maneira de devolver esses objetos que são considerados duplicatas dentro da lista. Eu não preciso do conjunto de resultados distinta, eu preciso de uma lista dos itens que eu irá excluir do meu repositório.

Para o propósito deste exemplo, vamos dizer que eu tenho uma lista de tipos de "Carro" e eu preciso saber qual destes carros são da mesma cor que outro na lista. Aqui estão os carros na lista e sua propriedade de cor:

Car1.Color = Red;

Car2.Color = Blue;

Car3.Color = Green;

Car4.Color = Red;

Car5.Color = Red;

Para este exemplo eu preciso do resultado (IEnumerable <>, List <>, ou qualquer outro) para conter car4 e Car5 porque eu quero excluir estes do meu repositório ou db modo que eu só tenho um carro por cor no meu repositório . Qualquer ajuda seria apreciada.

Foi útil?

Solução

Eu inadvertidamente codificado isso ontem, quando eu estava tentando escrever um "distinto por uma projeção". Eu incluí um! quando eu não deveria ter, mas desta vez é apenas para a direita:

public static IEnumerable<TSource> DuplicatesBy<TSource, TKey>
    (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> seenKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        // Yield it if the key hasn't actually been added - i.e. it
        // was already in the set
        if (!seenKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

Você iria em seguida, chamá-lo com:

var duplicates = cars.DuplicatesBy(car => car.Color);

Outras dicas

var duplicates = from car in cars
                 group car by car.Color into grouped
                 from car in grouped.Skip(1)
                 select car;

A grupos os carros de cor e, em seguida, ignora o primeiro resultado de cada grupo, retornando o restante a partir de cada grupo achatada numa única sequência.

Se você tem requisitos específicos sobre qual deles você deseja manter, por exemplo, se o carro tem uma propriedade Id e que pretende manter o carro com o menor Id, então você pode adicionar um pouco de ordenação lá, por exemplo.

var duplicates = from car in cars
                 group car by car.Color into grouped
                 from car in grouped.OrderBy(c => c.Id).Skip(1)
                 select car;

Aqui está uma solução ligeiramente diferente Linq que eu acho que torna mais óbvio que você está tentando fazer:

var s = from car in cars
    group car by car.Color into g
    where g.Count() == 1
    select g.First();

É só agrupamento carros por cor, jogar fora todos os grupos que têm mais de um elemento, e em seguida, colocar o restante para o IEnumerable retornado.

IEnumerable<Car> GetDuplicateColors(List<Car> cars)
{
    return cars.Where(c => cars.Any(c2 => c2.Color == c.Color && cars.IndexOf(c2) < cars.IndexOf(c) ) );
}    

Basicamente, significa "carros de retorno onde há qualquer carro na lista com a mesma cor e um índice menor".

Não tenho certeza do desempenho, no entanto. Eu suspeito que uma abordagem com um O (1) pesquisa de duplicatas (como o dicionário / método hashset) pode ser mais rápido para grandes conjuntos.

Criar um novo Dictionary<Color, Car> foundColors e uma List<Car> carsToDelete

Então você iterate através de sua lista original de carros assim:

foreach(Car c in listOfCars)
{
    if (foundColors.containsKey(c.Color))
    {
        carsToDelete.Add(c);
    }
    else
    {
        foundColors.Add(c.Color, c);
    }
}

Em seguida, você pode excluir todos os carros que está em foundColors.

Você pode obter um aumento de desempenho menor, colocando sua lógica "registro de exclusão" na demonstração if em vez de criar uma nova lista, mas a maneira como você formulada a questão sugeriu que você precisava para coletá-los em uma lista.

Sem realmente codificá-lo, como sobre um algoritmo algo como isto:

  • iterate através de seu List<T> criando um Dictionary<T, int>
  • iterate através de seus excluindo entradas Dictionary<T, int> onde o int é> 1

Qualquer coisa deixada na Dictionary tem duplicatas. A segunda parte onde você realmente eliminar é opcional, é claro. Você pode apenas percorrer os Dictionary e olhar para o> 1 de tomar a ação.

EDIT: OK, eu colidido até Ryan desde que ele realmente lhe deu código. ;)

A minha resposta tem inspiração (nesta ordem) dos seguidores entrevistados:. Joe Coehoorn, Greg Faia e Jon Skeet

Eu decidi dar um exemplo completo, com o ser premissa (para a eficiência palavra real) que você tem uma lista estática de cores do carro. Eu acredito que o código a seguir ilustra uma solução completa para o problema em um elegante, embora não necessariamente hiper-eficiente, maneira.

#region SearchForNonDistinctMembersInAGenericListSample
public static string[] carColors = new[]{"Red", "Blue", "Green"}; 
public static string[] carStyles = new[]{"Compact", "Sedan", "SUV", "Mini-Van", "Jeep"}; 
public class Car
{
    public Car(){}
    public string Color { get; set; }
    public string Style { get; set; }
}
public static List<Car> SearchForNonDistinctMembersInAList()
{
    // pass in cars normally, but declare here for brevity
    var cars = new List<Car>(5) { new Car(){Color=carColors[0], Style=carStyles[0]}, 
                                      new Car(){Color=carColors[1],Style=carStyles[1]},
                                      new Car(){Color=carColors[0],Style=carStyles[2]}, 
                                      new Car(){Color=carColors[2],Style=carStyles[3]}, 
                                      new Car(){Color=carColors[0],Style=carStyles[4]}};
    List<Car> carDupes = new List<Car>();

    for (int i = 0; i < carColors.Length; i++)
    {
        Func<Car,bool> dupeMatcher = c => c.Color == carColors[i];

        int count = cars.Count<Car>(dupeMatcher);

        if (count > 1) // we have duplicates
        {
            foreach (Car dupe in cars.Where<Car>(dupeMatcher).Skip<Car>(1))
            {
                carDupes.Add(dupe);
            }
        }
    }
    return carDupes;
}
#endregion

Eu vou voltar por aqui mais tarde e comparar esta solução para todos os seus três inspirações, apenas para contrastar os estilos. É bastante interessante.

public static IQueryable Duplicatas (esta fonte IEnumerable), onde TSource: IComparable {

if (source == null)   
     throw new ArgumentNullException("source");   
 return source.Where(x => source.Count(y=>y.Equals(x)) > 1).AsQueryable<TSource>();   

}

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top