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.
Related
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).
I have an objectA with an ID property and a Status property.
I have a ListA which is a collection of objectA.
I also have an objectB with an ID property and a Status property.
I have a ListB which is a collection of objectB.
I need to match listA collection with listB collection based on ID and if there is a match update the Status from ListB to ListA.
What is the best way to do this without foreach loop?
Thanks for the help.
You can use LINQ for this:
var objectsForUpdate = (from a in listA
join b in listB
on a.Id equals b.Id
select new { a, b });
foreach (var obj in objectsForUpdate)
{
obj.a.Status = obj.b.Status;
}
Note this cannot be achieved without using the foreach statement.
I had a similar scenario. I had cartProducts and productsPurchasePrices.
I used linq to join the two collections:
//join currentCart...
var p = currentCart.Rows.Join(
productsPurchasePrices, //... with productsPurchasePrices
row => row.ProductCode, //first collection key
purchasePrice => purchasePrice.Key, //second collection key
(row, purchasePrice) => new
{
productCode = row.ProductCode,
productMargin = purchasePrice.ProductMargin
});
References:
http://msdn.microsoft.com/en-us/library/bb397941.aspx
http://code.msdn.microsoft.com/LINQ-Join-Operators-dabef4e9
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));
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.
Preface: I don't understand what this does:
o => o.ID, i => i.ID, (o, id) => o
So go easy on me. :-)
I have 2 lists that I need to join together:
// list1 contains ALL contacts for a customer.
// Each item has a unique ID.
// There are no duplicates.
ContactCollection list1 = myCustomer.GetContacts();
// list2 contains the customer contacts (in list1) relevant to a REPORT
// the items in this list may have properties that differ from those in list1.
/*****/// e.g.:
/*****/ bool SelectedForNotification;
/*****/// may be different.
ContactCollection list2 = myReport.GetContacts();
I need to create a third ContactCollection that contains all of the contacts in list1 but with the properties of the items in list2, if the item is in the list[2] (list3.Count == list1.Count).
I need to replace all items in list1 with the items in list2 where items in list1 have the IDs of the items in list2. The resulting list (list3) should contain the same number of items at list1.
I feel as though I'm not making any sense. So, please ask questions in the comments and I'll try to clarify.
Joins are not so difficult, but your problem could probably use some further explanation.
To join two lists, you could do something like
var joined = from Item1 in list1
join Item2 in list2
on Item1.Id equals Item2.Id // join on some property
select new { Item1, Item2 };
this will give an IEnumerable<'a>, where 'a is an anonymous type holding an item from list1 and its related item from list2. You could then choose which objects' properties to use as needed.
To get the result to a concrete list, all that is needed is a call to .ToList(). You can do that like
var list3 = joined.ToList();
// or
var list3 = (from Item1 in list1
join Item2 in list2
on Item1.Id equals Item2.Id // join on some property
select new { Item1, Item2 }).ToList();
To do a left join to select all elements from list1 even without a match in list2, you can do something like this
var list3 = (from Item1 in list1
join Item2 in list2
on Item1.Id equals Item2.Id // join on some property
into grouping
from Item2 in grouping.DefaultIfEmpty()
select new { Item1, Item2 }).ToList();
This will give you a list where Item1 equals the item from the first list and Item2 will either equal the matching item from the second list or the default, which will be null for a reference type.
Here is what I came up with (based on this):
List<Contact> list3 = (from item1 in list1
join item2 in list2
on item1.ContactID equals item2.ContactID into g
from o in g.DefaultIfEmpty()
select o == null ? item1 :o).ToList<Contact>();
My favorite part is the big nosed smiley
:o)
Thanks for your help!
Here is a DotNetFiddle with a Linq Group Join
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
class Order
{
public int Id;
public string Name;
public Order(int id, string name)
{
this.Id = id;
this.Name = name;
}
}
class OrderItem
{
public int Id;
public string Name;
public int OrderId;
public OrderItem(int id, string name, int orderId)
{
this.Id = id;
this.Name = name;
this.OrderId = orderId;
}
}
List<Order> orders = new List<Order>()
{
new Order(1, "one"),
new Order(2, "two")
};
List<OrderItem> orderItems = new List<OrderItem>()
{
new OrderItem(1, "itemOne", 1),
new OrderItem(2, "itemTwo", 1),
new OrderItem(3, "itemThree", 1),
new OrderItem(4, "itemFour", 2),
new OrderItem(5, "itemFive", 2)
};
var joined =
from o in orders
join oi in orderItems
on o.Id equals oi.OrderId into gj // gj means group join and is a collection OrderItem
select new { o, gj };
// this is just to write the results to the console
string columns = "{0,-20} {1, -20}";
Console.WriteLine(string.Format(columns, "Order", "Item Count"));
foreach(var j in joined)
{
Console.WriteLine(columns, j.o.Name, j.gj.Count() );
}
It looks like you don't really need a full-join. You could instead do a semi-join, checking each contact in list 2 to see if it is contained in list 1:
ContactCollection list3 = list2.Where(c => list1.Contains(c));
I don't know how big your lists are, but note that this approach has O(nm) complexity unless list1 is sorted or supports fast lookups (as in a hashset), in which case it could be as efficient as O(nlog(m)) or rewritten as a merge-join and be O(n).