Which of these solutions is preferred?
For a List:
List<ExampleInfo> exampleList = new List<ExampleInfo>();
public class ExampleInfo
{
internal ExampleInfo()
{ }
/* Business Properties */
public int Id { get; set; }
public string Type { get; set; }
public decimal Total { get; set; }
}
I wish to get subtotals based off the 'Total' value.
Option 1:
var subtotal1 = exampleList.Where(x => x.Type == "Subtype1").Sum(x => x.Total);
var subtotal2 = exampleList.Where(x => x.Type == "Subtype2").Sum(x => x.Total);
Option 2:
decimal subtotal1 = 0m;
decimal subtotal2 = 0m;
foreach (ExampleInfo example in exampleList)
{
switch (example.Type)
{
case "Subtype1":
subtotal1 += example.Total;
break;
case "Subtype2":
subtotal2 += example.Total;
break;
default:
break;
}
}
The list will be <10 items in most cases.
Edit: Chris raised a very good point I did not mention. The program is already using .NET Framework 3.5 SP1 so compatibility isn't an important consideration here.
Regardless of list size, if you're targeting .NET 3.5 I'd go with LINQ, if only for readability.
I am a great fan of writing what you mean, not how it's done and LINQ makes this very easy in such cases.
You can probably even pull the calculations into a single LINQ statement, grouping by Type. That way you won't have two loops for LINQ but only one as in the second example:
var subtotals = from x in exampleList
group x by x.Type into g
select new { Type = x.Key, SubTotal = g.Sum(x => x.Total) };
(Not entirely sure whether the code works as it, it's just a quick adaption from one of the 101 LINQ Samples. Syntax should be ok, though.)
Both of these examples have duplicated code, and both aren't ready for a change on Type - what if it had three values? What if it had 30?
You could use linq to group by it and get the total:
var totals = from p in exampleList
group p by p.Type into g
select new { Type = g.Key, Total = g.Sum(p => p.Total ) };
So totals is a collection of objects with the properties Type and Total
Option 3
var groupings = exampleList
.GroupBy(x => x.Type, x => x.Total)
.Select(x => new { Type = x.Key, SubTotal = x.Sum() } );
You'll have a list of classes like so:
class <Anonymous>
{
public string Type { get; }
public decimal SubTotal { get; }
}
Enumerate and assign to the appropriate value, although it might be overkill for such a small set.
I don't think there would be much performance difference for such small lists.
Option 1 will iterate through the list twice while Option 2 only iterates through the list once. That may be more important to note for larger lists than small ones.
Option 1 is more readable, but I would definitely make sure to make a comment that it iterates through the list twice.
The obvious advantage to Option 2 is that the code works in .NET Framework 2.0. Using LINQ means that your application requires .NET Framework 3.5.
For option1, internally the foreach loop will be executed twice by the C# run-time env. Hence processing type will be more. But for <10 items, it hardly makes any difference, and option 1 seems more readable. I would go with option 1 for < 10 items.
Related
Suppose I have this table:
Image
Perimeter
a
1
b
1
b
2
d
3
e
1
I want to return the images that have relationship with only ONE perimeter.
The expected result would be images "a,d,e" because image "b" has relationship with perimeter "1" and "2".
The objective is to remove the releated image when I delete the perimeter. But if it is linked to another perimeter, I can't remove it.
How can I write this query with LINQ?
I think it would be something like this:
SELECT "ImageId"
WHERE "PerimeterId" = PerimeterId IN
(
SELECT "ImageId"
GROUP BY "ImageId"
HAVING COUNT("PerimeterId") = 1
)
but I don't know how to convert it to LINQ.
You could use a NOT EXISTS
var query = dbo.Table
.Where(t => !dbo.Table.Any(t2 => t.Image = t.Image && t.Perimeter != t2.Perimeter));
You can easily adapt this to only select the image part. But, if you are coming from SQL, thinking about "Selecting rows" based on a "HAVING()" group calculation, then you will want to look at the .SelectMany() LINQ method. This lets you "combine back together data partitioned into groups". While your needs are to only return "one from each group", it's easy to see where this can be adjusted.
This can be run in the "C# interactive window" of SSDT 2015:
struct imagePerimeter { //this might be whatever object type it is for you...
public string Image { get; set; } //a,b,b,d,e
public int Perimeter { get; set; } //1,1,2,3,1
}
Func<string, int, imagePerimeter> newIP = (i, p) => new imagePerimeter() { Image = i, Perimeter = p };
List<imagePerimeter> results = new List<imagePerimeter>() { {newIP("a",1) }
,{newIP("b",1) }
,{newIP("b",2) }
,{newIP("d",3) }
,{newIP("e",1) } };
Func<imagePerimeter, string> ipImage = (ip) => ip.Image; //the Func's "ipImage" and "newIP" could just be inlined into LINQ, but it helps to see and debug at times IMO.
var imagesWithOnePerimeter = results.GroupBy<imagePerimeter, string>(ipImage) //even in SQL, the "GROUP BY" conceptually comes first, in LINQ, it comes first in code too!
.Select(grp => new { Image = grp.Key, PerimeterCount = grp.Count(), Details = grp }) //there's probably a more technical term, but notice how we "carry forward" the original reference to [grp]
.Where(subTotals => subTotals.PerimeterCount == 1)
.SelectMany(filtered => filtered.Details.AsEnumerable())
.ToList();
Im trying to make a program that sorts objects by more then one parameters.
I need the order by to be in the same weight for all the parameters. what functions do i need to use in order to get that result?
I tried to use OrderBy() and then- ThenBy() but its ordering the first parameter first so the ordering isn't equal weight.
values = File.ReadAllLines(filepath)
.Skip(1)
.Select(v => Fund.FromCsv(v))
.OrderByDescending(x => x.sharp)
.ThenBy(x=>x.yearlychange)
.ToList();
For example you can take the stocks market, in that case i want to order the stocks by the yield in the last year but also to order by standard deviation. in that way i can get stock that have the best yield in the last year but also the best standard deviation. it wont be the best yield from all, it will be combined.
As you have been already informed, it is not really a programistic problem, more like algorithm/domain one. Nevertheless, if you already would have the algorithm, you can, of course, do it like this way. (basing on the example you present in the comment)
void Main()
{
var funds = new List<Fund>();
funds.Add(new Fund() { Age = 18, Money = 100000 });
funds.Add(new Fund() { Age = 20, Money = 101000 });
//Here is normally your code with loading it from CSV
//File.ReadAllLines(filepath)
// .Skip(1)
// .Select(v => Fund.FromCsv(v))
var value = funds.OrderBy(t => t.SortingFactor);
}
public class Fund
{
public int Age { get; set; }
public decimal Money { get; set; }
public decimal SortingFactor
{
get
{
//Here is your domain algorithm you must sort out first (your example data would be)
return Money / Age;
}
}
}
I'm not sure if I fully understand your aim but another alternative if fund is not code you can modify is an anonymous object in your order by e.g.
values = File.ReadAllLines(filepath)
.Skip(1)
.Select(v => Fund.FromCsv(v))
.OrderByDescending(x => new { x.sharp, x.yearlychange })
.ToList();
How to convert a query to bool?
I used the "ALL (x => x)" but did not give the answer I needed.
Code Line
checkItemInventory.Where(x => listCost.Contains(x.Id));
In this case, the listcost would have 2 items, I needed to check if the checkItemInventory has these 2 items.
"All items in the inventory have an id that present in listcost". listCost needs to have the same number of items as inventory (assuming Id is unique) possibly more, to stand a chance of returning true
checkItemInventory.All(x => listCost.Contains(x.Id))
"At least one item in the inventory has an id that is also in listCost". Listcost could minimally have only one id in it, to stand a chance of returning true
checkItemInventory.Any(x => listCost.Contains(x.Id))
As you can see, neither of these are what you want as you seem to be saying you want to check whether every item in listcost is also present in the inventory. This is like the top code, but the other way round ("all items in listCost are present in inventory" vs "all items in inventory are present in listcost"
I think I'd make a dictionary out of the inventory first, unless it's already something that supports a fast lookup:
var d = checkItemInventory.Select(x => new { x.Id, x.Id }).ToDictionary();
var boolResult = listCost.All(lc => d.ContainsKey(lc));
If inventory is small, you could use this approach:
listCost.All(lc => checkItemInventory.Any(cii => cii.Id == lc));
Just be mindful that internally it might do something like:
bool all = true;
foreach(lc in listCost){
bool found = false;
foreach(cci in checkItemInventory)
if(lc == cci.Id){
found = true;
break;
}
all &= found;
if(!all)
return false;
}
return true;
Which is a lot of repeated comparisons (for every item in listCost, the whole inventory is scanned), could be slow
Edit
I asked for clarification of how you store your inventory and your costs of building items. Here's one assumption I made, and how a solutio based on it might work:
Assuming your inventory has the kind of item and a count saying how many of that item the player is carrying:
class InventoryItem{
int ItemKindId { get; set;}
int CountOf { get; set; }
}
player.Inventory.Add(new InventoryItem() {
ItemKindId = Constants.WOOD, //1
CountOf = 10 //holding 10 items of wood
};
player.Inventory.Add(new InventoryItem() {
ItemKindId = Constants.STONE, //2
CountOf = 5 //holding 5 items of stone
};
Assuming you have a Recipe for making e.g. an axe, it needs 1 wood and 2 stone, but it lists them in simple order:
int[] axeRecipe = new int[] { Constants.WOOD, Constants.STONE, Constants.STONE };
Might be easiest to group the recipe:
var recipe = axeRecipe.GroupBy(item => item)
/*
now we have a grouping of the recipe[item].Key as the material and a
recipe[item].Count() of how much. The group is like a dictionary:
recipe[Constants.WOOD] = new List<int>{ Constants.WOOD };
recipe[Constants.STONE] = new List<int>{ Constants.STONE, Constants.STONE, };
A group item has a Key and a list of objects that have that key
Because my recipe was simply ints, the Key is the same number as all the
items in the list
*/
//for all items in the recipe
grp.All(groupItem =>
//does the player inventory contain any item
playerInventory.Any(inventoryItem =>
//where the material kind is the same as the recipe key (material)
inventoryItem.ItemKindId == groupItem.Key &&
//and the count they have of it, is enough to make the recipe
inventoryItem.CountOf >= groupItem.Count()
);
You can of course reduce this to a single line if you want: axeRecipe.GroupBy(...).All(...)
You could map the listCost to a list of int and then use Except() and Any() to check whether all items are contained:
bool containsAll = !listCost.Select(x => x.Id).Except(checkItemInventory).Any();
[UPDATE]
You are telling us the following:
How to convert a query to bool? I used the "ALL (x => x)" but did not give the answer I needed.
checkItemInventory.Where(x => listCost.Contains(x.Id));
In this case, the listcost would have 2 items, I needed to check if
the checkItemInventory has these 2 items.
if you need to check if there is any result then you can use:
bool hasItems = checkItemInventory.Where(x => listCost.Contains(x.Id)).Any();
if you need to count the result you can use
checkItemInventory.Where(x => listCost.Contains(x.Id)).Count();
You could use a Join to create a method based Linq query and use the results to check if the length of the list is greater than 0. Then turn that into a boolean.
var query = checkItemInventory.Join(listCost,
inventory => inventory.Id,
cost => cost.Id,
(inventory, cost) => new { id = inventory.Id });
var count = query.ToList().Count();
var b = (count > 0);
If I get it correctly, listCost can have less elements than checkItemInventory. You want to check that all elements in listCost have a corresponding element in checkItemInventory. Correct? If yes, try this:
listCost.All(x => checkItemInventory.Contains(x));
I don't know the type of these lists, so you might need to use x.id in some places
Is it possible to write a single-line query to select some items of a collection if the Linq function All returned true for that collection? My question arises from the fact that All() returns a boolean value, and not the initial list.
I'll give a short example. Say we have a list of accounts:
class Account
{
public DateTime? CloseDate { get; set; }
public decimal? AmountOnCloseDate { get; set; }
}
Is it possible to have a single-line query that selects accounts (from a given accounts list) with AmountOnCloseDate larger than some given value, but only if all the accounts have their CloseDate larger than some given value?
This is possible, only thing you have to do is assign it to a new list as you want it to remain empty if .All() fails:
List<Example> newList =
(originalList.All( r => r.A > 5 ))
?originalList.Where( r => r.B > 1 ).ToList()
: new List<Example>();
Or you could do the .All() check in the where, that would DRASTICALLY decrease performance but doesn't require the ternary operator.
List<Point> newL = originalList
.Where( r => r.X > 34 && originalList.All( t => t.Y > 241 ) )
.ToList();
Disclaimer: There is no advantages of doing this in 1 line, only disadvantages. In real world scenarios I will always advice against doing this, and just suggest the if.
Hint: stick to the if
I don't believe this is possible, the closest you will get is having an if on the All and then the Select:
if (accountList.All(a => a.CloseDate.Value > someValue))
{
var result = accountList.Where(a => a.AmountOnCloseDate > someOtherValue);
}
Note that I would caution you to stay with a normal if statement for this as the ternary becomes less readable.
I can't think of any C# statement/block/program that can't be made "single line" (except # directives)
var result = new[] { accounts }.Where(g => g.All(a => a.CloseDate > DateTime.Now))
.SelectMany(g => g).Where(a => a.AmountOnCloseDate > 42).ToList();
I have an object (KS), which holds ID and Title (which has a number as part of the Title).
All I'm trying to do is sort it into descending order. The object has:
ID Title
1 1 Outlook VPN
2 2 Outlook Access
3 4 Access VBA
4 3 Excel Automation
So when order by Title, it should read:
ID Title
3 4 Access VBA
4 3 Excel Automation
2 2 Outlook Access
1 1 Outlook VPN
The code I'm using to sort it is:
IEnumerable<KS> query = results.OrderByDescending(x => x.Title);
However, query still has the objects in the original order!
Is there something to do with having numbers at the start of Title that I'm missing?
EDIT
I've added the code from the controller for clarity:
[HttpPost]
// [ValidateAntiForgeryToken]
// id is a string of words eg: "outlook access vpn"
// I split the words and want to check the Title to see how many words appear
// Then sort by the most words found
public JsonResult Lookup(string id)
{
List<string> listOfSearch = id.Split(' ').ToList();
var results = db.KS.Where(x => listOfSearch.Any(item => x.Title.Contains(item)));
// search each result, and count how many of the search words in id are found
// then add the count to the start of Title
foreach (KS result in results)
{
result.KSId = 0;
foreach (string li in listOfSearch)
{
if (result.Title.ToLower().Contains(li.ToLower()))
{
result.KSId += 1;
}
}
result.Title = result.KSId.ToString() + " " + result.Title;
}
// sort the results based on the Title - which has number of words at the start
IEnumerable<KS> query = results.OrderByDescending(x => x.Title).ToList();
return Json(query, JsonRequestBehavior.AllowGet);
}
Here is a screenshot after query has been populated showing Titles in the order: 1, 2, 1, 1:
Model for the object if it helps is:
public class KS
{
public int KSId { get; set; }
public string KSSol { get; set; }
public string Title { get; set; }
public string Fix { get; set; }
}
As I said in a comment, put a .ToList() where you declare your results variable. That is:
var results = db.KS.Where(x => listOfSearch.Any(item => x.Title.Contains(item)))
.ToList();
If you don't do that, the foreach loop will modify objects that might not be the same as the objects you sort later, because the database query is run again each time you enumerate your IQueryable<>.
You can always just ignore the strange behavior and go the safe way:
List<KS> query = results.ToList();
query.Sort((a, b) => a.Whatever.CompareTo(b.Whatever));
return Json(query, blah);
I simple did this and it worked for me :-
var sortedOrder = Query.OrderBy(b => b.Title.Substring(b.Title.IndexOf(" ")));
All I have done is SubString the Title at the index of of the blank space when ordering the objects in the sequence, that way, the OrderBy is looking at the first character in the title rather than the number at the beginning.
Old question, but maybe this will help someone using C#. I used the following expressions to sort a list of objects based on their quantity parameter in ascending or descending order. Can modify it to compare text as the original question was concerned with.
Ascending Order:
locationMaterials.Sort((x, y) => x.Quantity.CompareTo(y.Quantity));
Descending Order:
locationMaterials.Sort((x, y) => y.Quantity.CompareTo(x.Quantity));
You are missing .ToList()
IEnumerable<KS> query = results.OrderByDescending(x => x.Title).ToList();
results.OrderByDescending(x => x.Title) is a query, and it has no data.
ToList() forces the query to be executed.
[EDIT]
My answer assumes that your results has acually not been materialized, and that that is the source of your problem.