How to replace nested loops with LINQ - in a clean, manageable manner - c#

Codewise, what it the cleanest way to do this using linq? Below, I have a crude example where I want to find a matching class instance based on name.
class item
{
string name {get;set;}
int identifier {get;set;}
}
void DoSomething()
{
List<item> List1 = GetSampleItems();
List<item> List2 = GetOtherSampleItems();
for(int a=0;a<List1.count;a++)
{
for(int b=0;b<List2.count;b++)
{
if(List1[a].identifier == List2[b].identifier)
{
List1[a].name = List2[b].name;
}
}
}
}

Linq is for querying, not updating, so you'll still need to loop through the results to make the changes, but you can join to match up the two lists like so:
var query = from l1 in List1
join l2 in List2
on l1.identifier equals l2.identifier
select new {l1, l2};
Now loop through the query to update the l1 items:
foreach(var item in query)
item.l1.name = item.l2.name;
As a side note, there's nothing wrong with the way you're doing it (other than you could break out of the inner loop if a match is found). If you understand how it works and the performance is acceptable, there's no compelling reason to change it.

This should work:
var query = from l1 in List1
join l2 in List2 on l1.identifier equals l2.identifier
select new
{
l1values = l1,
l2Name = l2.name
};
foreach(var item in query)
item.l1Values.name = item.l2Name;

A better way is using a Dictionary<TK,TV>:
Dictionary<int,item> l2dic = List2.ToDictionary(x => x.identifier);
item itm;
List1.ForEach(x => {
if(l2dic.TryGetValue(x.identifier,out itm)) {
x.name = itm.name;
}
});
Or as #Rawling says, use a foreach loop instead:
Dictionary<int,item> l2dic = List2.ToDictionary(x => x.identifier);
item itm;
foreach(item x in List1) {
if(l2dic.TryGetValue(x.identifier,out itm)) {
x.name = itm.name;
}
}
Ideone demo (with slight modifications to your item class).
This runs on average in linear time whereas your approach runs in quadratic time.
The assumption is however that the identifiers are unique: no two elements in the same list can have the same identifier.
A concluding note is that variables in general start with a lowercase character so list1 and list2 whereas classes and properties start with a capital one (this Item, Identifier and Name).

Related

Find all ids from list in another list

I have a list containing models where each model has a number.
Now I'd like to search another list containg other models where each model could potentially have the number from the first list.
This is how my models look:
public class Task
{
int AddressId; //foreign key
}
public class Address
{
int Id; //primary key
}
Now I have a list of addresses and a list of tasks.
I'd like to filter my list of tasks to only those tasks where the AddressId is inside my addresses list.
How would my linq look like?
result = tasks.Where(t => t.AddressId == ???)
Take a look at Join
from m in model1s
join n in model2s on m.Id equals n.Fk
select n
To get them into arrays, if you don't know:
var model1s = new[] { model1A, model1B, model1C };
var model2s = new[] { model2A, model2B, model2C };
Use GroupJoin to avoid duplicates:
result = from l2 in list2
join l1 in list1 on l2.FK equals l1.Id into g
select l2;
List<Task> taskList = new List<Task>{new Task(2), new Task(3), new Task(5)};
List<Address> addrList = new List<Address>{new Address(1), new Address(3), new Address(2)};
var result = taskList.Where(x => addrList.Select(y => y.Id).Contains(x.AddressId));
Just one more alternative I often like to use:
I u could also use a HashSet<T> instead of List<T> then you could use the IntersectWith Method or Intersect Method which makes a more smooth code:-)
so you would have
var result = items1.Intersect(items2)
Notice the AddressId must be public (however it is not recommended to have public fields so you may change it to a property) so you can do this:
var yourList = addressList.Select(i => i.Id).Distinct();
taskList.Where(i => yourList.Contains(i.AddressId));

Comparing strings in 2 list using Linq

I need to compare to list with strings, and find whitch elements that are similar. ie.
List<string> listA = {"ProfileHeight", "ProfileWidth", "WebThickness"}
List<string> listB ={"ProfileHeightVisibility", "ProfileWidthVisibility", "WebThicknessVisibility", "FlangeThicknessVisibility", "DepthVisibility"}
I was wondering if it is possible to use Linq. I want the result to be a list with the elements from ListB that have a name similar to the strings in listA. Where the statemant i.e. "ProfileHeightVisibility.Contains("ProfileHeight") = true
You mean you want items in listB where it contains any of the elements in listA?
listB.Where(b => listA.Any(a => b.Contains(a))
You can do it in LINQ:
listB.Where(e => listA.Any(a => e.Contains(a)));
Bear in mind that this algorithm has a runtime cost similar to O(n^2), it will therefore slow down quickly as the number of elements in your lists grows.
You can also implement things like that :
public class EqualityComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return y.Contains(x);
}
public int GetHashCode(string obj)
{
return 1;
}
}
and then use intersect like that:
listB.Intersect(listA, new EqualityComparer()).ToList();
Yes you can do such thing with LINQ
public List<string> GetSimilarStrings(List<string> list1, List<string> list2)
{
return (from item in list1 from item2 in list2 where string.Equals(item,item2,StringComparison.InvariantCultureIgnoreCase) select item).ToList();
}
List<string> listA = new List<string> { "zinmin", "zin1", "zin2" };
List<string> listB = new List<string> { "zinmin", "min1", "min2" };
List<string> matching = listA.Intersect(listB).ToList();
matching result will come {"zinmin"} . Sorting order doesn't matter.

c# where in with list and linq

I have two lists, one have a list of object A an other a list of objects B, like this:
ObjectA
{
Int64 idObjectA;
String name;
....
}
ObjectB
{
Int64 idObjectB;
Int64 idObjectA;
String name;
....
}
I have two list, one with Object A and other with Object B. I want to create a new list C that have only objects B, which IDObjectA is any ID of the list A.
In SQL it would be somthing line that:
select * from B where IDObjectA IN(1,2,3,4...);
In my case, the list of values for the IN clause is the list of ObjectA, which have the property idObjectA.
You can use the Join linq method to achieve this by joining listB and listA by their idObjectA, then select itemB.
var result = (from itemB in listB
join itemA in listA on itemB.idObjectA equals itemA.idObjectA
select itemB).ToList();
This method has a linear complexity (O(n)). Using Where(... => ....Contains()) or double foreach has a quadratic complexity (O(n^2)).
The same with Join and without Contains:
var listC = listB.Join(listA, b => b.ObjectAId, a => a.Id, (b, a) => b).ToList();
This is slightly different way of doing it as opposed to a join.
List<ObjectA> listA = ..
List<ObjectB> listB = ..
int[] listAIds = listA.Select(a => a.idObjectA).ToList();
//^^ this projects the list of objects into a list of ints
//It reads like this...
//get items in listB WHERE..
listB.Where(b => listAIds.Contains(b.idObjectA)).ToList();
//b.idObjectA is in listA, OR where listA contains b.idObjectA
Not linq, but does what you want it to:
List<ObjectB> C = new List<ObjectB>();
foreach (n in B)
{
foreach (c in A)
{
if (n.idObjectA == c.idObjectA)
{
C.Add(n)
break;
}
}
}
Or if you wanted higher performance, use a for, and higher than that use Cédric Bignon's solution.

How to Map Id in List?

Dictionary<int, string> lstSrc = new Dictionary<int, string>();
Dictionary<int, string> lstDest = new Dictionary<int, string>();
lstSrc.Add(1, "All");
lstSrc.Add(2, "Weekday");
lstSrc.Add(3, "WeekEnd");
lstDest.Add(1, "All");
lstDest.Add(2, "X1");
lstDest.Add(3, "X2");
lstDest.Add(4, "Weekday");
lstDest.Add(5, "WeekEnd");
Compare only when name matches in Source and Destination
var matchingItems = lstDest
.Where(l2 => lstSrc.Any(l1 => l1.Value.Equals(l2.Value))).ToList();
matchingItems.AddRange(lstDest.Except(matchingItems));
This query gives result as see in attached image how to get that result without using LINQ ?
How i can achieve this ?
[1]: http://i.stack.imgur.com/FLicZ.png
To get the matching items you could use a query like this:
var matchingItems = List2
.Where(l2 => List1.Any(l1 => l1.TimeGroupName.Equals(l2.TimeGroupName));
matchingItems.AddRange(List2.Except(matchingItems)));
Edited: equivalent without using Linq: (It's easy to forget how much boiler plate Linq saves you from writing!)
// Get the matching items
List<TIMEGROUPINFO> matchingItems = new List<TIMEGROUPINFO>();
foreach (TIMEGROUPINFO l1 in List1)
{
foreach (TIMEGROUPINFO l2 in List2)
{
if (l1.TimeGroupName.Equals(l2.TimeGroupName))
{
matchingItems.Add(l1);
continue;
}
}
}
// Append the items from List2 which aren't already in the list:
foreach (TIMEGROUPINFO l2 in List2)
{
bool exists = false;
foreach (TIMEGROUPINFO match in matchingItems)
{
if (match.TimeGroupName.Equals(l2.TimeGroupName))
{
// This item is already in the list.
exists = true;
break;
}
}
if (exists = false)
matchingItems.Add(l2);
}
I understand that you want to perform a query on list 2 based on list 1. Linq is very good for that.
so, if you wrote something like
//List1Element is a single element in the first list.
List1Element = List1[i];
List2.Where(l2 => l2.TimeGroupName == List1Element.TimeGroupName).ToList();
That might accomplish what I think you're trying to accomplish.
If you're trying to match the entire List1 at once, you can either iterate through all the list1 elements, or you can look into Linq Join operations

Overlay/Join two collections with Linq

I have the following scenario:
List 1 has 20 items of type TItem, List 2 has 5 items of the same type. List 1 already contains the items from List 2 but in a different state. I want to overwrite the 5 items in List 1 with the items from List 2.
I thought a join might work, but I want to overwrite the items in List 1, not join them together and have duplicates.
There is a unique key that can be used to find which items to overwrite in List 1 the key is of type int
You could use the built in Linq .Except() but it wants an IEqualityComparer so use a fluid version of .Except() instead.
Assuming an object with an integer key as you indicated:
public class Item
{
public int Key { get; set; }
public int Value { get; set; }
public override string ToString()
{
return String.Format("{{{0}:{1}}}", Key, Value);
}
}
The original list of objects can be merged with the changed one as follows:
IEnumerable<Item> original = new[] { 1, 2, 3, 4, 5 }.Select(x => new Item
{
Key = x,
Value = x
});
IEnumerable<Item> changed = new[] { 2, 3, 5 }.Select(x => new Item
{
Key = x,
Value = x * x
});
IEnumerable<Item> result = original.Except(changed, x => x.Key).Concat(changed);
result.ForEach(Console.WriteLine);
output:
{1:1}
{4:4}
{2:4}
{3:9}
{5:25}
LINQ isn't used to perform actual modifications to the underlying data sources; it's strictly a query language. You could, of course, do an outer join on List2 from List1 and select List2's entity if it's not null and List1's entity if it is, but that is going to give you an IEnumerable<> of the results; it won't actually modify the collection. You could do a ToList() on the result and assign it to List1, but that would change the reference; I don't know if that would affect the rest of your application.
Taking your question literally, in that you want to REPLACE the items in List1 with those from List2 if they exist, then you'll have to do that manually in a for loop over List1, checking for the existence of a corresponding entry in List2 and replacing the List1 entry by index with that from List2.
As Adam says, LINQ is about querying. However, you can create a new collection in the right way using Enumerable.Union. You'd need to create an appropriate IEqualityComparer though - it would be nice to have UnionBy. (Another one for MoreLINQ perhaps?)
Basically:
var list3 = list2.Union(list1, keyComparer);
Where keyComparer would be an implementation to compare the two keys. MiscUtil contains a ProjectionEqualityComparer which would make this slightly easier.
Alternatively, you could use DistinctBy from MoreLINQ after concatenation:
var list3 = list2.Concat(list1).DistinctBy(item => item.Key);
Here's a solution with GroupJoin.
List<string> source = new List<string>() { "1", "22", "333" };
List<string> modifications = new List<string>() { "4", "555"};
//alternate implementation
//List<string> result = source.GroupJoin(
// modifications,
// s => s.Length,
// m => m.Length,
// (s, g) => g.Any() ? g.First() : s
//).ToList();
List<string> result =
(
from s in source
join m in modifications
on s.Length equals m.Length into g
select g.Any() ? g.First() : s
).ToList();
foreach (string s in result)
Console.WriteLine(s);
Hmm, how about a re-usable extension method while I'm at it:
public static IEnumerable<T> UnionBy<T, U>
(
this IEnumerable<T> source,
IEnumerable<T> otherSource,
Func<T, U> selector
)
{
return source.GroupJoin(
otherSource,
selector,
selector,
(s, g) => g.Any() ? g.First() : s
);
}
Which is called by:
List<string> result = source
.UnionBy(modifications, s => s.Length)
.ToList();

Categories