Uncommon elements in multiple lists - c#

I have multiple lists and i want to find distinct uncommon elements in everylist.
I came to a point but couldnt managed to get the uncommon elements but ended up common elements.
Here is the code:
private void button1_Click(object sender, EventArgs e)
{
List<DataTable> dataTables = new List<DataTable>();
List<string> c = new List<string>();
c.Add("A");
c.Add("B");
c.Add("C");
dataTables.Add(ss(c));
c.Add("B");
c.Add("C");
dataTables.Add(ss(c));
c.Add("A");
c.Add("B");
dataTables.Add(ss(c));
var setsOfIds = dataTables.Select(t => t.AsEnumerable().Select(x => x.Field<string>("ELIGIBLE")).OfType<string>());
var commonIds = IntersectAll<string>(setsOfIds);
var rows = dataTables.SelectMany(t => t.AsEnumerable()).Where(r => commonIds.Contains(r.Field<string>("ELIGIBLE")));
var resultRows = rows.GroupBy(r => r.Field<string>("TEDARIKCI")).Select(r => r.First());
}
private DataTable ss(List<string> aa)
{
DataTable dt = new DataTable("x");
dt.Columns.Add("ELIGIBLE", typeof(string));
DataRow dr = null;//= dt.NewRow();
foreach (string item in aa)
{
dr = dt.NewRow();
dr[0] = item;
dt.Rows.Add(dr);
}
return dt;
}
public List<T> IntersectAll<T>(IEnumerable<IEnumerable<T>> lists)
{
HashSet<T> hashSet = null;
foreach (var list in lists)
{
if (hashSet == null)
{
hashSet = new HashSet<T>(list);
}
else
{
hashSet.IntersectWith(list);
}
}
return hashSet == null ? new List<T>() : hashSet.ToList();
}
IntersectAll does not help me because it gives me A,B,C. But i only want B (which is common to all of each list)
I would appreciate if s.o. could give a sample. This is driving me crazy!! Pleeeeaaseeee

var l1 = new List<string> {"A", "B", "C", "D"};
var l2 = new List<string> {"B", "C", "E"};
var l3 = new List<string> {"A", "B"};
var c = l1.Intersect(l2).Intersect(l3);
returns "B"
var uc1= l1.Except(l2).Except(l3);
var uc2= l2.Except(l1).Except(l3);
var uc3= l3.Except(l1).Except(l2);
var uc=uc1.Union(uc2).Union(uc3);
should give:
uc1 = "D"
uc2 = "E"
uc3 = {empty}
uc = "D","E"

In your code, the first list had 3 elements (ABC), the second had 5(ABCBC) and the third had 7 (ABCBCAB).
In other words, all the list had A, B, C ..
Creating new List before adding to dataTable would solve this issue
List<DataTable> dataTables = new List<DataTable>();
List<string> c = new List<string>();
c.Add("A");
c.Add("B");
c.Add("C");
dataTables.Add(ss(c));
List<string> c2 = new List<string>();//New List
c2.Add("B");
c2.Add("C");
dataTables.Add(ss(c2));
List<string> c3 = new List<string>();//New List
c3.Add("A");
c3.Add("B");
dataTables.Add(ss(c3));
var setsOfIds = dataTables.Select(t => t.AsEnumerable().Select(x => x.Field<string>("ELIGIBLE")).OfType<string>());
var commonIds = IntersectAll<string>(setsOfIds);
Here commonIds list would contain only one element B

It's not entirely clear what you want. You seem to be asking for a method to find elements that only appear in one list (no 2 lists have them in common), but your expected results seem to suggest you want to find elements that ALL lists have in common. In any case, both are fairly easy with Linq.
This will find all elements that appear exactly once in all lists:
public IEnumerable<T> FindUniques<T>(IEnumerable<IEnumerable<T>> lists)
{
return lists.SelectMany(x => x).GroupBy(x => x)
.Where(g => !g.Skip(1).Any())
.Select(g => g.First());
}
This fill find all elements that appear in every list:
public IEnumerable<T> IntersectAll<T>(IEnumerable<IEnumerable<T>> lists)
{
return lists.Aggregate((a, x) => a.Intersect(x));
}
For example:
var list1 = new[] { "A", "B", "C" };
var list2 = new[] { "B", "C", "D" };
var list3 = new[] { "C", "D", "E" };
FindUniques(new[] { list1, list2, list3 }); // { "A", "E" }
IntersectAll(new[] { list1, list2, list3 }); // { "C" }

Related

Count occurrences of order dependent List<T> in List<List<T>>

There is a similar question that doesn't answer my question. --> Count number of element in List>
I have a list which contains sublists:
List<string> sublist1 = new List<string>() { "a", "b" };
List<string> sublist2 = new List<string>() { "a", "b" };
List<string> sublist3 = new List<string>() { "a", "c" };
Now I want to count the occurrences of each list.
a, b --> 2
a, c --> 1
I used distinct() from LINQ, but I got the output:
a, b --> 1
a, b --> 1
a, c --> 1
I assume that the hashcode is different.
Is there an alternative to distinct() which is looking at the list values instead?
I want to solve this in LINQ if possible.
Edit:
The order of list items has to be the same!
To use GroupBy() to do this, you will need a suitable IEqualityComparer<List<string>> that compares lists of strings. There is no built-in implementation, so you have to roll your own:
public sealed class StringListEqualityComparer : IEqualityComparer<List<string>>
{
public bool Equals(List<string> x, List<string> y)
{
if (ReferenceEquals(x, y))
return true;
if (x == null || y == null)
return false;
return x.SequenceEqual(y);
}
public int GetHashCode(List<string> strings)
{
int hash = 17;
foreach (var s in strings)
{
unchecked
{
hash = hash * 23 + s?.GetHashCode() ?? 0;
}
}
return hash;
}
}
Once you've got that, you can use it with GroupBy() as follows:
public static void Main()
{
var sublist1 = new List<string>{ "a", "b" };
var sublist2 = new List<string>{ "a", "b" };
var sublist3 = new List<string>{ "a", "c" };
var listOfLists = new List<List<string>> {sublist1, sublist2, sublist3};
var groups = listOfLists.GroupBy(item => item, new StringListEqualityComparer());
foreach (var group in groups)
{
Console.WriteLine($"Group: {string.Join(", ", group.Key)}, Count: {group.Count()}");
}
}
public JsonResult CountList(){
List<List<string>> d = new List<List<string>>(); //SuperList
d.Add(new List<string> { "a", "b" }); //List 1
d.Add(new List<string> { "a", "b" }); // List 2
d.Add(new List<string> { "a", "c" }); // List 3
d.Add(new List<string> { "a", "c", "z" }); //List 4
var listCount = from items in d
group items by items.Aggregate((a,b)=>a+""+b) into groups
select new { groups.Key, Count = groups.Count() };
return new JsonResult(listCount);
}
This will give the following Result as output in Post Man or Advanced REST Client
[{
"key": "ab",
"count": 2
},
{
"key": "ac",
"count": 1
},
{
"key": "acz",
"count": 1
}],
I think this will be helpful
var list = new List<List<string>>() { sublist1, sublist2, sublist3};
var result = list.GroupBy(x => string.Join(",",x)).ToDictionary(x => x.Key.Split(',').ToList(), x => x.Count());
You can try the below code:-
List<string> sublist1 = new List<string>() { "a", "b" };
List<string> sublist2 = new List<string>() { "a", "b" };
List<string> sublist3 = new List<string>() { "a", "c" };
List<List<string>> listOfLists = new List<List<string>> { sublist1, sublist2, sublist3 };
Dictionary<string, int> counterDictionary = new Dictionary<string, int>();
foreach (List<string> strList in listOfLists)
{
string concat = strList.Aggregate((s1, s2) => s1 + ", " + s2);
if (!counterDictionary.ContainsKey(concat))
counterDictionary.Add(concat, 1);
else
counterDictionary[concat] = counterDictionary[concat] + 1;
}
foreach (KeyValuePair<string, int> keyValue in counterDictionary)
{
Console.WriteLine(keyValue.Key + "=>" + keyValue.Value);
}
I think I will solve this with:
var equallists = list1.SequenceEqual(list2);
Therefore I compare distinct lists and lists with SequenceEquals() and counting them.
Better solutions welcome. :)

Finding an item in a complex object

I have a sequence of elements
string[]:
new string[] { "A", "B", "C", "D" }
And there is also an object consisting of such several sequences
object:
List<Test> obj = new List<Test>()
{
new Test() {Lists = new List<string>() { "A", "B" } },
new Test() {Lists = new List<string>() { "A", "C" } },
new Test() {Lists = new List<string>() { "C" } }
};
I want to find the missing element ("D") in all object collections.
That's what I got:
private static List<string> FindeMissingElements()
{
string nonExistentElement = null;
List<string> nonExistentElements = new List<string>();
foreach (var elemArr in arr)
{
foreach (var elemObj in obj)
{
if (elemObj.Lists.Any(a => a.Contains(elemArr)))
{
nonExistentElement = null;
break;
}
nonExistentElement = elemArr;
}
if (nonExistentElement != null)
nonExistentElements.Add(nonExistentElement);
}
return nonExistentElements;
}
I would like to simplify the code and use LINQ if possible...
First, I would flatten the sources into a list so I have a collection of actual values. To do this, it's best to use SelectMany (tests is the original list, and domain is the array of possible elements)
var sourceElements = tests.SelectMany(test => test.Lists);
This will get the Lists properties for each test, and join all the results together, so in your example, you'll have a result of
["A", "B", "A", "C", "C"]
You can use Distinct to only get unique items, so the code is
var sourceElements = tests.SelectMany(test => test.Lists).Distinct();
Now, the only thing left to do is to find the items that are in the domain but not in the sourceElements, i.e. their set difference. That can easily be done with the Except method.
var missing = domain.Except(sourceElements);
So, to put everything together, your method should be:
private static IEnumerable<string> FindMissingElements(
IEnumerable<Test> tests, IEnumerable<string> domain)
{
var sourceElements = tests.SelectMany(test => test.Lists).Distinct();
var missing = domain.Except(sourceElements)
return missing;
}
Here is a working example.
//Flatten the complex object - Get all the elements from the complex object into an IEnumerable collection
//Flatten the list --
var listB = (from lm in obj select lm.Lists).SelectMany(it => it.ToList());
//Below list A is input elements
//use IEnumerable except extension as below - missingElements IEnumerable will have your elements from complex object --
string[] listA = new string[] { "A", "B", "C", "D", "E" };
var missinElements = listA.Except(listB);
I think this can help you:
var list1 = new string[] { "A", "B", "C", "D" }
var nonExistentElements = new List<string>(list1);
obj.ForEach(o => nonExistentElements = nonExistentElements.Except(o.Lists).ToList());
I Try and test your question like this:
private static List<string> FindeMissingElements()
{
var objectArray = new[] {"A", "B", "C", "D"};
var obj = new List<Test>()
{
new Test() {Lists = new List<string>() { "A", "B" } },
new Test() {Lists = new List<string>() { "A", "C" } },
new Test() {Lists = new List<string>() { "C" } }
};
var missingValues = objectArray.Where(x => !obj.Any(c => c.Lists.Any(v => v == x))).ToList();
return missingValues.any() ? missingValue:missingValue = new List<string>();
}
With 1 row you can find missing value.
GoodLuck.

Find common items in list of lists of strings

Hi I have allLists that contains lists of string I want to find common items among these string lists
i have tried
var intersection = allLists
.Skip(1)
.Aggregate(
new HashSet<string>(allLists.First()),
(h, e) => { h.IntersectWith(e); return h);`
and also intersection ( hard code lists by index) all of them did not work when I tried
var inter = allLists[0].Intersect(allLists[1]).Intersect(allLists[2])
.Intersect(allLists[3]).ToList();
foreach ( string s in inter) Debug.WriteLine(s+"\n ");
So how am I going to do this dynamically and get common string items in the lists;
is there a way to avoid Linq?
Isn't this the easiest way?
var stringLists = new List<string>[]
{
new List<string>(){ "a", "b", "c" },
new List<string>(){ "d", "b", "c" },
new List<string>(){ "a", "e", "c" }
};
var commonElements =
stringLists
.Aggregate((xs, ys) => xs.Intersect(ys).ToList());
I get a list with just "c" in it.
This also handles the case if elements within each list can be repeated.
I'd do it like this:
class Program
{
static void Main(string[] args)
{
List<string>[] stringLists = new List<string>[]
{
new List<string>(){ "a", "b", "c" },
new List<string>(){ "d", "b", "c" },
new List<string>(){ "a", "e", "c" }
};
// Will contian only 'c' because it's the only common item in all three groups.
var commonItems =
stringLists
.SelectMany(list => list)
.GroupBy(item => item)
.Select(group => new { Count = group.Count(), Item = group.Key })
.Where(item => item.Count == stringLists.Length);
foreach (var item in commonItems)
{
Console.WriteLine(String.Format("Item: {0}, Count: {1}", item.Item, item.Count));
}
Console.ReadKey();
}
}
An item is a common item if it occurs in all groups hence the condition that its count must be equal to the number of groups:
.Where(item => item.Count == stringLists.Length)
EDIT:
I should have used the HashSet like in the question. For lists you can replace the SelectMany line with this one:
.SelectMany(list => list.Distinct())

Find duplicate in a list from a reference list

I'd like know if at least one element of listRef is present more than once in listA ? The other values can be present more than once.
List<string> listA = new List<string> { "A", "A", "B", "C", "D", "E" };
List<string> listRef = new List<string> { "B", "D" };
Thanks,
Try this:
bool hasRef = listref.Any(r => listA.Count(a => a == r) > 1);
I would use ToLookup method to generate Lookup<string, string> first, and then use it to check your condition:
var lookup = listA.ToLookup(x => x);
return listRef.Any(x => lookup.Contains(x) && lookup[x].Count() > 1);
You could use GroupBy and ToDictionary to achieve the same:
var groups = listA.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Count());
return listRef.Any(x => groups.ContainsKey(x) && groups[x] > 1);
something like this
var query = listRef.Where(x=>
listA.Where(a => a == x)
.Skip(1)
.Any());
listRef.ForEach(refEl => {
var count = listA.Count(aEl => aEl == refEl);
if(count > 1) {
//Do something
}
});
Finding the best performing option in this case is not simple because that depends on the number of items in the lists and the expected result.
Here's a way to do it that is performant in the face of big lists:
var appearances = listA.GroupBy(s => s)
.Where(g => g.Count() > 1)
.ToDictionary(g => g.Key, g => g.Count());
var hasItemAppearingMoreThanOnce = listRef.Any(r => appearances.ContainsKey(r));
this works
List<string> listA = new List<string> { "A", "A", "B", "C", "D", "E" };
List<string> listRef = new List<string> { "A", "D" };
foreach (var item in listRef)
{
if (listA.Where(x => x.Equals(item)).Count() > 1)
{
//item is present more than once
}
}
this can be another way to do
List<string> listA = new List<string> { "A", "A", "B", "C", "D", "E" , "D" };
List<string> listRef = new List<string> { "B", "D" };
var duplicates = listA.GroupBy(s => s).SelectMany(grp => grp.Skip(1));
var newData = duplicates.Select(i => i.ToString()).Intersect(listRef);
var result = listA.GroupBy(x=>x)
.Where(g=>g.Count()>1&&listRef.Contains(g.Key))
.Select(x=>x.First());
bool a = result.Any();
If the second list is large and can contain duplicates i would use a HashSet<string> and IntersectWith to remove possible duplicates and strings which are not in the first list from the second:
var refSet = new HashSet<string>(listRef);
refSet.IntersectWith(listA);
bool anyMoreThanOne = refSet.Any(rs => listA.ContainsMoreThanOnce(rs, StringComparison.OrdinalIgnoreCase));
Here the extension which is not very elegant but works:
public static bool ContainsMoreThanOnce(this IEnumerable<string> coll, String value, StringComparison comparer)
{
if (coll == null) throw new ArgumentNullException("col");
bool contains = false;
foreach (string str in coll)
{
if (String.Compare(value, str, comparer) == 0)
{
if (contains)
return true;
else
contains = true;
}
}
return false;
}
DEMO
However, if the second listRef isn't large or doesn't contain duplicates you can just use:
bool anyMoreThanOne = listRef
.Any(rs => listA.ContainsMoreThanOnce(rs, StringComparison.OrdinalIgnoreCase));

What is the correct way to sort List <string> using C#?

I have LIST1 <> and LIST2 <> and like to compare these two lists. Followings are my conditions..
1-If LIST1 and LIST2 have the same items than add same items to LIST3
2-If LIST1 doesnt contain LIST2 items than add different items to LIST4
3-if LIST2 doesnt contain LIST1 items than add different items to LIST5
lets say my result is like below depends on the conditions;
LIST1<string> = A,B,C,D
LIST2<string> = A,K,F,C
LIST3<string> = A,C
LIST4<string> = B,D
LIST5<string> = K,F
here is my code;
foreach (string src in LIST1)
{
foreach (string trg in LIST2)
{
if (LIST1.ToString() == LIST2.ToString())
{
LIST3.Add(LIST1.ToString());
}
else
{
LIST4.Clear();
foreach (string l3 in LIST1)
{
if (!LIST2.Contains(l3))
LIST4.Add(l3);
}
LIST5.Clear();
foreach (string l4 in LIST2)
{
if (!LIST1.Contains(l4))
{
LIST5.Add(l4);
}
}
}
}
}
A quick way to do this would be:
var list3 = list1.Intersect(list2).ToList();
var list4 = list1.Except(list2).ToList();
var list5 = list2.Except(list1).ToList();
Update: If you have to do with larger lists (and/or have to write this in multiple places), you can write an extension method like below:
public static Tuple<IEnumerable<T>, IEnumerable<T>, IEnumerable<T>> Diff<T>(
this IEnumerable<T> first, IEnumerable<T> second)
{
var intersection = new List<T>();
var onlyInFirst = new HashSet<T>();
var onlyInSecond = new HashSet<T>(second);
foreach (var item in first)
{
if (onlyInSecond.Remove(item)) intersection.Add(item);
else onlyInFirst.Add(item);
}
return Tuple.Create<IEnumerable<T>, IEnumerable<T>, IEnumerable<T>>
(intersection, onlyInFirst, onlyInSecond);
}
This method returns a tuple of three IEnumerable<T>s representing the set of intersection, set of items only in the first collection, and set of items only in the second collection; respectively.
Usage:
var list1 = new[] { "A", "B", "C", "D" };
var list2 = new[] { "A", "K", "F", "C" };
var diff = list1.Diff(list2);
// diff.Item1 = A,C (intersection)
// diff.Item2 = B,D (only in first)
// diff.Item3 = K,F (only in second)
Not sure what this has to do with sorting, but here's Linq statements for each condition:
List3 = List1.Intersect(List2).ToList();
List4 = List1.Where(l1 => !List2.Any(l2 => l2 == l1)).ToList();
List5 = List2.Where(l2 => !List1.Any(l1 => l2 == l1)).ToList();
as pointed out in comments Except will work too:
List4 = List1.Except(List2).ToList();
List5 = List2.Except(List1).ToList();

Categories