Fetch Multiple items from linq - c#

I have below legacy code in my application and would like to optimize it. arrayOfAttrValue has unique attributes. Can I use LINQ to acheive the loop optimization? If so then can you please show me how?
foreach (AttrValue attr in arrayOfAttrValue)
{
switch(attr.Attribute)
{
case Constants.Gender
mymodel.Gender = attr.Value;
break;
case Constants.Identifier
mymodel.AppIdentifier = attr.Value;
break;
}
}
My intention is not necessarily to use LINQ only. Any other way to minimize the loop would also help.
Thanks.

No, you can't do it in "true" LINQ because LINQ is about producing new objects from old objects. Here mymodel is a preexisting object that you want to modify.
You could use the Array.ForEach or the List.ForEach but
They aren't "true" LINQ and
The resulting code would be equivalent (a little slower because there would be a delegate)
Still, the downvoter probably wanted some LINQ, so I'll give some LINQ:
arrayOfAttrValue.All(attr => {
mymodel.Gender = attr.Attribute == Constants.Gender ? attr.Value : mymodel.Gender;
mymodel.AppIdentifier = attr.Attribute == Constants.Identifier ? attr.Value : mymodel.AppIdentifier;
return true;
});
One less line, ignoring the {} lines.

You have list of attributes, which represent key-value pairs. Natural way to keep such data structures is a dictionary. So, convert your input data format to dictionary:
var attributes = arrrayOfAttrValue.ToDictionary(a => a.Attribute, a => a.Value);
Or if each attribute is not unique in your array, dictionary creation will be more difficult, but that's data format you have. In order to make working with your data easier you should convert them to handy format:
var attributes = arrayOfAttrValue
.GroupBy(a => a.Attribute)
.ToDictionary(g => g.Key, g => g.Select(a => a.Value).Last());
After creating attributes dictionary, you can simply check if you have value for attribute and assign that value to model property. Attributes retrieving now simple and clear for any reader:
if (attributes.ContainsKey(Constants.Gender))
model.Gender = attributes[Constants.Gender];
if (attributes.ContainsKey(Constants.Identifier))
model.AppIdentifier = attributes[Constants.Identifier];

No need to do the loop manually in code, you can do it simply with .LastOrDefault():
mymodel.Gender = arrayOfAttrValue
.Where(attr => attr.Attribute == Constants.Gender)
.Select(attr => attr.Value).LastOrDefault() ?? mymodel.Gender;
mymodel.AppIdentifier = arrayOfAttrValue
.Where(attr => attr.Attribute == Constants.Identifier)
.Select(attr => attr.Value).LastOrDefault() ?? mymodel.AppIdentifier;
The ?? mymodel.Gender makes sure we're not setting it to Default(T) (i.e. null) in a situation where it was otherwise set to a value previously. This then matches the functional logic of your initial question.
Doing it this way makes it very clear what you are trying to do. Of course this approach means that you're looping over the array twice, however if your array is actually an array then this performance cost will be very small.
If you still have performance issues with this then you probably want to consider using a better data structure than an arrayOfAttrValue (something that is index accessable by Attribute such as a Dictionary<,>).

Related

Use LINQ for AddRange to Listbox

Hello I try To Convert My old code to new version with Linq but i have problem for do it
old :
foreach (var item in NetworkInterface.GetAllNetworkInterfaces())
{
if (item.NetworkInterfaceType == NetworkInterfaceType.Ethernet)
{
lstTrace.Items.Add(item.Name);
}
}
to this:
lstTrace.Items.Add(
NetworkInterface.GetAllNetworkInterfaces()
.Where(nic => nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet)
.FirstOrDefault()
.Name
);
But it just returns one result.
How can I get all found items?
I would not recommend to create 'one-liner' code which spans for 5 lines and mixes both data selection and filling list view. Make both things easy to read and understand. Split (1) retrieving and filtering data with (2) assigning data to list view:
var ethernetInterfaceNames =
from i in NetworkInterface.GetAllNetworkInterfaces()
where i.NetworkInterfaceType == NetworkInterfaceType.Ethernet
select i.Name;
foreach(var name in ethernetInterfaceNames)
lstTrace.Items.Add(name);
I would also move getting ethernet interface names to separate method or layer. Thus you will split business logic and presentation logic. You can use AddRange here, but it willl not make your code any simpler:
lstTrace.Items.AddRange(ethernetInterfaceNames.Select(n => new ListViewItem(n)).ToArray())
I believe simple foreach loop is far more readable.
You need to use the AddRange method to add multiple items and then you just need to use select to get the Names of your nics.
Your current code is using FirstOrDefault which will only ever return a single value (the first) from your enumerable.
lstTrace.Items.AddRange(
NetworkInterface
.GetAllNetworkInterfaces()
.Where(nic => nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet)
.Select(nic => nic.Name)
.ToArray()
);
Additionally xanatos's comment on your question is worth repeating here. Your previous code worked fine, and was readable. Doing this with LINQ isn't going to make your code faster and I would probably say makes it harder to read if anything. While the above code should work I would seriously consider just keeping your original code.
lstTrace.Items.AddRange(NetworkInterface.GetAllNetworkInterfaces().Where(nic => nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet).Select(a => a.Name).ToArray());
You are only getting one, because you are only asking for one: FirstOrDefault will give you the first member of the sequence or the type's default value if its empty.
What you need to do is project the sequence to what you really need using Select:
lstTrace.Items
.AddRange(NetworkInterface.GetAllNetworkInterfaces()
.Where(nic => nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet)
.Select(item => item.Name));
Here is an example on how to use AddRange
var projects = multi.Read<ProjectDto>();
var projectAct = multi.Read<ProjectActivity>();
foreach (var project in projects)
{
project.ProjectActivities = new List<ProjectActivity>();
project.ProjectActivities.AddRange(projectAct.Where(x => x.ProjectId == project.ProjectId));
}
Note: One cannot use the addrange on a data type IEnumerable only on the data type list.

Filter in linq with ID's in a List<int>

I need do a filter that request data with a parameter included in a list.
if (filter.Sc.Count > 0)
socios.Where(s => filter.Sc.Contains(s.ScID));
I try on this way but this not work, I tried also...
socios.Where( s => filter.Sc.All(f => f == s.ScID));
How I can do a filter like this?
socios.Where(s => filter.Sc.Contains(s.ScID));
returns a filtered query. It does not modify the query. You are ignoring the returned value. You need something like:
socios = socios.Where(s => filter.Sc.Contains(s.ScID));
but depending on the type of socios the exact syntax may be different.
In addition to needing to use the return value of your LINQ .Where(), you have a potential logic error in your second statement. The equivalent logic for a .Contains() is checking if Any of the elements pass the match criteria. In your case, the second statement would be
var filteredSocios = socios.Where( s => filter.Sc.Any(f => f == s.ScID));
Of course if you can compare object-to-object directly, the .Contains() is still adequate as long as you remember to use the return value.

How to optimize a LINQ with minimum and additional condition

Asume we have a list of objects (to make it more clear no properties etc.pp are used)
public class SomeObject{
public bool IsValid;
public int Height;
}
List<SomeObject> objects = new List<SomeObject>();
Now I want only the value from a list, which is both valid and has the lowest height.
Classically i would have used sth like:
SomeObject temp;
foreach(SomeObject so in objects)
{
if(so.IsValid)
{
if (null == temp)
temp = so;
else if (temp.Height > so.Height)
temp = so;
}
}
return temp;
I was thinking that it can be done more clearly with LinQ.
The first approach which came to my mind was:
List<SomeObject> sos = objects.Where(obj => obj.IsValid);
if(sos.Count>0)
{
return sos.OrderBy(obj => obj.Height).FirstOrDefault();
}
But then i waas thinking: In the foreach approach i am going one time through the list. With Linq i would go one time through the list for filtering, and one time for ordering even i do not need to complete order the list.
Would something like
return objects.OrderBy(obj => obj.Height).FirstOrDefault(o => o.IsValid);
also go twice throught the list?
Can this be somehow optimized, so that the linw also only needs to run once through the list?
You can use GroupBy:
IEnumerable<SomeObject> validHighestHeights = objects
.Where(o => o.IsValid)
.GroupBy(o => o.Height)
.OrderByDescending(g => g.Key)
.First();
This group contains all valid objects with the highest height.
The most efficient way to do this with Linq is as follows:
var result = objects.Aggregate(
default(SomeObject),
(acc, current) =>
!current.IsValid ? acc :
acc == null ? current :
current.Height < acc.Height ? current :
acc);
This will loop over the collection only once.
However, you said "I was thinking that it can be done more clearly with LinQ." Whether this is more clear or not, I leave that up to you to decide.
You can try this one:
return (from _Object in Objects Where _Object.isValid OrderBy _Object.Height).FirstOrDefault();
or
return _Objects.Where(_Object => _Object.isValid).OrderBy(_Object => _Object.Height).FirstOrDefault();
Would something like
return objects.OrderBy(obj => obj.Height).FirstOrDefault(o => o.IsValid);
also go twice throught the list?
Only in the worst case scenario, where the first valid object is the last in order of obj.Height (or there is none to be found). Iterating the collection using FirstOrDefault will stop as soon as a valid element is found.
Can this be somehow optimized, so that the linw also only needs to run
once through the list?
I'm afraid you'd have to make your own extension method. Considering what I've written above though, I'd consider it pretty optimized as it is.
**UPDATE**
Actually, the following would be a bit faster, as we'd avoid sorting invalid items:
return object.Where(o => o.IsValid).OrderBy(o => o.Height).FirstOrDefault();

Add on condition with Lambda

Is there a way I can do something like this with Lambda expressions?
responses.Add(sr).Where(v.Responses.TryGetValue(v.responseType, out sr));
I want to use lambda expressions or a ternary operator instead of a typical if expression.
NB:
responses is a List<string> type.
v.Responses is a Dictionary of <enum ResponseType, string>
v is some object
sr is a string.
What you want to do is:
string sr;
if (v.Responses.TryGetValue(v.responseType, out sr))
responses.Add(sr);
There is no way to ease the syntax and get the same performance.
But you could do:
responses.AddRange( v.Responses.Where( p => p.Key == v.responseType )
.Select( p => p.Value ) );
You may want to think about what the last one is doing, because it is kind of stupid...
EDIT: the reason why it is stupid is because the last expression translates into:
foreach(var pair in v.Responses)
{
if (pair.Key == v.responseType)
responses.Add(pair.Value);
}
So if your ResponseType enumeration had 6 million entries, the program would iterate over the entire set of keys to find the correct entry. In your case, since you already know the key, you should use v.Responses[key] as it is extremely fast (see in which cases dictionaries must be use).
LINQ is not supposed to modify collections.
Couldn't you simply do something like this:
string sr;
if(v.Responses.TryGetValue(v.responseType, out sr))
responses.Add(sr);
If I understand your question correctly, this might do what you're looking for.
Based on your example code, I'm assuming that your object "v" contains a field named "responseType" of type RepsonseType.
var responses = v.Responses
.Where(r => r.Key == v.responseType)
.Select(r => r.Value)
.ToList();

Can I use linq to achieve the same thing this foreach loop does?

Here's the c# code that I have:
private double get806Fees (Loan loan)
{
Loan.Fee.Items class806;
foreach (Loan.Fee.Item currentFee in loan.Item.Fees)
{
if (currentFee.Classification == 806) class806.Add(currentFee);
}
// then down here I will return the sum of all items in class806
}
Can I do this using linq? If so, how? I have never used linq and i've read in several places that using linq instead of a foreach loop is faster... is this true?
Similar to some existing answers, but doing the projection in the query, to make the Sum call a lot simpler:
var sum = (from fee in loan.Items.Fees
where fee.Classification == 806
select fee.SomeValueToSum).Sum();
loan.Item.Fees.
Where(x => x.Classification == 806).
Sum(x => x.SomeValueProperty)
Whether it is faster or not is debatable. IMO, both complexities are the same, the non-LINQ version may be faster.
var q =
from currentFee in loan.Item.Fees
where currentFee.Classification == 806
select currentFee;
var sum = q.Sum(currentFee => currentFee.Fee);
private double get806Fees(Loan loan)
{
return load.Item.Fees.
Where(f => f.Classification == 806).
Sum(f => f.ValueToCalculateSum);
}
I'm assuming here that ValueToCalculateSum is also a double. If it's not then you have to convert it before it is returned.
All of the answers so far are assuming that you're summing up loan.Fees. But the code you actually posted calls Items.Add() to add each Item in loan.Fees.Items to an Items object, and it's that Items object (and not loan.Fees, which is also an Items object) that you say you want to sum up.
Now, if Items is just a simple collection class, then there's no need to do anything other than what people are suggesting here. But if there's some side-effect of the Add method that we don't know about (or, worse, that you don't know about), simply summing up a filtered list of Item objects might not give you the results you're looking for.
You could still use Linq:
foreach (Loan.Fee.Item currentFee in loan.Item.Fees.Where(x => x.Classification == 806)
{
class806.Add(currentFee);
}
return class806.Sum(x => x.Fee)
I'll confess that I'm a little perplexed by the class hierarchy implied here, though, in which the Loan.Item.Fees property is a collection of Loan.Fee.Item objects. I don't know if what I'm seeing is a namespace hierarchy that conflicts with a class hierarchy, or if you're using nested classes, or what. I know I don't like it.

Categories

Resources