Merge two lists of objects which hold different data - c#

I have an object Project which has a number of fields, one of which is Name. I have one spreadsheet of projects which contains some of these fields, and another which contains the rest. However, both spreadsheets have the Name field.
I've read them in and populated two List<Project>, only populating the available fields from that particular source. E.g. a Project from List 1 looks like:
{Name="MyProj", Type="Form", Priority=NULL}
Whereas a Project from List 2:
{Name="MyProj", Type=NULL, Priority="High"}
Now, I want to merge these two lists into one in which each Project object has all of its fields populated, with the Name field being used to match the elements.
How can I achieve this? Are there any nice ways of doing this concisely?
Thanks

I would probably use the ?? operator to find the values. A dictionary could be useful.
// put one into a dict for faster access
var dict2 = list2.ToDictionaty(x => x.Name, x);
// merge the lists using ??
var merged = list1.Select(x =>
{
var p2 = dict2[x.Name];
return new Project()
{
Name = x.Name,
Type = x.Type ?? p2.Type,
Priority = x.Priority ?? p2.Priority
}
});
Or
var merged = list1
// join the lists by the name
.Join(list2, x => x.Name, x => x.Name, (p1, p2) => new { P1 = p1, P2 = p2 } )
.Select(x =>
new Project()
{
Name = P1.Name,
Type = P1.Type ?? P2.Type,
Priority = P1.Priority ?? P2.Priority
});
There are hundred of variants of this. You could only join and and process the result in a foreach to merge the data of one list into the other. Etc.
It gets more complicated when the projects could be missing in one of the lists, and even more complicated if none of the lists is complete. Then it could be useful to create a complete list of project names at the beginning.

Related

Clone new object of old object without old reference in C#

I have a huge object with a lot of attributes and child objects. Because of a poorly designed database which I can't control, I need to find matching objects in allCourses with the attribute CourseType = "SVYE". For those who matches the condition, I want to change all values from "SVYE" to "SVYR" instead and add them to the original object allCourses.
I realized that when you declare svCourse you still have the old references in courses which will cause all objects with the value on CourseType = "SVYR". Instead of every match should be one with CourseType = "SVYE" and one with CourseType = "SVYR".
How could I create a copy of the matching values without having the reference to allCourses in the new var svCourses without declaring every attribute again?
new Course(){
name = a.name
// etc..
}
My code:
var svCourses = allCourses.Where(x => x.Occasions
.Any(y => y.CourseType.Equals("SVYE")))
.ToList();
foreach(var svCourse in svCourses)
{
foreach(var o in svCourse.Occasions)
{
o.CourseType = "SVYR";
}
allCourses.Add(svCourse);
}
return allCourses;
If I understand you correctly, you want to keep Courses that have Occasions with CourseType.Equals("SVYE") unchanged in your collection and add a copy of that courses with modified only CourseType (CourseType = "SVYR"). Right?
If so, you must somehow deep-clone the svCourses collection with 3-rd party library:
Json.NET NuGet:
var svCourses = allCourses.Select(x => new
{
Course = x,
MatchedOccasions = x.Occasions
.Where(y => string.Equals(y.CourseType, "SVYE"))
.ToArray()
})
.Where(x => x.MatchedOccasions.Length > 0)
.ToList();
var clonedSvCourses = JArray.FromObject(svCourses.Select(x => x.Course).ToList()).ToObject<List<Course>>();
allCourses.AddRange(clonedSvCourses);
foreach(var occasion in svCourses.SelectMany(x => x.MatchedOccasions))
{
occasion.CourseType = "SVYR";
}
return allCourses;
FastDeepCloner NuGet I think, can be used too.
I found a solution on my own!
I used a third-party library called CloneExtensions. This library could be used to create a deep copy of your object without declaring every attribute. This new variable newCourses which is a deep copy of svCourses doesn't have the old reference to allCourses, which solves the problem.
This solution will replace o.CourseType = "SVYR"; for all ocassions in the variable newCourses.
var svCourses = allCourses.Where(x => x.Occasions
.Any(y => y.CourseType.Equals("SVYE")))
.ToList();
var newCourses = svCourses.Select(x =>
CloneExtensions.CloneFactory.GetClone(x));
foreach(var svCourse in newCourses)
{
foreach(var o in svCourse.Occasions)
{
o.CourseType = "SVYR";
}
allCourses.Add(svCourse);
}
return allCourses;
Any means Determines whether any element of a sequence satisfies a condition.
What you need is
List all occasions of all courses.
Choose occasions that CourseType = SVYE
var svCourses = allCourses
.SelectMany(c => c.Occasions)
.Where(o => o.CourseType.Equals("SVYE"))
.ToList();

Filtering a List using a separate List of values

I have a List of Vehicles with various fields such as registration, age, engine size, etc. One of these fields is a "tag" field which in itself is a List of tags.
I am trying to filter this list of vehicles to only show the ones which include a tag which matches a value in a separate neededTags list.
I am trying to do this using Linq and Lambda expressions.
I have managed to get this working in situations where the Tag field in the main List is just a normal string field, NOT a list of strings. The code for this is here:
filteredVehicles = Vehicles.Where(x => neededTags.Any(y => y == x.tags)).ToList();
where neededTags is my list of tags that I am interested in.
My problem now is that if the Tag element in the vehicles list is actually a list of tags then the compare element above says "Operator '==' cannot be applied to operands of type 'string' and 'List'"
I think I need to compare every element in the Vehicles Tag list with those in the neededTags list, but I just do not know how to do this.
Any assistance is greatly appreciated.
You want to check if the intersection of the vehicle's list of tags and the list of needed tags has any elements (if there is at least on element in vehicle's tags that is also in needed tags):
filteredVehicles = Vehicles.Where(v => v.Tags.Intersect(neededTags).Any()).ToList();
If the vehicle's tag may be null, you can use the null-conditional operator
filteredVehicles = Vehicles.Where(v => v.Tags?.Intersect(neededTags).Any() == true).ToList();
You can't compare a string to a list of strings for equality. Use the .Contains method to check for that:
// this is the short form for: .Where(x => neededTags.Any(y => x.tags.Contains(y)))
filteredVehicles = Vehicles.Where(x => neededTags.Any(x.tags.Contains)).ToList();
I think Rene V has the best answer, but this also works and I've see it done this way in other posts. You can just use the Any method on both lists. I added test data also.
filteredVehicles = Vehicles.Where(v => (v.tags ?? new List<string>()).Any(t => neededTags.Any(n => n == t))).ToList();
Test Code:
var neededTags = new List<string> { "tag1", "tag2" };
var Vehicles = new[] {
new {name = "vehicle1", tags = new List<string> { "tag1" } },
new {name = "vehicle2", tags = new List<string> { "tag5" } }
}.ToList();
var filteredVehicles = Vehicles.Where(v => (v.tags ?? new List<string>()).Any(t => neededTags.Any(n => n == t))).ToList();

How to store the result of a linq query in a KeyDictionary variable

So I have a collection of objects who have multiple properties, two of these are groupname and personname. Now I need to count in the collection how many of each object belong to a certain group and person. So in other words, I need to group by groupname, then personname and then count how many objects have this combination. First I created this
public MultiKeyDictionary<string, string, int> GetPersonsPerGroup(IEnumerable<Home> homes ,List<string> gr, List<string> na)
{
List<string> groups = gr;
groups.Add("");
List<string> names = na;
names.Add("");
List<Home> Filtered = homes.ToList();
Filtered.ForEach(h => h.RemoveNull());
var result = new MultiKeyDictionary<string, string, int>();
int counter1 = 0;
foreach (var g in groups)
{
int counter2 = 0;
foreach (var n in names)
{
int counter3 = 0;
foreach (Home h in Filtered)
{
if (h.GroupName == g && h.PersonName == n)
{
counter3++;
if (counter3 > 100)
break;
}
}
if (counter3 > 0)
{
result.Add(g,n,counter3);
}
counter2++;
}
counter1++;
}
Which may look good, but the problem is that the "home" parameter can contain more than 10000 objects, with more than 1500 unique names and around 200 unique groups. Which causes this to iterate like a billion times really slowing my program down. So I need an other way of handling this. Which made me decide to try using linq. Which led to this creation:
var newList = Filtered.GroupBy(x => new { x.GroupName, x.PersonName })
.Select(y => (MultiKeyDictionary<string, string, int>)result.Add(y.Key.GroupName, y.Key.PersonName, y.ToList().Count));
Which gives an error "Cannot convert type 'void' to 'MultiKeyDictionary<string,string,int>' and I have no idea how to solve it. How can I make it so that the result of this query gets stored all in one MultikeyDictionary without having to iterate over each possible combination and counting all of them.
Some information:
MultiKeyDictionary is a class I defined (something I found on here actually), it's just a normal dictionary but with two keys assosiated to one value.
The RemoveNull() method on the Home object makes sure that all the properties of the Home object are not null. If it is the case the value gets sets to something not null ("null", basic date, 0, ...).
The parameters are:
homes = a list of Home objects received from an other class
gr = a list of all the unique groups in the list of homes
na = a list of all the unique names in the list of homes
The same name can occur on different groups
Hopefully someone can help me get further!
Thanks in advance!
Select must return something. You are not returning but only adding to an existing list. Do this instead:
var newList = Filtered.GroupBy(x => new { x.GroupName, x.PersonName }):
var result = new MultiKeyDictionary<string, string, int>);
foreach(var y in newList)
{
result.Add(y.Key.GroupName, y.Key.PersonName, y.ToList().Count));
}
The reason you are getting error below:
"Cannot convert type 'void' to 'MultiKeyDictionary'
is because you are trying to cast the returned value from Add which is void to MultiKeyDictionary<string,string,int> which clearly cannot be done.
If MultiKeyDictionary requires the two keys to match in order to find a result, then you might want to just use a regular Dictionary with a Tuple as a composite type. C# 7 has features that make this pretty easy:
public Dictionary<(string, string), int> GetPersonsPerGroup(IEnumerable<Home> homes ,List<string> gr, List<string> na)
{
return Filtered.GroupBy(x => (x.GroupName, x.PersonName))
.ToDictionary(g => g.Key, g => g.Count);
}
You can even associate optional compile-time names with your tuple's values, by declaring it like this: Dictionary<(string groupName, string personName), int>.
Your grouping key anonymous object should work fine as a standard Dictionary key, so no reason to create a new type of Dictionary unless it offers special access via single keys, so just convert the grouping to a standard Dictionary:
var result = Filtered.GroupBy(f => new { f.GroupName, f.PersonName })
.ToDictionary(fg => fg.Key, fg => fg.Count());

Splitting LINQ results into array based on column

I am trying to select a few columns from a single row using LINQ to Entities and then split each column into its own point of an array.
I have the LINQ query getting the results I want, but I can't figure out how to split it into the array. I thought using .ToArray() would work but it doesn't split them.
var LinkData = db.Sections
.Where(s => s.ID == SectionID)
.Select(s => new
{
s.Type,
s.Route
}).ToArray();
How can I split the results from the query so I have a single array of two elements: one for Type and one for Route?
Your Select-statement already creates a list of two-value-items which are stored in instances of anonymous type. So there is no need to create a new two-dimensional array for this. Your linkdata already contains the data you want, however if you´re after one specific combination of (Type, Route) simply call linkedData[myIndex].Route or linkedData[myIndex].Type respectively.
EDIT: If you really want arrays then the following should work:
var arr = linkedData.Select(x => new[] { x.Rote, x.Type }).ToArray();
Which will give you an array of arrays where every element itself contains an array of two elements.
var section = db.Sections
.Where(s => s.ID == SectionID)
.Select(s => new
{
s.Type,
s.Route
})
.SingleOrDefault();
var LinkData = new [] {section.Type, section.Route};
Use a List<> instead
Thats what i would do.
Create a public class of Link Data so:
public class LinkData
{
public string type {get; set;}
public string Route {get;set;}
}
Then in your code
create a List of the Link Data:
List<LinkData> LinkDataList = new List<LinkData>();
then create an object
LinkData obj = new LinkData
add stuff to the object
obj.type = db.Section.Where(s => s.ID==SectionID).Select s=> s.Type new s.Type).SingleOrDefault();
obj.Route = db.Section.Where(s => s.ID==SectionID).Select s=> s.Route new s.Type).SingleOrDefault();;
LinkDataList.Add(obj)
This should give you a clear indication of whats what :)

Checking two lists for Modifications

I have got two lists of two different type which has the following common properties.
Id -->used to identify corresponding objects
Bunch of other Properties
ModificationDate
Need to compare these two lists based on Modification date.If the modified date is different (first list ModificationDate greater than second list's ModificationDate then, copy all the properties if that item from first list to second.
Please let me know the best way to do this.
EDITED:Second list may or maynot contain all elements of the first and vice versa.My first list is always the source list. so if an item is present in list 1 and not present in list 2 we need to add it in list 2. also if an item present in list 2 but in not in list 1 then remove it from list2.
Finding added/deleted items
var list1 = new List<MyType>();
var list2 = new List<MyType>();
// These two assume MyType : IEquatable<MyType>
var added = list1.Except(list2);
var deleted = list2.Except(list1);
// Now add "added" to list2, remove "deleted" from list2
If MyType does not implement IEquatable<MyType>, or the implementation is not based solely on comparing ids, you will need to create an IEqualityComparer<MyType>:
class MyTypeIdComparer : IEqualityComparer<MyType>
{
public bool Equals(MyType x, MyType y)
{
return x.Id.CompareTo(y.Id);
}
public int GetHashCode(MyType obj)
{
return obj.Id.GetHashCode();
}
}
Which will allow you to do:
// This does not assume so much for MyType
var comparer = new MyTypeIdComparer();
var added = list1.Except(list2, comparer);
var deleted = list2.Except(list1, comparer);
Finding modified items
var modified = list1.Concat(list2)
.GroupBy(item => item.Id)
.Where(g => g.Select(item => item.ModificationDate)
.Distinct().Count() != 1);
// To propagate the modifications:
foreach(var grp in modified) {
var items = grp.OrderBy(item => item.ModificationDate);
var target = items.First(); // earliest modification date = old
var source = grp.Last(); // latest modification date = new
// And now copy properties from source to target
}
This might be able to help. The Linq library has lots of decent functions, such as Except, Intersection.
http://msdn.microsoft.com/en-us/library/bb397894.aspx
The provided link was helpful in comparing two lists of different types
Comparing Collections in .Net

Categories

Resources