Order Objects with outer and sometimes inner position - c#

I have two classes:
public class OuterObject
{
public int OuterPosition { get; set; }
public InnerObject InnerObject { get; set; }
}
public class InnerObject
{
public int InnerPosition { get; set; }
}
I need to order a list of OuterObjects: first, after OuterPosition. Then, only OuterObjects which have a InnerObject, should be ordered after the InnerPosition of these.
For example, if I have four OuterObjects:
A { OuterPosition = 1, new InnerObject { InnerPosition = 2 } }
B { OuterPosition = 2 }
C { OuterPosition = 3, new InnerObject { InnerPosition = 1 } }
D { OuterPosition = 4 }
I should get the order:
C { OuterPosition = 3, new InnerObject { InnerPosition = 1 } }
B { OuterPosition = 2 }
A { OuterPosition = 1, new InnerObject { InnerPosition = 2 } }
D { OuterPosition = 4 }
Is there an easy way to accomplish it?

Something like this ought to work.
// First, order by outer position
var result = list.OrderBy(o => o.OuterPosition).ToList();
// Next, rearrange the items with inner objects
var outerPositions = result.Select((o, i) => new{i, o});
var withInner = outerPositions.Where(e => e.o.InnerObject != null).ToList();
var withInnerPositions = withInner.OrderBy(e => e.o.InnerObject.InnerPosition)
.Select((e, i) => new{newPosition = withInner[i].i, e.o});
foreach(var p in withInnerPositions)
{
result[p.newPosition] = p.o;
}

IEnumerable<OuterObject> result = source
.OrderBy(obj => obj.OuterPostion)
.OrderBy(obj => obj.InnerObject != null ? obj.InnerObject.InnerPosition : obj.OuterPosition)
//the second OrderBy preserves the original order in the event of ties in the second.
IEnumerable<OuterObject> result = source
.OrderBy(obj => obj.InnerObject != null ? obj.InnerObject.InnerPosition : obj.OuterPosition)
.ThenBy(obj => obj.OuterPostion)
//ThenBy breaks ties in the first order with sub-sorting.
These may not give the order stipulated in the question, but some arrangment of these conditionals and OrderBy/ThenBy can be found to do so.

Related

Filter data from a collection

I have a collection of a Parent class. Parent class has an ID property and some other class property. So, I would like to fetch those child property values on the basis of the Parent ID. I am able to fetch an item of the collection but I need a single value from that item. Below is my code:
public class Parent
{
public int Id { get; set; }
public Child MyChild { get; set; }
}
public class Child
{
public string abc { get; set; }
public string xyz { get; set; }
}
class Program
{
static void Main(string[] args)
{
var d = new List<Parent>();
d.Add(new Parent
{
Id = 1,
MyChild = new Child()
{
xyz = "XYZ one",
abc = "ABC one"
}
});
d.Add(new Parent
{
Id = 2,
MyChild = new Child()
{
xyz = "XYZ two",
abc = "ABC two"
}
});
for (int i = 1; i < 2; i++)
{
var xyzValueOfParentIdOneValue = d.SingleOrDefault(x => x.Id = 1) // Here, I want to get XYZ property value of Parent ID 1.
}
}
}
You could use this
var xyzValueOfParentIdOneValue = d.SingleOrDefault(x => x.Id == 1)
?.MyChild
?.xyz;
if (xyzValueOfParentIdOneValue != null)
{
......
}
Or
var foundItem = d.SingleOrDefault(x => x.Id == 1);
if (foundItem != null && foundItem.MyChild != null)
{
var xyzValueOfParentIdOneValue = foundItem.MyChild.xyz;
}
These two above codes are completely similar.
I think you just want to access the MyChild property of the Parent, like:
var parent = d.SingleOrDefault(x => x.Id == 1);
var xyz = parent.MyChild.xyz;
Since you want to return a default value "0" if the Parent Id doesn't exist, you could use
var xyzValueOfParentIdOneValue = d.SingleOrDefault(x => x.Id == idToSearch)?
.MyChild?
.xyz ?? "0";

Best approach to compare if one list is subset of another in C#

I have the below two classes:
public class FirstInner
{
public int Id { get; set; }
public string Type { get; set; }
public string RoleId { get; set; }
}
public class SecondInner
{
public int Id { get; set; }
public string Type { get; set; }
}
Again, there are lists of those types inside the below two classes:
public class FirstOuter
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public List<FirstInner> Inners { get; set; }
}
public class SecondOuter
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondInner> Inners { get; set; }
}
Now, I have list of FirstOuter and SecondOuter. I need to check if FirstOuter list is a subset of SecondOuter list.
Please note:
The names of the classes cannot be changed as they are from different systems.
Some additional properties are present in FirstOuter but not in SecondOuter. When comparing subset, we can ignore their presence in SecondOuter.
No.2 is true for FirstInner and SecondInner as well.
List items can be in any order---FirstOuterList[1] could be found in SecondOuterList[3], based on Id, but inside that again need to compare that FirstOuterList[1].FirstInner[3], could be found in SecondOuterList[3].SecondInner[2], based on Id.
I tried Intersect, but that is failing as the property names are mismatching. Another solution I have is doing the crude for each iteration, which I want to avoid.
Should I convert the SecondOuter list to FirstOuter list, ignoring the additional properties?
Basically, here is a test data:
var firstInnerList = new List<FirstInner>();
firstInnerList.Add(new FirstInner
{
Id = 1,
Type = "xx",
RoleId = "5"
});
var secondInnerList = new List<SecondInner>();
secondInner.Add(new SecondInner
{
Id = 1,
Type = "xx"
});
var firstOuter = new FirstOuter
{
Id = 1,
Name = "John",
Title = "Cena",
Inners = firstInnerList
}
var secondOuter = new SecondOuter
{
Id = 1,
Name = "John",
Inners = secondInnerList,
}
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
Need to check if firstOuterList is part of secondOuterList (ignoring the additional properties).
So the foreach way that I have is:
foreach (var item in firstOuterList)
{
var secondItem = secondOuterList.Find(so => so.Id == item.Id);
//if secondItem is null->throw exception
if (item.Name == secondItem.Name)
{
foreach (var firstInnerItem in item.Inners)
{
var secondInnerItem = secondItem.Inners.Find(sI => sI.Id == firstInnerItem.Id);
//if secondInnerItem is null,throw exception
if (firstInnerItem.Type != secondInnerItem.Type)
{
//throw exception
}
}
}
else
{
//throw exception
}
}
//move with normal flow
Please let me know if there is any better approach.
First, do the join of firstOuterList and secondOuterList
bool isSubset = false;
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
var jointOuterList = firstOuterList.Join(
secondOuterList,
p => new { p.Id, p.Name },
m => new { m.Id, m.Name },
(p, m) => new { FOuterList = p, SOuterList = m }
);
if(jointOuterList.Count != firstOuterList.Count)
{
isSubset = false;
return;
}
foreach(var item in jointOuterList)
{
var jointInnerList = item.firstInnerList.Join(
item.firstInnerList,
p => new { p.Id, p.Type },
m => new { m.Id, m.type },
(p, m) => p.Id
);
if(jointInnerList.Count != item.firstInnerList.Count)
{
isSubset = false;
return;
}
}
Note: I am assuming Id is unique in its outer lists. It means there will not be multiple entries with same id in a list. If no, then we need to use group by in above query
I think to break the question down..
We have two sets of Ids, the Inners and the Outers.
We have two instances of those sets, the Firsts and the Seconds.
We want Second's inner Ids to be a subset of First's inner Ids.
We want Second's outer Ids to be a subset of First's outer Ids.
If that's the case, these are a couple of working test cases:
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsTrue(isInnerSubset);
Assert.IsTrue(isOuterSubset);
}
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreNotSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
firstInnerIds.Clear();
firstInnerIds.Add(5);
firstOuterIds.Clear();
firstOuterIds.Add(5);
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsFalse(isInnerSubset);
Assert.IsFalse(isOuterSubset);
}
private List<FirstOuter> GetFirstOuterList() { ... }
private List<SecondOuter> GetSecondOuterList() { ... }

Ensuring a record has all relationships, not any

I have a linqpad script I am working on and it works but the problem is that it uses .AsEnumerable() which calls the collection into memory. There could be thousands of these some day, so I would like to keep everything deferred as much as possible.
I am trying to simply perform a check to make sure if I pass new long[] { 2, 4 } to the function, then any Experiences that have both IDs 2 and 4 will be returned.
Previously, I was using only the .Contains() but this would return Experiences that have either 2 or 4.
Is there a better way to write this code so that it would return an IQueryable<Experience> rather than a List<Experience> so I don't have to load all results into memory in order to perform the string concat?
void Main()
{
var AllExperiences = new List<_Experience>();
AllExperiences.Add(new _Experience { Id = 1, Name = "Experience 1" });
AllExperiences.Add(new _Experience { Id = 2, Name = "Experience 2" });
AllExperienceTags.Add(new _ExperienceTag { ExperienceId = 1, TagId = 2 });
AllExperienceTags.Add(new _ExperienceTag { ExperienceId = 1, TagId = 4 });
AllExperienceTags.Add(new _ExperienceTag { ExperienceId = 2, TagId = 2 });
var experiences = FilterBySelectedTags(AllExperiences, new[] { 2, 4 }.ToList());
experiences.Dump();
}
public List<_ExperienceTag> AllExperienceTags = new List<UserQuery._ExperienceTag>();
// Define other methods and classes here
public List<_Experience> FilterBySelectedTags(List<_Experience> experiences, List<int> selectedTagIds)
{
var filteredExperiencesTags = AllExperienceTags.Where(x => selectedTagIds.Contains(x.TagId));
var obj = filteredExperiencesTags.OrderBy(x => x.TagId).GroupBy(x => x.ExperienceId).AsEnumerable().Select(x => new
{
ExperienceId = x.Key,
ExpTags = string.Join(", ", x.Select(y => y.TagId))
});
var filteredTags = obj.Where(x => x.ExpTags == string.Join(", ", selectedTagIds));
// make sure all the selected tags are found in the experience, not just any
return experiences.Where(x => filteredTags.Select(y => y.ExperienceId).Contains(x.Id)).ToList();
}
public class _Experience
{
public int Id { get; set; }
public string Name { get; set; }
}
public class _ExperienceTag
{
public int ExperienceId { get; set; }
public int TagId { get; set; }
}
Experience entity should have a navigation property to ExperienceTags:
public virtual ICollection<ExperienceTag> ExperienceTags{get;set;}
If that is the case this should work:
var query= from e in Experiences
let experienceTagIds=e.ExperiencesTags.Select(et=>et.TagId)
where selectedTagIds.All(x=>experienceTagIds.Contains(x))
select e;
Assuming experiences, has a navigation property Tags on it, then you can do this:
void Main()
{
var experiences = Experiences.FilterBySelectedTags(new long[] { 2, 4 });
experiences.Dump();
}
public static class ExperienceExtensions {
public static IQueryable<Experience> FilterBySelectedTags(this IQueryable<Experience> experiences, IEnumerable<long> selectedTagIds)
{
return experiences.Where(e=>selectedTagIds.All(id=>e.Tags.Any(t=>t.TagId==id)));
}
}

LINQ query to find if all items in list 2 are contained in list 1

I was trying to follow and then extend an old example, Linq query list contains a list, but it didn't work for me.
class Part
{
public int id { get; set; }
public string name { get; set; }
}
class Program
{
static void Main(string[] args)
{
{
List<int> L1 = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> L2 = new List<int> { 4, 3 };
bool t = L2.Where(p => L2.All(q => L1.Contains(q))).Any();
}
{
List<Part> L1 = new List<Part> { new Part { id = 1 }, new Part { id = 2 }, new Part { id = 3 }, new Part { id = 4 } };
List<Part> L2 = new List<Part> { new Part { id = 3 }, new Part { id = 4 } };
bool u = L2.Where(p => L2.All(q => L1.Contains(q.id))).Any();
}
}
}
The first test works for me but doesn't exactly match the earlier code I found. My second test has a syntax error at "L1.Contains(q.id)". I'm stumped.
L1 is a List<Part>, q is a Part, q.id is an int.
L1 cannot Contain an item of type int
To check if L1 contains an item with that ID, use Any
L2.All(q => L1.Any(e => e.id == q.id))
When you use the equality operator you are checking to see if the lists contain references to the same objects. If you want to match then you have to do something like you did by choosing which values to compare on. Your syntax error is coming from the fact that you are comparing ids to Parts. If you project L1 to the ids (L1.Select(p => p.Id)) you should be good.
For "IsContained" question:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
class Part
{
public int id { get; set; }
public string name { get; set; }
}
public static void Main()
{
{
var L1 = new List<int> { 1, 2, 3, 4, 5, 6 };
var L2 = new List<int> { 4, 3 };
bool t = L2.All(l2 => L1.Contains(l2));
Console.WriteLine("L1 contains L2: {0}", t);
}
{
var L1 = new List<Part> { new Part { id = 1 }, new Part { id = 2 }, new Part { id = 3 }, new Part { id = 4 } };
var L2 = new List<Part> { new Part { id = 3 }, new Part { id = 4 } };
bool u = L2.All(l2 => L1.Any(l1 => l1.id == l2.id));
Console.WriteLine("L1 contains L2: {0}", u);
}
}
}
You can't use Contains there because the type is a List<Part>, at least not without some kind of projection or a different argument. If you're going to commonly be intersecting lists with these types I recommend implementing IEquatable<T> for part like so;
class Part : IEquatable<Part>
{
public int id { get; set; }
public string name { get; set; }
public bool Equals(Part other)
{
//Check whether the compared object is null.
if (Object.ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (Object.ReferenceEquals(this, other)) return true;
//Check whether the products' properties are equal.
return id.Equals(other.id) && name.Equals(other.name);
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public override int GetHashCode()
{
//Get hash code for the Name field if it is not null.
int nameHash = name == null ? 0 : name.GetHashCode();
int idHash = id.GetHashCode();
//Calculate the hash code for the part.
return nameHash ^ idHash;
}
}
In doing so you can simply do L1.Intersect(L2) and get the expected results. If you're just going to do this once probably simpler to write something like; L2.All(q => L1.Any(e => e.id == q.id)) though I wouldn't recommend repeated use of that in place of Intersect.

LINQ Query to Filter Items By Criteria From Multiple Lists

I'm having trouble conceptualizing something that should be fairly simple using LINQ. I have a collection that I want to narrow down, or filter, based on the id values of child objects.
My primary collection consists of a List of Spots. This is what a spot looks like:
public class Spot
{
public virtual int? ID { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual string TheGood { get; set; }
public virtual string TheBad { get; set; }
public virtual IEnumerable<Season> Seasons { get; set; }
public virtual IEnumerable<PhotographyType> PhotographyTypes { get; set; }
}
I'm trying to filter the list of Spots by PhotographyType and Season. I have a list of ids for PhotographyTypes and Seasons, each in an int[] array. Those lists look like this:
criteria.PhotographyTypeIds //an int[]
criteria.SeasonIds //an int[]
I want to build a collection that only contains Spots with child objects (ids) matching those in the above lists. The goal of this functionality is filtering a set of photography spots by type and season and only displaying those that match. Any suggestions would be greatly appreciated.
Thanks everyone for the suggestions. I ended up solving the problem. It's not the best way I'm sure but it's working now. Because this is a search filter, there are a lot of conditions.
private List<Spot> FilterSpots(List<Spot> spots, SearchCriteriaModel criteria)
{
if (criteria.PhotographyTypeIds != null || criteria.SeasonIds != null)
{
List<Spot> filteredSpots = new List<Spot>();
if (criteria.PhotographyTypeIds != null)
{
foreach (int id in criteria.PhotographyTypeIds)
{
var matchingSpots = spots.Where(x => x.PhotographyTypes.Any(p => p.ID == id));
filteredSpots.AddRange(matchingSpots.ToList());
}
}
if (criteria.SeasonIds != null)
{
foreach (int id in criteria.SeasonIds)
{
if (filteredSpots.Count() > 0)
{
filteredSpots = filteredSpots.Where(x => x.Seasons.Any(p => p.ID == id)).ToList();
}
else
{
var matchingSpots = spots.Where(x => x.Seasons.Any(p => p.ID == id));
filteredSpots.AddRange(matchingSpots.ToList());
}
}
}
return filteredSpots;
}
else
{
return spots;
}
}
You have an array of IDs that has a Contains extension method that will return true when the ID is in the list. Combined with LINQ Where you'll get:
List<Spot> spots; // List of spots
int[] seasonIDs; // List of season IDs
var seasonSpots = from s in spots
where s.ID != null
where seasonIDs.Contains((int)s.ID)
select s;
You can then convert the returned IEnumerable<Spot> into a list if you want:
var seasonSpotsList = seasonSpots.ToList();
This may helps you:
List<Spot> spots = new List<Spot>();
Spot s1 = new Spot();
s1.Seasons = new List<Season>()
{ new Season() { ID = 1 },
new Season() { ID = 2 },
new Season() { ID = 3 }
};
s1.PhotographyTypes = new List<PhotographyType>()
{ new PhotographyType() { ID = 1 },
new PhotographyType() { ID = 2 }
};
Spot s2 = new Spot();
s2.Seasons = new List<Season>()
{ new Season() { ID = 3 },
new Season() { ID = 4 },
new Season() { ID = 5 }
};
s2.PhotographyTypes = new List<PhotographyType>()
{ new PhotographyType() { ID = 2 },
new PhotographyType() { ID = 3 }
};
List<int> PhotographyTypeIds = new List<int>() { 1, 2};
List<int> SeasonIds = new List<int>() { 1, 2, 3, 4 };
spots.Add(s1);
spots.Add(s2);
Then:
var result = spots
.Where(input => input.Seasons.All
(i => SeasonIds.Contains(i.ID))
&& input.PhotographyTypes.All
(j => PhotographyTypeIds.Contains(j.ID))
).ToList();
// it will return 1 value
Assuming:
public class Season
{
public int ID { get; set; }
//some codes
}
public class PhotographyType
{
public int ID { get; set; }
//some codes
}

Categories