I have a list which I get from a database. The structure looks like (which I'm representing with JSON as it's easier for me to visualise)
{id:1
value:"a"
},
{id:1
value:"b"
},
{id:1
value:"c"
},
{id:2
value:"t"
}
As you can see, I have 2 unique ID's, ID 1 and 2. I want to group by the ID. The end result I'd like is
{id:1,
values:["a","b","c"],
},
{id:2,
values["g"]
}
Is this possible with Linq? At the moment, I have a massive complex foreach, which first sorts the list (by ID) and then detects if it's already been added etc but this monstrous loop made me realise I'm doing wrong and honestly, it's too embarrassing to share.
You can group by the item Id and have the resulting type be a Dictionary<int, List<string>>
var result = myList.GroupBy(item => item.Id)
.ToDictionary(item => item.Key,
item => item.Select(i => i.Value).ToList());
You can either use GroupBy method on IEnumerable to create IGrouping object that contains a key and grouped objects or you can use ToLookupto create exactly what you want in result:
yourList.ToLookup(m => m.id, m => m.value);
This creates a hashed collection of keys with their values.
For more information please see below post:
https://www.c-sharpcorner.com/UploadFile/d3e4b1/practical-usage-of-using-tolookup-method-in-linq-C-Sharp/
Just a little more detail to emphasize the difference between the ToLookup approach and the GroupBy approach:
// class definition
public class Item
{
public long Id { get; set; }
public string Value { get; set; }
}
// create your list
var items = new List<Item>
{
new Item{Id = 0, Value = "value0a"},
new Item{Id = 0, Value = "value0b"},
new Item{Id = 1, Value = "value1"}
};
// this approach results in a List<string> (a collection of the values)
var lookup = items.ToLookup(i => i.Id, i => i.Value);
var groupOfValues = lookup[0].ToList();
// this approach results in a List<Item> (a collection of the objects)
var itemsGroupedById = items.GroupBy(i => i.Id).ToList();
var groupOfItems = itemsGroupedById[0].ToList();
So, if you want to work with values only after grouping, then you could take the first approach; if you want to work with objects after grouping, you could take the second approach. And, these are just a couple example implementations, there are plenty of ways to accomplish your goal.
First convert to a Lookup then select into a list, like so:
var groups = list
.ToLookup
(
item => item.ID,
item => item.Value
)
.Select
(
item => new
{
ID = item.Key,
Values = item.ToList()
}
)
.ToList();
The resulting JSON looks like this:
[{"ID":1,"Values":["a","b","c"]},{"ID":2,"Values":["t"]}]
Link to working example on DotNetFiddle.
Related
In my c# MVC project I have a list of items in that I want to sort in order of another list
var FruitTypes = new List<Fruit> {
new Fruit { Id = 1, Name = "Banana"},
new Fruit { Id = 2, Name = "Apple" },
new Fruit { Id = 3, Name = "Orange" },
new Fruit { Id = 4, Name = "Plum"},
new Fruit { Id = 5, Name = "Pear" },
};
SortValues = new List<int> {5,4,3,1,2};
Currently my list is showing as default of fruit type.
How can I sort the Fruit list by SortValues?
It's unclear if you are sorting by the indexes in SortValues or whether SortValues contains corresponding Id values that should be joined.
In the first case:
First you have to Zip your two lists together, then you can sort the composite type that Zip generates, then select the FruitType back out.
IEnumerable<FruitType> sortedFruitTypes = FruitTypes
.Zip(SortValues, (ft, idx) => new {ft, idx})
.OrderBy(x => x.idx)
.Select(x => x.ft);
However, this is simply sorting the first list by the ordering indicated in SortValues, not joining the ids.
In the second case, a simple join will suffice:
IEnumerable<FruitType> sortedFruitTypes = SortValues
.Join(FruitTypes, sv => sv, ft => ft.Id, (_, ft) => ft);
This works because Enumerable.Join maintains the order of the "left" hand side of the join.
While there is almost certainly a more LINQ-y way, if you tend towards verbosity, you could accomplish this with an iterator function. For example:
public IEnumerable<Fruit> SortFruits(IEnumerable<Fruit> unordered, IEnumerable<int> sortValues)
{
foreach (var value in sortValues)
yield return unordered.Single(f => f.Id == value);
}
I like that it's explicit about what it's doing. You may consider throwing an exception when the number of items in each list is different, or maybe you just don't return an item if there is no sort value for it. You'll have to decide what the behaviour should be for "missing" values in either collection is. I think that having to handle these scenarios is a good reason to put it all in a single method this way, instead of a longer LINQ query.
Time complexity:O(n) + TM of Linq.
Declare list of fruits to store result.
Iterate through each fruit type.
Use Linq FirstOrDefault to get element by sorted value.
List<int> SortValues = new List<int> { 5, 4, 3, 1, 2 };
List<Fruit> result = new List<Fruit>();
foreach (var element in SortValues)
{
Fruit f = FruitTypes.FirstOrDefault(fruitElement => fruitElement.Id == element);
result.Add(f);
}
Implementation: DotNetFiddler
I've list of object which looks like below
public class test
{
public int ID{ get; set; }
public string Name { get; set; }
public Dictionary<string, string> SampleXML { get; set; }
}
I want to group the list based on values in 'SampleXML'. e.g. It contains values like price. I want to group List based on price.
How to implement the same. I tried below code but it just seperates the list by key. I need unique records.
Dictionary<string, List<test>> result = Obj.LstTest
.GroupBy(x => x.SampleXML["Price"])
.ToDictionary(g => g.Key,
g => g.ToList());
The above code generates the list based on price. e.g. If there are 6 records with 3 different prices, above code return 3 results,but again the list contains two records each.
EDIT
If the price is blank then it needs to be ignored in grouping that is if there are 3 records and all doesn't have prices then list will contain 3 item(as it is).
Any help is appreciated.
Edit: Based on Vicky S edit
To still have the result as a List of test:
List<test> list = new List<test>();
List<test> result = new List<test>();
result = list.GroupBy(x => x.SampleXML["Price"]).Select(g=>g.FirstOrDefault());
Edit: To include tests that have SampleXML with empty Price value or no Price Key. Do the following:
First we need to separate tests with "No Price" and include them to our final result, from test that have "Price" value to group them.
var emptyPrice = list.Where(l => !l.SampleXML.ContainsKey("Price") || l.SampleXML["Price"] == string.Empty).ToList();
var withoutEmptyPrice = list.Where(l => l.SampleXML.ContainsKey("Price") && !string.IsNullOrEmpty(l.SampleXML["Price"]));
Then we will group tests with "Price" value as my first answer.
var resultWithEmptyPrice = withoutEmptyPrice.GroupBy(x => x.SampleXML["Price"]).Select(g => g.FirstOrDefault()).ToList();
Finally, we will add the "No Price" tests to our result.
resultWithEmptyPrice.AddRange(emptyPrice);
I have this class:
public class RecipeLine
{
public List<string> PossibleNames { get; set; }
public string Name { get; set; }
public int Index { get; set; }
}
I have a list of multiple RecipeLine objects. For example, one of them looks like this:
Name: apple
PossibleNames: {red delicious, yellow delicious, ... }
Index = 3
I also have a table in my db which is called tblFruit and has 2 columns: name and id. the id isn't the same as the index in the class.
What I want to do is this:
for the whole list of RecipeLine objects, find all the records in tblFruit whose name is in PossibleNames, and give me back the index of the class and the id in the table. So we have a list in a list (a list of RecipeLine objects who have a list of strings). How can I do this with Linq in c#?
I'm pretty sure there isn't going to be a LINQ statement that you can construct for this that will create a SQL query to get the data exactly how you want. Assuming tblFruit doesn't have too much data, pull down the whole table and process it in memory with something like...
var result = tblFruitList.Select((f) => new {Id = f.id, Index = recipeLineList.Where((r) => r.PossibleNames.Contains(f.name)).Select((r) => r.Index).FirstOrDefault()});
Keeping in mind that Index will be 0 if there isn't a recipeLine with the tblFruit's name in it's PossibleNames list.
A more readable method that doesn't one-line it into a nasty linq statement is...
Class ResultItem {
int Index {get;set;}
int Id {get;set;}
}
IEnumerable<ResultItem> GetRecipeFruitList(IEnumerable<FruitItem> tblFruitList, IEnumerable<RecipeLine> recipeLineList) {
var result = new List<ResultItem>();
foreach (FruitItem fruitItem in tblFruitList) {
var match = recipeLineList.FirstOrDefault((r) => r.PossibleNames.Contains(fruitItem.Name));
if (match != null) {
result.Add(new ResultItem() {Index = match.Index, Id = fruitItem.Id});
}
}
return result;
}
If tblFruit has a lot of data you can try and pull down only those items that have a name in the RecipeLine list's of PossibleName lists with something like...
var allNames = recipeLineList.SelectMany((r) => r.PossibleNames).Distinct();
var tblFruitList = DbContext.tblFruit.Where((f) => allNames.Contains(f.Name));
To get all the fruits within your table whose Name is in PossibleNames use the following:
var query = myData.Where(x => myRecipeLines.SelectMany(y => y.PossibleNames).Contains(x.Name));
I don't think you can do this in a single step.
I would first create a map of the possible names to indexes:
var possibleNameToIndexMap = recipes
.SelectMany(r => r.PossibleNames.Select(possibleName => new { Index = r.Index, PossbileName = possibleName }))
.ToDictionary(x => x.PossbileName, x => x.Index);
Then, I would retrieve the matching names from the table:
var matchingNamesFromTable = TblFruits
.Where(fruit => possibleNameToIndexMap.Keys.Contains(fruit.Name))
.Select(fruit => fruit.Name);
Then you can use the names retrieved from the tables as keys into your original map:
var result = matchingNamesFromTable
.Select(name => new { Name = name, Index = possibleNameToIndexMap[name]});
Not fancy, but it should be easy to read and maintain.
I have this need to know how many rows have the same month from a table and I have no idea of how to do it. I thought I'd try some LINQ but I've never used it before so I don't even know if it's possible. Please help me out!
public ActionResult returTest()
{
ViewData["RowsWithSameMonth"] = // I'm guessing I can put some LINQ here?
var returer = from s in db2.ReturerDB select s;
return View(returer.ToList());
}
The ideal would be to get, maybe a two dimensional array with the month in the first cell and the amount of rows from the db in the second?
I'd like the result to be sort of :
string[,] statistics = new string[,]
{
{"2013-11", "5"},
{"2013-12", "10"},
{"2014-01", "3"}
};
Is this doable? Or should I just query the database and do a whole lot of stuff? I'm thinking that I can solve this on my own, but it would mean a lot of ugly code. Background: self taught C# developer at IT-company with 1 years experience of ugly codesmanship and no official degree of any kind.
EDIT
var returer = from s in db2.ReturerDB select s;
var dateRange = returer.ToList();
var groupedData = dateRange.GroupBy(dateRow => dateRow.ToString())
.OrderBy(monthGroup => monthGroup.Key)
.Select(monthGroup => new
{
Month = monthGroup.Key,
MountCount = monthGroup.Count()
});
string test01 = "";
string test02 = "";
foreach (var item in groupedData)
{
test01 = item.Month.ToString();
test02 = item.MountCount.ToString();
}
In debug, test01 is "Namespace.Models.ReturerDB" and test02 is "6" as was expected, or at least wanted. What am I doing wrong?
You can do this:
var groupedData = db2.ReturerDB.GroupBy(r => new { r.Date.Year, r.Date.Month })
.Select(g => new { g.Key.Year, g.Key.Month, Count = g.Count() })
.OrderBy(x => x.Year).ThenBy(x => x.Month);
.ToList();
var result = groupedData
.ToDictionary(g => string.Format("{0}-{1:00}", g.Year, g.Month),
g => g.Count);
Which will give you
Key Value
---------------
2013-11 5
2013-12 10
2014-01 3
(Creating a dictionary is slightly easier than a two-dimensional array)
This will work against a SQL back-end like entity framework of linq-to-sql, because the expressions r.Date.Year and r.Date.Month can be translated into SQL.
with a nod to mehrandvd, here is how you'd achieve this using linq method chain approach:
var dateRange = { // your base collection with the dates};
// make sure you change MyDateField to match your won datetime field
var groupedData = dateRange
.GroupBy(dateRow => dateRow.MyDateField.ToString("yyyy-mm"))
.OrderBy(monthGroup => monthGroup.Key)
.Select(monthGroup => new
{
Month = monthGroup.Key,
MountCount = monthGroup.Count()
});
This would give you the results you required, as per the OP.
[edit] - as requested, example of how to access the newly created anonymous type:
foreach (var item in groupedData)
{
Console.WriteLine(item.Month);
Console.WriteLine(item.MountCount);
}
OR, you could return the whole caboodle as a jsonresult to your client app and iterate inside that, i.e the final line of your view would be:
return Json(groupedData, JsonRequestBehavior.AllowGet);
hope this clarifies.
What you need is grouping.
Considering you have a list of dates a solution would be this:
var dateRows = // Get from database
var monthlyRows = from dateRow in dateRows
group dateRow by dateRow.ToString("yyyy/mm") into monthGroup
orderby monthGroup.Key
select new { Month=monthGroup.Key, MountCount=monthGroup.Count };
// Your results would be a list of objects which have `Month` and `MonthCount` properties.
// {Month="2014/01", MonthCount=24}
// {Month="2014/02", MonthCount=28}
I have applied IGrouping<> over a list - here's what it looks like:
IEnumerable<IGrouping<TierRequest,PingtreeNode>> Tiers
{
get { return ActiveNodes.GroupBy(x => new TierRequest(x.TierID, x.TierTimeout, x.TierMaxRequests)); }
}
Later in my code I iterate over Tiers. Its simple to get the key data using the Key element, but how do I get the IEnumerable<PingtreeNode> that forms the value part?
Thanks in advance
Tiers.Select(group => group.Select(element => ...));
in foreach you can get values like this
foreach(var group in tiers)
{
TierRequest key = group.Key;
PingtreeNode[] values = group.ToArray();
}
The group itself implements IEnumerable<T> and can be iterated over, or used with linq methods.
var firstGroup = Tiers.First();
foreach(var item in firstGroup)
{
item.DoSomething();
}
// or using linq:
firstGroup.Select(item => item.ToString());
// or if you want to iterate over all items at once (kind of unwinds
// the grouping):
var itemNames = Tiers.SelectMany(g => g.ToString()).ToList();