Can I add same object to multiple groups in LINQ? - c#

I have a set of objects I want to group in Linq. However the key I want to use is a combination of multiple keys. for eg
Object1: Key=SomeKeyString1
Object2: Key=SomeKeyString2
Object3: Key=SomeKeyString1,SomeKeyString2
Now I'd like the results to be only two groups
Grouping1: Key=SomeKeyString1 : Objet1, Object3
Grouping2: Key=SomeKeyString2 : Object2, Object3
Basically I want the same object to be part of two groups. Is that possible in Linq?

Well, not directly with GroupBy or GroupJoin. Both of those extract a single grouping key from an object. However, you could do something like:
from groupingKey in groupingKeys
from item in items
where item.Keys.Contains(groupingKey)
group item by groupingKey;
Sample code:
using System;
using System.Collections.Generic;
using System.Linq;
class Item
{
// Don't make fields public normally!
public readonly List<string> Keys = new List<string>();
public string Name { get; set; }
}
class Test
{
static void Main()
{
var groupingKeys = new List<string> { "Key1", "Key2" };
var items = new List<Item>
{
new Item { Name="Object1", Keys = { "Key1" } },
new Item { Name="Object2", Keys = { "Key2" } },
new Item { Name="Object3", Keys = { "Key1", "Key2" } },
};
var query = from groupingKey in groupingKeys
from item in items
where item.Keys.Contains(groupingKey)
group item by groupingKey;
foreach (var group in query)
{
Console.WriteLine("Key: {0}", group.Key);
foreach (var item in group)
{
Console.WriteLine(" {0}", item.Name);
}
}
}
}

Related

filter by list in an attribute that is also a list C#

I have a class named SchoolClass, which has a list of students as attribute.
Then I have a search filter, in which I can search by a list of students and I have to return all the SchoolClasses where those students are part of their "list of students".
public class SchoolClass
{
public List<string> students { get; set; }
}
List<string> searchedStudents = new List<string>
{ "Brian","Adrian","Matt","Chloe"};
So I have a list of SchoolClasses:
List<SchoolClass> schoolClasses = new List<SchoolClass>();
SchoolClass 1 ==>
//(it should return it because it matches Brian, one of the searchedStudents)
schoolClasses[0].students = { "Brian","Zara"};
SchoolClass 2 ==>
//(it shouldn't return it because there are no matches)
schoolClasses[1].students = { "Sophie","Zara"};
i assume this is some sort of school work / project.
please make sure that you actually understand the answer and learn some sort of lesson out of it!
since this isnt a very data intense operation (with billions of entries) we can simply use go over the lists and search for the students.
if you got huge amounts of data to go through, you should consider different data structures (eg. hashtables).
//polulate the list with data
SchoolClass schoolClass1 = new SchoolClass();
SchoolClass schoolClass2 = new SchoolClass();
SchoolClass schoolClass3 = new SchoolClass();
schoolClass1.students = new List<string> { "Brian", "Adrian", "Matt" };
schoolClass2.students = new List<string> { "Adrian", "Matt" };
schoolClass3.students = new List<string> { "Brian", "Matt", "Chloe" };
List<SchoolClass> schoolClasses = new List<SchoolClass>();
schoolClasses.Add(schoolClass1);
schoolClasses.Add(schoolClass2);
schoolClasses.Add(schoolClass3);
//set our filter
List<string> searchedStudents = new List<string> { "Brian", "Chloe" };
//filter the data by going over the lists
List<SchoolClass> classesWithSearchedStudents = new List<SchoolClass>();
for (int classIterator = 0; classIterator < schoolClasses.Count(); classIterator++)
{
for (int filterIterator = 0; filterIterator < searchedStudents.Count(); filterIterator++)
{
//comparing with our filter and add class to the results list
if (schoolClasses[classIterator].students.Contains(searchedStudents[filterIterator]))
{
classesWithSearchedStudents.Add(schoolClasses[classIterator]);
break;
}
}
}
printResult(classesWithSearchedStudents);
just for demonstration, here is a more compact version using linq
var searchResult = new List<SchoolClass>();
foreach (var student in searchedStudents)
{
searchResult.AddRange(schoolClasses.Where(x => x.students.Contains(student)));
}
searchResult = searchResult.Distinct().ToList();
printResult(searchResult);
for printing the answer, i used this function:
private static void printResult(List<SchoolClass> schoolClasses)
{
for (int classIterator = 0; classIterator < schoolClasses.Count(); classIterator++)
{
Console.Write("found class with students: ");
for (int studentIterator = 0; studentIterator < schoolClasses[classIterator].students.Count; studentIterator++)
{
Console.Write(schoolClasses[classIterator].students[studentIterator] + ", ");
}
Console.WriteLine();
}
}
output:
found class with students: Brian, Adrian, Matt,
found class withstudents: Brian, Matt, Chloe,
If I understand it correctly:
You want to filter all schoolclasses that have "any" student from the filterlist in their student list.
If that assumption is correct than here you go.
Here's a simpeler and shorter function you can use.
It has one less for loop and cuts off whenever it finds 1 of the students.
It looks through all classes and sees if "any" of the filtered students are in there".
public IEnumerable<SchoolClass> FindMatches(List<SchoolClass> schoolClasses, List<string> namesFilter)
{
return schoolClasses.FindAll(schoolClass => schoolClass.students.Any(student => namesFilter.Contains(student)));
}
Obviously you can get the single line out of the method, but I thought I post it like this so you have an idea of the inputs and what the variable names actually are.
An easy way to check whether two lists contain any identical values is to use .Intersect() and .Any() (both methods are found in the System.Linq namespace).
.Intersect() returns an IEnumerable of all distinct objects that are found in both lists.
By using listA.Intersect(listB).Any(), you basically get the answer to the question
Are there any items that exists in both listA and listB?
In your example, the intersection of each school class's students with the searched students would result in the following (pseudo code):
School class 1
    schoolClasses[0].Students .Intersect( searchedStudents )
= { "Brian", "Zara" } .Intersect( { "Brian", "Adrian", "Matt", "Chloe" } )
= { "Brian" }
"Brian" is the only student that is present in both lists.
School class 2
    schoolClasses[1].Students .Intersect( searchedStudents )
= { "Sophie", "Zara" } .Intersect( { "Brian", "Adrian", "Matt", "Chloe" } )
= { }
No student is present in both lists.
Now, let's imagine school class 3 (schoolClasses[2]) existed and had the following students:
{ "Stuart", "Chloe", "Adrian", "Maya", "Chloe" }
An intersection of school class 3's students and the searched students would result in:
School class 3
    schoolClasses[2].Students .Intersect( searchedStudents )
= { "Stuart", "Chloe", "Adrian", "Maya", "Chloe" } .Intersect( { "Brian", "Adrian", "Matt", "Chloe" } )
= { "Chloe", "Adrian" }
Both "Chloe" and "Adrian" are present in both lists.
Note how "Chloe" is present twice in the school class's student list, but only once in the result from the intersection. Seeing as your need simply is to know whether a student name is present in both lists, (and not how many times a student name is present in either list,) .Intersect() nonetheless suits your use case.
So, to the implementation.
If your SchoolClass class looks like this:
public class SchoolClass
{
public List<string> Students { get; set; }
}
and you define searchedStudents and schoolClasses as follows:
List<string> searchedStudents = new() { "Brian", "Adrian", "Matt", "Chloe" };
List<SchoolClass> schoolClasses = new()
{
new() { Students = new() { "Brian", "Zara" } },
new() { Students = new() { "Sophie", "Zara" } },
};
, the intersection may be implemented as follows, using a foreach loop:
var matchingSchoolClasses = new List<SchoolClass>();
foreach (var schoolClass in schoolClasses)
{
if (schoolClass.Students.Intersect(searchedStudents).Any())
{
matchingSchoolClasses.Add(schoolClass);
}
}
or as follows, using .Where(), also from the System.Linq namespace:
var matchingSchoolClasses = schoolClasses
.Where(schoolClass => schoolClass.Students.Intersect(searchedStudents).Any())
.ToList();
Either implementation will result in matchingSchoolClasses containing one object. That one object is school class 1 (schoolClasses[0]), which contains the following students:
{ "Brian", "Zara" }
Example fiddle here.

Get the value of the Key from a Json Array

I have the below Json Array and I need to get the value of the key inside my "relation" object. How do I do that? The relation object has different set of key-value pair for each object in the array.
[
{
"data": {
"_hash": null,
"kind": "ENTITY",
"id": "test122",
"payload": {
"attributes": {
"bbl:27": false
},
"relations": {
"bbl:45": "P18",
"bbl:P18": "P562"
},
"type": [
"P185"
]
}
}
}
]
In above inside relations the keys are "bbl:45" and "bbl:P18". I need these values, in specific I need to extract 45 and P18, considering bbl remain constant inside relation object. how do i do it. There are multiple objects in the Jarray, I have shown only one.
I would mention, if you can change data format, consider doing so, having payload as part as a member proper name is highly unusual but you could do like this:
[Fact]
public void DynamicJsonHandlingTest()
{
var serialized = "[{\"data\":{\"_hash\":null,\"kind\":\"ENTITY\",\"id\":\"test122\",\"payload\":{\"attributes\":{\"bbl:27\":false},\"relations\":{\"bbl:45\":\"P18\",\"bbl:P18\":\"P562\"},\"type\":[\"P185\"]}}}]";
using var jDoc = JsonDocument.Parse(serialized);
var enumerator = jDoc.RootElement.EnumerateArray();
foreach(var JsonElement in enumerator)
{
var relationsElement = JsonElement.GetProperty("data").GetProperty("payload").GetProperty("relations");
foreach (var ele in relationsElement.EnumerateObject())
{
var sought = ele.Name.Split(":")[1];
//sought now cointains first 45 then P18
}
}
}
The following Code solves your problem.
using Newtonsoft.Json.Linq;
using System;
using System.Linq;
using Json.Net;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string json = "{\"data\":{\"_hash\":null,\"kind\":\"ENTITY\",\"id\":\"test122\",\"payload\":{\"attributes\":{\"bbl:27\":false},\"relations\":{\"bbl:45\":\"P18\",\"bbl:P18\":\"P562\"},\"type\":[\"P185\"]}}}";
JObject jsonObject = JObject.Parse(json);
var results = jsonObject["data"]["payload"]["relations"];
foreach (var item in results)
{
string propName = ((JProperty)item).Name;
string requiredValue = propName.Split(':')[1];
Console.WriteLine($"Required: {requiredValue} !");
}
}
}
}
You should add some error handling when the property name does not contains :
For dynamic keys you can use dictionary:
public class Root
{
public Data data { get; set; }
}
public class Data
{
public Payload payload { get; set; }
}
public class Payload
{
public Dictionary<string, string> relations { get; set; }
}
var result = JsonConvert.DeserializeObject<List<Root>>(sourcejson);
And then next will contain all keys:
var keys = result
.Select(r => r.data)
.Select(r => r.payload)
.SelectMany(r => r.relations.Keys)
Or use the LINQ to JSON API:
var result = JsonConvert.DeserializeObject<JArray>(sourcejson);
var list = result
.Children()
.SelectMany(c => c["data"]["payload"]["relations"].Children<JProperty>())
.Select(p => p.Name)
.ToList(); // list is {"bbl:45", "bbl:P18"}
And then you can process the keys as you need - for example using Split(':') to turn "bbl:45" into array of "bbl" and `"45".

Having syntax issues when trying to print a list within a list

I cant quite figure out the syntax on how to get the values of this list within a list.
public class Toppings
{
public List<string> PizzaToppings { get; set; }
}
static void Main(string[] args)
{
List<Toppings> items = new List<Toppings>();
using (StreamReader r = new StreamReader("C:\\pizzas.json"))
{
string json = r.ReadToEnd();
items = JsonConvert.DeserializeObject<List<Toppings>>(json);
}
I've populated my list as shown above but I am not sure how to print. This is one of the many things I've tried:
foreach (Toppings item in items)
{
foreach (List<string> s in item)
{
Console.WriteLine(s.PizzaToppings);
}
}
But I keep getting errors about not having a public instance definition of "GetNumerator" for "item.
The JSON looks like this
[
{
"toppings": [
"pepperoni"
]
},
{
"toppings": [
"feta cheese",
"bacon"
]
},
And when I tried doing foreach (String s in item.PizzaToppings I got an object reference not set to an instance of object error.
You'll need to make sure the object matches the JSON. Also you can do a SelectMany to collapse the list of list. The following code will print out all the toppings in the JSON:
void Main()
{
var json = File.ReadAllText("C:\\pizzas.json");
var obj = JsonConvert.DeserializeObject<List<PizzaToppings>>(json);
foreach(var topping in obj.SelectMany(o => o.Toppings))
{
Console.WriteLine(topping);
}
}
public class PizzaToppings
{
public List<string> Toppings {get;set;}
}

How to iterate through JObject Properties via Foreach/LINQ

I have an established JObject object. Trying to loop through it to acquire a Key/value based on anothers Key/value (example of json below with code currently stuck on)
For a tad more detail - looking to loop through "value", get the "KeyID" based on "MailState"
definitely feel like I am missing the step of filtering by MailState/ColName apparently - I have searched through threads a bunch but if someone knows of one that answered this that i was unable to find i will happily pull this down/reference it
// JSON DATA
{
"odata.metadata": "https://..com/odata/$metadata#JCJMCDXes",
"value": [
{
"KeyID": "10379",
"MailCity": "Chicago",
"MailState": "IL"
},
{
"KeyID": "9846",
"MailCity": "Chicago",
"MailState": "IL"
},
{
"KeyID": "2234",
"MailCity": "Madison",
"MailState": "WI"
}]
}
// Current code example
// class in play
public class datastorage
{
public string ID { get; set; }
public string Col { get; set; }
}
public class listData
{
public string ColName {get;set;}
}
// getVPData is a string response from a call to an API
getVPData.Replace(System.Environment.NewLine, "");
JObject jobj = (JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(getVPData);
List<datastorage> data = new List<datastorage>();
// Loop
foreach(var r in listData) // has distinct State abeviations so only 1 occurence
{
foreach (var j in jobj) // This the right path?
{
//add KeyID into ID
data.add(new datastorage
{
ID = ,//add KeyID into ID
Col = r.ColName
});
}
}
You can use Newtonsoft.Json library to parse and loop to the items of value
here is a sample code:
dynamic json = JsonConvert.DeserializeObject(getVPData);
foreach (dynamic item in json["value"])
{
//you can access the fields inside value.
var KeyID = item["KeyID"];
var MailCity = item["MailCity"];
var MailState = item["MailState"];
//just for showing...
Console.WriteLine("KeyID:{0}, MailCity:{1}, MailState:{2}", KeyID, MailCity, MailState);
}
Let me know if the snippet works.
Straightforward ways are:
using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ConsoleApp7
{
internal class Program
{
private static void Main(string[] args)
{
var mailStates = new[] {"IL", "WI"};
var jObject = (JObject) JsonConvert.DeserializeObject(json);
var values = (JArray) jObject["value"];
// 1st way
foreach (var mailState in mailStates)
{
var key = values
.Where(v => mailState == v.SelectToken("MailState").Value<string>())
.Select(v => v.Value<string>("KeyID"))
.FirstOrDefault();
Console.WriteLine($"1st case: {mailState} - {key}");
}
/* 2nd way based on JSONPath
* api: https://www.newtonsoft.com/json/help/html/QueryJsonSelectTokenJsonPath.htm
* dox: https://support.smartbear.com/alertsite/docs/monitors/api/endpoint/jsonpath.html
* tester: https://jsonpath.curiousconcept.com/
*/
foreach (var mailState in mailStates)
{
var key = values.SelectTokens($"$[?(#.MailState == '{mailState}')].KeyID")
.Select(v => v.ToString())
.FirstOrDefault();
Console.WriteLine($"2nd case: {mailState} - {key}");
}
Console.ReadKey();
}
private static string json = #"
{
""odata.metadata"": ""https://cdxapiclient.palmercg.com/odata/$metadata#JCJMCDXes"",
""value"": [
{
""KeyID"": ""10379"",
""MailCity"": ""Chicago"",
""MailState"": ""IL""
},
{
""KeyID"": ""9846"",
""MailCity"": ""Chicago"",
""MailState"": ""IL""
},
{
""KeyID"": ""2234"",
""MailCity"": ""Madison"",
""MailState"": ""WI""
}]
}";
}
}

Foreach group items from a list of objects

I need to group a big list of elements according to a certain atribute.
Is it possible in C# to do a foreach with a 'where' clause in a list of objects or is there a better way?
For example, I have 5000 records and 3 groups that separate them.
Foreach list.item where item.group = group1{
do action one for every record from group1
}
and so on...
ps.: I already have the records at this point of code so I don't think Linq would help.
You can separate a larger list into smaller ones, based on a property, by using ToLookup. The ToLookup method will produce a dictionary of lists, where the key is the property value that you are separating them by and the list contains all of the elements that match.
For example, if your objects have a CategoryID you can separate them into a dictionary of lists like this:
var smallLists = bigList.ToLookup( item => item.CategoryID, item => item );
You can then iterate them like this:
foreach (var bucket in smallLists)
{
Console.WriteLine("Bucket:");
foreach (var item in bucket)
{
Console.WriteLine("Item {0} with category {1}", item.Name, item.CategoryID);
}
}
See a working example on DotNetFiddle.
I think that you want to do is to group items of list by a Group and then create another list with each group and his items.
If that is the case, you can do something like this:
var grouped = items/*.Where(c => c.group == //desired group if want's to filter//)*/
.GroupBy(c => c.group);
var results = grouped.Select(c => new {
Group = c.Key.group,
Items = c.Select(c => new { c.PropertyOfItem1, c.PropertyOfItem2, // etc // })
});
This basic template should do what you need. You can also use a dictionary to map the groups to.
using System.Linq;
class Program
{
class Item
{
public int Key { get; set; }
public string Name { get; set; }
}
static void Main(string[] args)
{
var actions = new Dictionary<int, Action<Item>> {
{ 1, Action1 },
{ 2, Action2 },
{ 3, Action3 }
};
var items = new List<Item>();
foreach (var group in items.GroupBy(x => x.Key))
{
var action = actions[group.Key];
foreach (var item in group)
{
action(item);
}
}
}
static void Action1(Item item)
{
}
static void Action2(Item item)
{
}
static void Action3(Item item)
{
}
}

Categories

Resources