Find common items in list of lists of strings - c#

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())

Related

How can I create a list of each item in another list, and the count of how many times that item appears?

So I have a list of items -> A, B, C, D.
C and D are included more than once, and A and B, more than twice. This list can go on and on, so we do not know how many times an item will be included.
I need to create a new list that will have the item in one column and the number of instances of that item in another column, but I do not know how to do this. I may need to use a tuple or a class, but I am not fully sure how to implement either...
What you actually need is to Group the items of your list and perform a group operation, which is Count in your case to calculate how many times does it exist.
This is how you may initialize your list:
List<string> myList = new List<string>() { "A", "B", "C", "D", "A", "B", "C", "D", "A", "B" };
and then you will group it using GroupBy function and apply the Count aggregate function on each group.
myList
.GroupBy(item => item)
.Select(g => new {Key = g.Key, Count = g.Count()})
.ToList();
This will result in the table you need.
You can try like this:
var myList = new List<String>() { "A","B", "C", "D","A","B", "C", "D", "A","B"};
var grp = myList.GroupBy( x => x );
foreach( var g in grp )
{
Console.WriteLine( "{0} {1}", g.Key, g.Count() );
}
DOTNET FIDDLE
char[] items = new[] { 'A', 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A', 'B' };
Dictionary<char, int> counts = new();
foreach(char c in items)
{
if (counts.TryGetValue(c, out int n))
{
counts[c] = n + 1;
}
else
{
counts.Add(c, 1);
}
}
While not a one liner, a simple and fast option.
I may need to use a tuple or a class, but I am not fully sure how to implement either...
Since you mentioned you may want to use a class, here is an example:
public class TextCount
{
public string Text { get; set; }
public int Count { get; set; }
}
public class Program
{
public static void Main()
{
// Initialize the list of strings
List<string> data = new List<string> { "A", "B", "C", "D", "A", "B", "C", "D", "A", "B" };
// Use LINQ to group the strings by their value and count the number of occurrences of each string
List<TextCount> result = data
.GroupBy(s => s)
.Select(g => new TextCount { Text = g.Key, Count = g.Count() })
.ToList();
// Print the results
foreach (TextCount sc in result)
{
Console.WriteLine("{0}: {1}", sc.Text, sc.Count);
}
}
}
Demo: https://dotnetfiddle.net/2FRBbK

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. :)

C# - TextLogger

My English isn't that good.
I try to make a TextLogger, for example:
if I have two different arrays:
string[] array1 = {"a", "b", "c", "d"}
string[] array2 = {"y", "c", "h", "f"}
and I have the char "c" in both of the arrays, then both of the char "c" should be removed.
Output:
a, b, d, h, y, f
this is what I managed to do so far:
string[] array1 = {"a", "b", "c", "d"}
string[] array2 = {"y", "c", "h", "f"}
for(int i = 0; i < array1.Length; i++)
{
if(array1[i] == array2[i])
{
}
}
edit(sorry for keep changing my question):
and how I can do it with this:
ArrayList array1 = new ArrayList();
array1.Add("a");
array1.Add("b");
array1.Add("c");
array1.Add("d");
ArrayList array2 = new ArrayList();
array1.Add("y");
array1.Add("c");
array1.Add("h");
array1.Add("f");
1- Get common items using Enumerable.Intersect
2- replace each array by the same array except common items using Enumerable.Except
string[] array1 = { "a", "b", "c", "d" };
string[] array2 = { "y", "c", "h", "f" };
var intersect = array1.Intersect(array2); // 1
array1 = array1.Except(intersect).ToArray(); //2
array2 = array2.Except(intersect).ToArray(); //2
Edit: to take into account double values as mentioned in the comment:
string[] array1 = { "a", "b", "b", "b", "c", "d" };
string[] array2 = { "y", "b", "c", "h", "f" };
var grpArray1 = array1.GroupBy(a => a)
.Select(grp => new { item = grp.Key, count = grp.Count() });
var grpArray2 = array2.GroupBy(a => a)
.Select(grp => new { item = grp.Key, count = grp.Count() });
array1 = grpArray1.Select(a =>
{
var bCount = array2.Count(x => x.Equals(a.item));
return new { item = a.item, finalCount = a.count - bCount };
})
.Where(a => a.finalCount > 0)
.SelectMany(a => Enumerable.Repeat(a.item, a.finalCount))
.ToArray();
array2 = grpArray2.Select(a =>
{
var bCount = array1.Count(x => x.Equals(a.item));
return new { item = a.item, finalCount = a.count - bCount };
})
.Where(a => a.finalCount > 0)
.SelectMany(a => Enumerable.Repeat(a.item, a.finalCount))
.ToArray();
Console.WriteLine("-->array1:");
foreach (var item in array1)
Console.WriteLine(item);
Console.WriteLine("-->array2:");
foreach (var item in array2)
Console.WriteLine(item);
The results:
-->array1:
a
b
b
d
-->array2:
y
h
f
If if understand your problem correctly, you don't need to change that arrays (array1 and array2) but get a result from both of them.
so, you can solve your problem using the GroupBy method
string[] array1 = { "a", "b", "c", "d" };
string[] array2 = { "y", "c", "h", "f" };
var filteredArray = array1.Concat(array2).GroupBy(x => x).Where(x => x.Count() == 1).Select(x=>x.Key);
Console.WriteLine(string.Join(" ", filteredArray));
Console.ReadLine();
what we can see here, is concat the arrays into 1 list, then group by the chars into groups, and then filter the groups that contain more than 1 element, and in the end revert it into list of chars (instead of list of groups)
Edit:
out of the comment about the duplicated "b" inside each of the arrays, i created a new (with little bit more complexity) that works for your case:
string[] array1 = { "a", "b", "b", "c", "d" };
string[] array2 = { "y", "c", "h", "f" };
var filteredArray = array1.GroupBy(x => x)
.Concat(array2.GroupBy(x => x))
.GroupBy(x => x.Key)
.Where(x => x.Count() == 1).SelectMany(x => x.Key);
Console.WriteLine(string.Join(" ", filteredArray));
Console.ReadLine();
whats happen there? we group the arrays each for himself, then concat the groups together, and then we group the groups by their keys, and the filter where each group contain more then 1 inner group, and in the end we select the groups keys (in addition it's promise us there is only 1 instance of each char)
Hope that helps!
The following code snippet should provide you a clear insight about how to perform your task:
String[] array1 = new String[] {"a", "b", "c", "d"};
String[] array2 = new String[] {"y", "c", "h", "f"};
String[] n1 = array1.Where(x => !array2.Contains(x)).ToArray();
String[] n2 = array2.Where(x => !array1.Contains(x)).ToArray();
Console.WriteLine("Array 1");
foreach (String s in n1)
Console.WriteLine(s);
Console.WriteLine("\nArray 2");
foreach (String s in n2)
Console.WriteLine(s);
Console.ReadLine();
The output is:
Array 1
a
b
d
Array 2
y
h
f
and can see a working demo by visiting this link. For more information concerning the methods I used in order to accomplish, visit the following links:
Enumerable.Contains
Enumerable.ToArray
Enumerable.Where

Find most common combination of elements in several arrays

I have several arrays, like:
var arr1 = new[] { "A", "B", "C", "D" };
var arr2 = new[] { "A", "D" };
var arr3 = new[] { "A", "B", };
var arr4 = new[] { "C", "D" };
var arr5 = new[] { "B", "C", "D" };
var arr6 = new[] { "B", "A", };
... etc.
How can I get most common combination of elements in all of those arrays?
In this case it is A and B, because they occur in arr1, arr3 and arr6, and C and D, because they occur in arrays arr1, arr4 and arr5.
Just to mention that elements can be in any kind of collection, ie. in ArrayLists also.
UPDATE uuhhh, I was not clear enough...... Most common combinations of two elements in an array. That's what I tried to show in example, but did not mention in my question.
Sorry
:-((
If you are sure that each item appears only once in each array, you could just concatenate them together and get the counts, for example:
var arrs = new[] { arr1, arr2, arr3, arr4, arr5, arr6 };
var intermediate = arrs.SelectMany(a => a)
.GroupBy(x => x)
.Select(g => new { g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count);
var maxCount = intermediate.First().Count;
var results = intermediate.TakeWhile(x => x.Count == maxCount);
Or if you prefer query syntax, that would be:
var arrs = new[] { arr1, arr2, arr3, arr4, arr5, arr6 };
var intermediate =
from a in arrs.SelectMany(a => a)
group a by a into g
orderby g.Count() descending
select new { g.Key, Count = g.Count() };
var maxCount = intermediate.First().Count;
var results = intermediate.TakeWhile(x => x.Count == maxCount);
The result set will contain 3 items:
Key, Count
"A", 4
"B", 4
"D", 4
Update
Given your updated question, something like this should work:
var items = arrs.SelectMany(a => a).Distinct();
var pairs =
from a in items
from b in items
where a.CompareTo(b) < 0
select new { a, b };
var results =
(from arr in arrs
from p in pairs
where arr.Contains(p.a) && arr.Contains(p.b)
group arr by p into g
orderby g.Count() descending
select g.Key)
.First();
The logic here is:
First find all distinct items in any array
Then find every pair of items to search for
Get of every pair, grouped by a list of what arrays contain that pair
Order by the groups by the number of arrays that contain each pair, descending
Return the first pair
use a Dictionary which will store an element as an index, and the occurrence count as a value. Iterate each list and count the occurrences.
var arr1 = new[] { "A", "B", "C", "D" };
var arr2 = new[] { "A", "D" };
var arr3 = new[] { "A", "B", };
var arr4 = new[] { "C", "D" };
var arr5 = new[] { "B", "C", "D" };
var arr6 = new[] { "B", "A", };
var results = new List<IEnumerable<string>>() { arr1, arr2, arr3, arr4, arr5, arr6 }
.Select(arr => arr.Distinct())
.SelectMany(s => s)
.GroupBy(s => s)
.Select(grp => new { Text = grp.Key, Count = grp.Count() })
.OrderByDescending(t => t.Count)
.ToList();
Gives you {A, 4}, {B, 4}, {D, 4}, {C, 3}
var result = new IEnumerable<String>[] {arr1, arr2, arr3, arr4, arr5, arr6}
.SelectMany(a => a)
.GroupBy(s => s)
.GroupBy(g => g.Count())
.OrderByDescending(g => g.Key)
.FirstOrDefault()
.SelectMany(g => g.Key);
Your question is unclear as you have not clearly defined what you are looking for. In general, you could combine all the arrays into one large array and count the distinct elements. By then ordering the elements you can do whatever you intend to do with the "most common".
static void Main()
{
var arr1 = new[] { "A", "B", "C", "D" };
var arr2 = new[] { "A", "D" };
var arr3 = new[] { "A", "B", };
var arr4 = new[] { "C", "D" };
var arr5 = new[] { "B", "C", "D" };
var arr6 = new[] { "B", "A", };
List<string> combined = Combine(arr1, arr2, arr3, arr4, arr5, arr6);
var ordered = combined.OrderBy(i => i);//sorted list will probably help other functions work more quickly such as distinct
var distinct = ordered.Distinct();
var counts = new Dictionary<string, int>();
foreach (var element in distinct)
{
var count = ordered.Count(i => i == element);
counts.Add(element, count);
}
var orderedCount = counts.OrderByDescending(c => c.Value);
foreach (var count in orderedCount)
{
Console.WriteLine("{0} : {1}", count.Key, count.Value);
}
Console.ReadLine();
}
private static List<string> Combine(string[] arr1, string[] arr2, string[] arr3, string[] arr4, string[] arr5, string[] arr6)
{
List<string> combined = new List<string>();
combined.AddRange(arr1);
combined.AddRange(arr2);
combined.AddRange(arr3);
combined.AddRange(arr4);
combined.AddRange(arr5);
combined.AddRange(arr6);
return combined;
}
Outputs: A : 4, B : 4, D : 4, C : 3

Add Range of items to list without duplication

I have a List of List of Strings, and I need to use the AddRange() Function to add set of items to it, but never duplicate items.
I used the following code :
List<List<string>> eList = new List<List<string>>();
List<List<string>> mergedList = new List<List<string>>();
//
// some code here
//
mergedList.AddRange(eList.Where(x => !mergedList.Contains(x)).ToList());
However it does not work.
All Duplicated items are added, so how could I solve that?
A)
If what you mean from duplicate is both lists contain the same elements int the same order, then
List<List<string>> eList = new List<List<string>>();
eList.Add(new List<string>() { "a", "b" });
eList.Add(new List<string>() { "a", "c" });
eList.Add(new List<string>() { "a", "b" });
var mergedList = eList.Distinct(new ListComparer()).ToList();
public class ListComparer : IEqualityComparer<List<string>>
{
public bool Equals(List<string> x, List<string> y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(List<string> obj)
{
return obj.Take(5).Aggregate(23,(sum,s)=> sum ^= s.GetHashCode());
}
}
B)
If the order of elements in the list is not important, then
List<List<string>> eList = new List<List<string>>();
eList.Add(new List<string>() { "a", "b" }); <--
eList.Add(new List<string>() { "a", "c" });
eList.Add(new List<string>() { "b", "a" }); <--
var mergedList = eList.Select(x => new HashSet<string>(x))
.Distinct(HashSet<string>.CreateSetComparer()).ToList();
Try following LINQ query
mergeList.AddRange( eList.Where (x => mergeList.Where ( y => y.Intersect(x)).Any()));

Categories