I have these arrays correlated by index. So for each index (ObjId, ObjState) form a pair.
string[] ObjIds = { "Obj1", "Obj1", "Obj2", "Obj2", "Obj1", "Obj3", "Obj2", "Obj2" };
string[] ObjStates = { "OS11", "OS12", "OS21", "OS22", "OS13", "OS31", "OS22", "OS23" };
However I only want the most recent pair (closest to index 0) for each ObjId.
For the above input, the output would be
{ Obj1, OS11 }, {Obj2, OS21}, {Obj3, OS31}
How do I do this using an IEnumerable query? I'm having trouble with figuring out how to keep track of the index.
You should be able to do:
var items = ObjIds.Zip(ObjStates, (id, st) => new {Id = id, State = st})
.GroupBy(i => i.Id)
.Select(g => g.First());
Enumerable.Zip is used here "pair up" the items for you into a new anonymous type, and then the items are grouped, and the first item is selected from each group.
Related
I would like to get a result of Linq query as groups of anonymous objects. The items within the groups should be ordered by ID field. I can reach this partly by lambda syntax, but can't get an anonymous objects as result. So I need part of each example.
Executable code: https://dotnetfiddle.net/cPJUN9
var res_g = (from dg in list
group new { dg.ID, dg.IDOperation, dg.IDDiagnosis } by dg.IDOperation
into dg_group
select dg_group);
lambda syntax
var res_g = list
.GroupBy(x => x.IDOperation)
.Select(x => x.OrderBy(x => x.ID)); // order dg by ID asc within group
Alas, you didn't describe exactly your requirements where, only that you want some anonymous type and that they should be ordered by Id. Your query syntax makes different groups than your method syntax. So I can only give an example to create your sequence of anonymous objects
So you have a sequence of similar items, where every item has at least properties Id and IdOperation. You want to make groups of items where every item in each group has the same value for IdOperation. You want to order the elements in each group by ascending Id, and create some anonymous type.
You didn't specify what you want in your anonymous object (after all: your code doesn't do what you want, so I can't deduct it from your code)
Whenever I use GroupBy, and I want to specify the elements of each group, I use the overload of GroupBy that has a parameter resultSelector. With the resultSelector I can precisely define the elements of the group. (The link refers to IQueryable, there is also an IEnumerable version)
IEnumerable<Operations> operations = ... // = your list
// Make Groups of Operations that have the same value for IdOperation
var result = operations.GroupBy(operation => operation.IdOperation,
// parameter resultSelector: take the key (=idOperation) and all Operations that have
// this idOperation, to make one new.
(idOperation, operationsWithThisId) => new
{
// do you need the common idOperation?
IdOperation = idOperation,
// Order the elements in each group by Id:
Operations = operationsWithThisId.OrderBy(operation => operation.Id)
.Select(operation => new
{
// Select only the operation properties that you plan to use
Id = operation.Id,
Name = operation.Name,
StartDate = operation.StartDate,
...
})
.ToList(),
});
In words: from your sequence of Operations, make groups of Operations that have the same value for IdOperation. Then take this common IdOperation, and all Operations that are in this group, to make one anonymous object: this is the anonymous object that you were talking about. So per group, you make one anonymous object.
IdOperation is the value that all Operations in this group have in common
Operations is a list. All Operations in this group are ordered by ascending Id. Several properties are Selected and the result is put in a List.
If you want to group differently, like in you query syntax, simply change parameter keySelector:
var result = operations.GroupBy(operation => new
{
Id = operation.ID,
IdOperation = operation.IDOperation,
IdDiagnosis = operation.IDDiagnosis
},
Although this corresponds with what you did in your query syntax, you will have groups of Operations that have same value for Id / IdOperation / IDDiagnosis. It will be useless to sort the elements in the group by Id, because all Ids in this group will be equal.
Conclusion
With parameter resultSelector you can define the result exactly as you want: the result is not an IEnumerable<IGrouping<Tkey, TElement>>, but an IEnumerable<TResult>.
The TResult is one object created from all elements in one group and the common group value.
you can update GetDict function inner loop to use orderby on group elements
public static void GetDict(List<Operation> list)
{
var res_g = (from dg in list
group new { dg.ID, dg.IDOperation, dg.IDDiagnosis } by dg.IDOperation
into dg_group
select dg_group);
foreach (var x in res_g)
{
Console.WriteLine("Key: " + x.Key);
foreach (var y in x.OrderBy(o=>o.ID))
{
Console.WriteLine("{0} {1} {2}", y.ID, y.IDOperation, y.IDDiagnosis); ;
}
}
}
I hope this solve the issue.
Depending on your Requirements EDIT but I'm Tuning the result set
var res_g_2 = list.Select( p=> new { p.ID, p.IDOperation, p.IDDiagnosis }) .GroupBy(g => g.IDOperation)
.Select(g => new {
IDOperation = g.Key,
Records = g.OrderBy(group => group.ID)
});
Edit full version
public static void GetDict(List<Operation> list)
{
var res_g = (from dg in list
group new { dg.ID, dg.IDOperation, dg.IDDiagnosis } by dg.IDOperation
into dg_group
select new
{
Key = dg_group.Key,
Records = dg_group.OrderBy(g => g.ID)
}) ;
var res_g_2 = list.Select( p=> new { p.ID, p.IDOperation, p.IDDiagnosis }) .GroupBy(g => g.IDOperation)
.Select(g => new {
Key = g.Key,
Records = g.OrderBy(group => group.ID)
});
// you can use either res_g or res_g_2 both give the same results
foreach (var x in res_g)
{
Console.WriteLine("Key: " + x.Key);
foreach (var y in x.Records)
{
Console.WriteLine("{0} {1} {2}", y.ID, y.IDOperation, y.IDDiagnosis); ;
}
}
}
I am just not understanding the LINQ non-query syntax for GroupBy.
I have a collection of objects that I want to group by a single property. In this case Name
{ Id="1", Name="Bob", Age="23" }
{ Id="2", Name="Sally", Age="41" }
{ Id="3", Name="Bob", Age="73" }
{ Id="4", Name="Bob", Age="34" }
I would like to end up with a collection of all the unique names
{ Name="Bob" }
{ Name="Sally" }
Based on some examples I looked at I thought this would be the way to do it
var uniqueNameCollection = Persons.GroupBy(x => x.Name).Select(y => y.Key).ToList();
But I ended up with a collection with one item. So I though maybe I was over complicating things with the projection. I tried this
var uniqueNameCollection = Persons.GroupBy(x => x.Name).ToList();
Same result. I ended up with a single item in the collection. What am I doing wrong here? I am just looking to GroupBy the Name property.
var names = Persons.Select(p => p.Name).Distinct().ToList()
If you just want names
LINQ's GroupBy doesn't work the same way that SQL's GROUP BY does.
GroupBy takes a sequence and a function to find the field to group by as parameters, and return a sequence of IGroupings that each have a Key that is the field value that was grouped by and sequence of elements in that group.
IEnumerable<IGrouping<TSource>> GroupBy<TSource, TKey>(
IEnumerable<TSource> sequence,
Func<TSource, TKey> keySelector)
{ ... }
So if you start with a list like this:
class Person
{
public string Name;
}
var people = new List<Person> {
new Person { Name = "Adam" },
new Person { Name = "Eve" }
}
Grouping by name will look like this
IEnumerable<IGrouping<Person>> groups = people.GroupBy(person => person.Name);
You could then select the key from each group like this:
IEnumerable<string> names = groups.Select(group => group.Key);
names will be distinct because if there were multiple people with the same name, they would have been in the same group and there would only be one group with that name.
For what you need, it would probably be more efficient to just select the names and then use Distinct
var names = people.Select(p => p.Name).Distinct();
var uniqueNameCollection = Persons.GroupBy(x => x.Name).Select(y => y.Key).ToList();
Appears valid to me. .net Fiddle showing proper expected outcome: https://dotnetfiddle.net/2hqOvt
Using your data I ran the following code statement
var uniqueNameCollection = people.GroupBy(x => x.Name).Select(y => y.Key).ToList();
The return results were List
Bob
Sally
With 2 items in the List
run the following statement and your count should be 2.
people.GroupBy(x => x.Name).Select(y => y.Key).ToList().Count();
Works for me, download a nugget MoreLinq
using MoreLinq
var distinctitems = list.DistinctBy( u => u.Name);
I have a generic list of assets (List<Asset>) and one of the assets properties is called Tags which is a list of strings
How would I do a Linq query to get a distinct list of tags. I tried
assetList.Select(a => a.Tags).Distinct()
but this returns me an IEnumerable<List<string>> instead of an IEnumerable<string>
You was close. You need to use Enumerable.SelectMany to select all tags and flatten them into one sequence:
assetList.SelectMany(a => a.Tags).Distinct()
assetList.SelecMany(a => a.Tags).Distinct() . correct it
more information about differences Difference Between Select and SelectMany
Select only takes objects as they are and Tags is a list, so it takes lists. If you need items from these lists, you have to flatten them into one list and then proceed with other operations.
assetList.SelectMany(a => a.Tags).Distinct();
A nice example from MSDN on SelectMany
PetOwner[] petOwners =
{ new PetOwner { Name="Higa, Sidney",
Pets = new List<string>{ "Scruffy", "Sam" } },
new PetOwner { Name="Ashkenazi, Ronen",
Pets = new List<string>{ "Walker", "Sugar" } },
new PetOwner { Name="Price, Vernette",
Pets = new List<string>{ "Scratches", "Diesel" } } };
IEnumerable<string> query1 = petOwners.SelectMany(petOwner => petOwner.Pets);
produces the following list
Scruffy, Sam, Walker, Sugar, Scratches, Diesel
I have a cluttery piece of code that I would like to shorten using Linq. It's about the part in the foreach() loop that performs an additional grouping on the result set and builds a nested Dictionary.
Is this possible using a shorter Linq syntax?
var q = from entity in this.Context.Entities
join text in this.Context.Texts on new { ObjectType = 1, ObjectId = entity.EntityId} equals new { ObjectType = text.ObjectType, ObjectId = text.ObjectId}
into texts
select new {entity, texts};
foreach (var result in q)
{
//Can this grouping be performed in the LINQ query above?
var grouped = from tx in result.texts
group tx by tx.Language
into langGroup
select new
{
langGroup.Key,
langGroup
};
//End grouping
var byLanguage = grouped.ToDictionary(x => x.Key, x => x.langGroup.ToDictionary(y => y.PropertyName, y => y.Text));
result.f.Apply(x => x.Texts = byLanguage);
}
return q.Select(x => x.entity);
Sideinfo:
What basically happens is that "texts" for every language and for every property for a certain objecttype (in this case hardcoded 1) are selected and grouped by language. A dictionary of dictionaries is created for every language and then for every property.
Entities have a property called Texts (the dictionary of dictionaries). Apply is a custom extension method which looks like this:
public static T Apply<T>(this T subject, Action<T> action)
{
action(subject);
return subject;
}
isn't this far simpler?
foreach(var entity in Context.Entities)
{
// Create the result dictionary.
entity.Texts = new Dictionary<Language,Dictionary<PropertyName,Text>>();
// loop through each text we want to classify
foreach(var text in Context.Texts.Where(t => t.ObjectType == 1
&& t.ObjectId == entity.ObjectId))
{
var language = text.Language;
var property = text.PropertyName;
// Create the sub-level dictionary, if required
if (!entity.Texts.ContainsKey(language))
entity.Texts[language] = new Dictionary<PropertyName,Text>();
entity.Texts[language][property] = text;
}
}
Sometimes good old foreach loops do the job much better.
Language, PropertyName and Text have no type in your code, so I named my types after the names...
I'm using LINQ to manipulate a datatable. I have 3 columns - I would like group by one and then select the remaining 2 columns together. At the moment I have something like this
var query = reportDataTable.AsEnumerable()
.GroupBy(c => c["Code"])
.Select(g =>
new {
Code = g.Key,
Rank = g.Select(f => new
{ f["rank"],
f["Name"]}).ToArray()
});
but I get issues due to anonymous types. I know this syntax would work if I could reference the the column headers directly (in say a list or w/e). How can I get around this with DataTables? Cheers.
Edit:
Well I'd like to be able to reference the fields later when I come to populate the data into a different datatable:
foreach (var q in query)
{
DataRow df = dp.NewRow();
df["Code"] = q.Code;
foreach (var rank in q.Rank)
{
df[rank.name] = rank.rank;
}
dp.Rows.Add(df);
}
define your Rank fields, Also if you have a class for it, call related class constructor,
you can see this in bellow code, before ToArray.
var query = reportDataTable.AsEnumerable()
.GroupBy(c => c["Code"])
.Select(g =>
new { Code = g.Key, Rank =
g.Select(f => new { rank = f["rank"], name = f["Name"]})
.ToArray() });