I'm trying to check the difference between a master list of items in c# vs an array of lists.
Not quite sure how to build this logic in an efficient way.
Example:
My master list of items is: var MasterOrderIDs = {1,2,3,4}
Then I have an Customer/Order object where it's storing the Customer ID along with its OrderID
CustomerID|OrderID
1 | 1
1 | 2
1 | 3
1 | 4
1 | 5
2 | 1
2 | 2
2 | 3
2 | 4
2 | 5
2 | 6
3 | 2
3 | 3
3 | 4
I want to return an array which has the CustomerID along with the OrderIDs where the difference of the MasterOrderIDs has a difference of less than 2.
Something like:
var OrderDifferences = new Dictionary<int, List<int>>();
So in the case of the Customer/Order, I want to return:
{[1, [5]], [3, [1]}
This is because for CustomerID 1, there is a Order ID 5, which is less than 2 differences. Likewise with CustomerID 3, there is a Order ID 1, which appears in MasterOrderIDs and is less than 2 differences.
How can I create such a check?
Ina real-life scenario I will have big data so wondering what would be the best efficient way of doing it.
Based on the information that we got I can think of two relatively small optimizations. So my disclaimer is that the basic approach is still brute force and maybe there is a smarter way to extract the information but we can still perform some checks in order to exclude some of the uneccessary data.
Small optimization 1
We are looking for Customers who compared to the the master list of orders have one more or one less order at most. In other words, based on your example for
var MasterOrderIDs = {1,2,3,4}
a Customer with 5 orders like customerOrders = { 7, 8, 9, 10, 11 } is still potentially valid but customer with 6 orders customerOrders = { 7, 8, 9, 10, 11, 12 } is not.
The same for the bottom number. A Customer with 3 orders is also potentially valid customerOrders = { 7, 8, 9 } but a customer with less the two orders customerOrders = { 7, 8 } is not.
So based on this we can perform our first small optimization filering customers who have more than MasterOrderIDs.Count() + 2 orders or with less than MasterOrderIDs.Count() - 2
Small optimization 2
Even if we are in the appropriate range of orders we want to make sure that our orderIds overlap. We can allow only 1 order which is present in one of the lists and not present in the other. Basically this is not exactly an optimization, but this is second criteria based on which we can construct our query.
Which is:
First seed some data:
class Order
{
public int CustomerId { get; set; }
public int OrderId { get; set; }
public static List<Order> Seed()
{
return new List<Order>
{
new Order { CustomerId = 1, OrderId = 1},
new Order { CustomerId = 1, OrderId = 2},
new Order { CustomerId = 1, OrderId = 3},
new Order { CustomerId = 1, OrderId = 4},
new Order { CustomerId = 1, OrderId = 5},
new Order { CustomerId = 2, OrderId = 1},
new Order { CustomerId = 2, OrderId = 2},
new Order { CustomerId = 2, OrderId = 3},
new Order { CustomerId = 2, OrderId = 4},
new Order { CustomerId = 2, OrderId = 5},
new Order { CustomerId = 2, OrderId = 6},
new Order { CustomerId = 3, OrderId = 2},
new Order { CustomerId = 3, OrderId = 3},
new Order { CustomerId = 3, OrderId = 4}
};
}
}
Then set the initial data:
var masterList = new List<int> { 1, 2, 3, 4 };
var upperBorder = masterList.Count() + 2;
var bottomBorder = masterList.Count() - 2;
var orders = Order.Seed();
And finally extract the records that we need:
var ordersWithinRange = orders
.GroupBy(o => o.CustomerId)
.Where(x => x.Count() < upperBorder && x.Count() > bottomBorder && x.Select(o => o.OrderId).Except(masterList).Concat(masterList.Except(x.Select(o => o.OrderId))).Count() < 2)
.ToDictionary(d => d.Key, d => d.Select(o => o.OrderId).Except(masterList).Concat(masterList.Except(d.Select(o => o.OrderId))).ToList());
Again. This will take a lot of computing time but I think it's a little bit faster than a sequence of for loops filtering one thing at a time.
Related
I have a list of objects dailySchedule with the properties Order, District, and other properties.
public class dailySchedule
{
public int Order { get; set; }
public string Title { get; }
public string District { get; }
....
}
the list is loaded with these values
Each district must have 6 orders,
var ordersValue = new List<int> {1, 2, 3, 4, 5, 6};
I want to find for each district, which order is missing.
The result must be
District 0 order {2,3,5,6}
District 12 order {5,6}
How can do that with linq c#?
This is a case for Except():
you can achieve your desired result by "substracting two lists" like so:
var required = new List<int>() {1, 2, 3, 4, 5, 6};
var groupedByDistrict = orders.GroupBy(x => x.District);
foreach (var group in groupedByDistrict)
{
var missing = required.Except(group.Select(x => x.Order).Distinct());
// Do something with that informaton here
}
This is mere non working pseudocode to get you on the right track
actually it may work, but i haven't tested it.
Try the below code which will give you result with each district and its missing orders
var allOrders = new List<int>() { 1, 2, 3, 4, 5, 6 };
var result = orders.GroupBy(gp => gp.District).Select(sl =>
new { District = sl.Key, Order = allOrders.Where(wh => sl.All(all => all.Order != wh)) });
This question already has answers here:
C# List grouping and assigning a value
(2 answers)
Closed 6 years ago.
I need help to solve this.
I have used Console Application to solve this and I am stuck.
Every row in ORDER needs to have an unique value inside a group med same GROUP-value. The Max-value on ORDER inside the group need to be Count of Rows -1 and Min value has to be 0. The sorting doesnt matter.
It only needs to be an UNIQUE value between min and max.
Example: 012345 or 041532
ID GROUP VALUE ORDER
1 1 10 0
2 1 2 0
3 2 1 0
4 3 2 0
5 3 6 0
6 3 1 0
7 3 9 0
GROUP 1 have 2(-1) Rows, ORDER value has to be 0-1.
GROUP 2 have 1(-1) Rows, ORDER value has to be 0.
GROUP 3 have 4(-1) Rows, ORDER value has to be 0-3.
End Result:
ID GROUP VALUE ORDER
1 1 10 0
2 1 2 1
3 2 1 0
4 3 2 0
5 3 6 1
6 3 1 3
7 3 9 2
Here is the properties i have used.
public class OrderRow
{
public int ID { get; set; }
public int GROUP { get; set; }
public int VALUE { get; set; }
public int ORDER { get; set; }
}
new OrderRow {ID = 1, GROUP = 1, VALUE = 10, ORDER = 0},
new OrderRow {ID = 2, GROUP = 1, VALUE = 2, ORDER = 0},
new OrderRow {ID = 3, GROUP = 2, VALUE = 1, ORDER = 0},
new OrderRow {ID = 4, GROUP = 3, VALUE = 2, ORDER = 0},
new OrderRow {ID = 5, GROUP = 3, VALUE = 6, ORDER = 0},
new OrderRow {ID = 6, GROUP = 3, VALUE = 1, ORDER = 0},
new OrderRow {ID = 7, GROUP = 3, VALUE = 9, ORDER = 0},
THANKS
I'd go like follows:
List<OrderRow> orders = new List<OrderRow>() {
new OrderRow { ID = 1, GROUP = 1, VALUE = 10, ORDER = 0 },
new OrderRow { ID = 2, GROUP = 1, VALUE = 2, ORDER = 0 },
new OrderRow { ID = 3, GROUP = 2, VALUE = 1, ORDER = 0 },
new OrderRow { ID = 4, GROUP = 3, VALUE = 2, ORDER = 0 },
new OrderRow { ID = 5, GROUP = 3, VALUE = 6, ORDER = 0 },
new OrderRow { ID = 6, GROUP = 3, VALUE = 1, ORDER = 0 },
new OrderRow { ID = 7, GROUP = 3, VALUE = 9, ORDER = 0 },
};
foreach (var item in orders.GroupBy(order => order.GROUP))
{
int order = 0;
foreach (var item2 in item)
{
item2.ORDER = order++;
}
}
I want to create an array with the taken indexes of an array. Let's suppose I have an array like this:
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
And the result I get from my query looks like:
Which means that places 1, 3, 4 (LengthUnit = 2)and 7 are busy (already taken). So, the array now would look like:
| T | 2 | T | T | 5 | 6 | T | 8 | 9 | 10 |
Where, T stands for taken.
How can I create two arrays of integers using the result from query which would look like:
int[] taken = { 1, 3, 4, 7 };
int[] notTaken = { 2, 5, 6, 8, 9, 10 };
Enumerable.Range proofs to be useful in this case:
Dictionary<int, int> startAndLength = new Dictionary<int, int>()
{ { 1, 1 }, { 3, 2 }, { 7, 1 } };
int[] taken = startAndLength
.SelectMany(kvp => Enumerable.Range(kvp.Key, kvp.Value)).ToArray();
int[] notTaken = Enumerable.Range(0, 10).Except(taken).ToArray();
Start creating a set of starts and lengths, then determine the taken items using Enumerable.Range. Then use Enumerable.Range again to determine the items not taken.
Use Enumerable.Range to create the collection of items you want. Then use Except to get the others.
List<int> values = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<Tuple<int, int>> ranges = new List<Tuple<int, int>>
{
new Tuple<int, int>(1,1),
new Tuple<int, int>(3,2),
new Tuple<int, int>(7,1),
};
var t = ranges.SelectMany(range => Enumerable.Range(range.Item1, range.Item2)).ToList();
// Here I decide to use Intersect instead of just having t for the case
// where you requested a range that doesn't exist
var taken = values.Intersect(t).ToArray();
var notTaken = values.Except(taken).ToList();
In the case that you want the values and that they aren't sequential like in the example then instead: create a collection of all desired indexes and then get all items of those indexes:
var indexes = ranges.SelectMany(range => Enumerable.Range(range.Item1, range.Item2)).Select(item => item - 1).ToList();
var taken = values.Where((item, index) => indexes.Contains(index)).ToList();
var notTaken = values.Where((item, index) => !indexes.Contains(index)).ToList();
A little Linq will get you a long way:
public class QueryResult
{
public int StartUnit { get; set; }
public int LengthUnit { get; set; }
}
var input = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var queryResult = new QueryResult[]
{
new QueryResult { StartUnit = 1, LengthUnit = 1 },
new QueryResult { StartUnit = 3, LengthUnit = 2 },
new QueryResult { StartUnit = 7, LengthUnit = 1 },
};
var taken = new List<int>();
taken.AddRange(queryResult.SelectMany(q => (input.Skip(q.StartUnit - 1).Take(q.LengthUnit))));
Console.WriteLine("Taken: {0}", string.Join(",", taken));
var notTaken = input.Except(taken);
Console.WriteLine("Not taken: {0}", string.Join(",", notTaken));
Using SelectMany(), Skip() and Take(), you can select the ranges that you wish to include. Using Except() you can then get the items not taken.
Note that this will perform horribly, as it iterates the collections way too many times. It also assumes that the StartUnit actually denotes an (index + 1) in the input collection, not a value.
If you don't actually want to look at the input array, and the values are always contiguous (i.e. no holes in the input), you can use Enumerable.Range() to generate the requested ranges:
taken.AddRange(queryResult.SelectMany(q => Enumerable.Range(q.StartUnit, q.LengthUnit)));
And generate the full range for the Except() to exclude from:
var notTaken = Enumerable.Range(1, 10).Except(taken);
And of course if you want the output to actually be arrays, do a call to ToArray() here and there.
I have 2 tables and i want to match up 2 Id values.
First table
Id - 1, 2, 3, 4, 5
DepartmentId - 2, 4, 5, 2, 1
Second table
Id- 1, 2, 10, 30, 40
I want to match up first table's Id's with second table's Id's so i can get DepartmentId values.
I need to get this virtual result:
Id- 1, 2, 10, 30, 40
DepartmentId -2, 4, null, null, null
Here is my code:
for (int i = 0; i < model1.Count(); i++)
{
model1[i].DepartmentId= model2.FirstOrDefault(k => k.Id== model1[i].Id).DepartmentId;
}
I get this error:
An exception of type 'System.NullReferenceException' occurred in
IYP.UserInterfaceLayer.dll but was not handled in user code
I think loop fails because of it can't find 10, 30, 40 Id values. If my Id values are same in 2 tables( Id = 1,2,3,4,5) loop works.
How can i do this with Linq?
You are basically looking for Left Join in LINQ. Try this:-
var query = from emp2 in Employee2
join emp1 in Employee1
on emp2.Id equals emp1.Id into allEmployees
from result in allEmployees.DefaultIfEmpty()
select new
{
ID = emp2.Id,
DeptID = result == null ? "No Department" : result.DepartmentId.ToString()
};
Where I have used following types:-
var Employee1 = new[]
{
new { Id = 1, DepartmentId = 2 },
new { Id = 2, DepartmentId = 4 },
new { Id = 3, DepartmentId = 5 },
new { Id = 4, DepartmentId = 2 },
new { Id = 5, DepartmentId = 1 },
};
var Employee2 = new[]
{
new { Id = 1 },
new { Id = 2 },
new { Id = 10 },
new { Id = 30 },
new { Id = 40 },
};
Complete Working Fiddle.
You should use the Join LINQ extension method. In the form of query syntax (which I believe is more readable for this case) it will look like:
var matchedValues =
from second in model2
join first in model1
on second.Id equals first.Id
into temp
from tempFirst in temp.DefaultIfEmpty()
select
new
{
second.Id,
DepartmentId = tempFirst == null ? null : tempFirst.DepartmentId
};
You join on the Id property and for any value you don't find in the model1, you use a default (DefaultIfEmpty call). Then you choose the resulting DepartmentId based on the join result.
try this
List<long> idlist=model2.tolist().select(t=>t.Id);
List<long> depIdList=model1.where(t=>idlist.contains(t.id)).toList();
I am going to assume that model1 and model2 are both IEnumerable. In that case the following should work.
var result = from x in model2
select
new Model1Type {DepartamentId = x,
Value=
model1.FirstOrDefault(y=>y.DepartamentId==x)
.Select(y=>y.Value)};
This is called Lamq :D
Hope this helps :)
I have a List<OrderItem> orderItems that contains all the items from the database for the batch of orders I'm working on.
I also have a Dictionary<int, Order> order_list that contains the orders the items need to go in.
The Order object has a List<OrderItem> member named OrderItems.
I have an Order with an ID of 1 in order_list.
I have three items in OrderItems that carry the order_id of 1 with three different product variant id's.
The fetch from the database retrieves the items in the order of the product variant id. This means that orderItems looks something like this:
[0] => orderId = 1, productVariantId = 4 [1] => orderId = 1,
productVariantId = 5 [2] => orderId = 1, productVariantId = 6
In my code I have a loop that goes through the orderItems and assigns them to their respective orders:
foreach (OrderItem orderItem in orderItems)
{
if (order_list.ContainsKey(orderItem.OrderId))
{
order_list[orderItem.OrderId].OrderItems.Add(orderItem);
}
}
After the loop executes, I find the OrderItems member for order 1 looks like this:
[0] => orderId = 1, productVariantId = 5 [1] => orderId = 1,
productVariantId = 6 [2] => orderId = 1, productVariantId = 4
I stepped through the code carefully and watched it insert the elements. Here is what I saw it do:
Iteration 1 for product variant 4, the final OrderItems looked like this:
[0] => orderId = 1, productVariantId = 4
Iteration 2 for product variant 5, the final OrderItems looked like this:
[0] => orderId = 1, productVariantId = 5 [1] => orderId = 1,
productVariantId = 4
Iteration 3 for product variant 6, the final OrderItems looked like this:
[0] => orderId = 1, productVariantId = 5 [1] => orderId = 1,
productVariantId = 6 [2] => orderId = 1, productVariantId = 4
My research has led me to conclude this should not be behaving such because the List<T>.Add() method should always add the new element to the END of the list.
Can anyone tell me why the List<T>.Add() does not add the elements in order in my application?
The first line in the MSDN docs says "Adds an object to the end of the List<T>", so yes, order is maintained. You have a lot more going on in your code than simple list manipulation, but your sample is not complete enough to tell you what your mistake is.
I find that question does not provide enough code. Since I cannot comment, here is my interpretation, via LinqPad:
void Main()
{
//populate
var dbOrderItems = new List<OrderItem>();
dbOrderItems.Add(new OrderItem { OrderId = 1, ProductVariantId = 4 });
dbOrderItems.Add(new OrderItem { OrderId = 1, ProductVariantId = 5 });
dbOrderItems.Add(new OrderItem { OrderId = 1, ProductVariantId = 6 });
dbOrderItems.Add(new OrderItem { OrderId = 2, ProductVariantId = 10 });
dbOrderItems.Add(new OrderItem { OrderId = 2, ProductVariantId = 11 });
dbOrderItems.Dump();
Dictionary<int, Order> order_list = new Dictionary<int, Order>();
foreach(OrderItem orderItem in dbOrderItems)
{
if (order_list.ContainsKey(orderItem.OrderId))
{
var currOrderItems = order_list[orderItem.OrderId].OrderItems;
if (currOrderItems.Contains(orderItem) == false)
{
// order exists, add new order item
currOrderItems.Add(orderItem);
order_list[orderItem.OrderId].OrderItems = currOrderItems;
}
}
else
{
// new order
order_list.Add(orderItem.OrderId, new Order { OrderId = orderItem.OrderId, OrderItems = new List<OrderItem> { orderItem } });
}
}
order_list.Dump();
}
// Define other methods and classes here
public class OrderItem
{
public int OrderId {get;set;}
public int ProductVariantId {get;set;}
}
public class Order
{
public int OrderId {get;set;}
public List<OrderItem> OrderItems {get;set;}
}
Output for Order 1:
OrderId ProductVariantId
1 4
1 5
1 6
Output for Order 2:
OrderId ProductVariantId
2 10
2 11
Now, what was the question?