I am trying to understand the code below:
Color32[] colors = mesh.colors32;
IEnumerable<IGrouping<byte, int>> hierarchyMap = colors.Select((color, index) => new { color, index }).GroupBy(c => c.color.g, c => c.index);
I used google and only found some tutorials for GroupBy(xxx)(only one parameter inside brackets), which means xxx is the key for the group. What if there were two parameters inside the brackets?
Technically, the accepted answer is trying to group by using two keys. It doesn't explain what if there are two parameters inside the GroupBy bracket.
If there are two parameters inside the bracket, it will group the elements of a sequence according to a specified key selector function and projects the elements for each group by using a specified function.
Let say we have an Employee class
public class Employee
{
public string Name { get; set; }
public int Age { get; set; }
}
And then the code logic is below.
var employees = new List<Employee>
{
new Employee { Name="Dave", Age=25 },
new Employee { Name="John", Age=23 },
new Employee { Name="Michael", Age=30 },
new Employee { Name="Bobby", Age=30 },
new Employee { Name="Tom", Age=25 },
new Employee { Name="Jane", Age=21 }
};
var query = employees.GroupBy(employee => employee.Age, employee => employee.Name);
foreach (IGrouping<int, string> employeeGroup in query)
{
Console.WriteLine(employeeGroup.Key);
foreach (string name in employeeGroup)
{
Console.WriteLine($"=> {name}");
}
}
The output will be:
25
=> Dave
=> Tom
23
=> John
30
=> Michael
=> Bobby
21
=> Jane
Reference from MSDN
Update
When using two parameters in the GroupBy method, the two parameters represents,
keySelector Func<TSource,TKey>
A function to extract the key for each element.
elementSelector Func<TSource,TElement>
A function to map each source element to an element in the
IGrouping.
What it does it is that it groups the sequence based on the first parameters, and projects each element of the group using the function specificied as second parameter.
Groups the elements of a sequence according to a specified key selector function and projects the elements for each group by using a specified function.
Grouping with Two Keys
If your intension is to group by two keys, then could use an anonymous type for grouping by multiple keys
.GroupBy(c => new {c.color.g, c.index})
For example, from the code in OP
colors.Select((color, index) => new { color, index })
.GroupBy(c => new {c.color.g, c.index});
Related
I am new to the entity framework and LINQ. Trying to learn it by example.
I have an Entity called "Participant" as below:
public class Participant
{
public int Id { get; set; }
public int Count { get; set; }
public string Zip { get; set; }
public string ProjectStatus { get; set; }
public string IncomingSource { get; set; }
}
I am trying to use Group by and return the result as Task<IEnumerable<Participant>>. The Sql Query that I found the is :
SELECT Count(Id) as #, Zip FROM [database].[dbo].[Participants] GROUP BY Zip Order By Zip
The Code that I am trying to accomplish the same result is like below:
public Task<IEnumerable<Participant>> GetResults()
{
var results = context.Participants
.GroupBy(i => i)
.Select(i => new {
Count = i.Key,
Zip = i.Count()
}
).ToList();
return results;
}
However, this gives me a conversion issue. The complete error stack is:
Cannot implicitly convert type 'System.Collections.Generic.List<<anonymous type: project.API.Models.Participant Count, int Zip>>' to 'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<project.API.Models.Participant>>'
I am not sure how to solve convert these. Any help would be appreciated.
When you use GroupBy, you want to make groups of elements that have something in common. The property that you want to be in common is specified in parameter keySelector.
With this parameter you say: please make groups of Paraticipants, all with the same value of the property specified in the keySelector.
In your case: you want to make groups of Participants that have the same value of ZIP. In other words: if you fetch two Participants that are in the same group, you want to be certain that they have the same value for ZIP.
So first, change your keySelector:
var result = dbContext.Participants.GroupBy(participant => participant.Zip)
The result is a sequence of Groups. Every Group has a Key, and every Group IS (not has!) a sequence of Participants. All Participants have a value for property Zip that equals the value of Key.
After that, you want to take every group, and from every group you want to make a new Participant object, that has only two properties filled
Count is the number of Participants in the Group
Zip is the Zip of any of the elements in the Group, which is, as we saw earlier the Key of the Group.
.Select(groupOfParticipantsWithSameKey => new Participant
{
Count = groupOfParticipantsWithSameKey.Count(),
Zip = groupOfParticipantsWithSameKey.Key,
});
Did you notice that I changed the identifier i with a proper identifier. Choosing the proper identifier will help you identifying problems in LINQ. It might be a little more tying, but it helps you to understand what each element of the sequence your are processing represents.
By the way, there is an overload of Enumerable.GroupBy, the one with a parameter resultSelector. This makes your Select unnecessary.
var result = context.Participants
.GroupBy(participanti => participant.Zip,
// parameter resultSelector, take each common Zip, and all Participants that have this Zip
// to make one new object
(zip, participantsWithThisZip) => new Participant
{
Zip = zip,
Count = participantsWithThisZip.Count(),
});
This one is even easier to understand, because you have eliminated the identifier Key.
A small design improvement
You have created a method, that takes all Participants and returns one Participant per used Zip, where the Count is the number of Participants that have this Zip.
If you will be using this more often, then it would be a good idea to create a separate method for this, an extension method of IQueryable<Participant>. This way you can reuse the method with every sequence of Participants, not only all Participants within the database. See Extension Methods demystified
public static class ParticpantExtensions
{
public static IQueryable<Participant> ToParticipantsWithSameZip(
this IEnumerable<Participant> participants)
{
return participants.GroupBy(
participanti => participant.Zip,
(zip, participantsWithThisZip) => new Participant
{
Zip = zip,
Count = participantsWithThisZip.Count(),
});
}
}
Usage:
Your original method:
Task<IList<Participant>> FetchParticipantsWithSameZipAsync()
{
using (var dbContext in new MyDbContext(...))
{
return await dbContext.ToParticipantsWithSameZip().ToListAsync();
}
}
You can reuse it in the non-async version:
IList<Participant>> FetchParticipantsWithSameZipAsync()
{
using (var dbContext in new MyDbContext(...))
{
return dbContext.ToParticipantsWithSameZip().ToList();
}
}
But now you can also intertwine it with other LINQ methods:
var newYorkParticipantsWithSameZip = dbContext.Participants
.Where(participant => participant.State == "New York")
.ToParticipantsWithSameZip()
.OrderBy(participant => participant.Count())
.ToList();
Several advantages:
Reusable
Code looks cleaner,
Easier to understand what it does
You can unit test it without a database: any IQueryable<Participant> will do.
If you need to change ToParticipantsWithSameZip, there is only one place that you have to change and to rewrite the test.
So if you will be using it on several places: consider the extension method
The 2 easiest approaches would be either remove Task from the method signature making the method synchronous
public IEnumerable<Participant> GetResults()
Or if you wanted the method to use the async and await pattern use the async keyword in the method signature and call await and ToListAsync()
public async Task<IEnumerable<Participant>> GetResults()
{
var results = await context.Participants
.GroupBy(i => i)
.Select(i => new {
Count = i.Key,
Zip = i.Count()
}
).ToListAsync();
Note : in such case, you would likely want to rename the method GetResultsAsync
As TheGeneral mentioned.. use async and rename method:
public async Task<IEnumerable<Participant>> GetResultsAsync()
{
return context.Participants
.GroupBy(i => i.Zip)
.Select(i => new Participant
{
Count = i.Count(),
Zip = i.Zip
}
).ToListAsync();
}
Select is like What do u want to extract from ur particular query.
here u are creating an anonymous type by using new {...}
.Select(i => new {
Count = i.Key,
Zip = i.Count()
}
thats why its producing
List<anonymous>' this type.
but U want List so what to do ? anonymous type return Participant.
like this
.Select(i => new Participant
{
Count = i.Count(),
Zip = i.Zip
}
Using LINQ and lambda expressions, I am trying to write data that I have pulled to a text file.
using (var contextDb = new TimoToolEntities())
{
using (var writeFile = new StreamWriter(saveTo))
{
var randomData = contextDb.WorkCenter_Operations.Where(d => d.Job_Number >= 1 && d.Part_Number.Length >= 1 && d.Oper_Number >= 1 )
.OrderBy(d => d.Oper_Number)
.GroupBy(d => d.Job_Number , d => d.Part_Number ).ToList();
foreach (var record in randomData)
{
Console.WriteLine(record.Job_Number + "," + record.Part_Number); // error here
}
}
Console.ReadLine();
}
I am getting the error the 'IGrouping does not contain a definition for 'name' and no extension method 'name' accepting a first argument of type 'IGrouping' could be found.
I have looked around and believe that the objects are anonymous, but I haven't been able to find a fix that will work.
When you use this overload of GroupBy
.GroupBy(d => d.Job_Number , d => d.Part_Number )
the first lambda is a key selector (you group by Job_Number) and the second one is a value selector. Your record will be a collection of Part_Number with Job_Number as a key.
This MSDN example illustrates the basic usage:
// Group the pets using Age as the key value
// and selecting only the pet's Name for each value.
IEnumerable<IGrouping<int, string>> query =
pets.GroupBy(pet => pet.Age, pet => pet.Name);
// Iterate over each IGrouping in the collection.
foreach (IGrouping<int, string> petGroup in query)
{
// Print the key value of the IGrouping.
Console.WriteLine(petGroup.Key);
// Iterate over each value in the
// IGrouping and print the value.
foreach (string name in petGroup)
Console.WriteLine(" {0}", name);
}
Your intent is not 100% clear, so just in case you actually wanted to group by multiple fields, use a different overload like this:
.GroupBy(d => new { d.Job_Number, d.Part_Number })
Then your record will be a collection of whatever your data is and will have an anonymous key where you can access for example record.Key.Job_Number
I have 2 lists
List 1
var hashTags = new List<HashTag>();
hashTags.Add(new HashTag
{
Name = "#HashTag1",
Index = 1
});
hashTags.Add(new HashTag
{
Name = "#HashTag2",
Index = 2
});
hashTags.Add(new HashTag
{
Name = "#HashTag3",
Index = 3
});
hashTags.Add(new HashTag
{
Name = "#HashTag4",
Index = 4
});
List 2
var hashTags2 = new List<HashTag>();
hashTags2.Add(new HashTag
{
Name = "#HashTag1",
Index = 1
});
hashTags2.Add(new HashTag
{
Name = "#HashTag3",
Index = 3
});
hashTags2.Add(new HashTag
{
Name = "#HashTag4",
Index = 4
});
How do I check if all the elements in hashTags2 exist in hashTags? The index can be ignored and only the name matching is crucial. I can write a for loop to check element but I am looking for a LINQ solution.
Simple linq approach.
hashTags2.All(h=> hashTags.Any(h1 => h1.Name == h.Name))
Working Demo
As only equality of the names is to be taken into account, the problem can be solved by first mapping to the names and then checking containment as follows.
var hashTags2Names = hashTags2.Select( iItem => iItem.Name );
var hashTagsNames = hashTags.Select( iItem => iItem.Name );
var Result = hashTags2Names.Except( hashTagsNames ).Any();
So you want a boolean linq expression that returns true if the name of every element in hashTags2 exists in hashTags?
For this you want the function Enumerable.All, you want that every Hashtag in hashTags2 ...
bool result = hashTags2.All(hashTag => ...)
what do you want to check for every hashTag in hashTags2? That the name is a name in hashTags. So we need the names of hashTags:
IEnumerable<string> names = hashTags.Select(hashTag => hashTag.Name);
and to check if an item is in a sequence: Enumerable.Contains.
Put it all together:
IEnumerable<string> names = hashTags.Select(hashTag => hashTag.Name);
bool result = hashTags2.All(hashTag => names.Contains(hashTag.Name));
Of if you want one fairly unreadable expression:
bool result = hashTags2.All(hashTagX =>
hashTags.Select(hashTagY => hashTagY.Name)
.Contains(hashtagX)))
Because of delayed execution there is no difference between the first and the second method. The first one will be more readable.
With Linq to objects you will need at least one IEqualityComparar, to
tell linq how to compare objects and to determine when they are equal.
A simple comparer would be the following that uses the Name property to determine equality of your HashTag.
public class NameEquality : IEqualityComparer<HashTag>
{
public bool Equals(HashTag tag, HashTag tag2)
{
return tag.Name == tag2.Name;
}
public int GetHashCode(HashTag tag)
{
return tag.Name.GetHashCode();
}
}
With this Equality Comparer you can use the linq method Except(), to get all Elements from your list hashTag that are not part of hashTag2.
hashTags.Except(hashTags2, new NameEquality())
I prefer the join operator, however it is just a matter of taste, I guess:
var hashMatched = hashTags.Join(hashTags2,_o => _o.Name, _i => _i.Name, (_o,_i) => _o);
I am confused by the groupby behavior in LINQ to objects. Lets assume for this I have the following class;
public class person
{
public string name { get; set; }
public int age { get; set; }
}
Lets say I have a list or type person; List<person> people
Now, I want to produce an IEnumerable<T> with an IGrouping or anonymous type that has two properties 1) the name (the key) and 2) the sum of all of the ages for people with that name.
Here are a couple of examples of things I've tried (unsuccessfully);
people.GroupBy(x => x.name, x => x, (key, value) => value.Aggregate((c, n) => c + n));
That won't compile with the error cannot convert type "int" to "namespace.person"
Before that I was trying something more along the lines of;
people.GroupBy(x => x.name).Select(g => new { g.Key, g.Aggregate((c, n) => c + n)) } );
Which essentially gives the same error. I'm basically struggling to understand what the value returned by GroupBy really is. At first I thought that basic overload was giving me a key value pair where x.Key was the key I specified with my delegate and x.Value would be an IEnumerable<T> where the typeof T would be the type of x. Of course, if that were the case my second example would work like a charm. My question is somewhat open ended but can someone explain 2 things, firstly; How do I accomplish my end goal with LINQ? And Secondly, why isn't the result of GroupBy more along the lines of what I describe here? What is it? I feel like a key value pair where the value is a collection of objects that match that key is far more intuitive than what is actually returned.
var grouped = people.GroupBy(x => x.name)
.Select(x => new
{
Name = x.Key,
Age = x.Sum(v => v.age),
Result = g.Aggregate(new Int32(), (current, next) => next.age + next.age)
});
If you want you can group the result of that again by Name and it will be a grouping with Key as the name and Age as the value
you can do it with expression syntax
var results = from p in persons
group p.car by p.name into g
select new { name = g.Key, age = g.Sum(c=>.age };
I have now 2 lists:
list<string> names;
list<int> numbers;
and I need to sort my names based on the values in numbers.
I've been searching, and most use something like x.ID, but i don't really know what that value is. So that didn't work.
Does anyone know, what to do, or can help me out in the ID part?
So i assume that the elements in both lists are related through the index.
names.Select((n, index) => new { Name = n, Index = index })
.OrderBy(x => numbers.ElementAtOrDefault(x.Index))
.Select(x => x.Name)
.ToList();
But i would use another collection type like Dictionary<int,string> instead if both lists are related insomuch.
Maybe this is a task for the Zip method. Something like
names.Zip(numbers, (name, number) => new { name, number, })
will "zip" the two sequences into one. From there you can either order the sequence immediately, like
.OrderBy(a => a.number)
or you can instead create a Dictionary<,>, like
.ToDictionary(a => a.number, a => a.name)
But it sounds like what you really want is a SortedDictionary<,>, not a Dictionary<,> which is organized by hash codes. There's no LINQ method for creating a sorted dictionary, but just say
var sorted = new SortedDictionary<int, string>();
foreach (var a in zipResultSequence)
sorted.Add(a.number, a.name);
Or alternatively, with a SortedDictionary<,>, skip Linq entirely, an go like:
var sorted = new SortedDictionary<int, string>();
for (int idx = 0; idx < numbers.Count; ++idx) // supposing the two list have same Count
sorted.Add(numbers[idx], names[idx]);
To complement Tims answer, you can also use a custom data structure to associate one name with a number.
public class Person
{
public int Number { get; set; } // in this case you could also name it ID
public string Name { get; set; }
}
Then you would have a List<Person> persons; and you can sort this List by whatever Attribute you like:
List<Person> persons = new List<Person>();
persons.Add(new Person(){Number = 10, Name = "John Doe"});
persons.Add(new Person(){Number = 3, Name = "Max Muster"});
// sort by number
persons = persons.OrderBy(p=>p.Number).ToList();
// alternative sorting method
persons.Sort((a,b) => a.Number-b.Number);
I fixed it by doing it with an dictionary, this was the result:
dictionary.OrderBy(kv => kv.Value).Reverse().Select(kv => kv.Key).ToList();