I'm sure there is an easy answer to this, but I can't seem to phrase the search to get the right results.
In my controller, lets say I have a list of instances of class x, which in turn has a member variable of class y, which contains a date variable. What I am trying to do is count how many instances of each date there are, to build a graph. So all I want out of this is an array with 1 row for each different date, and a count of the number of times that date occurred.
Any suggestions on the best way to do this would be appreciated.
It sounds like you want something like this.
var countByDate = items.GroupBy(x => x.Invoice.ReceivedDate) // Or whatever
.Select(g => new { Date = g.Key, Count = g.Count() })
.OrderBy(pair => pair.Date);
.ToArray();
LINQ rocks :)
You can use Linq's Enumerable.GroupBy:
var dayGroups = listX.GroupBy(x => x.Y.DateTimeVar.Date)
.Select(g => new { Day = g.Key, Count = g.Count() })
.ToArray();
Now you have all you need, the date and the occurence:
foreach(var dayGroup in dayGroups)
{
Console.WriteLine("Day: {0} Count: {1}", dayGroup.Day.ToString(), dayGroup.Count);
}
Assuming DateTimeVar is the property and you want to group by the day.
Using extensions methods should help. This is the best I can do without knowing the types you have:
ListOfx.Select(x => x.ClassY.Date)
The previous line will give you all dates. Then you can use GroupBy to group them by their value. This should give you a list of lists.
ListOfx.Select(x => x.ClassY.Date).GroupBy(x => x.Date)
I think this should work. I cannot try the code at the moment.
The code might be clunky but this should work for you:
using System;
using System.Collections.Generic;
using System.Linq;
namespace App
{
class Program
{
static void Main(string[] args)
{
List<X> xs = new List<X>
{
new X { Y = new Y {D = DateTime.Now}},
new X { Y = new Y {D = DateTime.Now}},
new X { Y = new Y {D = DateTime.Now}},
};
IEnumerable<DateTime> ds = xs.Select(x => x.Y.D).Distinct();
var q = from d in ds
select new
{
D = d,
Count = xs.Count(x => x.Y.D.Equals(d))
};
foreach (var i in q)
{
Console.WriteLine(i);
}
}
class X
{
public Y Y { get; set; }
}
class Y
{
public DateTime D { get; set; }
}
}
}
Related
I have this document, a post :
{Content:"blabla",Tags:["test","toto"], CreatedOn:"2019-05-01 01:02:01"}
I want to have a page that displays themost used tags since the last 30 days.
So far I tried to create an index like this
public class Toss_TagPerDay : AbstractIndexCreationTask<TossEntity, TagByDayIndex>
{
public Toss_TagPerDay()
{
Map = tosses => from toss in tosses
from tag in toss.Tags
select new TagByDayIndex()
{
Tag = tag,
CreatedOn = toss.CreatedOn.Date,
Count = 1
};
Reduce = results => from result in results
group result by new { result.Tag, result.CreatedOn }
into g
select new TagByDayIndex()
{
Tag = g.Key.Tag,
CreatedOn = g.Key.CreatedOn,
Count = g.Sum(i => i.Count)
};
}
}
And I query it like that
await _session
.Query<TagByDayIndex, Toss_TagPerDay>()
.Where(i => i.CreatedOn >= firstDay)
.GroupBy(i => i.Tag)
.OrderByDescending(g => g.Sum(i => i.Count))
.Take(50)
.Select(t => new BestTagsResult()
{
CountLastMonth = t.Count(),
Tag = t.Key
})
.ToListAsync()
But this gives me the error
Message: System.NotSupportedException : Could not understand expression: from index 'Toss/TagPerDay'.Where(i => (Convert(i.CreatedOn, DateTimeOffset) >= value(Toss.Server.Models.Tosses.BestTagsQueryHandler+<>c__DisplayClass3_0).firstDay)).GroupBy(i => i.Tag).OrderByDescending(g => g.Sum(i => i.Count)).Take(50).Select(t => new BestTagsResult() {CountLastMonth = t.Count(), Tag = t.Key})
---- System.NotSupportedException : GroupBy method is only supported in dynamic map-reduce queries
Any idea how can I make this work ? I could query for all the index data from the past 30 days and do the groupby / order / take in memory but this could make my app load a lot of data.
The results from the map-reduce index you created will give you the number of tags per day. You want to have the most popular ones from the last 30 days so you need to do the following query:
var tagCountPerDay = session
.Query<TagByDayIndex, Toss_TagPerDay>()
.Where(i => i.CreatedOn >= DateTime.Now.AddDays(-30))
.ToList();
Then you can the the client side grouping by Tag:
var mostUsedTags = tagCountPerDay.GroupBy(x => x.Tag)
.Select(t => new BestTagsResult()
{
CountLastMonth = t.Count(),
Tag = t.Key
})
.OrderByDescending(g => g.CountLastMonth)
.ToList();
#Kuepper
Based on your index definition. You can handle that by the following index:
public class TrendingSongs : AbstractIndexCreationTask<TrackPlayedEvent, TrendingSongs.Result>
{
public TrendingSongs()
{
Map = events => from e in events
where e.TypeOfTrack == TrackSubtype.song && e.Percentage >= 80 && !e.Tags.Contains(Podcast.Tags.FraKaare)
select new Result
{
TrackId = e.TrackId,
Count = 1,
Timestamp = new DateTime(e.TimestampStart.Year, e.TimestampStart.Month, e.TimestampStart.Day)
};
Reduce = results => from r in results
group r by new {r.TrackId, r.Timestamp}
into g
select new Result
{
TrackId = g.Key.TrackId,
Count = g.Sum(x => x.Count),
Timestamp = g.Key.Timestamp
};
}
}
and the query using facets:
from index TrendingSongs where Timestamp between $then and $now select facet(TrackId, sum(Count))
The reason for the error is that you can't use 'GroupBy' in a query made on an index.
'GroupBy' can be used when performing a 'dynamic query',
i.e. a query that is made on a collection, without specifying an index.
See:
https://ravendb.net/docs/article-page/4.1/Csharp/client-api/session/querying/how-to-perform-group-by-query
I solved a similar problem, by using AdditionalSources that uses dynamic values.
Then I update the index every morning to increase the Earliest Timestamp. await IndexCreation.CreateIndexesAsync(new AbstractIndexCreationTask[] {new TrendingSongs()}, _store);
I still have to try it in production, but my tests so far look like it's a lot faster than the alternatives. It does feel pretty hacky though and I'm surprised RavenDB does not offer a better solution.
public class TrendingSongs : AbstractIndexCreationTask<TrackPlayedEvent, TrendingSongs.Result>
{
public DateTime Earliest = DateTime.UtcNow.AddDays(-16);
public TrendingSongs()
{
Map = events => from e in events
where e.TypeOfTrack == TrackSubtype.song && e.Percentage >= 80 && !e.Tags.Contains(Podcast.Tags.FraKaare)
&& e.TimestampStart > new DateTime(TrendingHelpers.Year, TrendingHelpers.Month, TrendingHelpers.Day)
select new Result
{
TrackId = e.TrackId,
Count = 1
};
Reduce = results => from r in results
group r by new {r.TrackId}
into g
select new Result
{
TrackId = g.Key.TrackId,
Count = g.Sum(x => x.Count)
};
AdditionalSources = new Dictionary<string, string>
{
{
"TrendingHelpers",
#"namespace Helpers
{
public static class TrendingHelpers
{
public static int Day = "+Earliest.Day+#";
public static int Month = "+Earliest.Month+#";
public static int Year = "+Earliest.Year+#";
}
}"
}
};
}
}
I have 2 Lists each of different objects. Each list contains a date element. What I am trying to do is pull items from each list in sequence and do something.
Object1
{
string description
date updateDate
int value
}
Object2
{
string description
date updateDate
string descritpion2
}
IE
List<object1>
object1.date = 10/1/2017
object1.date = 9/3/2017
List<object2>
object2.date = 10/15/2017
object2.date = 9/1/2017
I want to process these in order so i would do List 2 object 9/1, List 1 object 9/2, List 1 object 9/3, List 2 object 10/5
How can one achieve this?
How about this?
var list1 = new List<Object1>();
var list2 = new List<Object2>();
var newOrderedByDateCollection = list1
.Select(i => new TempClass(i, i.updateDate))
.Concat(list2
.Select(j => new TempClass(j, j.updateDate)))
.OrderBy(tmp => tmp.Date)
.Select(tmp => tmp.OrigItem);
//This could be replaced by a tuple like Tuple<object, DateTime> but I thought this would come across clearer
public class TempClass
{
public TempClass(object origItem, DateTime date)
{
OrigItem = origItem;
Date = date;
}
public object OrigItem { get; set; }
public DateTime Date { get; set; }
}
You now have a ordered list of type object. Which I can't see a way of getting around, So as you iterate through that list, you'll need to cast each object appropriately back by doing a switch and some pattern matching
Edit: for comepleteness here is the tuple version (I think its probably the best way to do it)
var newOrderedByDateCollection = list1
.Select(i => new Tuple<object,DateTime>(i, i.updateDate))
.Concat(list2
.Select(j => new Tuple<object, DateTime>(j, j.updateDate)))
.OrderBy(tmp => tmp.Item2)
.Select(tmp => tmp.Item1);
If you want to keep type safety (avoid object) and don't mind sorting the lists to new lists, you can do a loop with both indexes:
var l1count = l1.Count;
var l2count = l2.Count;
var ocount = l1count + l2count;
var l1o = l1.OrderBy(o => o.updateDate).ToList();
var l2o = l2.OrderBy(o => o.updateDate).ToList();
for (int j1 = 0, j2 = 0; j1 + j2 < ocount;) {
if (j1 < l1count && (l1o[j1].updateDate <= l2o[j2].updateDate || j2 >= l2count)) {
// process l1o[j1]
++j1;
}
else {
// process l2o[j2]
++j2;
}
}
Let's say I have the following two lists..
var unscoredList = new List<string> { "John", "Robert", "Rebecca" };
var scoredList = new List<WordScore>
{
new WordScore("John", 10),
new WordScore("Robert", 40),
new WordScore("Rebecca", 30)
};
Is there a way I can sort the values in unscoredList by the values in scoredList where the word with the highest score appears first in unscoredList?
Below is the WordScore class if required..
public class WordScore {
public string Word;
public int Score;
public WordScore(string word, int score) {
Word = word;
Score = score;
}
}
If you don't need an in-place sort, you can do this:
var scoreLookup = scoredList.ToDictionary(l => l.Word, l => l.Score);
var result = unscoredList.OrderByDescending(l => scoreLookup[l]);
Alternatively, you can use:
unscoredList.Sort((l,r) => scoreLookup[r].CompareTo(scoreLookup[l]));
And of course, there should be some sanity checks done (duplicate values in scoredList, values in unscoredList which are not in scoredList, etc).
var test = unscoredList.OrderByDescending(x => scoredList
.Where(y => y.Word == x.ToString())
.Select(z => z.Word)
.FirstOrDefault()).ToList();
This returns another list, but not a big deal.
i want to run and print a query that shows the number of orders per each hour in a day(24).
should look like:
hour-1:00, number of orders-5
hour-2:00, number of orders-45
hour-3:00, number of orders-25
hour-4:00, number of orders-3
hour-5:00, number of orders-43
and so on...
i try:
public void ShowBestHours()
{
using (NorthwindDataContext db = new NorthwindDataContext())
{
var query =
from z in db.Orders
select new Stime
{
HourTime = db.Orders.GroupBy(x => x.OrderDate.Value.Hour).Count(),
};
foreach (var item in query)
{
Console.WriteLine("Hour : {0},Order(s) Number : {1}", item.HourTime, item.Count);
}
}
}
public class Stime
{
public int HourTime { get; set; }
public int Count { get; set; }
}
You need to change your query to
var query =
from z in db.Orders
group z by z.OrderDate.Value.Hour into g
select new Stime{ HourTime = g.Key, Count=g.Count () };
or alternatively
var query = db,Orders.GroupBy (o => o.OrderDate.Value.Hour).Select (
g => new Stime{ HourTime=g.Key, Count=g.Count () });
In my copy of Northwind all of the OrderDate values are dates only so the result is just
HourTime = 0, Count = 830.
I'm assuming you're just experimenting with grouping. Try grouping by day of week like this
var query = db.Orders.GroupBy (o => o.OrderDate.Value.DayOfWeek).Select (
g => new { DayOfWeek=g.Key, Count=g.Count () });
which gives a more useful result.
You aren't setting Stime.Count anywhere in your query and you aren't grouping by hour correctly. I haven't seen your exact setup of course, but I think the following should work for you.
var query =
from z in db.Orders
group z by z.OrderDate.Value.Hour into g
select new Stime() { HourTime = g.Key, Count = g.Count() };
foreach (var item in query)
{
Console.WriteLine("Hour : {0},Order(s) Number : {1}", item.HourTime, item.Count);
}
Try this:
public void ShowBestHours()
{
using (NorthwindDataContext db = new NorthwindDataContext())
{
var query = db.Orders.GroupBy(x => x.OrderDate.Value.Hour).OrderByDescending(x => x.Count()).Select(x => new Stime { HourTime = x.Key, Count = x.Count() });
foreach (var item in query)
{
Console.WriteLine("Hour : {0},Order(s) Number : {1}", item.HourTime, item.Count);
}
}
}
I have a list of string items declared like this:
List<string> methodList = new List<string>();
I do what I need to get done and the results are something like this:
Method1;15
Method2;30
Method3;45
Method1;60
I need to loop through this list and display a distinct list and the addition of the totals. Something like this:
Method1 75
Method2 30
Method3 45
In order to do this, you'll need to split this up, then sum:
var results = methodList
.Select(l => l.Split(';'))
.GroupBy(a => a[0])
.Select(g =>
new
{
Group = g.Key,
Count = g.Count(),
Total = g.Sum(arr => Int32.Parse(arr[1]))
});
foreach(var result in results)
Console.WriteLine("{0} {1} {2}", result.Group, result.Count, result.Total);
Something like this would work:
var sumList = methodList.Select( x=>
{
var parts = x.Split(';');
return new
{
Method = parts [0],
Cost = Convert.ToInt32(parts[1])
};
})
.GroupBy( x=> x.Method)
.Select( g=> new { Method = g.Key, Sum = g.Sum( x=> x.Cost) })
.ToList();
foreach(var item in sumList)
Console.WriteLine("Total for {0}:{1}", item.Method, item.Sum);
A better approach would be to keep the individual methods and their cost in a strongly typed class, so you don't have to do string parsing to operate them:
public class MethodCost
{
public string MethodName { get; set; }
public int Cost { get; set; }
}
Now you can use a List<MethodCost> instead and have direct access to the cost - use strings for presentation (i.e. writing to the console), not for internal storage when it is not appropriate.
Try the following
var result = methodList
.Select(
x =>
{
var items = x.Split(';');
return new { Name = items[0], Value = Int32.Parse(items[1]) };
})
.GroupBy(pair => pair.Name)
.Select(
grouping =>
{
var sum = grouping.Sum(x => x.Value);
return String.Format("{0} {1}", grouping.Key, sum);
});
Yet another, slightly compacter variant is
var results = from item in methodList
let parts = item.Split(';')
group Int32.Parse(parts[1]) by parts[0];
foreach(var item in results)
Console.WriteLine("{0} {1}", item.Key, item.Sum());