Reputation: 63
I have 2 list which names are listA and listB.
I want to remove strings in listB which are in listA, but I want to do this in this way:
if listA contains: "bar", "bar", "bar", "foo" and listB contains : "bar"
it removes only 1 bar and the result will be: "bar", "bar", "foo"
the code I wrote removes all "bar":
List<string> result = listA.Except(listB).ToList();
Upvotes: 6
Views: 4231
Reputation: 14389
This is a faster method but it is likely to change the order of elements of first list. Steps:
Dictionary<string, int>
(let's call it listAMap
), where key is the element of the list and value is the total number of times that value has occurred in listA;listAMap
, reduce its count;listMapA
using Keys property of C# dictionaries, and iterate through all the keys. For every key which has positive value, add that key to another list a total of its count times. So if an entry is "bar" -> 2
, then add "bar" twice in the new list.Total run time of the algorithm is O(m + n), where m and n are number of elements in both the original lists. It is a better running time than other approaches mentioned here which have O(m * n) running time. Obviously this algorithm uses more space.
Supportive Code for the algorithm above:
//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.
Upvotes: 1
Reputation: 205629
Here is a more efficient way to do that:
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;
});
Upvotes: 1
Reputation: 341
var listA = new List<string>() { "bar", "bar", "bar", "foo" };
var listB = new List<string>() { "bar" };
foreach (var word in listB){
listA.Remove(word);
}
Upvotes: 3
Reputation: 30813
You can try to remove it one by one:
foreach (var word in listB)
listA.Remove(word);
The Remove method will only remove one element at a time and is not throwing exception (but returning false) when the item is not found: https://msdn.microsoft.com/en-us/library/cd666k3e(v=vs.110).aspx
Upvotes: 5