I have a list that contains Categories and their accept/reject counts but there is a problem with this list. I'm using a LINQ query to access the data, and I grouped them by both category name and Accept/Reject Code(ResultCode). So the data is in this form:
Almost all of the Categories have both AP counts and RJ counts. And what I'm trying to do is to show each Category's accept and reject count. What should I use? Hashtables don't fit in this problem, I tried Dictionary with int List as value but couldn't add when the same key appeared.
UPDATE:
List<ModReportingDM.ReportObjects.AllCategories> allcats = new List<ModReportingDM.ReportObjects.AllCategories>();
Dictionary<string, ModReportingDM.ReportObjects.ResultCode> dict = new Dictionary<string, ModReportingDM.ReportObjects.ResultCode>();
ModReportingDM.ReportObjects.ResultCode x = new ModReportingDM.ReportObjects.ResultCode();
allcats = reportBLL.GetAllCats(model.ModId, model.ReportStartDate, model.ReportEndDate);
if (allcats != null)
{
model.AllCatsList = new List<ModReportingDM.ReportObjects.AllCategories>();
foreach (var item in allcats)
{
x.Accepted = item.Count;
x.Rejected = item.Count;
dict.Add(item.Category, x);
}
}
Query:
public List<AllCategories> GetAllCats(int modId, DateTime startDate, DateTime endDate)
{
using (entities = new ModReportingEntities())
{
var query = (from c in entities.Content
where c.ModId == modId && c.CreatedTime >= startDate && c.CreatedTime <= endDate && c.Category != null
group c by new { c.Category, c.ResultCode } into g
orderby g.Count() ascending
select new AllCategories
{
Category = g.Key.Category,
ResultCode = g.Key.ResultCode,
AcceptCount = g.Count(),
RejectCount = g.Count()
});
return query.ToList();
}
}
What i would do is create a ResultCode class:
public class ResultCode
{
public int Ap { get; set; }
public int Rj { get; set; }
}
and then use a Dictionary<string, ResultCode> which maps each category to its report.
You could also take a different approach using a Tuple<T1, T2> (which personally i like less) which simply maps your key to two distinct values:
Dictionary<string, Tuple<int, int>> categoryToResultCode;
List<Tuple<string, string, int>> listOfTuples = new List<Tuple<string, string, int>>();
Tuple<string, string, int> tupleItem1 = new Tuple<string, string, int>("A", "AP", 1);
listOfTuples.Add(tupleItem1);
You can use Tuple. Please refer http://msdn.microsoft.com/en-us/library/system.tuple%28v=vs.110%29.aspx
My colleague and I realized that we can keep track of the Category's and if same Category occurs, it means that only a field of it should be changed(either AcceptCount or RejectCount). So we've created a lambda expression like this:
foreach(var item in allcats)
{
if (model.AllCategories.Select(m => m).Where(x => x.Category == item.Category).ToList().Count == 0)
{
if (item.ResultCode == "AP") {
model.AllCategories.Add(new ModReportingDM.ReportObjects.AllCategories()
{
Category = item.Category,
AcceptCount = item.Count
});
}
else
{
model.AllCategories.Add(new ModReportingDM.ReportObjects.AllCategories()
{
Category = item.Category,
RejectCount = item.Count
});
}
}
else
{
ModReportingDM.ReportObjects.AllCategories x = model.AllCategories.Select(n => n).Where(y => y.Category == item.Category).ToList().First();
if (item.ResultCode == "AP")
{
x.AcceptCount = item.Count;
}
else
{
x.RejectCount = item.Count;
}
}
}
If same Category occurs, go ahead and change its AcceptCount or RejectCount accordingly. That's the way I solved the problem.
Related
I have a LINQ statement which returns an IQueryable of the class below.
Class:
public class SupplierSummaryReport {
public int Year { get; set; }
public string SupplierName { get; set; }
public decimal TurnOverValues { get; set; }
}
Eg: {2012,Supplier1,90},{2011,Supplier2,95}
However, I then need to convert this data into a Dictionary of Dictionaries. I have the extension method which me and a friend have built, however we are stumped at the final section.
Extension Method:
public static Dictionary<TFirstKey, Dictionary<TSecondKey, TValue>> Pivot<TSource, TFirstKey, TSecondKey, TValue>(this IEnumerable<TSource> source, Func<TSource, TFirstKey> firstKeySelector, Func<TSource, TSecondKey> secondKeySelector, Func<TSource, TValue> Value) {
var retVal = new Dictionary<TFirstKey, Dictionary<TSecondKey, TValue>>();
var l = source.ToLookup(firstKeySelector);
foreach (var item in l) {
var dict = new Dictionary<TSecondKey, TValue>();
retVal.Add(item.Key, dict);
var subdict = item.ToLookup(secondKeySelector);
foreach (var subitem in subdict) {
dict.Add(subitem.Key, subitem.Value /*Insert value here*/);
}
}
return retVal;
}
We call the method like so:
public Dictionary<string,Dictionary<int,decimal>> GetSupplierSummaryReportData(List<int> last5Years) {
_ctx.Database.CommandTimeout = 5 * 60;
//values - Gets the data required for the Supplier summary report in a hierachical order. E.g - {2012,Supplier1,3},{2011,Supplier1,4}
var values = (from i in _ctx.Invoices
where i.Turnover == true
group i by new { i.AccountName, i.InvoiceDate.Year } into summ
select new APData.Audit.Models.ReportModels.SupplierSummaryReport {
Year = summ.Key.Year,
SupplierName = summ.Key.AccountName,
TurnOverValues = summ.Sum(r => r.VATAmount_Home ?? 0)
});
//accounts - Get all the account names
var accounts = (from i in _ctx.Invoices
where i.Turnover == true
group i by new { i.AccountName } into summ
select new {
summ.Key.AccountName
});
/*crossJoin - select all SupplierNames from accounts and all years from last5Years and assign each SupplierName the last 5 years. Assign each turnover value null for each year.
This is in preparation for the cross join as not all suppliers will have the last 5 year in data */
var crossJoin = accounts.SelectMany(a => last5Years, (a, y) => new APData.Audit.Models.ReportModels.SupplierSummaryReport {
Year = y,
SupplierName = a.AccountName,
TurnOverValues = 0
});
/*Join crossJoin and values together, wherever the join is empty, assign the cross join values. If not assign the turnover value from a*/
var result =
(from cj in crossJoin
join v in values
on new { cj.Year, cj.SupplierName }
equals new { v.Year, v.SupplierName } into lJoin
from a in lJoin.DefaultIfEmpty(new APData.Audit.Models.ReportModels.SupplierSummaryReport {
Year = cj.Year,
SupplierName = cj.SupplierName,
TurnOverValues = cj.TurnOverValues
})
select new APData.Audit.Models.ReportModels.SupplierSummaryReport {
Year = cj.Year,
SupplierName = cj.SupplierName,
TurnOverValues = a.TurnOverValues
}).OrderBy(r => r.SupplierName).ThenBy(r => r.Year);
return result.Pivot(r => r.SupplierName, c => c.Year, y => y.TurnOverValues);
We need to insert the decimal value of TurnOverValues as the third item in the dictionary.
Expected outcome:
{Supplier1, {2012,60}}, {Supplier2, {2014,90}}
If anyone needs anymore information, please let me know.
TL;DR, but if what you have is an IEnumerable<SupplierSummaryReport> and you're sure that each SupplierName, Year, TurnOver combination is unique or can be made unique then ToDictionary makes this easy:
var data = new List<SupplierSummaryReport> { ... };
// avoiding var just to verify correct result Type
Dictionary<string, Dictionary<int, decimal>> result = data
.GroupBy(ssr => ssr.SupplierName)
.ToDictionary(g1 => g1.Key, g1 => (
g1.GroupBy(g2 => g2.Year)
.ToDictionary(g3 => g3.Key,
g3 => g3.First().TurnOverValues))
);
Maybe the g3.First() should become .Single() or .Sum() or something.
I have the below class:
public class FactoryOrder
{
public string Text { get; set; }
public int OrderNo { get; set; }
}
and collection holding the list of FactoryOrders
List<FactoryOrder>()
here is the sample data
FactoryOrder("Apple",20)
FactoryOrder("Orange",21)
FactoryOrder("WaterMelon",42)
FactoryOrder("JackFruit",51)
FactoryOrder("Grapes",71)
FactoryOrder("mango",72)
FactoryOrder("Cherry",73)
My requirement is to merge the Text of FactoryOrders where orderNo are in sequence and retain the lower orderNo for the merged FactoryOrder
- so the resulting output will be
FactoryOrder("Apple Orange",20) //Merged Apple and Orange and retained Lower OrderNo 20
FactoryOrder("WaterMelon",42)
FactoryOrder("JackFruit",51)
FactoryOrder("Grapes mango Cherry",71)//Merged Grapes,Mango,cherry and retained Lower OrderNo 71
I am new to Linq so not sure how to go about this. Any help or pointers would be appreciated
As commented, if your logic depends on consecutive items so heavily LINQ is not the easiest appoach. Use a simple loop.
You could order them first with LINQ: orders.OrderBy(x => x.OrderNo )
var consecutiveOrdernoGroups = new List<List<FactoryOrder>> { new List<FactoryOrder>() };
FactoryOrder lastOrder = null;
foreach (FactoryOrder order in orders.OrderBy(o => o.OrderNo))
{
if (lastOrder == null || lastOrder.OrderNo == order.OrderNo - 1)
consecutiveOrdernoGroups.Last().Add(order);
else
consecutiveOrdernoGroups.Add(new List<FactoryOrder> { order });
lastOrder = order;
}
Now you just need to build the list of FactoryOrder with the joined names for every group. This is where LINQ and String.Join can come in handy:
orders = consecutiveOrdernoGroups
.Select(list => new FactoryOrder
{
Text = String.Join(" ", list.Select(o => o.Text)),
OrderNo = list.First().OrderNo // is the minimum number
})
.ToList();
Result with your sample:
I'm not sure this can be done using a single comprehensible LINQ expression. What would work is a simple enumeration:
private static IEnumerable<FactoryOrder> Merge(IEnumerable<FactoryOrder> orders)
{
var enumerator = orders.OrderBy(x => x.OrderNo).GetEnumerator();
FactoryOrder previousOrder = null;
FactoryOrder mergedOrder = null;
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (mergedOrder == null)
{
mergedOrder = new FactoryOrder(current.Text, current.OrderNo);
}
else
{
if (current.OrderNo == previousOrder.OrderNo + 1)
{
mergedOrder.Text += current.Text;
}
else
{
yield return mergedOrder;
mergedOrder = new FactoryOrder(current.Text, current.OrderNo);
}
}
previousOrder = current;
}
if (mergedOrder != null)
yield return mergedOrder;
}
This assumes FactoryOrder has a constructor accepting Text and OrderNo.
Linq implementation using side effects:
var groupId = 0;
var previous = Int32.MinValue;
var grouped = GetItems()
.OrderBy(x => x.OrderNo)
.Select(x =>
{
var #group = x.OrderNo != previous + 1 ? (groupId = x.OrderNo) : groupId;
previous = x.OrderNo;
return new
{
GroupId = group,
Item = x
};
})
.GroupBy(x => x.GroupId)
.Select(x => new FactoryOrder(
String.Join(" ", x.Select(y => y.Item.Text).ToArray()),
x.Key))
.ToArray();
foreach (var item in grouped)
{
Console.WriteLine(item.Text + "\t" + item.OrderNo);
}
output:
Apple Orange 20
WaterMelon 42
JackFruit 51
Grapes mango Cherry 71
Or, eliminate the side effects by using a generator extension method
public static class IEnumerableExtensions
{
public static IEnumerable<IList<T>> MakeSets<T>(this IEnumerable<T> items, Func<T, T, bool> areInSameGroup)
{
var result = new List<T>();
foreach (var item in items)
{
if (!result.Any() || areInSameGroup(result[result.Count - 1], item))
{
result.Add(item);
continue;
}
yield return result;
result = new List<T> { item };
}
if (result.Any())
{
yield return result;
}
}
}
and your implementation becomes
var grouped = GetItems()
.OrderBy(x => x.OrderNo)
.MakeSets((prev, next) => next.OrderNo == prev.OrderNo + 1)
.Select(x => new FactoryOrder(
String.Join(" ", x.Select(y => y.Text).ToArray()),
x.First().OrderNo))
.ToList();
foreach (var item in grouped)
{
Console.WriteLine(item.Text + "\t" + item.OrderNo);
}
The output is the same but the code is easier to follow and maintain.
LINQ + sequential processing = Aggregate.
It's not said though that using Aggregate is always the best option. Sequential processing in a for(each) loop usually makes for better readable code (see Tim's answer). Anyway, here's a pure LINQ solution.
It loops through the orders and first collects them in a dictionary having the first Id of consecutive orders as Key, and a collection of orders as Value. Then it produces a result using string.Join:
Class:
class FactoryOrder
{
public FactoryOrder(int id, string name)
{
this.Id = id;
this.Name = name;
}
public int Id { get; set; }
public string Name { get; set; }
}
The program:
IEnumerable<FactoryOrder> orders =
new[]
{
new FactoryOrder(20, "Apple"),
new FactoryOrder(21, "Orange"),
new FactoryOrder(22, "Pear"),
new FactoryOrder(42, "WaterMelon"),
new FactoryOrder(51, "JackFruit"),
new FactoryOrder(71, "Grapes"),
new FactoryOrder(72, "Mango"),
new FactoryOrder(73, "Cherry"),
};
var result = orders.OrderBy(t => t.Id).Aggregate(new Dictionary<int, List<FactoryOrder>>(),
(dir, curr) =>
{
var prevId = dir.SelectMany(d => d.Value.Select(v => v.Id))
.OrderBy(i => i).DefaultIfEmpty(-1)
.LastOrDefault();
var newKey = dir.Select(d => d.Key).OrderBy(i => i).LastOrDefault();
if (prevId == -1 || curr.Id - prevId > 1)
{
newKey = curr.Id;
}
if (!dir.ContainsKey(newKey))
{
dir[newKey] = new List<FactoryOrder>();
}
dir[newKey].Add(curr);
return dir;
}, c => c)
.Select(t => new
{
t.Key,
Items = string.Join(" ", t.Value.Select(v => v.Name))
}).ToList();
As you see, it's not really straightforward what happens here, and chances are that it performs badly when there are "many" items, because the growing dictionary is accessed over and over again.
Which is a long-winded way to say: don't use Aggregate.
Just coded a method, it's compact and quite good in terms of performance :
static List<FactoryOrder> MergeValues(List<FactoryOrder> dirtyList)
{
FactoryOrder[] temp1 = dirtyList.ToArray();
int index = -1;
for (int i = 1; i < temp1.Length; i++)
{
if (temp1[i].OrderNo - temp1[i - 1].OrderNo != 1) { index = -1; continue; }
if(index == -1 ) index = dirtyList.IndexOf(temp1[i - 1]);
dirtyList[index].Text += " " + temp1[i].Text;
dirtyList.Remove(temp1[i]);
}
return dirtyList;
}
I created a Web Api in VS 2012.
I am trying to get all the value from one column "Category", that is all the unique value, I don't want the list to be returned with duplicates.
I used this code to get products in a particular category. How do I get a full list of categories (All the unique values in the Category Column)?
public IEnumerable<Product> GetProductsByCategory(string category)
{
return repository.GetAllProducts().Where(
p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));
}
To have unique Categories:
var uniqueCategories = repository.GetAllProducts()
.Select(p => p.Category)
.Distinct();
var uniq = allvalues.GroupBy(x => x.Id).Select(y=>y.First()).Distinct();
Easy and simple
I have to find distinct rows with the following details
class : Scountry
columns: countryID, countryName,isactive
There is no primary key in this. I have succeeded with the followin queries
public DbSet<SCountry> country { get; set; }
public List<SCountry> DoDistinct()
{
var query = (from m in country group m by new { m.CountryID, m.CountryName, m.isactive } into mygroup select mygroup.FirstOrDefault()).Distinct();
var Countries = query.ToList().Select(m => new SCountry { CountryID = m.CountryID, CountryName = m.CountryName, isactive = m.isactive }).ToList();
return Countries;
}
Interestingly enough I tried both of these in LinqPad and the variant using group from Dmitry Gribkov by appears to be quicker. (also the final distinct is not required as the result is already distinct.
My (somewhat simple) code was:
public class Pair
{
public int id {get;set;}
public string Arb {get;set;}
}
void Main()
{
var theList = new List<Pair>();
var randomiser = new Random();
for (int count = 1; count < 10000; count++)
{
theList.Add(new Pair
{
id = randomiser.Next(1, 50),
Arb = "not used"
});
}
var timer = new Stopwatch();
timer.Start();
var distinct = theList.GroupBy(c => c.id).Select(p => p.First().id);
timer.Stop();
Debug.WriteLine(timer.Elapsed);
timer.Start();
var otherDistinct = theList.Select(p => p.id).Distinct();
timer.Stop();
Debug.WriteLine(timer.Elapsed);
}
i want to group by multiple columns in a datatable by linq query.
i tried like this,
var _result = from row in tbl.AsEnumerable()
group row by new
{
id=row.Field<object>(_strMapColumn),
value=row.Field<object>(_strValueColumn),
} into g
select new
{
_strMapColumn = g.Key.id,
ToolTip = g.Sum(r => grp.Sum(r => r.Field<Double>(__strToolTip[1]))),
};
its works fine. my question is i have 10 column names in a strToolTip array i want to access 10 column names dynamically like for loop is it possible?
i want like this
select new
{_strMapColumn = g.Key.id,
for(int index = 1; index <= 10; index++)
{
ToolTip+index = g.Sum(r => getDoubleValue(r.Field<Double>(__strToolTip[1])))
}
};
and also want to add a DataType Dynamically please kindly provide the answer for solve this.
linq query is new for me.
You could group by a Dictionary and pass a custom comparer:
public class MyComparer : IEqualityComparer<Dictionary<string, object>> {
public bool Equals(Dictionary<string, object> a, Dictionary<string, object> b) {
if (a == b) { return true; }
if (a == null || b == null || a.Count != b.Count) { return false; }
return !a.Except(b).Any();
}
}
IEnumerable<string> columnsToGroupBy = ...
var rows = tbl.AsEnumerable();
var grouped = rows.GroupBy(r => columnsToGroupBy.ToDictionary(c => c, c => r[c]), new MyComparer());
var result = grouped.Select(g => {
// whatever logic you want with each grouping
var id = g.Key["id"];
var sum = g.Sum(r => r.Field<int>("someCol"));
});
Thanks to ChaseMedallion, I got dynamic grouping working.
Equals method was not enough, I had to add GetHashCode to MyComparer as well:
public int GetHashCode(Dictionary<string, object> a)
{
return a.ToString().ToLower().GetHashCode();
}
I'm looking to fill an object model with the count of a linq-to-sql query that groups by its key.
The object model looks somewhat like this:
public class MyCountModel()
{
int CountSomeByte1 { get; set; }
int CountSomeByte2 { get; set; }
int CountSomeByte3 { get; set; }
int CountSomeByte4 { get; set; }
int CountSomeByte5 { get; set; }
int CountSomeByte6 { get; set; }
}
This is what I have for the query:
var TheQuery = from x in MyDC.TheTable
where ListOfRecordIDs.Contains(x.RecordID) && x.SomeByte < 7
group x by x.SomeByte into TheCount
select new MyCountModel()
{
CountSomeByte1 = TheCount.Where(TheCount => TheCount.Key == 1)
.Select(TheCount).Count(),
CountSomeByte2 = TheCount.Where(TheCount => TheCount.Key == 2)
.Select(TheCount).Count(),
.....
CountSomeByte6 = TheCount.Where(TheCount => TheCount.Key == 6)
.Select(TheCount).Count(),
}.Single();
ListOfRecordIDs is list of longs that's passed in as a parameter. All the CountSomeByteN are underlined red. How do you do a count of grouped elements with the group's key mapped to an object model?
Thanks for your suggestions.
The select is taking each element of your group and projecting them to identical newly created MyCountModels, and you're only using one of them. Here's how I'd do it:
var dict = MyDC.TheTable
.Where(x => ListOfRecordIDs.Contains(x.RecordID) && x.SomeByte < 7)
.GroupBy(x => x.SomeByte)
.ToDictionary(grp => grp.Key, grp => grp.Count());
var result = new MyCountModel()
{
CountSomeByte1 = dict[1];
CountSomeByte2 = dict[2];
CountSomeByte3 = dict[3];
CountSomeByte4 = dict[4];
CountSomeByte5 = dict[5];
CountSomeByte6 = dict[6];
}
EDIT: Here's one way to do it in one statement. It uses an extension method called Into, which basically works as x.Into(f) == f(x). In this context, it can be viewed as like a Select that works on the whole enumerable rather than on its members. I find it handy for eliminating temporary variables in this sort of situation, and if I were to write this in one statement, it's probably how I'd do it:
public static U Into<T, U>(this T self, Func<T, U> func)
{
return func(self);
}
var result = MyDC.TheTable
.Where(x => ListOfRecordIDs.Contains(x.RecordID) && x.SomeByte < 7)
.GroupBy(x => x.SomeByte)
.ToDictionary(grp => grp.Key, grp => grp.Count())
.Into(dict => new MyCountModel()
{
CountSomeByte1 = dict[1];
CountSomeByte2 = dict[2];
CountSomeByte3 = dict[3];
CountSomeByte4 = dict[4];
CountSomeByte5 = dict[5];
CountSomeByte6 = dict[6];
});
Your range variable is not correct in the subqueries:
CountSomeByte6 = TheCount.Where(TheCount => TheCount.Key == 6)
.Select(TheCount).Count(),
In method notation you don't need the extra select:
CountSomeByte6 = TheCount.Where(theCount => theCount.Key == 6).Count(),
If you want to use it anyway:
CountSomeByte6 = TheCount.Where(theCount => theCount.Key == 6).Select(theCount => theCount).Count(),