C# aggregating attributes in a list - c#

I have an object like as shown below
public class SampleObject
{
public int MsfId { get; set; }
public List<string> PgId { get; set; }
public List<string> DcId { get; set; }
}
In the above aggregation of the PgId values grouped by MsfId. Same is the case with DcId as well.
For example:
MsfId: 100
PgId: "abc"
DcId: "123"
MsfId: 100
PgId: "def"
DcId: "456"
MsfId: 100
PgId: "ghi"
DcId: "789"
MsfId: 101
PgId: "abc"
DcId: "123"
How to write a LINQ query to aggregate this and create a list of SampleObjects like below?
MsfId: 100
PgId: "abc", "def", "ghi"
DcId: "123", "456", "789"
MsfId: 101
PgId: "abc"
DcId: "123"

Try aggregation like this:
var result = col.GroupBy(x => x.MsfId)
.Select(x => new SampleObject {
MsfId = x.Key,
PgCodes = x.Select(t=>t.PgId).ToList(),
DcCodes = x.Select(t=>t.DcId).ToList()
});

Scenario 1
public class SampleObject
{
public int MsfId { get; set; }
public List<string> PgId { get; set; }
public List<string> DcId { get; set; }
}
Scenario 2
public class SampleObjectSource
{
public int MsfId { get; set; }
public string PgId { get; set; }
public string DcId { get; set; }
}
Scenario 1 Answer
var collection = new List<SampleObject>();
var result = collection.GroupBy(y => y.MsfId)
.Select(y => new SampleObject
{
MsfId = y.Key,
PgId = y.SelectMany(g => g.PgId).Distinct().ToList(),
}).ToList();
Scenario 2
var collection2 = new List<SampleObjectSource>();
var result1 = collection2.GroupBy(y => y.MsfId)
.Select(y => new SampleObject
{
MsfId = y.Key,
PgId = y.Select(h => h.PgId).Distinct().ToList(),
}).ToList();
Update : Please see the dotnetfiddle

You need to group the items with a query. This linq grouping will create a collection of collections(with a key)
I just made a full working example:
// The class to start with
public class SampleObjectSource
{
public int MsfId { get; set; }
public string PgId { get; set; }
public string DcId { get; set; }
}
// the result class
public class SampleObject
{
public int MsfId { get; set; }
public List<string> PgId { get; set; }
public List<string> DcId { get; set; }
}
// for example:
public class Example
{
public Example()
{
// create a list that contains the items.
var list = new List<SampleObjectSource>
{
new SampleObjectSource { MsfId= 100, PgId= "abc", DcId= "123" },
new SampleObjectSource { MsfId= 100, PgId= "def", DcId= "456" },
new SampleObjectSource { MsfId= 100, PgId= "ghi", DcId= "789" },
new SampleObjectSource { MsfId= 101, PgId= "abc", DcId= "123" },
};
// the linq query that does the grouping.
var query = from item in list
// group the items by MsfId
group item by item.MsfId into itemgroup
// create the new class and initialize the properties
select new SampleObject
{
// the grouping item is the .Key (in this case MsfId)
MsfId = itemgroup.Key,
// the itemgroup is a collection of all grouped items, so you need to select the properties you're interrested in.
DcId = itemgroup.Select(i => i.DcId).ToList(),
PgId = itemgroup.Select(i => i.PgId).ToList()
};
// show the results in the Output window.
foreach (var item in query)
{
Trace.WriteLine($"MsfId: {item.MsfId}");
// some trick to format a list of strings to one string
Trace.WriteLine($"PgId: {string.Join(", ", item.PgId.Select(s => Quote(s)))}");
Trace.WriteLine($"DcId: {string.Join(", ", item.DcId.Select(s => Quote(s)))}");
Trace.WriteLine("");
}
}
// this method will surround the passed string with quotes.
private string Quote(string item)
{
return "\"" + item + "\"";
}
}
results:
MsfId: 100
PgId: "abc", "def", "ghi"
DcId: "123", "456", "789"
MsfId: 101
PgId: "abc"
DcId: "123"

Do it all with one GroupBy using the appropriate overload. Working Fiddle Here.
Note the use of SelectMany to concatenate the grouped collections into one.
var result = sampleObjects
.GroupBy(
o => o.MsfId,
(k, g) => new SampleObject
{
MsfId = k,
PgId = g.SelectMany(p => p.PgId).ToList(),
DcId = g.SelectMany(p => p.DcId).ToList()
});
If you want to remove duplicates from the collections consider Distinct() e.g.
var result = sampleObjects
.GroupBy(
o => o.MsfId,
(k, g) => new SampleObject
{
MsfId = k,
PgId = g.SelectMany(p => p.PgId).Distinct().ToList(),
DcId = g.SelectMany(p => p.DcId).Distinct().ToList()
});

Related

Linq Group By in nested array

I have a List of Incident object (List) with the following way:
Incident
-Title
-Category
-Name
-Subcategory
-Name
Im looking a Linq eficient way, to get the count of incidents related to each category and subcategories
expected obj result -
CategoryName
Count
Subcategories
SubcategoryName
Count
var categoryGroup = incidentsModel.GroupBy(i => i.Category.Name);
var categoryGroupAndSubcategoryGroup = categoryGroup.Select(group =>
{
var subcategories = group.SelectMany(item => item.Category.Subcategories).GroupBy(item => item.Name).Select(item => new IncidentSubcategoriesCount
{
SubcategoryName = item.Key,
SubcategoryCount = item.Count()
});
return new IncidentCategoriesCount
{
CategoryName = group.Key,
catagoryCount = group.Count(),
Subcategories = subcategories
};
});
return categoryGroupAndSubcategoryGroup;
testing #hannan answer the code above return:
[
{
"categoryName": "Alumbrado",
"catagoryCount": 1,
"subcategories": [
{
"subcategoryName": "Lamparas",
"subcategoryCount": 1
}
]
},
{
"categoryName": "Seguridad",
"catagoryCount": 3,
"subcategories": [
{
"subcategoryName": "Narcotráfico",
"subcategoryCount": 3
},
{
"subcategoryName": "Robo",
"subcategoryCount": 3
}
]
}
]
CategoryName and CategoryCount are OK, the error is in SubcategoriesCount, "Narcotrafico" SubcategoryCount must be 2 and "Robo" SubcategoryCount must be 1 so the Total is 3.
Expected result
[
{
"categoryName": "Alumbrado",
"catagoryCount": 1,
"subcategories": [
{
"subcategoryName": "Lamparas",
"subcategoryCount": 1
}
]
},
{
"categoryName": "Seguridad",
"catagoryCount": 3,
"subcategories": [
{
"subcategoryName": "Narcotráfico",
"subcategoryCount": 2
},
{
"subcategoryName": "Robo",
"subcategoryCount": 1
}
]
}
]
Use Select Many then you can group any way you want
class Program
{
static void Main(string[] args)
{
List<Incident> indcidents = new List<Incident>();
var results = indcidents.SelectMany(x => x.catogories
.SelectMany(y => y.subcategories.Select(z =>
new { title = x.Title, CategoryName = y.Name, Subcategory = z.Name }
))).ToList();
}
}
public class Incident
{
public string Title { get; set; }
public List<Category> catogories { get; set; }
}
public class Category
{
public string Name { get; set; }
public List<Subcategory> subcategories { get; set; }
}
public class Subcategory
{
public string Name { get; set; }
}
if u are looking for all categories and subcategories I'm afraid u have to do nested groupBy
public class Subcategory
{
public string Name { get; set; }
}
public class Category
{
public string Name { get; set; }
public List<Subcategory> Categories { get; set; } = new List<Subcategory>()
}
public class Incident {
public List<Category> Categories { get; set; } = new List<Category>()
}
var categoryGroup = new Incident().Categories.GroupBy(category => category.Name);
var categoryGroupAndSubcategoryGroup = categoryGroup.Select(group =>
{
var subcategories = group.SelectMany(item => item.Categories).GroupBy(item => item.Name).Select(item => new
{
Name = item.Key,
count = item.Count()
})
return
new
{
Name = group.Key,
Count = group.Count()
Subcategories = subcategories
}
})

Can I use LINQ GroupBy to do this more cleanly?

In this contrived example, which closely resembles my real-world problem, I have a data set coming from an external source. Each record from the external source takes the following form:
[Classification] NVARCHAR(32),
[Rank] INT,
[Data] NVARCHAR(1024)
I am looking to build an object where the Rank and Data are patched into a single instance of a response object that contains list properties for the three hard-coded Classification values, ordered by Rank.
I have something that works, but I can't help but think that it could be done better. This is what I have:
public static void Main()
{
IEnumerable<GroupingTestRecord> records = new List<GroupingTestRecord>
{
new GroupingTestRecord { Classification = "A", Rank = 1, Data = "A1" },
new GroupingTestRecord { Classification = "A", Rank = 2, Data = "A2" },
new GroupingTestRecord { Classification = "A", Rank = 3, Data = "A3" },
new GroupingTestRecord { Classification = "B", Rank = 1, Data = "B1" },
new GroupingTestRecord { Classification = "B", Rank = 2, Data = "B2" },
new GroupingTestRecord { Classification = "B", Rank = 3, Data = "B3" },
new GroupingTestRecord { Classification = "C", Rank = 1, Data = "C1" },
new GroupingTestRecord { Classification = "C", Rank = 2, Data = "C2" },
new GroupingTestRecord { Classification = "C", Rank = 3, Data = "C3" },
};
GroupTestResult r = new GroupTestResult
{
A = records.Where(i => i.Classification == "A").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
B = records.Where(i => i.Classification == "B").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
C = records.Where(i => i.Classification == "C").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
};
The source record DTO:
public class GroupingTestRecord
{
public string Classification { get; set; }
public int? Rank { get; set; }
public string Data { get; set; }
}
The destination single class:
public class GroupTestResult
{
public IEnumerable<GroupTestResultItem> A { get; set; }
public IEnumerable<GroupTestResultItem> B { get; set; }
public IEnumerable<GroupTestResultItem> C { get; set; }
}
The distination child class:
public class GroupTestResultItem
{
public int? Rank { get; set; }
public string Data { get; set; }
}
Ouput
{
"A":[
{
"Rank":1,
"Data":"A1"
},
{
"Rank":2,
"Data":"A2"
},
{
"Rank":3,
"Data":"A3"
}
],
"B":[
{
"Rank":1,
"Data":"B1"
},
{
"Rank":2,
"Data":"B2"
},
{
"Rank":3,
"Data":"B3"
}
],
"C":[
{
"Rank":1,
"Data":"C1"
},
{
"Rank":2,
"Data":"C2"
},
{
"Rank":3,
"Data":"C3"
}
]
}
Fiddle
Is there a better way to achieve my goal here?
The same JSON output was achieved using GroupBy first on the Classification and applying ToDictionary on the resulting IGrouping<string, GroupingTestRecord>.Key
var r = records
.GroupBy(_ => _.Classification)
.ToDictionary(
k => k.Key,
v => v.Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank).ToArray()
);
var json = JsonConvert.SerializeObject(r);
Console.WriteLine(json);
which should easily deserialize to the destination single class (for example on a client)
var result = JsonConvert.DeserializeObject<GroupTestResult>(json);
is it possible to get the top level result into a GroupTestResult object?
Build the result from the dictionary
var result = new GroupTestResult {
A = r.ContainsKey("A") ? r["A"] : Enumerable.Empty<GroupTestResultItem>();,
B = r.ContainsKey("B") ? r["B"] : Enumerable.Empty<GroupTestResultItem>();,
C = r.ContainsKey("C") ? r["C"] : Enumerable.Empty<GroupTestResultItem>();,
};
Or this
var result = records.GroupBy(x => x.Classification)
.ToDictionary(x => x.Key, x => x.Select(y => new {y.Rank, y.Data})
.OrderBy(y => y.Rank));
Console.WriteLine(JsonConvert.SerializeObject(result));
Full Demo Here

Group by List using inner list

Below is my List collection.
public class A
{
public String id{ get; set; }
public string name{ get; set; }
public List<B> nestedList { get; set; }
}
public class B
{
public String innerid{ get; set; }
public string innername { get; set; }
}
I want to use group by on nested collection properties
So I can have output as
innerid="1",
innername="Name1",
{
id= "1", name= "One"
id= "2", name= "Two"
id= "4", name= "Four"
}
innerid="2",
innername="Name2",
{
id= "3", name= "Three"
id= "6", name= "Six"
id= "8", name= "Eight"
}
I tried
.GroupBy(a => a.nestedList.First().innerid).ToList()
But I am not getting required output.
You want to reverse the hierarchy, so grouping by the nested-B and list the parent-A as children?
var groups = aList
.SelectMany(a => a.nestedList.Select(b => new { A = a, B = b }))
.GroupBy(x => new { x.B.innerid, x.B.innername })
.Select(g => new {
g.Key.innerid,
g.Key.innername,
aItems = g.Select(x => new { x.A.id, x.A.name })
});
Instead of selecting anonymous types you can also select the original A and B instances.
Assuming the items from the result must contain items of type A, you could use query syntax to solve this:
var grouping = from a in collectionOfA
from b in a.nestedList
group a by new { b.innerid, b.innername } into g
select new
{
innerid = g.Key.innerid,
innername = g.Key.innername,
items = g.ToList()
};

The most frequently occurring item in a list

I have a list in this table
public class Fruits
{
public int ID { get; set; }
public string Name{ get; set; }
}
I want to know what are the most frequent fruit in this table what is the code that appears to me this result
I am use
var max = db.Fruits.Max();
There is an error in that?
Try
public class Fruits
{
public int ID { get; set; }
public string Name{ get; set; }
}
var Val = fruitList.GroupBy(x => x.ID,
(key, y) => y.MaxBy(x => x.ID).value)
As Drew said in the comments, you want to GroupBy on the value that you care about (I did Name, since ID tends to be unique in most data structures) and then OrderByDescending based on the count.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var fruits = new List<Fruit> { new Fruit { ID = 1, Name = "Apple" }, new Fruit { ID = 2, Name = "Apple" }, new Fruit { ID = 3, Name = "Pear" } };
var most = fruits.GroupBy(f => f.Name).OrderByDescending(group => group.Count());
Console.WriteLine(most.First().Key);
}
}
public class Fruit
{
public int ID { get; set; }
public string Name{ get; set; }
}
If you want to get the name of the item that exists most in your list, first find the id that is most occurring:
var fruitAnon = fruits
.GroupBy(item => item.ID)
.Select(item => new {
Key = item.Key,
Count = item.Count()
})
.OrderByDescending(item => item.Count)
.FirstOrDefault();
This will return an anonymous object that will have the most frequent id, and the count represents the number of times it exists in the list. You can then find that object's name:
var fruit = fruits.FirstOrDefault(x => x.ID == fruitAnon.Key);
If you had a list like this:
List<Fruits> fruits = new List<Fruits>() {
new Fruits { ID = 1, Name = "Apple" },
new Fruits { ID = 1, Name = "Apple" },
new Fruits { ID = 2, Name = "Orange" },
new Fruits { ID = 2, Name = "Orange" },
new Fruits { ID = 2, Name = "Orange" },
new Fruits { ID = 2, Name = "Orange" }
};
Then:
Console.WriteLine(fruit.Name);
Would print Orange.

Linq query to get the distinct values for repeated id's in a list

Suppose this is my member class
class Member
{
public string CategoryId { get; set; }
public string MemberName { get; set; }
public int Distance { get; set; }
}
And, this is list.
var list = new List<Member>();
list.Add(new { CategoryId = "01", MemberName = "andy", Distance = 3 });
list.Add(new { CategoryId = "02", MemberName = "john", Distance = 5 });
list.Add(new { CategoryId = "01", MemberName = "mathew", Distance = 7 });
list.Add(new { CategoryId = "03", MemberName = "bakara", Distance = 2 });
Can anyone please suggest the logic/ linq query to get the List having distinct/unique categoryID with respective MemberNames separated with comma
The output should be :
list.Add(new { CategoryId = "01", MemberName="andy,mathew"});
list.Add(new { CategoryId = "02", MemberName="john"});
list.Add(new { CategoryId = "03", MemberName="bakara"});
You need to group by CategoryId and then join the MemberName values for each group like this:
var result =
list.GroupBy(member => member.CategoryId)
.Select(group => new //Do you want an anonymous type or a Member object?
{
CategoryId = group.Key,
MemberName = string.Join(",", group.Select(member => member.MemberName))
})
.ToList();

Categories