I have a list of order items with the given structure :
OrderItem { Id = 1, Name = "First Item", Quantity = 2 }
OrderItem { Id = 2, Name = "Second Item", Quantity = 2 }
OrderItem { Id = 3, Name = "Third Item", Quantity = 1 }
I want to flatten it to the following structure :
DBItem{ Id = 1, Name = "First Item" }
DBItem{ Id = 2, Name = "First Item" }
DBItem{ Id = 3, Name = "Second Item" }
DBItem{ Id = 4, Name = "Second Item" }
DBItem{ Id = 5, Name = "Third Item" }
Is there a way using LINQ SelectMany?
You can use Enumerable.Range to generate aditional records:
var dbItems = orderItems
.OrderBy(oi => oi.Id) // ensure correct order
.SelectMany(oi => Enumerable.Range(0, oi.Quantity), (oi, n) => oi) // records duplication
.Select((oi, idx) => new DBItem // Select overload with index
{
Id = idx + 1,
Name = oi.Name,
})
.ToList();
This seems quite straight forward to me:
var items = new[]
{
new OrderItem { Id = 1,Name = "First Item", Quantity = 2 },
new OrderItem { Id = 2,Name = "Second Item", Quantity = 2 },
new OrderItem { Id = 3,Name = "Third Item", Quantity = 1 },
};
var dbItems =
items
.SelectMany(item => Enumerable.Repeat(item.Name, item.Quantity))
.Select((name, index) => new DBItem { Id = index + 1, Name = name });
var items = new List<OrderItem>
{
new OrderItem { Id = 1,Name = "First Item", Quantity = 2 },
new OrderItem { Id = 2,Name = "Second Item", Quantity = 2 },
new OrderItem { Id = 3,Name = "Third Item", Quantity = 1 },
};
var dbitems = new List<DBItem>();
var counter = 1;
items.ForEach(item =>
{
for (int i = 0; i < item.Quantity; i++)
{
dbitems.Add(new DBItem
{
Id = counter++,
Name = item.Name,
});
}
});
Related
List<empl> lstSource = new List<empl>();
lstSource.Add(new empl { departmentId = 2, Id = 101, Name = "S1" });
lstSource.Add(new empl { departmentId = 2, Id = 109, Name = "S9" });
lstSource.Add(new empl { departmentId = 2, Id = 102, Name = "S2" });
lstSource.Add(new empl { departmentId = 4, Id = 101, Name = "S1" });
lstSource.Add(new empl { departmentId = 4, Id = 102, Name = "S2" });
lstSource.Add(new empl { departmentId = 4, Id = 108, Name = "S8" });
lstSource.Add(new empl { departmentId = 3, Id = 105, Name = "S5" });
lstSource.Add(new empl { departmentId = 3, Id = 103, Name = "S3" });
lstSource.Add(new empl { departmentId = 3, Id = 102, Name = "S2" });
should result {Id = 102, Name = "S2"}
if I add
lstSource.Add(new empl { departmentId = 3, Id = 101, Name = "S1" });
should result {Id = 102, Name = "S2"} {Id = 101, Name = "S1"}
Hint : we can group with departmentId and find common Id in 3 group.
Based on your comments and example above, I take it that the Name associated with any given Id is always the same. In that case, you could split the Ids registered on each department into separate lists, then intersect those lists to find the common Ids, and then find the associated Name for each common Id.
You have done something similar in your own example. By rewriting the code (e.g. by replacing the foreach loop with an Aggregate() function) you could achieve a more straight forward approach:
var idsPerDepartment = lstSource
.GroupBy(item => item.departmentId)
.Select(gr => gr.Select(item => item.Id));
var commonIds = idsPerDepartment
.Aggregate((common, next) => common.Intersect(next));
var commonItems = commonIds
.Select(id => lstSource.First(item => item.Id == id))
.Select(commonItem => new { commonItem.Id, commonItem.Name })
.OrderByDescending(commonItem => commonItem.Name);
commonItems is empty (not null) if there are no common items.
It could all be written as one single expression, but I've spilt it into several variables to clarify what is happening along the way.
var groups = lstSource.GroupBy(t1=> t1.departmentId)
.ToList();
var validIds = groups.First().Select(t1 => t1.Id).ToList();
foreach (var g in groups.Skip(0))
{
var otherGroupItemIds = g.Select(t1 => t1.Id).ToList();
validIds = validIds.Intersect(otherGroupItemIds).ToList();
}
if (validSRIds.Count > 0)
return lstSource.FindAll(t1 => validSRIds.Contains(t1.Id)).GroupBy(t2 => new { t2.Id, t2.Name }).Select(g => g.First()).OrderByDescending(t => t.Name).ToList();
you will get all common id,name which belongs to all group
I would like to filter out "" names then select each unique location where there are duplicate IDs regardless of name:
Data Setup
var list = new[]
{
new { id = 3, Name = "", Location = "LocationA" },
new { id = 2, Name = "", Location = "LocationA" },
new { id = 1, Name = "T", Location = "LocationB" },
new { id = 2, Name = "H", Location = "LocationB" },
new { id = 3, Name = "E", Location = "LocationB" },
new { id = 3, Name = "R", Location = "LocationB" },
new { id = 5, Name = "U", Location = "LocationC" },
new { id = 5, Name = "S", Location = "LocationC" },
new { id = 5, Name = "S", Location = "LocationD" },
new { id = 4, Name = "O", Location = "LocationD" },
new { id = 4, Name = "Z", Location = "LocationE" },
};
Query
var query1 = list
.Where(s => s.Name != "")
.GroupBy(g => g.Location)
.Where(w => w.Select(s => s.Location).Count() > 1)
.SelectMany(s => s)
.GroupBy(g => g.id)
.Where(w => w.Select(s => s.id).Count() > 1)
.SelectMany(s => s)
.ToList();
Console.WriteLine("output\n" + string.Join("\n", query1));
Returns
{ id = 3, Name = E, Location = LocationB }
{ id = 3, Name = R, Location = LocationB }
{ id = 5, Name = U, Location = LocationC }
{ id = 5, Name = S, Location = LocationC }
{ id = 5, Name = S, Location = LocationD }
vs What I actually wanted
{ id = 3, Name = E, Location = LocationB }
{ id = 3, Name = R, Location = LocationB }
{ id = 5, Name = U, Location = LocationC }
{ id = 5, Name = S, Location = LocationC }
LocationD has IDs 4 & 5 so it should've been filtered out, I wasn't able to do so. What am I doing wrong? How do I correct it?
Given
var list = new[]
{
new { id = 3, Name = "", Location = "LocationA" },
new { id = 2, Name = "", Location = "LocationA" },
new { id = 1, Name = "T", Location = "LocationB" },
new { id = 2, Name = "H", Location = "LocationB" },
new { id = 3, Name = "E", Location = "LocationB" },
new { id = 3, Name = "R", Location = "LocationB" },
new { id = 5, Name = "U", Location = "LocationC" },
new { id = 5, Name = "S", Location = "LocationC" },
new { id = 5, Name = "S", Location = "LocationD" },
new { id = 4, Name = "O", Location = "LocationD" },
new { id = 4, Name = "Z", Location = "LocationE" },
};
Example
var results = list
.Where(s => s.Name != "")
.GroupBy(x => new {x.id, x.Location})
.Where(g => g.Count() > 1)
.SelectMany(y => y);
foreach (var result in results)
Console.WriteLine($"{result.id}, {result.Name}, {result.Location}");
Output
3, E, LocationB
3, R, LocationB
5, U, LocationC
5, S, LocationC
Group by id and Location. And get .Count() more than 1.
var query1 = list
.Where(s => s.Name != "")
.GroupBy(g => new { g.Location, g.id })
.Where(g => g.Count() > 1)
.SelectMany(g => g)
.ToList();
Sample demo
I have a model class which looks something like this:
public class Employee
{
public int Id {get;set;}
public int ParentId {get;set;}
public string Name{get;set;}
public string Designation {get;set;}
}
using which I simulated a list:
new List<Employee> employees
{
new Employee{Id = 1, ParentId = 0, Name = "A", Designation = "CEO" },
new Employee{Id = 2, ParentId = 1, Name = "B", Designation = "Manager" },
new Employee{Id = 3, ParentId = 1, Name = "C", Designation = "Manager" },
new Employee{Id = 4, ParentId = 2, Name = "D", Designation = "Lead" },
new Employee{Id = 5, ParentId = 3, Name = "E", Designation = "Lead" },
new Employee{Id = 6, ParentId = 4, Name = "F", Designation = "Developer" },
new Employee{Id = 7, ParentId = 4, Name = "G", Designation = "Developer" },
new Employee{Id = 8, ParentId = 5, Name = "H", Designation = "Developer" }
};
Well I need to write a LINQ query to filter the above list so that even the parent objects(if present) are retained during the filtering. I could not quiet wrap my head around the retainment of the parent part where I always end up getting it wrong.
To make it more clear this is what is the expected filtered list in case the filter search criteria are the Ids 6 and 7:
{
new Employee{Id = 1, ParentId = 0, Name = "A", Designation = "CEO" },
new Employee{Id = 2, ParentId = 1, Name = "B", Designation = "Manager" },
new Employee{Id = 4, ParentId = 2, Name = "D", Designation = "Lead" }
new Employee{Id = 6, ParentId = 4, Name = "F", Designation = "Developer" },
new Employee{Id = 7, ParentId = 4, Name = "G", Designation = "Developer" }
}
and if the Id to filter is 8:
{
new Employee{Id = 1, ParentId = 0, Name = "A", Designation = "CEO" },
new Employee{Id = 3, ParentId = 1, Name = "C", Designation = "Manager" },
new Employee{Id = 5, ParentId = 3, Name = "E", Designation = "Lead" },
new Employee{Id = 8, ParentId = 5, Name = "H", Designation = "Developer" }
}
and if the Id to filter is 2:
{
new Employee{Id = 1, ParentId = 0, Name = "A", Designation = "CEO" },
new Employee{Id = 2, ParentId = 1, Name = "B", Designation = "Manager" }
}
You can implement a help method, EmployeeAndBosses which returns given employee and all the parents:
private static IEnumerable<Employee> EmployeeAndBosses(Employee value,
IEnumerable<Employee> collection) {
for (Employee item = value;
item != null;
item = collection.FirstOrDefault(x => x.ParentId == item.Id))
yield return item;
}
then you can filter topmost employee in the hierarchy, and add their bosses then:
HashSet<int> ids = new HashSet<int>() {
6, 7
};
var result = employees
.Where(item => ids.Contains(item.Id)) // topmost
.SelectMany(item => EmployeeAndBosses(item, employees)) // topmost and parents
.GroupBy(item => item.Id) // Duplicates (e.g. CEO) removing
.Select(group => group.First()); //
Edit: If you have a huge collection(s) and that's why FirstOrDefault and GroupBy are bad choice, you can implement Bread First Search:
private static IEnumerable<Employee> MyFilter(IEnumerable<Employee> list,
IEnumerable<int> idsToFind) {
Dictionary<int, Employee> stuff = list
.ToDictionary(item => item.Id, item => item);
HashSet<int> ids = new HashSet<int>(idsToFind);
HashSet<int> completed = new HashSet<int>();
Queue<Employee> agenda = new Queue<Employee>(list.Where(item => ids.Contains(item.Id)));
while (agenda.Count > 0) {
Employee current = agenda.Dequeue();
if (null != current && completed.Add(current.Id)) {
yield return current;
if (stuff.TryGetValue(current.ParentId, out current))
agenda.Enqueue(current);
}
}
}
As some comments seem to be quite... Subjective... Here is a simple (but somewhat inefficient) extension that handles your requirements like a charm:
(assuming you'll never hire an employee as a boss to another employee that in turn is their boss, but such madness would probably break the company quicker than it breaks the query)
public static IEnumerable<Employee> FindByIdsAndIncludeParents(this IEnumerable<Employee> employees, params int[] targetIds)
=> employees
.Where(r => targetIds.Contains(r.Id))
.SelectMany(r => employees.FindByIdsAndIncludeParents(r.ParentId).Append(r))
.Distinct();
As some are not quite as keen of exchanging this quite expensive operation for the mere beauty of it, we could trade in some beauty for speed using a dictionary as entry point for instant access to the appended boss-search:
public static IEnumerable<Employee> FindFriendsFaster(this IEnumerable<Employee> employees, params int[] targetIds)
=> employees
.ToDictionary(e => e.Id, e => e)
.FindWithBossFriend(targetIds)
.Distinct();
public static IEnumerable<Employee> FindWithBossFriend(this IDictionary<int, Employee> hierarchy, params int[] targetIds)
=> targetIds
.Where(eId => hierarchy.ContainsKey(eId))
.Select(eId => hierarchy[eId])
.SelectMany(e => hierarchy.FindWithBossFriend(e.ParentId).Append(e));
As you might be able to spot, I personally can't seem to be able to trade in any more of my dignity for the possible removal of that last .Distinct(), but there are rumors going around some would be.
Found this Post and it has good solution when shuffling items in List<T>.
But in my case i have a class Person which is defined as this:
class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Position { get; set; }
}
This is my implementation and usage:
List<Person> workers = new List<Person>()
{
new Person { Id = 1, Name = "Emp 1", Position = "Cashier"},
new Person { Id = 2, Name = "Emp 2", Position = "Sales Clerk"},
new Person { Id = 3, Name = "Emp 3", Position = "Cashier"},
new Person { Id = 4, Name = "Emp 4", Position = "Sales Clerk"},
new Person { Id = 5, Name = "Emp 5", Position = "Sales Clerk"},
new Person { Id = 6, Name = "Emp 6", Position = "Cashier"},
new Person { Id = 7, Name = "Emp 7", Position = "Sales Clerk"}
};
Now i want to shuffle all records and get 1 Sales Clerk. Here is my code and is working:
var worker = workers.OrderBy(x => Guid.NewGuid()).Where(x => x.Position == "Sales Clerk").First();
// This can yield 1 of this random result (Emp 2, Emp 4, Emp 5 and Emp 7).
Console.WriteLine(worker.Name);
But according to the given Post GUID is not good for randomizing record. And the worst is i cant use Shuffle() and call the Where and First() extensions to get the desired result.
How can i do that with Shuffle() extension?
If the question is how to get it so you can chain Shuffle() with the rest of your Linq operators, the answer is to modify the Shuffle method to return reference to the list shuffled:
public static IEnumerable<T> Shuffle<T>(this IList<T> list)
{
RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
int n = list.Count;
while (n > 1)
{
byte[] box = new byte[1];
do provider.GetBytes(box);
while (!(box[0] < n * (Byte.MaxValue / n)));
int k = (box[0] % n);
n--;
T value = list[k];
list[k] = list[n];
list[n] = value;
}
return list;
}
Your code then becomes:
var worker = workers.Shuffle().Where(x => x.Position == "Sales Clerk").First();
Random oRandom = new Random();
var worker = workers[oRandom.Next(0,workers.Count)];
I am trying to sort an ObservableCollection that has items that must be sorted partially by name on some and preserve the sort as the data is already given from it source.
The data comes to me as:
The result of the above will print as below:
First Item
A Second Item
Third Item
Item 1
Item 10
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Here is the collection sample to generate the above data (This is just a sample I created to give me data like above):
ObservableCollection<MyObject> items = new ObservableCollection<MyObject>();
items.Add(new MyObject { ItemID = 1, Name = "First Item", Category = "0" });
items.Add(new MyObject { ItemID = 2, Name = "A Second Item", Category = "0" });
items.Add(new MyObject { ItemID = 3, Name = "Third Item", Category = "0" });
items.Add(new MyObject { ItemID = 4, Name = "Item 1", Category = "1" });
items.Add(new MyObject { ItemID = 13, Name = "Item 10", Category = "1" });
items.Add(new MyObject { ItemID = 5, Name = "Item 2", Category = "1" });
items.Add(new MyObject { ItemID = 6, Name = "Item 3", Category = "1" });
items.Add(new MyObject { ItemID = 7, Name = "Item 4", Category = "1" });
items.Add(new MyObject { ItemID = 8, Name = "Item 5", Category = "1" });
items.Add(new MyObject { ItemID = 9, Name = "Item 6", Category = "1" });
items.Add(new MyObject { ItemID = 10, Name = "Item 7", Category = "1" });
items.Add(new MyObject { ItemID = 11, Name = "Item 8", Category = "1" });
items.Add(new MyObject { ItemID = 12, Name = "Item 9", Category = "1" });
foreach (var item in items)
{
Console.WriteLine(item.Name);
}
Here is my MyObject
public class MyObject
{
public int ItemID { get; set; }
public string Category { get; set; }
public string Name { get; set; }
}
I want the above to be sorted as:
First Item // This must be preserved like this
A Second Item // This must be preserved like this
Third Item // This must be preserved like this
Item 1 // Rest below must be sorted like below and not Item 1, Item 11...
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
If I use, var sorted = items.OrderBy(x => x.Name) I don't get the result what I want.
How can I get the sorting be done only on partial items?
Based on the comment. I cant rely on the ItemID. It is not reliable.
The only piece I can rely to sort in the name and category.
This is my final solution I did:
Func<string, object> convert = str =>
{
int k;
if (int.TryParse(str, out k))
return k;
return str;
};
var sorted = items.Where(x => x.Category != "1").Union(items.Where(x => x.Category == "1")
.OrderBy(str => Regex.Split(str.Name.Replace(" ", ""), "([0-9]+)").Select(convert),
new EnumerableComparer<object>()));
foreach (var item in sorted)
{
Console.WriteLine(item.Name);
}
I used Tim. S's link for implementing the natural sort comparer with Badak's idea.
Or you can distinguish items by
foreach (var item in items.Where(X => !X.Name.StartsWith("Item")).Union(items.Where(X => X.Name.StartsWith("Item")).OrderBy(X => Convert.ToInt32(X.Name.Replace("Item ", "")))))
{
Console.WriteLine(item.Name);
}
This works if and only if the the items that starts with Item are followed by an int, other elements stays above
var items = new ObservableCollection<MyObject>();
///...
items.OrderBy(item=>item, comparer);
where comparer is a class what implements IComparer<MyObject>
Take a look here.