Nested classes query and join - linq

I have the following situation, in this case I am looking flattening the nested objects and combine parent and child fields in a single row, instead of creating objects parent and child.
Also I want to combine v1 and v2. Any Advice?
public class Person
{
public string idPerson;
public string names;
private List<Subject> subjects = new List<Subject>();
private List<MedicalRecord> medicalRecords = new List<MedicalRecord>();
}
public class Subject
{
public string subjectName;
public string subjectId;
}
public class MedicalRecord
{
public int recordId;
public string doctorName;
public string medicalCare;
}
void main()
{
/// Assume that Person is filled.
/// How can I join the two lists?
List<Person> all = new List<Person>();
var v1 = all.SelectMany(p => p.subjects, (child, parent) => new { child, parent });
var v2 = all.SelectMany(p => p.medicalRecords, (child, parent) => new { child, parent }); /// here i want to select all fields instead of objects to avoid child and parent calling from below cycle.
/// I want to join v1 and v2 and flatten the lists.
foreach(var obj in v1)
{
Console.WriteLine(obj.child.subjectName + " " + obj.parent.idPerson);
}
///
}

You need to do as follows:
static void Main(string[] args)
{
List<Person> all = new List<Person>();
// Estract the flattened list of subjects
var subjects = all.SelectMany(p => p.Subjects, (person, subject) => new { person.idPerson, person.names, subject});
// Estract the flattened list of medical records
var medicalRecords = all.SelectMany(p => p.MedicalRecords, (person, medicalRecord) => new { person.idPerson, person.names, medicalRecord});
// Join the two lists and create a flattened object
var flattenedList =
subjects.Join(medicalRecords,
s => s.idPerson,
m => m.idPerson,
(s, m) => new {
IdPerson = s.idPerson,
Names = s.names,
s.subject.SubjectId,
s.subject.SubjectName,
m.medicalRecord.RecordId,
m.medicalRecord.DoctorName,
m.medicalRecord.MedicalCare
})
.ToList();
}

Related

Grouped custom object with a list property

I have a list of customObject, I want to group the "CustomObject" by the List property of the CustomObject object.
public class CustomObject
{
public string Name { get; set; }
public List<string> List { get; set; }
public CustomObject(string name, List<string> list)
{
this.Name = name;
this.List = list;
}
}
.....................
List<CustomObject> listCustomObject = new List<CustomObject>()
{
new CustomObject("A", new List<string>(){ "1","2","3", "4"} ),
new CustomObject("B", new List<string>(){ "4","8","5"}),
new CustomObject("C", new List<string>(){ "5","1","2", "4"})
};
Desired results :
"A"/"C" => identical item in the list ("1", "2")
"A"/"B"/"C" => identical item in the list ("4")
"B"/"C" => identical item in the list ("5")
Using some extension methods, you can generate all combinations of the inputs having at least two members:
public static IEnumerable<IEnumerable<T>> AtLeastCombinations<T>(this IEnumerable<T> elements, int minK) => Enumerable.Range(minK, elements.Count()+1-minK).SelectMany(k => elements.Combinations(k));
public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int k) {
return k == 0 ? new[] { new T[0] } :
elements.SelectMany((e, i) =>
elements.Skip(i + 1).Combinations(k - 1).Select(c => (new[] { e }).Concat(c)));
}
Now you can simply test each combination to see if they have any common elements:
var ans = listCustomObject.AtLeastCombinations(2)
.Select(c => new { CombinationNames = c.Select(co => co.Name).ToList(), CombinationIntersect = c.Select(co => co.List).Aggregate((sofar, coList) => sofar.Intersect(coList).ToList()) })
.Where(ci => ci.CombinationIntersect.Count > 0)
.ToList();

Intersection of arrays in LINQ to CosmosDB

I'm trying find all items in my database that have at least one value in an array that matches any value in an array that I have in my code (the intersection of the two arrays should not be empty).
Basically, I'm trying to achieve this :
public List<Book> ListBooks(string partitionKey, List<string> categories)
{
return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => b.Categories.Any(c => categories.Contains(c))
.ToList();
}
With the Book class looking like this :
public class Book
{
public string id {get;set;}
public string Title {get;set;}
public string AuthorName {get;set;}
public List<string> Categories {get;set;}
}
However the SDK throws an exception saying that Method 'Any' is not supported when executing this code.
This doesn't work either :
return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => categories.Any(c => b.Categories.Contains(c))
.ToList();
The following code works because there's only one category to find :
public List<Book> ListBooksAsync(string category)
{
return _client.CreateDocumentQuery<Book>(GetCollectionUri())
.Where(b => b.Categories.Contains(category))
.ToList();
}
In plain SQL, I can queue multiple ARRAY_CONTAINS with several OR the query executes correctly.
SELECT * FROM root
WHERE ARRAY_CONTAINS(root["Categories"], 'Humor')
OR ARRAY_CONTAINS(root["Categories"], 'Fantasy')
OR ARRAY_CONTAINS(root["Categories"], 'Legend')
I'm trying to find the best way to achieve this with LINQ, but I'm not even sure it's possible.
In this situation I've used a helper method to combine expressions in a way that evaluates to SQL like in your final example. The helper method 'MakeOrExpression' below lets you pass a number of predicates (in your case the individual checks for b.Categories.Contains(category)) and produces a single expression you can put in the argument to .Where(expression) on your document query.
class Program
{
private class Book
{
public string id { get; set; }
public string Title { get; set; }
public string AuthorName { get; set; }
public List<string> Categories { get; set; }
}
static void Main(string[] args)
{
var comparison = new[] { "a", "b", "c" };
var target = new Book[] {
new Book { id = "book1", Categories = new List<string> { "b", "z" } },
new Book { id = "book2", Categories = new List<string> { "s", "t" } },
new Book { id = "book3", Categories = new List<string> { "z", "a" } } };
var results = target.AsQueryable()
.Where(MakeOrExpression(comparison.Select(x => (Expression<Func<Book, bool>>)(y => y.Categories.Contains(x))).ToArray()));
foreach (var result in results)
{
// Should be book1 and book3
Console.WriteLine(result.id);
}
Console.ReadLine();
}
private static Expression<Func<T,bool>> MakeOrExpression<T>(params Expression<Func<T,bool>>[] inputExpressions)
{
var combinedExpression = inputExpressions.Skip(1).Aggregate(
inputExpressions[0].Body,
(agg, x) => Expression.OrElse(agg, x.Body));
var parameterExpression = Expression.Parameter(typeof(T));
var replaceParameterVisitor = new ReplaceParameterVisitor(parameterExpression,
Enumerable.SelectMany(inputExpressions, ((Expression<Func<T, bool>> x) => x.Parameters)));
var mergedExpression = replaceParameterVisitor.Visit(combinedExpression);
var result = Expression.Lambda<Func<T, bool>>(mergedExpression, parameterExpression);
return result;
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private readonly IEnumerable<ParameterExpression> targetParameterExpressions;
private readonly ParameterExpression parameterExpression;
public ReplaceParameterVisitor(ParameterExpression parameterExpressionParam, IEnumerable<ParameterExpression> targetParameterExpressionsParam)
{
this.parameterExpression = parameterExpressionParam;
this.targetParameterExpressions = targetParameterExpressionsParam;
}
public override Expression Visit(Expression node)
=> targetParameterExpressions.Contains(node) ? this.parameterExpression : base.Visit(node);
}
}

Sort List of Parent / Child Objects

I am having some trouble figuring out how to efficiently sort a list of parent items based on the child items.
I cannot just sort the child items. I need the result of the child item sort to affect the sorting of the parent list.
Essentially what I'm trying to do is sort the Parents in an order that reflects their children's name in descending order.
Is there a "linqish" way to do this once I already have a list of parents in memory? If so, any help you could afford would be great.
Here is an example....
//What I am trying to do is to figure out how to sort the order of parent1, parent2, parent3
//based on the names of their children.
//More specifically, the expected output would be:
//parent 1 (because she has a child with the name of Zoey),
//parent 3 (because she has a child next in desc order with the name of Yolanda),
//parent 2 (because her child names in desc order would Matt).
public class Parent
{
public int id { get; set; }
public int SortOrder { get; set; }
//some properties
public List<Child> Children { get; set; }
public static List<Parent> GetSortedParentsByChildName()
{
List<Parent> myUnsortedList = new List<Parent>()
{
new Parent()
{
id = 1,
Children = new List<Child>()
{
new Child(1, "Billy"),
new Child(1, "Zoey"),
new Child(1, "Robert"),
}
},
new Parent()
{
id = 2,
Children = new List<Child>()
{
new Child(1, "Gabe"),
new Child(1, "Matt"),
new Child(1, "Alyssa"),
}
},
new Parent()
{
id = 3,
Children = new List<Child>()
{
new Child(1, "Will"),
new Child(1, "Bob"),
new Child(1, "Yolanda"),
}
},
};
return myUnsortedList; //.OrderBy(my actual question);
}
}
public class Child
{
public int id { get; set; }
//some properties
public string Name { get; set; }
public Child(int id, string Name)
{
this.id = id;
this.Name = Name;
}
}
Okay so you can do it in this way as well:-
List<Parent> mySortedList =
myUnsortedList
.OrderByDescending(
x => x.Children.OrderByDescending(z => z.Name).First().Name)
.ToList();
This works for me to replace the return line in GetSortedParentsByChildName:
var childrenMap =
myUnsortedList
.SelectMany(x => x.Children)
.Select(x => x.Name)
.Distinct()
.OrderBy(n => n)
.Select((n, i) => new { n, i })
.ToDictionary(x => x.n, x => x.i);
return myUnsortedList
.Select(x => new
{
x,
max = x.Children
.Select(y => childrenMap[y.Name])
.Max()
})
.OrderByDescending(x => x.max)
.Select(x => x.x)
.ToList();
I get this result:

how to practically assign repeating objects from groups

I am having a difficult time finding a proper Linq query to utilize the group output.
I want to populate an existing students List where Student class has 2 properties ID and and int[] Repeats array (can be a list too) to keep how many times they took any of the 4 lectures (L101,L201,L202,L203). So if student takes L101 twice, L202 and L203 once, and but didn't take L201 this should be {2,0,1,1,}
class Student{
public string ID{get;set;}
public int[] Repeats{get;set;} //int[0]->L101, int[1]->L201...
}
In my main class I do this basic operation for this task:
foreach (var student in students)
{
var countL101 = from s in rawData
where student.Id==s.Id & s.Lecture =="L101"
select; //do for each lecture
student.Repeats = new int[4];
student.Repeats[0] = countL101.Count(); //do for each lecture
}
This works; but I wonder how do you make it practically using Linq in case where there are 100s of lectures?
I am using Lamba Expressions rather than query syntax. Then assuming rawData is IEnumerable<T> where T looks something like...
class DataRow
{
/// <summary>
/// Id of Student taking lecture
/// </summary>
public string Id { get; set; }
public string Lecture { get; set;}
}
Then you could do something like...
var lectures = rawData.Select(x => x.Lecture).Distinct().ToList();
int i = 0;
lectures.ForEach(l =>
{
students.ForEach(s =>
{
if (s.Repeats == null)
s.Repeats = new int[lectures.Count];
s.Repeats[i] = rawData.Count(x => x.Id == s.Id && x.Lecture == l);
});
i++;
});
Now if Repeats could just be of type IList<int> instead of int[] then...
var lectures = rawData.Select(x => x.Lecture).Distinct().ToList();
lectures.ForEach(l =>
{
students.ForEach(s =>
{
if (s.Repeats == null)
s.Repeats = new List<int>();
s.Repeats.Add(rawData.Count(x => x.Id == s.Id && x.Lecture == l));
});
});
Things are further simplified if Repeats could just be instantiated to a new List<int> in the Student constructor...
class Student
{
public Student()
{
Repeats = new List<int>();
}
public string Id { get; set; }
public IList<int> Repeats { get; private set; }
}
Then you can do it in one line...
rawData.Select(x => x.Lecture).Distinct().ToList()
.ForEach(l =>
{
students.ForEach(s =>
{
s.Repeats.Add(rawData.Count(x => x.Id == s.Id && x.Lecture == l));
});
});

LINQ on complex nested observable collection

I have a nested ObservableCollection<Student>, from which how can I get a particular student based on Id value using LINQ or Lambda ? Here is my Student class:
public class Student
{
public Student()
{
}
public string Name;
public int ID;
public ObservableCollection<Student> StudLists;
}
So each student object can have again student collections and it can go like any number of nested levels. how we can do it LINQ or using Lambda ? I have tried with
var studs = StudCollections.Where(c => c.StudLists.Any(m => m.ID == 122));
But this is not giving exact Student item ? Any idea ?
If you mean you want to search all descendants of StudCollections, then you could write an extension method like so:
static public class LinqExtensions
{
static public IEnumerable<T> Descendants<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> DescendBy)
{
foreach (T value in source)
{
yield return value;
foreach (T child in DescendBy(value).Descendants<T>(DescendBy))
{
yield return child;
}
}
}
}
and use it like so:
var students = StudCollections.Descendants(s => s.StudLists).Where(s => s.ID == 122);
If you want one student with a matching id, use:
var student = StudCollections.Descendants(s => s.StudLists).FirstOrDefault(s => s.ID == 122);
if (student != null)
{
// access student info here
}

Resources