Is there a way to preserve the order after this linq expression?
var results =
DateList
.GroupBy(x => x.Date.Subtract(firstDay).Days / 7 + 1)
.SelectMany(gx => gx, (gx, x) => new {Week = gx.Key,DateTime =x,Count = gx.Count(),});
I found this Preserving order with LINQ , but I'm not sure if its the GroupBy or SelectMany casing the issues
Yes, if you first select your DateList and combine it with an index, using an overload of .Select that uses a delegate with a second (int) parameter that is called with the index of the items from the sequence :
DateList
.Select((dateTime, idx) => new {dateTime, idx})
.GroupBy(x => x.dateTime.Date.Subtract(firstDay).Days / 7 + 1)
...and persist the value through the linq chain
.SelectMany(gx => gx, (gx, x) => new {Week = gx.Key,
DateTime = x.dateTime,
Count = gx.Count(),
x.idx})
...then use it to re-order the output
.OrderBy(x => x.idx)
...and strip it from your final selection
.Select(x => new {x.Week, x.DateTime, x.Count});
then you can maintain the same order as the original list.
Solution of #spender is good, but can it be done without OrderBy? It can, because we can use the index for direct indexing into array, but it would not be one linq query:
var resultsTmp =
DateList.Select((d, i) => new { d, i })
.GroupBy(x => x.d.Date.Subtract(firstDay).Days / 7 + 1)
.SelectMany(gx => gx, (gx, x) => new { Week = gx.Key, DateTime = x.d, Count = gx.Count(), x.i })
.ToArray();
var resultsTmp2 = resultsTmp.ToArray();
foreach (var r in resultsTmp) { resultsTmp2[r.i] = r; };
var results = resultsTmp2.Select(r => new { r.Week, r.DateTime, r.Count });
It looks a bit complex. I would probably do something more straightforward like:
var DateList2 = DateList.Select(d => new { DateTime = d, Week = d.Subtract(firstDay).Days / 7 + 1 }).ToArray();
var weeks = DateList2.GroupBy(d => d.Week).ToDictionary(k => k.Key, v => v.Count());
var results = DateList2.Select(d2 => new { d2.Week, d2.DateTime, Count = weeks[d2.Week] });
Related
I want to make a ranking from a list and output it on original order.
This is my code so far:
var data = new[] { 7.806468478, 7.806468478, 7.806468478, 7.173501754, 7.173501754, 7.173501754, 3.40877696, 3.40877696, 3.40877696,
4.097010736, 4.097010736, 4.097010736, 4.036494085, 4.036494085, 4.036494085, 38.94333318, 38.94333318, 38.94333318, 14.43588131, 14.43588131, 14.43588131 };
var rankings = data.OrderByDescending(x => x)
.GroupBy(x => x)
.SelectMany((g, i) =>
g.Select(e => new { Col1 = e, Rank = i + 1 }))
.ToList();
However, the result will be order it from descending:
What I want is to display by its original order.
e.g.: Rank = 3, Rank = 3, Rank = 3, Rank = 4, Rank = 4, Rank = 4, etc...
Thank You.
Using what you have, one method would be to keep track of the original order and sort a second time (ugly and potentially slow):
var rankings = data.Select((x, i) => new {Item = x, Index = i})
.OrderByDescending(x => x.Item)
.GroupBy(x => x.Item)
.SelectMany((g, i) =>
g.Select(e => new {
Index = e.Index,
Item = new { Col1 = e.Item, Rank = i + 1 }
}))
.OrderBy(x => x.Index)
.Select(x => x.Item)
.ToList();
I would instead suggest creating a dictionary with your rankings and joining this back with your list:
var rankings = data.Distinct()
.OrderByDescending(x => x)
.Select((g, i) => new { Key = g, Rank = i + 1 })
.ToDictionary(x => x.Key, x => x.Rank);
var output = data.Select(x => new { Col1 = x, Rank = rankings[x] })
.ToList();
As #AntonínLejsek kindly pointed out, replacing the above GroupBy call with Distinct() is the way to go.
Note doubles are not a precise type and thus are really not a good candidate for values in a lookup table, nor would I recommend using GroupBy/Distinct with a floating-point value as a key. Be mindful of your precision and consider using an appropriate string conversion. In light of this, you may want to define an epsilon value and forgo LINQ's GroupBy entirely, opting instead to encapsulate each data point into a (non-anonymous) reference type, then loop through a sorted list and assign ranks. For example (disclaimer: untested):
class DataPoint
{
decimal Value { get; set; }
int Rank { get; set; }
}
var dataPointsPreservingOrder = data.Select(x => new DataPoint {Value = x}).ToList();
var sortedDescending = dataPointsPreservingOrder.OrderByDescending(x => x.Value).ToList();
var epsilon = 1E-15; //use a value that makes sense here
int rank = 0;
double? currentValue = null;
foreach(var x in sortedDescending)
{
if(currentValue == null || Math.Abs(x.Value - currentValue.Value) > epsilon)
{
currentValue = x.Value;
++rank;
}
x.Rank = rank;
}
From review of the data you will need to iterate twice over the result set.
The first iteration will be to capture the rankings as.
var sorted = data
.OrderByDescending(x => x)
.GroupBy(x => x)
.Select((g, i) => new { Col1 = g.First(), Rank = i + 1 })
.ToList();
Now we have a ranking of highest to lowest with the correct rank value. Next we iterate the data again to find where the value exists in the overall ranks as:
var rankings = (from i in data
let rank = sorted.First(x => x.Col1 == i)
select new
{
Col1 = i,
Rank = rank.Rank
}).ToList();
This results in a ranked list in the original order of the data.
A bit shorter:
var L = data.Distinct().ToList(); // because SortedSet<T> doesn't have BinarySearch :[
L.Sort();
var rankings = Array.ConvertAll(data,
x => new { Col1 = x, Rank = L.Count - L.BinarySearch(x) });
I have a simple list
var list =
AppUtils.db.GetDataTable("dbo.RankSelectChart", view) // stopred procedure and getting datatable
.AsEnumerable()
.Select(i => new
{
Date = i.Field<DateTime>("lastDatetime"),
P1 = i.Field<decimal>("p1"),
P2 = i.Field<decimal>("P2"),
P3..... P(n)
}
)
.ToList()
.OrderBy(x => x.Date);
than i want to get a list of lists or dictionary like List<Dictionary<Datetime, Decimal>> means Dictionary<Date, P1> .... Dictionary<Date, P(n)>
how to write algorithm which is not depend how many P we have
As it stands, you will need to use reflection to access the properties:
var result = new[]{"P1", "P2", "P3", ...}.Select(p => list.ToDictionary(
i => i.Date,
i => i.GetType().GetProperty(p).GetValue(i)));
However, if you could avoid creating your list in the first place and just pull from the data table directly, it may be easier.
var dt = AppUtils.db.GetDataTable("dbo.RankSelectChart", view); // stopred procedure and getting datatable
var pColumns = dt.Columns.Cast<DataColumn>()
.Where(c => c.ColumnName.StartsWith("p"));
var result = pColumns
.Select(p => dt.AsEnumerable().ToDictionary(
i => i.Field<DateTime>("lastDatetime"),
i => i.Field<DateTime>(p.ColumnName)))
.ToList();
If 'Date' is unique for all records in 'list', then you can use reflection to get the P(i) value for a record in list. Like so:
// build sample data
var list = Enumerable.Range(0, 10)
.ToList()
.Select(x => new {
Date = DateTime.Now.AddMinutes(x),
P1 = new Decimal(x),
P2 = new Decimal(x + 1),
P3 = new Decimal(x + 2)
})
.ToList();
// list partionned by date; assumes that Date is unique in list
List<Dictionary<DateTime, Decimal>> partitionedList;
if (list.Count == 0) {
partitionedList = new List<Dictionary<DateTime, Decimal>>();
} else {
var n = 3;
var listElementType = list[0].GetType();
partitionedList = Enumerable.Range(1, n)
.Select(x => {
var prop = listElementType.GetProperty("P" + x);
var pList = list.ToDictionary(
ll => ll.Date,
ll => (Decimal)prop.GetValue(ll));
return pList;
})
.ToList();
}
If 'Date' is not unique, then it cannot be the key to a dictionary and the desired data structure is not achievable.
I have a list of strings which contain X in them. I want to select list(s) with the minimum count of X in them. For example:
CountMin("AXBXX", "AAX") will return AAX.
How can I write this qith LINQ in a concise way ?
public static string CountMin(IList<string> inputList)
{
if (inputList == null || !inputList.Any()) return null;
var result = inputList.Select(s => new
{
Item = s,
Count => s.Count(ch => ch == 'X')
})
.OrderBy(item => item.Count).First().Item;
}
Snippet assumes that all elements on list are different to null. If you need it, it could be easily improved.
You can also omit temporary class:
inputList.OrderBy(s => s.Count(c => c == 'X')).First();
string[] list = {"AXBXX", "AAX", "AXX"};
string result = (from word in list
select new { word, wordLen = (word.Length - (word.Replace("X", "")).Length) })
.OrderBy(x => x.wordLen).First().word;
MessageBox.Show(result);
Here's an answer that will get you all of the minimum X strings from the list.
var listOfStrings = new List<string>()
{
"AXB",
"ABXXC",
"ABX",
};
var minimumXs =
listOfStrings
.GroupBy(x => x.Count(y => y == 'X'))
.OrderBy(x => x.Key)
.Take(1)
.SelectMany(x => x);
That gives me:
AXB
ABX
I would like to do a group by and on that a sum and a count. I don't seem to be able to create the solution in linq. How can I convert my query to linq?
SELECT HistoricalBillingProductGroup,
COUNT(*),
BillingPeriod,
SUM(TotalMonthlyChargesOtcAndMrc)
FROM [x].[dbo].[tblReport]
group by BillingPeriod, HistoricalBillingProductGroup
order by BillingPeriod
This is what I got sofar in Linq
var result =
context.Reports.GroupBy(x => new {x.BillingPeriod, x.HistoricalBillingProductGroup})
.Select(x => new StatisticsReportLine
{
HistoricalBillingGroup = x.FirstOrDefault().HistoricalBillingProductGroup,
BillingPeriod = x.FirstOrDefault().BillingPeriod,
CountOfRows = x.Count(),
SumOfAmount = x.Sum(p => p.TotalMonthlyChargesOtcAndMrc) ?? 0
})
.ToString();
The query I get from this is enormous and takes a very long time to load. In SQL its a matter of milliseconds. I hardly doubt this is the solution.
I believe the calls to x.FirstOrDefault() are the source of your problem. Each one of these will result in a very costly inner query inside the SELECT clause of the generated SQL.
Try using the Key property of the IGrouping<T> instead :
var result = context.Reports
.GroupBy(x => new {x.BillingPeriod, x.HistoricalBillingProductGroup})
.OrderBy(x => x.Key.BillingPeriod)
.Select(x => new StatisticsReportLine
{
HistoricalBillingProductGroup = x.Key.HistoricalBillingProductGroup,
BillingPeriod = x.Key.BillingPeriod,
CountOfRows = x.Count(),
SumOfAmount = x.Sum(p => p.TotalMonthlyChargesOtcAndMrc) ?? 0
});
Or if you prefer query syntax:
var result =
(from r in context.Reports
group r by new { r.BillingPeriod, r.HistoricalBillingProductGroup } into g
orderby g.Key.BillingPeriod
select new StatisticsReportLine
{
HistoricalBillingProductGroup = g.Key.HistoricalBillingProductGroup,
BillingPeriod = g.Key.BillingPeriod,
CountOfRows = g.Count(),
SumOfAmount = x.Sum(p => p.TotalMonthlyChargesOtcAndMrc) ?? 0
});
You could try this one:
var result = context.Reports
.GroupBy(x => new {x.BillingPeriod, x.HistoricalBillingProductGroup})
.Select(x => new StatisticsReportLine
{
HistoricalBillingGroup = x.Key.HistoricalBillingProductGroup,
BillingPeriod = x.Key.BillingPeriod,
CountOfRows = x.Count(),
SumOfAmount = x.Sum(p => p.TotalMonthlyChargesOtcAndMrc) ?? 0
}).ToString();
In the above query you make a group by on two properties, BillingPeriod and HistoricalBillingProductGroup. So in each group that will be created, you will have a key, that will be consisted by these two properties.
// To get current user id
var currentUsrId = Convert.ToInt16(Membership.GetUser().ProviderUserKey);
var IDquery = My_usr_contacts_requests.Where(i => i.requests_to_usr_id == currentUsrId)
.Select(i => new { i.from_usr_id })
.ToArray();
var mainquery = My_usr_biographic_details
.Join(
My_usr_profiles_companies, i => i.usr_id, j => j.company_usr_id,
(i, j) => new
{
usr_id = j.company_usr_id,
})
.Where(i =>IDquery.contains(i.usr_id))
.ToArray();
while I am executing I am getting array of values in IDquery, and I want to use these values in the where condition of mainquery as shown above, but when I give like this it showing error at the mainquery where condition. Can you tell me how to use the array of values retrieved from IDquery in mainquery?
You wrote:
var IDquery = dbContext.My_usr_contacts_requests.Where(i =>
i.Usr_contacts_requests_to_usr_id==currentUsrId).Select(i => new {
i.Usr_contacts_requests_from_usr_id }).ToArray();
The last new is unnecessary. It creates an array of arrays. You need to change it to:
var IDquery = dbContext.My_usr_contacts_requests.Where(i =>
i.Usr_contacts_requests_to_usr_id==currentUsrId).Select(i =>
i.Usr_contacts_requests_from_usr_id).ToArray();