I have a collection of C# objects. Each object has, for data members, a string which is a guid, an index which is an int, and a string which is a document name. Here is what a typical collection looks like:
"guid1","c:\temp\doc1.docx",1
"guid1","c:\temp\doc2.docx",2
"guid1","c:\temp\doc3.docx",3
"guid1","c:\temp\doc4.docx",4
"guid2","c:\temp\doc5.docx",5
"guid1","c:\temp\doc6.docx",6
"guid1","c:\temp\doc7.docx",7
I need to end up breaking the collection into individual collections like this:
"guid1","c:\temp\doc1.docx",1
"guid1","c:\temp\doc2.docx",2
"guid1","c:\temp\doc3.docx",3
"guid1","c:\temp\doc4.docx",4
"guid2","c:\temp\doc5.docx",5
"guid1","c:\temp\doc6.docx",6
"guid1","c:\temp\doc7.docx",7
These individual collections will then be fed into another function for processing. Trying to figure out the best way to do this.
Try using Linq, GroupBy:
IEnumerable<MyClass> source = ...;
int group = 0;
Guid key = new Guid();
// Let's have an array of arrays (array of individual collections) as a result
MyClass[][] buckets = source
.GroupBy(item => {
if (group == 0 || key != item.guid) {
key = item.guid;
group += 1;
}
return group; })
.Select(chunk => chunk.ToArray())
.ToArray();
I did this with linq and no external variables
var list = new []{
new {Id = "guid1", Path = #"c:\temp\doc1.docx", Index = 1},
new {Id = "guid1", Path = #"c:\temp\doc2.docx", Index = 2},
new {Id = "guid1", Path = #"c:\temp\doc3.docx", Index = 3},
new {Id = "guid1", Path = #"c:\temp\doc4.docx", Index = 4},
new {Id = "guid2", Path = #"c:\temp\doc5.docx", Index = 5},
new {Id = "guid1", Path = #"c:\temp\doc6.docx", Index = 6},
new {Id = "guid1", Path = #"c:\temp\doc7.docx", Index = 7}
};
var batchSize = 3;
var batched = list.GroupBy(x => x.Id)
.Select(x => x.GroupBy(p => p.Index/batchSize)
.ToArray());
string json = JsonConvert.SerializeObject(batched);
Console.WriteLine(json);
The json serialization is just for printing to the screen the output, which would be:
[
[
[
{
"Id":"guid1",
"Path":"c:\\temp\\doc1.docx",
"Index":1
},
{
"Id":"guid1",
"Path":"c:\\temp\\doc2.docx",
"Index":2
}
],
[
{
"Id":"guid1",
"Path":"c:\\temp\\doc3.docx",
"Index":3
},
{
"Id":"guid1",
"Path":"c:\\temp\\doc4.docx",
"Index":4
}
],
[
{
"Id":"guid1",
"Path":"c:\\temp\\doc6.docx",
"Index":6
},
{
"Id":"guid1",
"Path":"c:\\temp\\doc7.docx",
"Index":7
}
]
],
[
[
{
"Id":"guid2",
"Path":"c:\\temp\\doc5.docx",
"Index":5
}
]
]
]
Related
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.
I have a List<IEnumerable<Foo>>
This makes an list like this:
{
[
{a: 1, b: 2},
{a: 1, b: 3}
],
[{a: 1, b: 2}]
}
And i need to arrange it in this way, grouping the objects by the a and b values. I was no able to make a group query to anything like the example below.
{
{a: 1, b: 2, count: 2},
{a: 1, b: 3, count: 1}
}
Edit:
Here's the code that i have and the output:
var list = new List<object>();
foreach (var f in fooList)
{
var x = from y in f
group y by new { y.a, y.b } into z
select new
{
Foo = z.Key,
Count = z.Count()
};
a.Add(x);
}
Output:
[
{
"Foo": {
"a": 1,
"b": 2
},
"count": 1
},
{
"Foo": {
"a": 1,
"b": 2
},
"count": 1
},
{
"Foo": {
"a": 1,
"b": 3
},
"count": 1
}
],
Something like this will work:
var list = new List<List<Foo>>();
list.Add(new List<Foo> {new Foo {A = 1, B = 2}, new Foo {A = 1, B = 3}});
list.Add(new List<Foo> {new Foo {A = 1, B = 2}});
var result = list.SelectMany(l => l)
.GroupBy(l => new {l.A, l.B})
.Select(grp => new {A = grp.Key.A, B = grp.Key.B, Count = grp.Count()});
First the list gets flattened with SelectMany(). After that we GroupBy multiple values by using an anonymous object. After grouping we are selecting the initial values and the count out of the grouping into an anonymous object.
It seems like you want your result to be serialized. Using Json.Net, this will be the output:
[
{
"A":1,
"B":2,
"Count":2
},
{
"A":1,
"B":3,
"Count":1
}
]
You have to flatten your first list, as the nested level is irrelevant to your desired result. This shows how to flatten. I think your GroupBy is correct.
List<List<Foo>> list = new List<List<Foo>>();
list.Add(new List<Foo>());
list[0].Add(new Foo { a = 1, b = 2 });
list[0].Add(new Foo { a = 1, b = 3 });
var subList = new List<Foo>();
subList.Add(new Foo { a = 1, b = 2 });
list.Add(subList);
var flat = list.SelectMany(i => i);
var grouped = from foo in flat group foo by new { foo.a, foo.b } into g select g;
Assert.AreEqual(true, grouped.First().Count() == 2);
Assert.AreEqual(true, grouped.Last().Count() == 1);
I have this code here in C# (just a sample):
var list1 = ( from x
join y
select new
{
id, name
});
var counta = list1.count;
if (counta > 0)
{
for (var i = 0; i < counta; i++)
{
var userid = list1[i].id;
var user = user.Where(a => a.id == user).Select(a => a.userid).FirstOrDefault();
var list2 = (
from f
join g.Where(a => a.userid = user)select new
{
hourId, hourName
}
);
if (list2 > 0)
{
foreach (var product in list2)
{
var list3 = (
from p.
where (a => a.id == user)join q.
where (a => a.id == user)select new
{
productId, productName
});
}
}
}
}
Here is list1,list2,list3 relationship: list1 has many list2, list2 has many list3 and here is the JSON that I expected to return:
[
{
"list1": {
"id": 1,
"name": "Adam",
"list2": [
{
"hourId": 1,
"hourName": "08:00",
"list3": [
{
"productId": 1,
"productName": "Candy"
},
{
"productId": 2,
"productName": "Cookie"
}
]
},
{
"hourId": 2,
"hourName": "09:00",
"list3": [
{
"productId": 1,
"productName": "Candy"
},
{
"productId": 2,
"productName": "Cookie"
}
]
}
]
}
}
]
So my question here is how can I put list1, list2, list3 together to return the below JSON? Do I have to join 3 lists in LINQ again?
Thank you!
I've got a dictionary laid out like so:
Dictionary<string, List<Series>> example = new Dictionary<string, List<Series>>();
example.Add("Meter1",new List<Series>(){ new Series{ name="Usage", data = new double[] {1,2,3}},
new Series{ name = "Demand", data= new double[]{4,5,6}}});
example.Add("Meter2", new List<Series>(){ new Series{ name="Usage", data = new double[] {1,2,3}},
new Series{ name = "Demand", data= new double[]{4,5,6}}});
What I need is:
Dictionary<string, List<Series>> exampleResult = new Dictionary<string, List<Series>>();
exampleResult.Add("Usage", new List<Series>(){ new Series{ name="Meter1", data = new double[] {1,2,3}},
new Series{ name = "Meter2", data= new double[]{1,2,3}}});
exampleResult.Add("Demand", new List<Series>(){ new Series{ name="Meter1", data = new double[] {4,5,6}},
new Series{ name = "Meter2", data= new double[]{4,5,6}}});
That is, the dictionary projected "sideways", with the name of each Series as the key in the new dictionary, with the key of the old dictionary used as the name of the series.
Here's the series class...
public class Series
{
public string name { get; set; }
public double[] data { get; set; }
}
Sorry if I am not expressing this problem clearly, please ask any questions you'd like, and thanks in advance for any help...
EDITED TO ADD EXAMPLE
Create a grouping and then select out the new keys and values to create a dictionary. Like this:
// source data
var d = new Dictionary<string, Series[]>
{
{
"key1", new[]
{
new Series
{
name = "Usage",
data = new double[] {1, 2, 3}
},
new Series
{
name = "Demand",
data = new double[] {4, 5, 6}
}
}
},
{
"key2", new[]
{
new Series
{
name = "Usage",
data = new double[] {1, 2, 3}
},
new Series
{
name = "Demand",
data = new double[] {4, 5, 6}
}
}
}
};
// transform
var y = (
from outer in d
from s in outer.Value
let n = new
{
Key = s.name,
Series = new Series
{
name = outer.Key,
data = s.data
}
}
group n by n.Key
into g
select g
).ToDictionary(g1 => g1.Key,
g2 => g2.Select(g3 => g3.Series).ToArray());
/* results:
var y = new Dictionary<string, Series[]>
{
{
"Usage",
new[]
{
new Series
{
name = "key1",
data = new double[] { 1, 2, 3 }
},
new Series
{
name = "key2",
data = new double[] { 1, 2, 3 }
}
}
},
{
"Demand",
new[]
{
new Series
{
name = "key1",
data = new double[] {4, 5, 6},
},
new Series
{
name = "key2",
data = new double[] {4, 5, 6}
}
}
}
};
*/
Try this:
example
.SelectMany(x => x.Value
.Select(y => y.name)
).Distinct()
.ToDictionary(
x => x,
x => example
.SelectMany(y => y.Value
.Where(z => z.name == x)
.Select(z => new Series{ name = y.Key, data = z.data })
).ToList()
)