Count of flattened parent child association in LINQ - c#

I'm trying to get a count of parents with no children plus parents children. As I write this I realize it is better explained with code.. So, here it goes:
With these example types:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public string Description { get; set; }
}
And this data:
var customers = new List<Customer>
{
new Customer
{
Id = 2,
Name = "Jane Doe"
},
new Customer
{
Id = 1,
Name = "John Doe",
Orders = new List<Order>
{
new Order { Id = 342, Description = "Ordered a ball" },
new Order { Id = 345, Description = "Ordered a bat" }
}
}
};
// I'm trying to get a count of customer orders added with customers with no orders
// In the above data, I would expect a count of 3 as detailed below
//
// CId Name OId
// ---- -------- ----
// 2 Jane Doe
// 1 John Doe 342
// 1 John Doe 345
int customerAndOrdersCount = {linq call here}; // equals 3
I am trying to get a count of 3 back.
Thank you in advance for your help.
-Jessy Houle
ADDED AFTER:
I was truly impressed with all the great (and quick) answers. For others coming to this question, looking for a few options, here is a Unit Test with a few of the working examples from below.
[TestMethod]
public void TestSolutions()
{
var customers = GetCustomers(); // data from above
var count1 = customers.Select(customer => customer.Orders).Sum(orders => (orders != null) ? orders.Count() : 1);
var count2 = (from c in customers from o in (c.Orders ?? Enumerable.Empty<Order>() ).DefaultIfEmpty() select c).Count();
var count3 = customers.Sum(c => c.Orders == null ? 1 : c.Orders.Count());
var count4 = customers.Sum(c => c.Orders==null ? 1 : Math.Max(1, c.Orders.Count()));
Assert.AreEqual(3, count1);
Assert.AreEqual(3, count2);
Assert.AreEqual(3, count3);
Assert.AreEqual(3, count4);
}
Again, thank you all for your help!

How about
int customerAndOrdersCount = customers.Sum(c => c.Orders==null ? 1 : Math.Max(1, c.Orders.Count()));

If you would initialize that Order property with an empty list instead of a null, you could do:
int count =
(
from c in customers
from o in c.Orders.DefaultIfEmpty()
select c
).Count();
If you decide to keep the uninitialized property around, then instead do:
int count =
(
from c in customers
from o in (c.Orders ?? Enumerable.Empty<Order>() ).DefaultIfEmpty()
select c
).Count();

customers
.Select(customer => customer.Order)
.Sum(orders => (orders != null) ? orders.Count() : 1)

This works if you want to count "no orders" as 1 and count the orders otherwise:
int customerOrders = customers.Sum(c => c.Orders == null ? 1 : c.Orders.Count());
By the way, the question is very exemplary.

You probabbly searching for something like this:
customers.GroupBy(customer=>customer). //group by object iyself
Select(c=> //select
new
{
ID = c.Key.Id,
Name = c.Key.Name,
Count = (c.Key.Orders!=null)? c.Key.Orders.Count():0
}
);

var orderFreeCustomers = customers.Where(c=>c.Orders== null || c.Orders.Any()==false);
var totalOrders = customers.Where (c => c.Orders !=null).
Aggregate (0,(v,e)=>(v+e.Orders.Count) );
Result is the sum of those two values

Related

Sort descending by salary where teacher year = 3

I have a list of teachers and I want to sort in descending order by salary teachers who have years of work experience = 3.
I want experience != 3 to keep their index (keep their position) and only sorting by salary teacher have experience = 3
Please help me to solve this problem.
class Teacher
{
public int id { get; set; }
public string name { get; set; }
public int year { get; set; }
public double salary { get; set; }
public Teacher()
{
}
public Teacher(int id, string name, int year, double salary)
{
this.id = id;
this.name = name;
this.year = year;
this.salary = salary;
}
}
List<Teacher> teacher = new List<Teacher>();
teacher.Add(new Teacher(1, "Teacher A", 4, 2000));
teacher.Add(new Teacher(2, "Teacher B", 3, 3000));
teacher.Add(new Teacher(3, "Teacher C", 5, 5000));
teacher.Add(new Teacher(4, "Teacher D", 3, 4000));
teacher.Add(new Teacher(5, "Teacher E", 3, 7000));
Output:
1, Teacher A, 4, 2000
5, Teacher E, 3, 7000
3, Teacher C, 5, 5000
4, Teacher D, 3, 4000
2, Teacher B, 3, 3000
Ugly Solution, but working:
Mind: Conversion to Array is not neccessary.
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<Teacher> teacher = new List<Teacher>();
teacher.Add(new Teacher(1, "Teacher A", 4, 2000));
teacher.Add(new Teacher(2, "Teacher B", 3, 3000));
teacher.Add(new Teacher(3, "Teacher C", 5, 5000));
teacher.Add(new Teacher(4, "Teacher D", 3, 4000));
teacher.Add(new Teacher(5, "Teacher E", 3, 7000));
var teachArr = teacher.ToArray();
// Create separate List of only those teacher, you want to re-order
// So, filter and sort.
var threeYearTeachArr = teacher
.Where(t => t.year == 3) // Filter
.OrderByDescending(t => t.salary) // Sort
.ToArray(); // Do it!
// Then replace all filtered items in the original collection
// with the sorted ones. => Only filtered will change places.
// We traverse 2 arrays, so we create two indexes and check both against their
// respective collection sizes, but we increment only the "original"
for( int i = 0, threes = 0; i < teachArr.Length && threes < threeYearTeachArr.Length; i++ )
{
// only if the current entry is one of those we sorted...
if( teachArr[i].year == 3 )
{
// ... replace it with the next entry in the sorted list.
// post-increment: use threes' value, then increment
teachArr[i] = threeYearTeachArr[threes++];
}
}
foreach( var t in teachArr )
{
Console.WriteLine($"{t.id} {t.name} | {t.year} | {t.salary}");
}
}
}
class Teacher
{
public int id { get; set; }
public string name { get; set; }
public int year { get; set; }
public double salary { get; set; }
public Teacher()
{
}
public Teacher(int id, string name, int year, double salary)
{
this.id = id;
this.name = name;
this.year = year;
this.salary = salary;
}
}
Output:
1 Teacher A | 4 | 2000
5 Teacher E | 3 | 7000
3 Teacher C | 5 | 5000
4 Teacher D | 3 | 4000
2 Teacher B | 3 | 3000
See in action: https://dotnetfiddle.net/AaIqzE
A simple and naive solution would be to just do a simple bubble sort where you only consider the year 3 teachers:
for (int i1 = 0; i1 < teacher.Count; i1++)
{
if (teacher[i1].year != 3)
continue;
for (int i2 = i1 + 1; i2 < teacher.Count; i2++)
{
if (teacher[i2].year != 3)
continue;
if (teacher[i1].salary > teacher[i2].salary)
(teacher[i1], teacher[i2]) = (teacher[i2], teacher[i1]);
}
}
This will have a performance characteristic of O(n^2) so it will perform badly if you have a lot of teachers. Fildor has a better solution, I'm just presenting an alternative.
Interesting puzzle.
My first thought is to pair the list with their indices, then split the list into pass/fail based on your filter criteria: teacher.year == 3. Then we can order the pass list, fix up the indices separately, and finally re-merge the pass and fail data back together.
Wow, sounds complex. Let's try it and see how it looks:
List<Teacher> SortYear3(IEnumerable<Teacher> source)
{
var indexed = source.Select((teacher, index) => (index, teacher)).ToArray();
var pass = indexed.Where(pair => pair.teacher.year == 3);
var passIndices = pass.Select(pair => pair.index).ToArray();
var passOrdered = pass.Select(pair => pair.teacher).OrderByDescending(teacher => teacher.salary).ToArray();
var reindex = Enumerable.Range(0, passIndices.Length).Select(i => (index: passIndices[i], teacher: passOrdered[i]));
var merged = indexed.Where(pair => pair.teacher.year != 3).Concat(reindex).OrderBy(p => p.index);
return merged.Select(pair => pair.teacher).ToList();
}
Well... it works, but mostly as an example of when LINQ is not the answer. And those intermediate arrays are a bit ugly, so let's not.
The next thought is to pull out the items you want to sort, sort them into an array, then feed them back in while adding items to a result list:
List<Teacher> SortYear3(List<Teacher> source)
{
var sorted = source.Where(t => t.year == 3).OrderByDescending(t => t.salary).ToArray();
var result = new List<Teacher>();
for (int i = 0, sortindex = 0; i < source.Count; i++)
{
var next = source[i];
if (next.year == 3)
result.Add(sorted[sortindex++]);
else
result.Add(next);
}
return result;
}
Down to one array allocation, but it still looks a little clunky. Let's copy the list to start with and just replace the ones that we sorted:
List<Teacher> SortYear3(List<Teacher> source)
{
var sorted = source.Where(t => t.year == 3).OrderByDescending(t => t.salary).ToArray();
var result = source.ToList();
for (int i = 0, sortindex = 0; i < result.Count; i++)
{
if (result[i].year == 3)
result[i] = sorted[sortindex++];
}
return result;
}
That looks much better... and is now almost exactly what #fildor wrote. Well, that's embarrassing. Let's spice it up a little: make it generic, give it some parameters to specify the filtering and sorting, etc.
IEnumerable<T> SortSelected<T, TKey>(IEnumerable<T> source, Func<T, bool> filter, Func<T, TKey> sortKey, bool descending = true)
{
var result = source.ToList();
var filtered = result.Where(filter);
var sorted = (descending ? filtered.OrderByDescending(sortKey) : filtered.OrderBy(sortKey)).ToArray();
for (int i = 0, j = 0; j < sorted.Count; i++)
{
if (filter(result[i]))
result[i] = sorted[j++];
}
return result;
}
List<Teacher> SortYear3(List<Teacher> source)
=> SortSelected(source, t => t.year == 3, t => t.salary, true).ToList();
(OK, so maybe I shouldn't answer these things when I've been up for more than 24 hours.)
Please check this answer, it is much more easier to understand and more optimised
using System;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<Teacher> teacher = new List<Teacher>();
teacher.Add(new Teacher(1, "Teacher A", 4, 2000));
teacher.Add(new Teacher(2, "Teacher B", 3, 3000));
teacher.Add(new Teacher(3, "Teacher C", 5, 5000));
teacher.Add(new Teacher(4, "Teacher D", 3, 4000));
teacher.Add(new Teacher(5, "Teacher E", 3, 7000));
var expTeacher=teacher.Where(x=>x.year==3).OrderByDescending(x=>x.salary).ToList();
for(int i=0,j=0;i<teacher.Count && j<expTeacher.Count;i++)
{
if(teacher[i].year==3)
{
teacher[i]= expTeacher[j];
j++;
}
}
foreach(var teach in teacher)
{
Console.WriteLine(teach.id+", "+teach.name+", "+teach.year+", "+teach.salary);
}
}
}
class Teacher
{
public int id { get; set; }
public string name { get; set; }
public int year { get; set; }
public double salary { get; set; }
public Teacher()
{
}
public Teacher(int id, string name, int year, double salary)
{
this.id = id;
this.name = name;
this.year = year;
this.salary = salary;
}
}
I'm just guessing with the answer because in general you question is not clear either in requirement as the output which I assume is that what you are already getting.
According to response, at first what came to my head was
var t2 = teachers.Where(t => t.year == 3).OrderByDescending(t => t.salary);
var t3 = teachers.Where(t => !t2.Select(ts => ts.id).Contains(t.id));
var final = t2.Concat(t3);
Yes, it is not optimal an probably there is a better way to achieve that, but it gives output as needed (?)
Teacher = 5 Teacher E 3 7000
Teacher = 4 Teacher D 3 4000
Teacher = 2 Teacher B 3 3000
Teacher = 1 Teacher A 4 2000
Teacher = 3 Teacher C 5 5000
I understood and solved it by my way. Fildor give me the idea
List<Coach> sorted = coaches.Where(x => x.YearOfExperience == 3).OrderByDescending(x => x.Salary).ToList();
List<Coach> originalList = coaches;
int index = 0;
for (int i = 0; i < originalList.Count; i++)
{
if (originalList[i].YearOfExperience == 3)
{
originalList[i] = sorted[index++];
}
}
foreach (var item in originalList)
{
item.show();
}
If you really want to filter your list for teachers having 3 years of experience then you can simply apply Where extension method using linq.
var requiredTeachers=teacher.Where(x=>x.year==3).OrderByDescending(x=>x.salary).ToList();

How to loop through the properties of anonymous type in a list

If I have the following LINQ query:
var outstandingDataTotalData = from t1 in dtTotal.AsEnumerable()
join t2 in dtOutstandingData.AsEnumerable() on
new
{
priv_code = t1["priv_code"],
pri_ded = t1["pri_ded"].ToString().Trim()
} equals
new
{
priv_code = t2["priv_code"],
pri_ded = t2["pri_ded"].ToString().Trim()
}
into ps
from t2 in ps.DefaultIfEmpty()
select new
{
adjustment_value = t2 == null ? string.Empty : t2["adjustment_value"].ToString(),
amount_outstanding = t2 == null ? string.Empty : t2["amount_outstanding"].ToString(),
amount_outstanding_priv = t2 == null ? string.Empty : t2["amount_outstanding_priv"].ToString(),
amount_outstanding_ded = t2 == null ? string.Empty : t2["amount_outstanding_ded"].ToString(),
diff_outstanding = t2 == null ? string.Empty : t2["diff_outstanding"].ToString(),
exchange_rate = t2 == null ? string.Empty : t2["exchange_rate"].ToString(),
SalYear = t2 == null ? string.Empty : t2["sal_year"].ToString(),
SalMonth = t2 == null ? string.Empty : t2["sal_mon"].ToString()
};
Now outstandingDataTotalData is a list of anonymous type. And I have the following class:
public class AdjustmentTotal
{
public string SalYear { get; set; }
public string SalMonth { get; set; }
public string Value { get; set; }
}
How to loop through outstandingDataTotalData properties to fill List<AdjustmentTotal> as the following example:
If the result set of outstandingDataTotalData =
[0]{ adjustment_value = "100.00", amount_outstanding = "80.00", amount_outstanding_priv = "60.00", amount_outstanding_ded = "30.52", diff_outstanding = "0.36", exchange_rate = "", SalYear = "2018", SalMonth = "1" }
[1]{ adjustment_value = "1500.00", amount_outstanding = "5040.00", amount_outstanding_priv = "", amount_outstanding_ded = "", diff_outstanding = "0.36", exchange_rate = "", SalYear = "2018", SalMonth = "1" }
I want the result set of List<AdjustmentTotal> as:
2018 1 100.00
2018 1 1500.00
2018 1 80.00
2018 1 5040.00
2018 1 60.00
2018 1
2018 1 30.52
2018 1
2018 1 0.36
2018 1 0.36
2018 1
2018 1
Make your life easy, don't extract to separate properties. Make the extract as an array:
select new {
someArray = new[]{
t2["adjustment_value"].ToString(),
t2["amount_outstanding"].ToString(),
t2["amount_outstanding_priv"].ToString(),
t2["amount_outstanding_ded"].ToString(),
...
},
SalYear = ...,
}
So you end up with an object with 3 properties, two strings SalXxx and an array of strings (the other values). The array of strings means you can use the LINQ SelectMany it to flatten it. You'll see in the msdn example they have owners with lists of pets (variable number of pets but your values are fixed number) and after selectmany it's flattened to a list where the owner repeats and there is one pet. Your lowercases values are the pets, the SalXxx values are the owners
Once you get a working query you can actually integrate it into the first query ..
Sorry for not posting a full example (and I skipped the null checks for clarity) - the code is very hard to work with on a cellphone
Edit:
So, you say you want the results in a particular order. Both Select and SelectMany have a version where they will give the index of the item, and we can use that.. because you basically want to have these objects:
var obj = new [] {
new { SalYear = 2018, SalMonth = 1, C = new[] { "av1", "ao1", "aop1", "aod1" } },
new { SalYear = 2018, SalMonth = 2, C = new[] { "av2", "ao2", "aop2", "aod2" } }
};
Be like av1, av2, ao1, ao2.. so we want to sort the results first by the index of the inner array, then by the index of the outer array
We use a SelectMany to dig out the inner array and then for every item in the inner array we make a new object that has the data and the indexes (of the inner and the outer):
obj.SelectMany((theOuter, outerIdx) =>
theOuter.C.Select((theInner, innerIdx) =>
new {
SalYear = theOuter.SalYear,
SalMonth = theOuter.SalMonth,
DataItem = theInner,
OuterIdx = outerIdx,
InnerIdx = innerIdx
}
)
).OrderBy(newObj => newObj.InnerIdx).ThenBy(newObj => newObj.OuterIdx)
You will probably find you don't need the ThenBy; sorting by the InnerIdx will leave a tie (every InnerIdx in my list is represented twice -there are two innerIdx=0 etc) and things in linq sort as far as they can then no futher - because theyre sorted by OuterIdx already (when they went into the query) they should remain sorted by OuterIdx after they tie on InnerIdx.. If that makes sense. Belt and braces!
outstandingDataTotalData.Select(s => new AdjustmentTotal {
SalYear = s.SalYear,
SalMonth = s.SalMonth,
Value = s.adjustment_value
}).ToList();
Use a eum :
class Program
{
static void Main(string[] args)
{
List<AdjustmentTotal> totals = new List<AdjustmentTotal>();
for (int i = 0; i < (int)VALUE.END; i++)
{
foreach (var data in outstandingDataTotalData)
{
AdjustmentTotal total = new AdjustmentTotal();
totals.Add(total);
total.SalMonth = data.SalMonth;
total.SalYear = data.SalYear;
total._Type = (VALUE)i;
switch ((VALUE)i)
{
case VALUE.adjustment_value :
total.Value = data.adjustment_value;
break;
case VALUE.amount_outstanding:
total.Value = data.amount_outstanding;
break;
case VALUE.amount_outstanding_ded:
total.Value = data.mount_outstanding_ded;
break;
case VALUE.amount_outstanding_priv:
total.Value = data.amount_outstanding_priv;
break;
case VALUE.diff_outstanding:
total.Value = data.diff_outstanding;
break;
case VALUE.exchange_rate:
total.Value = data.exchange_rate;
break;
}
}
}
}
}
public enum VALUE
{
adjustment_value = 0,
amount_outstanding = 1,
amount_outstanding_priv = 2,
amount_outstanding_ded = 3,
diff_outstanding = 4,
exchange_rate = 5,
END = 6
}
public class AdjustmentTotal
{
public string SalYear { get; set; }
public string SalMonth { get; set; }
public string Value { get; set; }
public VALUE _Type { get; set; }
}

Convert data collection into nested hierarchical object list

I'm wanting to make an API call that gets all the unique survey IDs and put them into an array with total answer counts based on the unique answer value and list of user ids. For example: ICollection<Survey>
ID Survey_Id Answer User
1 Apple_Survey 1 Jones
2 Apple_Survey 1 Smith
3 Banana_Survey 2 Smith
4 Apple_Survey 3 Jane
5 Banana_Survey 2 John
The API result I currently have:
{Data: [
{
survey_id: "Apple_Survey",
answer: "1",
user: "Jones"
},
...
]}
Where I get stuck is in the code to process the data:
foreach (var info in data
.GroupBy(x => x.Survey_Id)
.Select(group => new { SurveyId = group.Key,
Count = group.Count() }) )
{
Console.WriteLine("{0} {1}", info.SurveyId, info.Count);
//Result: Apple_Survey 3 Banana_Survey 2
}
Ideal results:
{Data: [
{
survey_id: "Apple_Survey",
answers: [//Example: rating answer would be 1-10, not an ID
{answer: "1", count: 2, users: ["Jones", "Smith"]},
{answer: "3", count: 1, users: ["Jane"]}
]
},
...
]}
How can I get the distinct answers based on survey_id and the list of users based on the answer? Any help would be greatly appreciated!
See if following helps :
class Program
{
static void Main(string[] args)
{
List<Survey> surveys = new List<Survey>() {
new Survey() { ID = 1, Survey_Id = "Apple_Survey", Answer = 1, User = "Jones"},
new Survey() { ID = 2, Survey_Id = "Apple_Survey", Answer = 1, User = "Smith"},
new Survey() { ID = 3, Survey_Id = "Banana_Survey", Answer = 2, User = "Smith"},
new Survey() { ID = 4, Survey_Id = "Apple_Survey", Answer = 3, User = "Jane"},
new Survey() { ID = 5, Survey_Id = "Banana_Survey", Answer = 2, User = "John"}
};
var results = surveys.GroupBy(x => x.Survey_Id).Select(x => x.GroupBy(y => y.Answer)
.Select(y => new { answer = y.Key, count = y.Count(), users = y.Select(z => z.User).ToList()}).ToList())
.ToList();
}
}
public class Survey
{
public int ID { get; set; }
public string Survey_Id { get; set; }
public int Answer { get; set; }
public string User { get; set; }
}
A simple way is based on sql only.. you could use a query as :
select Survey_Id, Answer, COUNT(*) answer_count, group_concat(user) answer_user
from my_table
group Survey_Id, Answer
I'd go for
table.GroupBy( x => x.Survey_Id ).Select( x => new { Survey_Id=x.Key, Answers=x.GroupBy( y => y.Answer ).Select( y => new { Answer=y.Key, Count=y.Count(), Users=y.Select( z => z.User)})} )
That creates an ienumerable of pairs of a survey and an ienumerable of answers, each with its count and an ienumerable of the users that voted for that answer.
Try it out on dotnetfiddle.net!

Add duplicates together in List

First question :)
I have a List<Materiau> (where Materiau implements IComparable<Materiau>), and I would like to remove all duplicates and add them together
(if two Materiau is the same (using the comparator), merge it to the first and remove the second from the list)
A Materiau contains an ID and a quantity, when I merge two Materiau using += or +, it keeps the same ID, and the quantity is added
I cannot control the input of the list.
I would like something like this:
List<Materiau> materiaux = getList().mergeDuplicates();
Thank you for your time :)
Check out Linq! Specifically the GroupBy method.
I don't know how familiar you are with sql, but Linq lets you query collections similarly to how sql works.
It's a bit in depth to explain of you are totally unfamiliar, but Code Project has a wonderful example
To sum it up:
Imagine we have this
List<Product> prodList = new List<Product>
{
new Product
{
ID = 1,
Quantity = 1
},
new Product
{
ID = 2,
Quantity = 2
},
new Product
{
ID = 3,
Quantity = 7
},
new Product
{
ID = 4,
Quantity = 3
}
};
and we wanted to group all the duplicate products, and sum their quantities.
We can do this:
var groupedProducts = prodList.GroupBy(item => item.ID)
and then select the values out of the grouping, with the aggregates as needed
var results = groupedProducts.Select( i => new Product
{
ID = i.Key, // this is what we Grouped By above
Quantity = i.Sum(prod => prod.Quantity) // we want to sum up all the quantities in this grouping
});
and boom! we have a list of aggregated products
Lets say you have a class
class Foo
{
public int Id { get; set; }
public int Value { get; set; }
}
and a bunch of them inside a list
var foocollection = new List<Foo> {
new Foo { Id = 1, Value = 1, },
new Foo { Id = 2, Value = 1, },
new Foo { Id = 2, Value = 1, },
};
then you can group them and build the aggregate on each group
var foogrouped = foocollection
.GroupBy( f => f.Id )
.Select( g => new Foo { Id = g.Key, Value = g.Aggregate( 0, ( a, f ) => a + f.Value ) } )
.ToList();
List<Materiau> distinctList = getList().Distinct(EqualityComparer<Materiau>.Default).ToList();

Linq: find similar objects from two different lists

I've got two separate lists of custom objects. In these two separate lists, there may be some objects that are identical between the two lists, with the exception of one field ("id"). I'd like to know a smart way to query these two lists to find this overlap. I've attached some code to help clarify. Any suggestions would be appreciated.
namespace ConsoleApplication1
{
class userObj
{
public int id;
public DateTime BirthDate;
public string FirstName;
public string LastName;
}
class Program
{
static void Main(string[] args)
{
List<userObj> list1 = new List<userObj>();
list1.Add(new userObj()
{
BirthDate=DateTime.Parse("1/1/2000"),
FirstName="John",
LastName="Smith",
id=0
});
list1.Add(new userObj()
{
BirthDate = DateTime.Parse("2/2/2000"),
FirstName = "Jane",
LastName = "Doe",
id = 1
});
list1.Add(new userObj()
{
BirthDate = DateTime.Parse("3/3/2000"),
FirstName = "Sam",
LastName = "Smith",
id = 2
});
List<userObj> list2 = new List<userObj>();
list2.Add(new userObj()
{
BirthDate = DateTime.Parse("1/1/2000"),
FirstName = "John",
LastName = "Smith",
id = 3
});
list2.Add(new userObj()
{
BirthDate = DateTime.Parse("2/2/2000"),
FirstName = "Jane",
LastName = "Doe",
id = 4
});
List<int> similarObjectsFromTwoLists = null;
//Would like this equal to the overlap. It could be the IDs on either side that have a "buddy" on the other side: (3,4) or (0,1) in the above case.
}
}
}
I don't know why you want a List<int>, i assume this is what you want:
var intersectingUser = from l1 in list1
join l2 in list2
on new { l1.FirstName, l1.LastName, l1.BirthDate }
equals new { l2.FirstName, l2.LastName, l2.BirthDate }
select new { ID1 = l1.id, ID2 = l2.id };
foreach (var bothIDs in intersectingUser)
{
Console.WriteLine("ID in List1: {0} ID in List2: {1}",
bothIDs.ID1, bothIDs.ID2);
}
Output:
ID in List1: 0 ID in List2: 3
ID in List1: 1 ID in List2: 4
You can implement your own IEqualityComparer<T> for your userObj class and use that to run a comparison between the two lists. This will be the most performant approach.
public class NameAndBirthdayComparer : IEqualityComparer<userObj>
{
public bool Equals(userObj x, userObj y)
{
return x.FirstName == y.FirstName && x.LastName == y.LastName && x.BirthDate == y.BirthDate;
}
public int GetHashCode(userObj obj)
{
unchecked
{
var hash = (int)2166136261;
hash = hash * 16777619 ^ obj.FirstName.GetHashCode();
hash = hash * 16777619 ^ obj.LastName.GetHashCode();
hash = hash * 16777619 ^ obj.BirthDate.GetHashCode();
return hash;
}
}
}
You can use this comparer like this:
list1.Intersect(list2, new NameAndBirthdayComparer()).Select(obj => obj.id).ToList();
You could simply join the lists on those 3 properties:
var result = from l1 in list1
join l2 in list2
on new {l1.BirthDate, l1.FirstName, l1.LastName}
equals new {l2.BirthDate, l2.FirstName, l2.LastName}
select new
{
fname = l1.FirstName,
name = l1.LastName,
bday = l1.BirthDate
};
Instead of doing a simple join on just one property (column), two anonymous objects are created new { prop1, prop2, ..., propN}, on which the join is executed.
In your case we are taking all properties, except the Id, which you want to be ignored and voila:
Output:
And Tim beat me to it by a minute
var similarObjectsFromTwoLists = list1.Where(x =>
list2.Exists(y => y.BirthDate == x.BirthDate && y.FirstName == x.FirstName && y.LastName == x.LastName)
).ToList();
This is shorter, but for large list is more efficient "Intersect" or "Join":
var similarObjectsFromTwoLists =
list1.Join(list2, x => x.GetHashCode(), y => y.GetHashCode(), (x, y) => x).ToList();
(suposing GetHashCode() is defined for userObj)
var query = list1.Join (list2,
obj => new {FirstName=obj.FirstName,LastName=obj.LastName, BirthDate=obj.BirthDate},
innObj => new {FirstName=innObj.FirstName, LastName=innObj.LastName, BirthDate=innObj.BirthDate},
(obj, userObj) => (new {List1Id = obj.id, List2Id = userObj.id}));
foreach (var item in query)
{
Console.WriteLine(item.List1Id + " " + item.List2Id);
}

Categories