2016-02-28 18 views
6

Mam 2 listy, których nazwy to listaA i listaB.Jak usunąć ciągi z listy z innej listy?

Chcę usunąć ciągi w listB które są w Lista, ale chcę to zrobić w ten sposób:

jeśli lista zawiera: "bar", "bar", "bar", "foo" i listB zawiera: "bar"

usuwa tylko 1 bar, a wynik będzie: "bar", "bar", "foo"

kod pisałem usuwa wszystkie "bar":

List<string> result = listA.Except(listB).ToList(); 
+0

Czy zachowując niektóre z oryginalnej listy w sprawie zamówienia? – hatchet

Odpowiedz

5

Można spróbować ją usunąć jeden po drugim:

foreach (var word in listB) 
    listA.Remove(word); 

Sposób Usuń usunie tylko jeden element na raz i nie rzuca wyjątek (ale wracając fałsz), gdy element nie zostanie znaleziony: https://msdn.microsoft.com/en-us/library/cd666k3e(v=vs.110).aspx

+0

Możesz uniknąć wywołania Contains używając bezpośrednio IndexOf, aby uzyskać pozycję – Steve

+1

Jest to nieefektywne, ale możesz sprawić, że przynajmniej użyje bezpośrednio 'listA.Remove (word)'. Nie ma potrzeby "Zawiera". –

+0

@IvanStoev prawo, po prostu zaktualizuj to – Ian

3
var listA = new List<string>() { "bar", "bar", "bar", "foo" }; 
var listB = new List<string>() { "bar" }; 

foreach (var word in listB){ 
    listA.Remove(word); 
} 
+0

Jest to w zasadzie kopia odpowiedzi @Ian wysłanej 10+ minut przed twoją –

+0

tak, to prawda, przegłosowałem jego odpowiedź – csa

1

Jest to szybsza metoda, ale prawdopodobnie zmieni kolejność elementów pierwszej listy. Kroki:

  • Mapowanie LISTA do Dictionary<string, int> (nazwijmy go listAMap), gdzie kluczem jest elementem listy, a wartość jest całkowitą liczbę razy, że wartość zaszła w Lišta;
  • Powtórz listę B i dla każdego elementu z listy B, jeśli ten element znajduje się w listAMap, zmniejsz jego liczbę;
  • Uzyskaj klucze od listMapA przy użyciu Keys property słowników C# i przeglądaj wszystkie klawisze. Dla każdego klucza, który ma dodatnią wartość, dodaj ten klucz do innej listy w sumie jej czasów zliczeń. Jeśli wpis to "bar" -> 2, dodaj "pasek" dwa razy na nowej liście.

Całkowity czas algorytmu jest O (m + n), w którym m i n są liczby elementów, zarówno pierwotnych list. Jest to lepszy czas działania niż inne wspomniane tutaj podejścia, które mają czas działania. Oczywiście ten algorytm wykorzystuje więcej miejsca.


wspomagające Kod dla algorytmu powyżej:

//Step-1: Create the dictionary... 
var listAMap = new Dictionary<string, int>(); 
foreach (var listAElement in listA) 
{ 
    listAMap.ContainsKey(listAElement) ? listAMap[listAElement]++ : listAMap.Add(listAElement, 1); 
} 

// Step-2: Remove the listB elements from dictionary... 
foreach (var listBElement in listB) 
{ 
    if (listAMap.Contains(listBElement)) listAMap[listBElement]--; 
} 

//Step-3: Create the new list from pruned dictionary... 
var prunedListA = new List<string>(); 
foreach (var key in listAMap.Keys) 
{ 
    if (listAMap[key] <= 0) continue; 
    for (var count = 0; count < listAMap[key]; count++) 
    { 
     prunedListA.Add(key); 
    } 
} 

//prunedListA contains the desired elements now. 
+0

Myślałem o czymś podobnym, ale zliczanie B i usuwanie elementy z listy Dopasuj (i zmniejszaj liczbę pasujących elementów). W każdym razie +1 za myślenie o skuteczności. –

+0

@IvanStoev: Nie dopasowujemy pozycji na liście.Wykonujemy wyszukiwanie O (1) w słowniku. Poważnie, rozwiązanie jest bardzo proste (nie możesz dać +2 na odpowiedź). Powinienem też dodać kod. Zrobię to, gdy będę miał dostęp do SO z laptopa. – displayName

+0

@IvanStoev: Ostatnią rzeczą, którą należy teraz zrobić, jest to, że powyższy kod jest podzielony na osobną metodę, dzięki czemu jest czystszy. – displayName

1

o to bardziej efektywny sposób, aby to zrobić:

var countB = new Dictionary<string, int>(listB.Count); 
foreach (var x in listB) 
{ 
    int count; 
    countB.TryGetValue(x, out count); 
    countB[x] = count + 1; 
} 
listA.RemoveAll(x => 
{ 
    int count; 
    if (!countB.TryGetValue(x, out count)) return false; 
    if (count == 1) 
     countB.Remove(x); 
    else 
     countB[x] = count - 1; 
    return true; 
}); 
+0

Tęskniłeś za krokiem, w którym wypełniłeś * countB *. – displayName

+0

@displayName Nie zrobiłem - spróbuj i zobacz (wskazówka - mała linia 'countB [x] = count + 1;)) :) –

+0

Oh widzę ... nie wiedziałem o tym zachowaniu' TryGetValue() ' w słownikach. Nauczyłem się czegoś nowego. – displayName