Compare two lists and add missing items by specific criteria - c#

I have written a code like below:
foreach (var itemA in itm)
{
foreach (var itemB in filteredList)
{
if (itemA.ItemID != itemB.ItemID)
{
missingList.Add(itemB);
ListToUpdate.Add(itemB);
}
else
{
if (itemA.QuantitySold != itemB.QuantitySold)
{
ListToUpdate.Add(itemB);
}
}
}
}
So as you can see i have two lists here which are identical in their structure and they are:
List #1 is "itm" list - which contains old records from DB
List #2 is "filteredList" - which has all items from DB and + new ones
I'm trying to add items to missingList and ListToUpdate on next criteria:
All items that are "new" in filteredList - meaning their ItemID doens't exists in "itm" list should be added to missingList.
And all items that are new in filteredList- filteredList - meaning their ItemID doens't exists in "itm" list should be added to .ListToUpdate
And final criteria to add items to ListToUpdate should be those items that exist in both lists - and if the quantitysold in "itm" list is different - add them to ListToUpdate
The code above that I written gives me completely wrong results, I end up having more than 50000 items extra in both lists...
I'd like to change this code in a manner that it works like I wrote above and to possibly use parallel loops or PLINQ to speed things up...
Can someone help me out ?

Let's use Parallel.ForEach, which is available in C# 4.0:
Parallel.ForEach(filteredList, (f) =>
{
var conditionMatchCount = itm.AsParallel().Max(i =>
// One point if ID matches
((i.ItemID == f.ItemID) ? 1 : 0) +
// One point if ID and QuantitySold match
((i.ItemID == f.ItemID && i.QuantitySold == f.QuantitySold) ? 1 : 0)
);
// Item is missing
if (conditionMatchCount == 0)
{
listToUpdate.Add(f);
missingList.Add(f);
}
// Item quantity is different
else if (conditionMatchCount == 1)
{
listToUpdate.Add(f);
}
});
The above code uses two nested parallelised list iterators.

Following is an example to compare two lists which will give you list of new IDs.
Class I used to hold the data
public class ItemList
{
public int ID { get; set; }
}
Function to get new IDs
private static void GetNewIdList()
{
List<ItemList> lstItm = new List<ItemList>();
List<ItemList> lstFiltered = new List<ItemList>();
ItemList oItemList = new ItemList();
oItemList.ID = 1;
lstItm.Add(oItemList);
lstFiltered.Add(oItemList);
oItemList = new ItemList();
oItemList.ID = 2;
lstItm.Add(oItemList);
lstFiltered.Add(oItemList);
oItemList = new ItemList();
oItemList.ID = 3;
lstFiltered.Add(oItemList);
var lstListToUpdate = lstFiltered.Except(lstItm);
Console.WriteLine(lstListToUpdate);
}
For getting the list of common IDs use following
var CommonList = from p in lstItm
join q in lstFiltered
on p.ID equals q.ID
select p;
UPDATE 2
For getting the list of new IDs from filtered list based on ID
var lstListToUpdate2 = lstFiltered.Where(a => !lstItm.Select(b => b.ID).Contains(a.ID));

Related

Random with condition

I have the following code to extract records from a dbcontext randomly using Guid class:
var CategoryList = {1,5};
var generatedQues = new List<Question>();
//Algorithm 1 :)
if (ColNum > 0)
{
generatedQues = db.Questions
.Where(q => CategoryList.Contains(q.CategoryId))
.OrderBy(q => Guid.NewGuid()).Take(ColNum).ToList();
}
First, I have a list of CategoryId stored in CategoryList as a condition to be fulfilled when getting records from the db. However, I would like to achieve an even distribution among the questions based on the CategoryId.
For example:
If the ColNum is 10, and the CategoryId obtained are {1,5}, I would like to achieve by getting 5 records that are from CategoryId = 1 and another set of 5 records from CategoryId = 5. If the ColNum is an odd number like 11, I would also like to achieve an even distribution as much as possible like maybe getting 5 records from CategoryId 1 and 6 records from CategoryId 2.
How do I do this?
This is a two step process,
Determine how many you want for each category
Select that many items from each category in a random order
For the first part, define a class to represent the category and how many items are required
public class CategoryLookup
{
public CategoryLookup(int catId)
{
this.CategoryId = catId;
}
public int CategoryId
{
get; private set;
}
public int RequiredAmount
{
get; private set;
}
public void Increment()
{
this.RequiredAmount++;
}
}
And then, given your inputs of the required categories and the total number of items required, work out how many are required for each category
var categoryList = new []{1,5};
var colNum = 7;
var categoryLookup = categoryList.Select(x => new CategoryLookup(x)).ToArray();
for(var i = 0;i<colNum;i++){
categoryLookup[i%categoryList.Length].Increment();
}
The second part is really easy, just use a SelectMany to get the list of questions (Ive used a straight linq to objects to test, should work fine for database query. questions in my code would just be db.Questions in yours)
var result = categoryLookup.SelectMany(
c => questions.Where(q => q.CategoryId == c.CategoryId)
.OrderBy(x => Guid.NewGuid())
.Take(c.RequiredAmount)
);
Live example: http://rextester.com/RHF33878
You could try something like this:
var CategoryList = {1,5};
var generatedQues = new List<Question>();
//Algorithm 1 :)
if (ColNum > 0 && CategoryList.Count > 0)
{
var take = // Calculate how many of each
// First category
var query = db.Questions
.Where(q => q.CategoryId == CategoryList[0])
.OrderBy(q => Guid.NewGuid()).Take(take);
// For all remaining categories
for(int i = 1; i < CategoryList.Count; i++)
{
// Calculate how many you want
take = // Calculate how many of each
// Union the questions for that category to query
query = query.Union(
query
.Where(q => q.CategoryId == CategoryList[i])
.OrderBy(q => Guid.NewGuid()).Take(take));
}
// Randomize again and execute query
generatedQues = query.OrderBy(q => Guid.NewGuid()).ToList()
}
The idea is to just get a random list for each category and add them all together. Then you randomize that again and create your list. I do not know if it will do all this on the database or in memory, but it should be database I think. The resulting SQL will look horrible though.

C# Lambda to filter list based on existence on another list

List A: 3,5,5,5,7,9
List B: 3,5
Both of the list are the same type and those values are from a field ID. My objective is to construct a forloop that will return me 7,9 because 7,9 is not existed in List B.
I've tried the following but no luck:
int counter = 0;
foreach(var item in ListA.Where(x=>ListB.Any(b=>x.ID != b.ID)))
{
counter++;
//Here I should perform operation with item that having ID 7 and 9
}
Updates:
Using a except method in the above case, counter will still return me 4 simply because each of the 5 in ListA are different object eventhou they are sharing the same ID. My ultimate objective is to have the counter as 2 irregardless whether the object is the same or not. As long as the ID of object in ListA is 3 or 5, I would wanna exclude it.
Just use the Except extension mtehod
foreach (var item in ListA.Except(ListB)) {
...
}
it should be "ALL", or "Not Any"
foreach(var item in ListA.Where(x=>ListB.All(b=>x.ID != b.ID)))
{
//Here I should perform operation with item that having ID 7 and 9
}
update:
As you actually want to have distinct result from A except B, so, you can do either:
foreach(var item in ListA.GroupBy(m=>m.ID).Where(x=>ListB.All(b=>b.ID != x.Key)))
{
counter ++;
Debug.writeline(item.Key);
}
or
foreach(var id in ListA.Select(x=>x.ID).Distinct().Except(ListB.Select(y=>y.ID)))
{
counter++;
}
note: all untested - i have no compiler with me for the moment.
Change your query like this:
foreach(var item in ListA.Where(x=> !ListB.Any(b => x.ID == b.ID)))
And it should work fine.
Try This:
List<int> listA=new List<int>(new[]{ 3,5,7,9});
List<int> listB=new List<int>(new[]{ 3,5});
var items=(from a in listA
select a).Except(from b in listB
select b);
foreach(var item in items)
{
Console.WriteLine(ll);
}
Output:
7
9
Except method can be used when both List are of same type.
If Type is different. We can use like this.
var outPut = _employees.Where(i => _employeeExtensions.Any(j => i.EmpId == j.EmpId));
I think you want to get the items in a list where the items' IDs are different:
Example that I put together in LinqPad:
void Main()
{
List<Person> a = new List<Person>()
{
new Person { ID = 1 },
new Person { ID = 2 },
new Person { ID = 3 },
};
List<Person> b = new List<Person>()
{
new Person { ID = 1 },
};
var c = a.Where(x => b.Any(bprime => bprime.ID != x.ID));
foreach(var item in c)
{
Console.WriteLine(item.ID);
}
}
class Person
{
public int ID { get; set; }
}
Output:
2
3
This works similar to the Except method but this will check the elements' properties.

LINQ look up list of objs in datable of different objs that share a common property

I have list A which is obj A and list B which is obj B. Both list share one property and I want to look up all the obj B of list B has in A and pull them out.
So, ex.
List A is a bunch of people
List B is a bunch of names
Both list have a personId
Now I want to get all the people with the names that are in List B. I was thinking something like a:
class names
{
public int id {get;set;}
public string name {get;set;}
}
class people
{
public int id {get;set;}
public string name {get;set;}
}
var newList = new List<person>();
foreach(var n in names)
{
var person = people.firstordefault(p => p.name == n);
if(person!=null)
{
newList.Add(person);
}
}
}
I was wondering is there is a more efficent way with LINQ I can do this because it wont be a list everytime it might be the database im calling it from and i dont want to call the database a thousands for no reason.
This is probably a bad example if i think about it.
This codes :
var newList = new List<person>();
foreach(var n in names)
{
var person = people.firstordefault(p => p.name == n);
if(person!=null)
{
newList.Add(person);
}
}
will produce the same result as :
var newList = new List<person>();
newList = people.Where(p => names.Contains(p.name)).ToList();
responding your update, if names is a list of names object instead of string, you can do as follow :
newList = people.Where(p => names.Select(o => o.name).Contains(p.name)).ToList();
With LINQ, You can do this:
var intersection = ListA.Intersect(ListB);
However, this is the set intersection, meaning if ListA and ListB don't have unique values in it, you won't get any copies. In other words if you have the following:
var ListA = new [] { 0, 0, 1, 2, 3 };
var ListB = new [] { 0, 0, 0, 2 };
Then ListA.Intersect(ListB) produces:
{ 0, 2 }
If you're expecting:
{ 0, 0, 2 }
Then you're going to have to maintain a count of the items yourself and yield/decrement as you scan the two lists.
Since you're dealing with two different classes, what you're really looking for is a join.
List<Person> people = new List<Person>{new Person{Name = "Mark"},
new Person{Name = "Alice"},
new Person{Name = "Jane"}};
List<string> names = new List<string>{"Mark"};
var query = from p in people
join n in names on p.Name equals n
select p; // will output Person Mark
Note: This has time complexity of O(p+n) (where p = number of people and n = number of names), because join is implemented as a hash join. Your nested loop above or a Where/Contains LINQ query time complexity O(p*n), since it's iterating n for every p. This may or may not be an issue depending on the sizes of your collections.

C# List grouping and assigning a value

I have a list of Orders. This list contains multiple orders for the same item, see the table below.
I then want to assign each item that is the same (i.e. ABC) the same block ID. So ABC would have a block ID of 1 & each GHJ would have a block ID of 2 etc. What is the best way of doing this?
Currently I order the list by Order ID and then have a for loop and check if the current Order ID is equal to the next Order ID if so assign the two the same block ID. Is there a better way of doing this using linq or any other approach?
Order ID Block ID
ABC
ABC
ABC
GHJ
GHJ
GHJ
MNO
MNO
You can do this that way, it will assign same blockid for same orderid
var ordered = listOrder.GroupBy(x => x.OrderId).ToList();
for (int i = 0; i < ordered.Count(); i++)
{
ordered[i].ForEach(x=>x.BlockId=i+1);
}
it will group orders by orderid then assign each group next blockid. Note that it won't be done fully in linq, because linq is for querying not changing data.
Always depends of what better means for you in this context.
There are a bunch of possible solutions to this trivial problem.
On top of my head, I could think of:
var blockId = 1;
foreach(var grp in yourOrders.GroupBy(o => o.OrderId))
{
foreach(var order in grp)
{
order.BlockId = blockId;
}
blockId++;
}
or (be more "linqy"):
foreach(var t in yourOrders.GroupBy(o => o.OrderId).Zip(Enumerable.Range(1, Int32.MaxValue), (grp, bid) => new {grp, bid}))
{
foreach(var order in t.grp)
{
order.BlockId = t.bid;
}
}
or (can you still follow the code?):
var orders = yourOrders.GroupBy(o => o.OrderId)
.Zip(Enumerable.Range(1, Int16.MaxValue), (grp, id) => new {orders = grp, id})
.SelectMany(grp => grp.orders, (grp, order) => new {order, grp.id});
foreach(var item in orders)
{
item.order.BlockId = item.id;
}
or (probably the closest to a simple for loop):
Order prev = null;
blockId = 1;
foreach (var order in yourOrders.OrderBy(o => o.OrderId))
{
order.BlockId = (prev == null || prev.OrderId == order.OrderId) ?
blockId :
++blockId;
prev = order;
}
Linq? Yes.
Better than a simple loop? Uhmmmm....
Using Linq will not magically make your code better. Surely, it can make it often more declarative/readable/faster (in terms of lazy evaluation), but sure enough you can make otherwise fine imperative loops unreadable if you try to force the use of Linq just because Linq.
As a side note:
if you want to have feedback on working code, you can ask at codereview.stackexchange.com

LINQ suming nested groups in one statement

I am converting some code to LINQ, at the same time exploring to what extent LINQ can accomplish.
Can the following code be condensed into a single LINQ query or method?
Dictionary<string, ItemPack> consolidated = new Dictionary<string, ItemPack>();
foreach (var product in Products)
{
foreach (var smallpack in product.ItemPacks)
{
ItemPack bigpack;
if (consolidated.TryGetValue(smallpack.ItemCode, out bigpack))
{
// the big pack quantity += quantity for making one product * the number of that product
bigpack.Quantity += smallpack.Quantity * product.Quantity;
// References: we make sure that the small pack is using the Item in the big pack.
// otherwise there will be 2 occurance of the same Item
smallpack.Item = bigpack.Item;
}
else
{
bigpack = new ItemPack(smallpack); // Copy constructor
bigpack.Quantity = smallpack.Quantity * product.Quantity;
consolidated.Add(smallpack.ItemCode, bigpack);
}
}
}
return consolidated;
In English, each product is made up of several items of different quantities. These items are grouped by item code and are packs into smallpacks. These smallpacks are shipped together as a unit product. There are many different products. A single item can be used in different product.
I now have a list of products and the quantity required for each for shipment. I want a LINQ statement to consolidate a flat list of items and their quantities.
I have gotten this far, but it looks like it does not work:
var packsQuery = from product in Products
from smallpack in product.ItemPacks
select new {Item = smallpack.Item, Quantity = smallpack.Quantity * product.Quantity};
foreach (var pack in packsQuery)
{
consolidated.Add(pack.Item.ItemCode, new ItemPack(pack.Item, pack.Quantity));
}
If I group first, then I cannot select item for its quantity. If I select first, then I lose the grouping. Chicken and egg story?
EDIT:
Useful note: smallpack is of type ItemPack which looks like this
public class ItemPack
{
Item { get; } // The item in this pack, which *must* be a shared reference across all objects that uses this Item. So that change in Item properties are updated everywhere it is used. e.g. Price.
ItemCode { get; } // The item code
Quantity { get; } // The number of such Item in this pack.
}
var query = (from product in Products
from smallPack in product.ItemPacks
select new
{
ItemCode = smallPack.ItemCode,
Item = smallPack.Item,
Quantity = smallPack.Quantity * product.Quantity,
})
.GroupBy(p => p.ItemCode)
.Select(p => new
{
ItemCode = p.Key,
Item = p.FirstOrDefault(),
Quantity = p.Sum(x=>x.Quantity)
})
.ToDictionary(p=>p.ItemCode);
Thanks for putting me in the right direction. I managed to work out the full query syntax version:
var query = from product in Products
from smallpack in product.ItemPacks
select new {
Item = smallpack.Item,
Quantity = smallpack.Quantity * product.Quantity
} into mediumpack
group mediumpack by mediumpack.Item.ItemCode into bigpack
select new {
Item = bigpack.First().Item, // shared reference
Quantity = bigpack.Sum(a => a.Quantity);
}
query.ToDictionary(...);
Any comments as to whether this is fine?

Categories

Resources