(Edited)
This is my code with examples of the collections I am working with:
That's what I meant when I wrote that myProducts1 and myProducts1 are nested collections
List<MyProducts> myProducts1= {
new MyProducts{id = 1, Name = "product1", isExcl= true},
new MyProducts{id = 2, Name = "product2", isExcl= false},
new MyProducts{id = 3, Name = "product3", isExcl= true},
new MyProducts{id = 4, Name = "product4", isExcl= false}
}
List<MyProducts> myProducts2= {
new MyProducts{id = 5, Name = "product5", isExcl= true},
new MyProducts{id = 6, Name = "product6", isExcl= false}
}
IEnumerable<SelectedProductRequest> selectedProducts = {
new SelectedProductRequest {id = 1, Name = "product1", Price = 23},
new SelectedProductRequest {id = 1, Name = "product1", Price = 44},
new SelectedProductRequest {id = 2, Name = "product2", Price = 11},
new SelectedProductRequest {id = 6, Name = "product6", Price = 34},
}
List<CategoryProduct> productsWithCategories = {
{CategoryName= "Category1", categoryProduct = myProducts1 },
{CategoryName= "Category2", categoryProduct = myProducts2 }
}
Here is my first code:
IEnumerable<SelectedProductViewModel> products1 =
.GroupBy(categoryProduct => categoryProduct .CategoryName)
.Select(categoryProduct => new ProductCategoryViewModel(
categoryProduct .Key,
categoryProduct
.Select(product => new SelectedProductViewModel(
product.Name,
selectedProducts.FirstOrDefault(selectedProduct => selectedProduct.id== product.id)?.Price ?? 0,
product.IsExclusive))
.OrderByDescending(product => product.id)))
Here is my second code:
IEnumerable<SelectedProductViewModel> products2 =
.GroupBy(categoryProduct => categoryProduct .CategoryName)
.Select(categoryProduct => new ProductCategoryViewModel(
categoryProduct .Key,
categoryProduct
.Join(contractSelectedProducts, product => product.id, selected => selected.id, (product, selected) =>
new SelectedProductViewModel(
product.Name,
selected?.Price ?? 0,
product.IsExclusive))
The results I get with those pieces of code:
products1= {"Category1",{
new SelectedProductRequest {id = 1, Name = "product1", Price = 23, IsExclusive = true},
new SelectedProductRequest {id = 2, Name = "product2", Price = 11, IsExclusive = false},
new SelectedProductRequest {id = 3, Name = "product3", Price = 0, IsExclusive = true},
new SelectedProductRequest {id = 4, Name = "product4", Price = 0, IsExclusive = false}
},
"Category2", {new SelectedProductRequest {id = 5, Name = "product5", Price = 0, IsExclusive = true},
new SelectedProductRequest {id = 6, Name = "product6", Price = 34, IsExclusive = false}}
products2= {"Category1",{
new SelectedProductRequest {id = 1, Name = "product1", Price = 23, IsExclusive = true},
new SelectedProductRequest {id = 1, Name = "product1", Price = 44, IsExclusive = true},
new SelectedProductRequest {id = 2, Name = "product2", Price = 11, IsExclusive = false}
}},
"Category2", {new SelectedProductRequest {id = 6, Name = "product6", Price = 34, IsExclusive = false}
But the result I want to achieve is this:
products= {"Category1",{
new SelectedProductRequest {id = 1, Name = "product1", Price = 23, IsExclusive = true},
new SelectedProductRequest {id = 1, Name = "product1", Price = 44, IsExclusive = true},
new SelectedProductRequest {id = 2, Name = "product2", Price = 11, IsExclusive = false},
new SelectedProductRequest {id = 3, Name = "product3", Price = 0, IsExclusive = true},
new SelectedProductRequest {id = 4, Name = "product4", Price = 0, IsExclusive = false}
}},
"Category2",{
{new SelectedProductRequest {id = 5, Name = "product5", Price = 0, IsExclusive = true},
{new SelectedProductRequest {id = 6, Name = "product6", Price = 34, IsExclusive = false}
}
(edited code)
My question: How can I achieve this result without using products1 and products2? Or how can i do it in a better way?
updated as per comment
Solution 1
I changed the last Linq query by :
List<ProductCategory> mergedList = products1
// concat products2 with products1
.Concat(products2)
// group by category name
.GroupBy(x => x.CategoryName)
//dictionay(categoryName, list of SelectedProductRequests)
.ToDictionary(key => key, value => value.SelectMany(x => x.SelectedProductRequests)
.GroupBy(x => new { x.id, x.Name, x.Price, x.IsExclusive })
// take the first in grouped element
.Select(x => x.First())
//convert to SelectedProductRequest
.Select(x => new SelectedProductRequest { id = x.id, Name = x.Name, Price = x.Price, IsExclusive = x.IsExclusive })
.OrderBy(x => x.id)//order by id
.ToList())
.Select(x => new ProductCategory { CategoryName = x.Key.Key, SelectedProductRequests = x.Value })
.ToList();
Solution 2
if i understand your demand, you need to build productsWithCategories to get Expected result with one linq request, then check the following proposition by using GroupJoin instead Join :
IEnumerable<ProductCategoryViewModel> result = productsWithCategories
.Select(x => new ProductCategoryViewModel
{
CategoryName = x.CategoryName,
SelectedProductViewModels = x.categoryProduct
.GroupJoin(selectedProducts, prd => prd.id, sel => sel.id,
(prd, sel) => sel != null && sel.Any() ?
sel.Select(y =>
new SelectedProductViewModel
{
id = prd.id,
isExcl = prd.isExcl,
Name = prd.Name,
Price = y.Price
}).ToList() :
new List<SelectedProductViewModel>
{
new SelectedProductViewModel
{
id = prd.id,
isExcl = prd.isExcl,
Name = prd.Name,
Price = 0
}
})
.SelectMany(z => z)
.ToList()
});
i hope that will help you fix the issue
You can use Concat method to join arrays:
var selectedProductsMapped = selectedProducts.Select(s => new SelectedProductRequest
{
id = s.id,
Name = s.Name,
Price = s.Price,
isExclusive = myProducts.Where(p => p.id == s.id).FirstOrDefault().isExclusive
});
var exlcludedProducts = myProducts.Where(p => !selectedProducts.Any(sp => sp.id == p.id))
.Select(s => new SelectedProductRequest
{
id = s.id,
Name = s.Name,
Price = 0
});
var result = selectedProductsMapped.Concat(exlcludedProducts);
An example:
var selectedProducts = new List<SelectedProductRequest> {
new SelectedProductRequest {id = 1, Name = "product1", Price = 23},
new SelectedProductRequest {id = 1, Name = "product1", Price = 44},
new SelectedProductRequest {id = 2, Name = "product2", Price = 11}
};
var myProducts = new List<MyProducts> {
new MyProducts{id = 1, Name = "product1", isExclusive= true},
new MyProducts{id = 2, Name = "product2", isExclusive= false},
new MyProducts{id = 3, Name = "product3", isExclusive= true},
new MyProducts{id = 4, Name = "product4", isExclusive= false}
};
var selectedProductsMapped = selectedProducts.Select(s => new SelectedProductRequest
{
id = s.id,
Name = s.Name,
Price = s.Price,
isExclusive = myProducts.Where(p => p.id == s.id).FirstOrDefault().isExclusive
});
var exlcludedProducts = myProducts.Where(p => !selectedProducts.Any(sp => sp.id == p.id))
.Select(s => new SelectedProductRequest
{
id = s.id,
Name = s.Name,
Price = 0
});
var result = selectedProductsMapped.Concat(exlcludedProducts);
You can produce the set of products1 and products2 with Linq's Union(). You will need to implement SelectedProductRequest's GetHashCode().
products = products1.Union(products2);
Try it Online!
Add this to SelectedProductRequest:
public class SelectedProductRequest : IEquatable<SelectedProductRequest>
{
public int id {get;set;}
public string Name {get;set;}
public int Price {get;set;}
public bool IsExclusive {get;set;}
public bool Equals(SelectedProductRequest other) => other is null
&& this.id == other.id
&& this.Name == other.Name
&& this.Price == other.Price
&& this.IsExclusive == other.IsExclusive;
public override bool Equals(object obj) => Equals(obj as SelectedProductRequest);
public override int GetHashCode() => (id, Name, Price, IsExclusive).GetHashCode();
}
Related
I have a list of Receipts
IEnumerable<Receipt> Receipts =
new List<Receipt>()
{
new Receipt
{
Id = 1, CustomerId = 1, IsCheckedOut = false, OperationDate = new DateTime(2021, 1, 2),
ReceiptDetails = new List<ReceiptDetail>()
{
new ReceiptDetail { Id = 1, ProductId = 1, UnitPrice = 10, Product = ProductEntities.ElementAt(0), DiscountUnitPrice = 9, Quantity = 2, ReceiptId = 1 },
new ReceiptDetail { Id = 2, ProductId = 2, UnitPrice = 20, Product = ProductEntities.ElementAt(1), DiscountUnitPrice = 19, Quantity = 8, ReceiptId = 1},
new ReceiptDetail { Id = 3, ProductId = 3, UnitPrice = 25, Product = ProductEntities.ElementAt(2), DiscountUnitPrice = 24, Quantity = 1, ReceiptId = 1 },
}
},
new Receipt
{
Id = 2, CustomerId = 2, IsCheckedOut = false, OperationDate = new DateTime(2021, 1, 15),
ReceiptDetails = new List<ReceiptDetail>()
{
new ReceiptDetail { Id = 4, ProductId = 1, UnitPrice = 10, Product = ProductEntities.ElementAt(0), DiscountUnitPrice = 9, Quantity = 10, ReceiptId = 2 },
new ReceiptDetail { Id = 5, ProductId = 3, UnitPrice = 25, Product = ProductEntities.ElementAt(2), DiscountUnitPrice = 24, Quantity = 1, ReceiptId = 2 }
}
},
new Receipt
{
Id = 3, CustomerId = 1, IsCheckedOut = false, OperationDate = new DateTime(2021, 2, 15),
ReceiptDetails = new List<ReceiptDetail>()
{
new ReceiptDetail { Id = 6, ProductId = 1, UnitPrice = 10, Product = ProductEntities.ElementAt(0), DiscountUnitPrice = 9, Quantity = 10, ReceiptId = 3 },
new ReceiptDetail { Id = 7, ProductId = 2, UnitPrice = 25, Product = ProductEntities.ElementAt(1), DiscountUnitPrice = 24, Quantity = 1, ReceiptId = 3 }
}
}
};
I need to get the best selling(by quantity) products based on CustomerId
Here is my function
public IEnumerable<Product> GetMostSoldProductByCustomer(int customerId, int productCount)
{
var a = ReceiptEntities.Where(rd => rd.CustomerId == customerId)..SelectMany(x => x.ReceiptDetails);
var b = a.GroupBy(rd => rd.ProductId);
var c = b.Select(p => p.Select(y => y.Quantity).Sum());
}
I'm stuck on this, I have no idea how to connect b and c.
For better understanding, if customerId = 1 and productCount = 3. The function should return 3 products with Id = 1, 2, 3 accordingly in descending order by quantities
One note! The customer whose id = 1 has two receipts, that's why I'm calculating the sum of Quantity as there are the same products in different receipts
Try the following query:
public IEnumerable<int> GetMostSoldProductByCustomer(int customerId, int productCount)
{
var query =
from re in ReceiptEntities
where re.CustomerId == customerId
from rd in re.ReceiptDetails
group rd by rd.ProductId into g
select new
{
ProductId = g.Key,
TotalQuantity = g.Sum(x => x.Quantity)
} into s
orderby descending s.TotalQuantity
select s.ProductId;
return query;
}
A sequence of information about goods goodList of type Good and a sequence of prices of
goods in various stores storePriceList of type StorePrice are given. Each element of the
goodList sequence includes the Product SKU, Category, Country of origin fields.
Each element of the storePriceList sequence includes the Product SKU, Store Title,
Price fields.
For each country of origin get the number of stores offering goods manufactured in that
country, as well as the minimum price for goods from this country for all stores (CountryStat
values). If no product is found for a certain country that is presented in any store, then the
number of stores and the minimum price is assumed to be 0. Sort the list by country of origin.
Example:
goodList: new[]
{
new Good{Id = 1, Country = "Ukraine", Category = "Food"},
new Good{Id = 2, Country = "Ukraine", Category = "Food"},
new Good{Id = 3, Country = "Ukraine", Category = "Food"},
new Good{Id = 4, Country = "Ukraine", Category = "Food"},
new Good{Id = 5, Country = "Germany", Category = "Food"},
new Good{Id = 6, Country = "Germany", Category = "Food"},
new Good{Id = 7, Country = "Germany", Category = "Food"},
new Good{Id = 8, Country = "Germany", Category = "Food"},
new Good{Id = 9, Country = "Greece", Category = "Food"},
new Good{Id = 10, Country = "Greece", Category = "Food"},
new Good{Id = 11, Country = "Greece", Category = "Food"},
new Good{Id = 12, Country = "Italy", Category = "Food"},
new Good{Id = 13, Country = "Italy", Category = "Food"},
new Good{Id = 14, Country = "Italy", Category = "Food"},
new Good{Id = 15, Country = "Slovenia", Category = "Food"}
}
storePriceList: new[]
{
new StorePrice{GoodId = 1, Price = 1.25M, Shop = "shop1"},
new StorePrice{GoodId = 3, Price = 2.25M, Shop = "shop1"},
new StorePrice{GoodId = 5, Price = 4.25M, Shop = "shop1"},
new StorePrice{GoodId = 7, Price = 9.25M, Shop = "shop1"},
new StorePrice{GoodId = 9, Price = 11.25M, Shop = "shop1"},
new StorePrice{GoodId = 11, Price = 12.25M, Shop = "shop1"},
new StorePrice{GoodId = 13, Price = 13.25M, Shop = "shop1"},
new StorePrice{GoodId = 14, Price = 14.25M, Shop = "shop1"},
new StorePrice{GoodId = 5, Price = 11.25M, Shop = "shop2"},
new StorePrice{GoodId = 4, Price = 16.25M, Shop = "shop2"},
new StorePrice{GoodId = 3, Price = 18.25M, Shop = "shop2"},
new StorePrice{GoodId = 2, Price = 11.25M, Shop = "shop2"},
new StorePrice{GoodId = 1, Price = 1.50M, Shop = "shop2"},
new StorePrice{GoodId = 3, Price = 4.25M, Shop = "shop3"},
new StorePrice{GoodId = 7, Price = 3.25M, Shop = "shop3"},
new StorePrice{GoodId = 10, Price = 13.25M, Shop = "shop3"},
new StorePrice{GoodId = 14, Price = 14.25M, Shop = "shop3"},
new StorePrice{GoodId = 3, Price = 11.25M, Shop = "shop4"},
new StorePrice{GoodId = 2, Price = 14.25M, Shop = "shop4"},
new StorePrice{GoodId = 12, Price = 2.25M, Shop = "shop4"},
new StorePrice{GoodId = 6, Price = 5.25M, Shop = "shop4"},
new StorePrice{GoodId = 8, Price = 6.25M, Shop = "shop4"},
new StorePrice{GoodId = 10, Price = 11.25M, Shop = "shop4"},
new StorePrice{GoodId = 4, Price = 15.25M, Shop = "shop5"},
new StorePrice{GoodId = 7, Price = 18.25M, Shop = "shop5"},
new StorePrice{GoodId = 8, Price = 13.25M, Shop = "shop5"},
new StorePrice{GoodId = 12, Price = 14.25M, Shop = "shop5"},
new StorePrice{GoodId = 1, Price = 3.25M, Shop = "shop6"},
new StorePrice{GoodId = 3, Price = 2.25M, Shop = "shop6"},
new StorePrice{GoodId = 1, Price = 1.20M, Shop = "shop7"}
}
Expected Result:
expected: new[]
{
new CountryStat{Country = "Germany", MinPrice = 3.25M, StoresNumber = 5},
new CountryStat{Country = "Greece", MinPrice = 11.25M, StoresNumber = 3},
new CountryStat{Country = "Italy", MinPrice = 2.25M, StoresNumber = 4},
new CountryStat{Country = "Slovenia", MinPrice = 0.0M, StoresNumber = 0},
new CountryStat{Country = "Ukraine", MinPrice = 1.20M, StoresNumber = 7},
});
I had an idea to group storedPriceList by GoodId and then select min Price, but I have no idea what to do next.
goodList left join storePriceList by matching Id with GoodId
Group by Country
Select:
3.1. Get min value of Price
3.2. Remove Shop with null, distinct value and perform count
Order by Country
(
from a in goodList
join b in storePriceList on a.Id equals b.GoodId into ab
from b in ab.DefaultIfEmpty()
group new
{
Country = a.Country,
Price = b == null ? 0 : b.Price,
Shop = b == null ? null : b.Shop
} by a.Country into g
select new
{
Country = g.Key,
MinPrice = g.Min(x => x.Price),
StoresNumber = g.Where(x => x.Shop != null)
.Select(x => x.Shop)
.Distinct()
.Count()
}
)
.OrderBy(x => x.Country)
.ToList();
Demo # .NET Fiddle
var result = goodList
.Select(x => x.Country).Distinct()
.GroupJoin(
goodList.Join(storePriceList, good => good.Id, price => price.GoodId,
(good, goodsGroup) =>
new
{
Good = good,
Prices = goodsGroup
}), country => country, goods => goods.Good.Country,
(country, goods) => new
{
Country = country,
Goods = goods
})
.AsEnumerable()
.Select(x =>
new CountryStat
{
Country = x.Country,
MinPrice = x.Goods.Any() ? x.Goods.Select(y => y.Prices).Min(y => y.Price) : decimal.Zero,
StoresNumber = x.Goods.Any() ? x.Goods.Select(y => y.Prices).DistinctBy(y => y.Shop).Count() : 0
})
.OrderBy(x => x.Country)
.ToList();
you could Select countries in a list and remove repeated elements (hence a list of all countries). and from there you can easily divide the main list into list of list of goods per country (List<(string Country, List<prices>)>)
List<empl> lstSource = new List<empl>();
lstSource.Add(new empl { departmentId = 2, Id = 101, Name = "S1" });
lstSource.Add(new empl { departmentId = 2, Id = 109, Name = "S9" });
lstSource.Add(new empl { departmentId = 2, Id = 102, Name = "S2" });
lstSource.Add(new empl { departmentId = 4, Id = 101, Name = "S1" });
lstSource.Add(new empl { departmentId = 4, Id = 102, Name = "S2" });
lstSource.Add(new empl { departmentId = 4, Id = 108, Name = "S8" });
lstSource.Add(new empl { departmentId = 3, Id = 105, Name = "S5" });
lstSource.Add(new empl { departmentId = 3, Id = 103, Name = "S3" });
lstSource.Add(new empl { departmentId = 3, Id = 102, Name = "S2" });
should result {Id = 102, Name = "S2"}
if I add
lstSource.Add(new empl { departmentId = 3, Id = 101, Name = "S1" });
should result {Id = 102, Name = "S2"} {Id = 101, Name = "S1"}
Hint : we can group with departmentId and find common Id in 3 group.
Based on your comments and example above, I take it that the Name associated with any given Id is always the same. In that case, you could split the Ids registered on each department into separate lists, then intersect those lists to find the common Ids, and then find the associated Name for each common Id.
You have done something similar in your own example. By rewriting the code (e.g. by replacing the foreach loop with an Aggregate() function) you could achieve a more straight forward approach:
var idsPerDepartment = lstSource
.GroupBy(item => item.departmentId)
.Select(gr => gr.Select(item => item.Id));
var commonIds = idsPerDepartment
.Aggregate((common, next) => common.Intersect(next));
var commonItems = commonIds
.Select(id => lstSource.First(item => item.Id == id))
.Select(commonItem => new { commonItem.Id, commonItem.Name })
.OrderByDescending(commonItem => commonItem.Name);
commonItems is empty (not null) if there are no common items.
It could all be written as one single expression, but I've spilt it into several variables to clarify what is happening along the way.
var groups = lstSource.GroupBy(t1=> t1.departmentId)
.ToList();
var validIds = groups.First().Select(t1 => t1.Id).ToList();
foreach (var g in groups.Skip(0))
{
var otherGroupItemIds = g.Select(t1 => t1.Id).ToList();
validIds = validIds.Intersect(otherGroupItemIds).ToList();
}
if (validSRIds.Count > 0)
return lstSource.FindAll(t1 => validSRIds.Contains(t1.Id)).GroupBy(t2 => new { t2.Id, t2.Name }).Select(g => g.First()).OrderByDescending(t => t.Name).ToList();
you will get all common id,name which belongs to all group
I would like to filter out "" names then select each unique location where there are duplicate IDs regardless of name:
Data Setup
var list = new[]
{
new { id = 3, Name = "", Location = "LocationA" },
new { id = 2, Name = "", Location = "LocationA" },
new { id = 1, Name = "T", Location = "LocationB" },
new { id = 2, Name = "H", Location = "LocationB" },
new { id = 3, Name = "E", Location = "LocationB" },
new { id = 3, Name = "R", Location = "LocationB" },
new { id = 5, Name = "U", Location = "LocationC" },
new { id = 5, Name = "S", Location = "LocationC" },
new { id = 5, Name = "S", Location = "LocationD" },
new { id = 4, Name = "O", Location = "LocationD" },
new { id = 4, Name = "Z", Location = "LocationE" },
};
Query
var query1 = list
.Where(s => s.Name != "")
.GroupBy(g => g.Location)
.Where(w => w.Select(s => s.Location).Count() > 1)
.SelectMany(s => s)
.GroupBy(g => g.id)
.Where(w => w.Select(s => s.id).Count() > 1)
.SelectMany(s => s)
.ToList();
Console.WriteLine("output\n" + string.Join("\n", query1));
Returns
{ id = 3, Name = E, Location = LocationB }
{ id = 3, Name = R, Location = LocationB }
{ id = 5, Name = U, Location = LocationC }
{ id = 5, Name = S, Location = LocationC }
{ id = 5, Name = S, Location = LocationD }
vs What I actually wanted
{ id = 3, Name = E, Location = LocationB }
{ id = 3, Name = R, Location = LocationB }
{ id = 5, Name = U, Location = LocationC }
{ id = 5, Name = S, Location = LocationC }
LocationD has IDs 4 & 5 so it should've been filtered out, I wasn't able to do so. What am I doing wrong? How do I correct it?
Given
var list = new[]
{
new { id = 3, Name = "", Location = "LocationA" },
new { id = 2, Name = "", Location = "LocationA" },
new { id = 1, Name = "T", Location = "LocationB" },
new { id = 2, Name = "H", Location = "LocationB" },
new { id = 3, Name = "E", Location = "LocationB" },
new { id = 3, Name = "R", Location = "LocationB" },
new { id = 5, Name = "U", Location = "LocationC" },
new { id = 5, Name = "S", Location = "LocationC" },
new { id = 5, Name = "S", Location = "LocationD" },
new { id = 4, Name = "O", Location = "LocationD" },
new { id = 4, Name = "Z", Location = "LocationE" },
};
Example
var results = list
.Where(s => s.Name != "")
.GroupBy(x => new {x.id, x.Location})
.Where(g => g.Count() > 1)
.SelectMany(y => y);
foreach (var result in results)
Console.WriteLine($"{result.id}, {result.Name}, {result.Location}");
Output
3, E, LocationB
3, R, LocationB
5, U, LocationC
5, S, LocationC
Group by id and Location. And get .Count() more than 1.
var query1 = list
.Where(s => s.Name != "")
.GroupBy(g => new { g.Location, g.id })
.Where(g => g.Count() > 1)
.SelectMany(g => g)
.ToList();
Sample demo
I need to print the people with average not greater than 4 for algebra.
For example, "SCHOOL: 131, NAME: BLA-BLA, AVERAGE: 3,5". Thats the thing i've tried but it gave me an exception "sequence contains no elements"
upd: "not greater than 4"
var result = students
.OrderBy(s => s.Surname)
.ThenBy(o => o.Initials)
.Select(n => new
{
Name = n.Surname + " " + n.Initials,
Class = n.Class,
Average = students
.Where(s => s.Subject == "Algebra" && n.Surname == s.Surname && n.Initials == s.Initials)
.Average(w => w.Mark)
});
var students = new[]
{
new {Subject = "Algebra", Surname = "Jopin", Initials = "S.S", Class = 5, Mark = 2},
new {Subject = "Algebra", Surname = "Jopin", Initials = "S.S", Class = 5, Mark = 2},
new {Subject = "Algebra", Surname = "Jopin", Initials = "S.S", Class = 5, Mark = 3},
new {Subject = "Geometry", Surname = "Jopin", Initials = "S.S", Class = 5, Mark = 5},
new {Subject = "C.S", Surname = "Jopin", Initials = "S.S", Class = 5, Mark = 5},
new {Subject = "C.S", Surname = "Jopin", Initials = "S.S", Class = 5, Mark = 5},
new {Subject = "Algebra", Surname = "Bloom", Initials = "Q.V", Class = 2, Mark = 5},
new {Subject = "Algebra", Surname = "Bloom", Initials = "Q.V", Class = 2, Mark = 5},
new {Subject = "Algebra", Surname = "Bloom", Initials = "Q.V", Class = 2, Mark = 5},
new {Subject = "Geometry", Surname = "Bloom", Initials = "Q.V", Class = 2, Mark = 5},
new {Subject = "C.S", Surname = "Bloom", Initials = "Q.V", Class = 2, Mark = 5},
new {Subject = "C.S", Surname = "Bloom", Initials = "Q.V", Class = 2, Mark = 5},
new {Subject = "Geometry", Surname = "Roflov", Initials = "E.Y", Class = 1, Mark = 2},
new {Subject = "C.S", Surname = "Roflov", Initials = "E.Y", Class = 1, Mark = 2},
new {Subject = "C.S", Surname = "Roflov", Initials = "E.Y", Class = 1, Mark = 2},
new {Subject = "Algebra", Surname = "Einstein", Initials = "B.H", Class = 4, Mark = 3},
new {Subject = "Algebra", Surname = "Einstein", Initials = "B.H", Class = 4, Mark = 4},
new {Subject = "Geometry", Surname = "Einstein", Initials = "B.H", Class = 4, Mark = 5}
};
The Average method can fail with InvalidOperationException if the sequence contains no elements.
From your example, it looks like at least for student "Roflow E.Y" there is no entry for "Algebra"
Something like this should do what you want.
var result = students
.Where(s => s.Subject == "Algebra")
.GroupBy(s => new{s.Surname, s.Initials, s.Class})
.Select(g => new{ Name = g.Key.Surname + g.Key.Initials, g.Key.Class, Average = g.Average(x => x.Mark)})
.Where(x => x.Average >= 4)
.OrderBy(x => x.Name);