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.
Related
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. :)
I have a following List, I need to iterate through the list and see if the list has identical elements in it and return only a unique list. Could anyone please let me know what's wrong with the following code and a proper way to do it?
Also, Linq way to do it, if any?
Expected Solution would be = {{"a", "b", "c"},{"e", "b", "c" }}
class Program1
{
static void Main(string[] args)
{
List<string>[] stringLists = new List<string>[3]
{
new List<string>(){ "a", "b", "c" },
new List<string>(){ "e", "b", "c" },
new List<string>(){ "a", "b", "c" }
};
List<List<string>> prt = new List<List<string>>();
/* I DONT UNDERSTAND WHY THIS IS NOT WORKING !!!!!!!!!!!!!!! */
foreach (var item in stringLists)
{
for (int i = 0; i < item.Count; i++)
{
if (item == stringLists[i] && (!prt.Contains(item)))
{
prt.Add(item);
}
}
}
}
}
You can try good old Distinct with a custom IEqualityComparer<T>:
using System.Linq;
...
public class SequenceComparer<T> : IEqualityComparer<IEnumerable<T>> {
public bool Equals(IEnumerable<T> x, IEnumerable<T> y) {
return Enumerable.SequenceEqual(x, y);
}
//TODO: Suboptimal HashCode implementation
public int GetHashCode(IEnumerable<T> obj) {
return obj == null
? 0
: obj.Count();
}
}
...
var stringLists = new List<string>() {
new List<string> {"a", "b", "c"},
new List<string> {"e", "b", "c"},
new List<string> {"a", "b", "c"}
};
// All you have to do is to put Distinct
var result = stringLists
.Distinct(new SequenceComparer<string>())
.ToList(); // If you want materialization
Test:
Console.Write(string.Join(Environment.NewLine, result
.Select(list => string.Join(", ", list))));
Outcome:
a, b, c
e, b, c
You need to do union on all the list and than do a distinct.
Something like this, just iterate through the list and union it with last result:
List<string> result = new List<string>();
foreach (var list in stringLists)
{
result = result.Union(list).ToList();
}
result = result.Distinct().ToList();
prt.Contains(item) expression compares List's with their reference not by their elements, so it's a bad choice for determining if two lists are duplicates.
Try following
var stringLists = new List<string>[3]
{
new List<string> {"a", "b", "c"},
new List<string> {"e", "b", "c"},
new List<string> {"a", "b", "c"}
};
var prt = new List<List<string>>();
foreach (var item in stringLists)
{
if(prt.All(it => it.Count != item.Count || it.Except(item).Any()))
prt.Add(item);
}
Dmitry Bychenko's answer is the way to go.
For your special case and with data that does not contain , you could get away with:
using System;
using System.Linq;
using System.Collections.Generic;
public class Program1
{
public static void Main()
{
var stringLists = new List<List<string>>{
new List<string> {"a", "b", "c"},
new List<string> {"e", "b", "c"},
new List<string> {"a", "b", "c"}
};
var prt = stringLists
.Select(l => string.Join(",", l)) // make it a string separated by ,
.Distinct() // distinct it using string.Distinct()
.Select(l => l.Split(',').ToList()); // split it again at , and make it List
foreach (var p in prt)
{
foreach (var c in p)
Console.WriteLine(c);
Console.WriteLine();
}
}
}
There is lots of un-needed object creation in this approach - but i would work (until your data contains a , - then is messes your lists up).
Output:
a
b
c
e
b
c
Your misunderstanding is that you expect the statement
prt.Contains(item)
to return true when the sequence of strings in item already exists in prt. However the test internal to Contains used to determine this is a reference equality, not a item by item equality. Here is a minimal example to illustrate this:
void Main()
{
Console.Write( (new []{new []{ "a", "b", "c" }}).Contains(new[] { "a", "b", "c" }));
// Prints false
}
You either need to use a deep equals comparer like #Dmitry's answer which creates a digest (hash) of each list and compares the digests, or to do it explicitly, like this code:
class Program1
{
static void Main(string[] args)
{
List<string>[] stringLists = new List<string>[3]
{
new List<string>(){ "a", "b", "c" },
new List<string>(){ "e", "b", "c" },
new List<string>(){ "a", "b", "c" }
};
List<List<string>> prt = new List<List<string>>();
for(int i = 0; i < 3; i++)
{
bool isDifferentFromAllOthers = true;
for(int j = 0; j < i; j++)
{
bool isSameAsThisItem = true;
for(int item = 0; item < 3; item++)
{
// !!! Here is the explicit item by item string comparison
if (stringLists[i][item] != stringLists[j][item])
{
isSameAsThisItem = false;
break;
}
}
if (isSameAsThisItem)
{
isDifferentFromAllOthers = false;
break;
}
}
if (isDifferentFromAllOthers)
{
prt.Add(stringLists[i]);
}
}
// /* I DONT UNDERSTAND WHY THIS IS NOT WORKING !!!!!!!!!!!!!!! */
// foreach (var item in stringLists)
// {
// for (int i = 0; i < item.Count; i++)
// {
// if (item == stringLists[i] && (!prt.Contains(item)))
// {
// prt.Add(item);
// }
// }
// }
}
}
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())
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" }
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()));