I have a collection of Car:
var cars = new List<Car>();
cars.Add(new Car { ProductionDate = new DateTime(2011,02,02) });
cars.Add(new Car { ProductionDate = new DateTime(2011, 01, 01) });
cars.Add(new Car { ProductionDate = new DateTime(2011,04,04,04,04,04) });
cars.Add(new Car { ProductionDate = new DateTime(2011, 03, 03, 3, 3, 3) });
I need to sort it by ProductionDate.
Result should be that at first I will have cars with date with time, so it should be car with 2011, 03, 03, 3, 3, 3 as production date, and at the end should be car with 2011, 02, 02 as production date. Second should be car with 2011, 04, 04, 04, 04, 04, and the third with 2011, 02, 02.
I can do it by using foreach but I believe that there is nicer way to do it.
cars.Sort((p, q) => {
var tm1 = p.ProductionDate.TimeOfDay;
var tm2 = q.ProductionDate.TimeOfDay;
if (tm1.Ticks == 0) {
if (tm2.Ticks == 0) {
return p.ProductionDate.CompareTo(q.ProductionDate);
}
return 1;
} else if (tm2.Ticks == 0) {
return -1;
} else {
return p.ProductionDate.CompareTo(q.ProductionDate);
}
});
But remember: what happens if a car is built at 0:00? A DateTime is made of a Data+Time. You can't see if the Time part is missing!
I'll add that if you need to use the Enumerable.OrderBy, then you can use my lamdba function with the LambdaComparer that you can find around the internet (as suggested by sll)
TimeSpan zeroTime = new TimeSpan(0);
var sortedCars = cars.OrderBy(c => c.ProductionDate.TimeOfDay.Equals(zeroTime) ? 1 : 0)
.ThenBy(c => c.ProductionDate)
.ToList();
I have something like that
var carsWithoutProductionTime = from car in cars
where car.ProductionDate.Hour == 0
orderby car.ProductionDate
select car;
var carsWithProductionTime = from car in cars
where car.ProductionDate.Hour != 0
orderby car.ProductionDate
select car;
var mergedCars = carsWithProductionTime.Union(carsWithoutProductionTime);
but It looks ugly. I would like to see something more sophisticated :)
Related
I have some google sheets in google drive. I am trying to send only last four months files in google drive to email. The problem is the months are defined in the file's name. So there is no timestamp
So far, I am searching the files by title contains "Mar 2019" or title contains "Apr 2019". if like this, I have to change the code every month after adding a file to the google drive.
function checkSales(){
var file, files = DriveApp.getFolderById("").searchFiles('title
contains "Mar 2019" or title contains "Apr 2019" or title contains "May
2019" or title contains "Jun 2019"')
var body = '';
var subject = [];
while (files.hasNext()) {
file = files.next();
var activeSpreadSheet = SpreadsheetApp.open(file);
var spreadsheetName = activeSpreadSheet.getName(); // Added
var sheets = activeSpreadSheet.getSheets();
for (var sheetIndex = 0; sheetIndex < sheets.length; sheetIndex++) {
var sheet = sheets[sheetIndex];
var data = sheet.getDataRange().getValues();
var resultArr = [];
for (var i=1;i<data.length;i++) {
for (var j=11;j<19;j++) {
var cellVal = data[i][j];
if (cellVal > 0) {
resultArr.push([data[i][0],data[0][j],cellVal]);
}
}
}
}
}
I want the output to send the last four files in the google drive to email.
You want to automatically create the search query using a script.
You want to search files using the created search query.
For example, when today is July 2019, you want to create the search query like below.
'title contains "Mar 2019" or title contains "Apr 2019" or title contains "May 2019" or title contains "Jun 2019"'
If my understanding is correct, how about this modification? Please think of this as one of several answers.
Modification points:
At first, a JSON object is created as follows.
{0: "Jan", 1: "Feb", 2: "Mar", 3: "Apr", 4: "May", 5: "Jun", 6: "Jul", 7: "Aug", 8: "Sep", 9: "Oct", 10: "Nov", 11: "Dec"}.
Using this object and the date object, the filenames are created.
At first, please check the values of above JSON object I prepared. If there are modification values, please modify them. If the wrong values are included, the files cannot be retrieved. Please be careful this.
Modified script:
Please modify as follows.
From:
var file, files = DriveApp.getFolderById("").searchFiles('title contains "Mar 2019" or title contains "Apr 2019" or title contains "May 2019" or title contains "Jun 2019"')
To:
var months = {0: "Jan", 1: "Feb", 2: "Mar", 3: "Apr", 4: "May", 5: "Jun", 6: "Jul", 7: "Aug", 8: "Sep", 9: "Oct", 10: "Nov", 11: "Dec"};
var date = new Date();
var y = date.getFullYear();
var m = date.getMonth();
var fileNames = [];
for (var i = 0; i < 4; i++) {
m--; // For example, when today is July, you need June, May, Apr and Mar.
if (m < 0) {
m += 12;
y--;
}
fileNames.push(months[m] + " " + y);
// m--; // For example, when today is July, you need July, June, May and Apr.
}
var searchQuery = fileNames.map(function(e) {return 'title contains "' + e + '"'}).join(" or ");
var file, files = DriveApp.getFolderById("root").searchFiles(searchQuery);
Note:
I couldn't understand about var spreadsheetNames = DriveApp.searchFiles(. I think that when your script is saved, an error occurs.
This is also mentioned by Tedinoz's comment.
In this sample script, when today is July, June, May, Apr and Mar are retrieved. If you want to retrieve July, June, May and Apr, please modify the commented out of m--; of above script.
References:
getFullYear()
getMonth()
If I misunderstood your question and this was not the direction you want, I apologize.
Try this:
function getLastFourMonthFileIds(){
var nA=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Nov','Dec'];
var mA=[];
for(var i=0;i<4;i++) {
var dt=new Date(new Date().getFullYear(),new Date().getMonth()-i,new Date().getDate());
mA.push(nA[dt.getMonth()]+dt.getFullYear());
}
var fA=[];
var files = DriveApp.getFolderById("").getFilesByType(MimeType.GOOGLE_SHEETS);
while(files.hasNext()) {
var file=files.next();
if(mA.indexOf(file.getName())>-1) {
fA.push(file.getId());
}
}
Logger.log(fA);
return fA();
}
A code example using the getLastUpdated() attribute.
function so5689562902() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetname = "56895629";
var sheet = ss.getSheetByName(sheetname);
sheet.activate;
// sundry variables
var filearray = [];
var filedetail = [];
var emailarray = [];
var emailattachments = [];
var numberReq = 4;
// get the files from the folder
var folder = DriveApp.getFolderById("<<insert Folder ID>>");
var files = folder.getFiles();
while (files.hasNext()) {
var file = files.next();
filedetail=[];
//Logger.log("DEBUG: file name: "+file.getName()+", file ID: "+file.getId()+", last updated: "+file.getLastUpdated());
filedetail.push(file.getName());
filedetail.push(file.getId());
filedetail.push(file.getLastUpdated());
// build cumulative array
filearray.push(filedetail);
}
// sort the array by date updated
filearray.sort(function(x,y){
var xp = x[2];
var yp = y[2];
return xp == yp ? 0 : xp > yp ? -1 : 1;
});
// get just the first N files
for (i=0;i<numberReq;i++){
filedetail=[];
filedetail.push(filearray[i][0] ); // name
filedetail.push(filearray[i][1] ); // file ID
filedetail.push(filearray[i][2] ); // last updated
// build the array of email files
emailarray.push(filedetail);
emailattachments.push(filearray[i][1])
}
// display the details for proof
//var arraylen = emailarray.length;
//Logger.log("DEBUG: the array length = "+arraylen);
//var targetrange = sheet.getRange(2,1,arraylen, 3);
//Logger.log("DEBUG: the target range = "+targetrange.getA1Notation());
//targetrange.setValues(emailarray);
// sample sendEmail.
// email attachments are included in the options
//GmailApp.sendEmail(<<recipient>>, <<Subject>>,nonhtmlmessage, {from:senderemail,htmlBody: htmlmessage,name: senderName,replyTo:senderemail ,attachments: emailattachments });
}
While i'm trying to run this code, this exception " Only parameterless constructors and initializes are supported in LINQ to Entities." was thrown. Can u help me to solve this.
var year = DateTime.Now.AddMinutes(318).Year;
var month = DateTime.Now.AddMinutes(318).Month;
var start = new DateTime();
if (month >= 4)
{
start = new DateTime(year, 04, 01);
}
else if (month < 4)
{
start = new DateTime(year - 1, 04, 01);
}
var qr = (from h in context.heads
join l in context.ledgers.Where(x => x.entry_date >= start && x.entry_date < new DateTime(year, month, 1))
on h.h_id equals l.h_id into hl where hl.Any()
let totalbalance = hl.Sum(d => d.debit) - hl.Sum(c => c.credit)
select new { h.name, totalbalance });
Repeater2.DataSource = qr.OrderByDescending(x => x.totalbalance).Take(4);
Repeater2.DataBind();
There is a request:
"Take two older (max age) children and complete their stay in kindergarten"
var s = db.Child.Max(e => (DateTime.Today - e.Birthday));
foreach (var n in db.Child.Where(e => (DateTime.Today - e.Birthday) == s).Take(2))
db.DeleteObject(n);
"Birthday" - data type => "datetime".
Error: DbArithmeticExpression arguments must have a numeric common type
Where did I go wrong!?
You overcomplicating things. The oldest children are the one... which are born first.
So
var oldestChildren = db.Child.OrderBy(c => c.Birthday).Take(2).ToList();
//if "complete they stay in kindergarten means delete
foreach (var child in oldestChildren)
db.DeleteObject(child);
EF do not supports arithmetic operations with DateTime.
var s = db.Child.Max(e => EntityFunctions.DiffDays(DateTime.Today - e.Birthday));
EntityFunctions
pull you where statement out of the foreach statement and check your database DataType.
class Program
{
static void Main(string[] args)
{
List<Test> Tests = new List<Test>();
Tests.Add(new Test(new DateTime(2014, 01, 01)));
Tests.Add(new Test(new DateTime(2013, 01, 01)));
Tests.Add(new Test(new DateTime(2012, 01, 01)));
Tests.Add(new Test(new DateTime(2011, 01, 01)));
Tests.Add(new Test(new DateTime(2010, 01, 01)));
Tests.Add(new Test(new DateTime(2009, 01, 01)));
Tests.Add(new Test(new DateTime(2008, 01, 01)));
var s = Tests.Max(e => (DateTime.Today - e.Birthday));
var d =Tests.Where(e => (DateTime.Today - e.Birthday) == s).Take(2);
}
}
public class Test
{
public DateTime Birthday { get; set; }
public Test(DateTime dateTime)
{
Birthday = dateTime;
}
}
but I would think your logic is incorrect if you want the two oldest children. This is give you oldest children if their birthdates are the same, look at
var oldestChildren = db.Child.OrderBy(c => c.Birthday).Take(2).ToList();
The user can select any number of week days from a list. An algorithm shall find the longest continuous group of selected days. The start day can be after the end day, if the group spans two weeks. If it makes it simpler, only a group of at least 3 days needs to be detected. With crossing the week border, this makes for a maximum of one group. (There can be no two groups of 3 days within a week that are not connected.)
For example, if the user selects Monday, Tuesday, Wednesday and Saturday from a list, the display should be something like "Monday-Wednesday and Saturday".
Another example is: Wed, Fri, Sat, Sun, Mon -> "Wed, Fri-Mon".
Is there an efficient algorithm for that, preferrably in C# or a similar language? My C# hackwork is now over a page long (incl. few comments) and still not finished.
Use this answer, slightly changed:
Use a modified version of dtb's GroupAdjacentBy which accepts a minCount as a parameter:
public static IEnumerable<IEnumerable<T>> GroupAdjacentBy<T>(
this IEnumerable<T> source, Func<T, T, bool> predicate, int minCount)
{
using (var e = source.GetEnumerator())
{
if (e.MoveNext())
{
var list = new List<T> { e.Current };
var pred = e.Current;
while (e.MoveNext())
{
// if adjacent, add to list
if (predicate(pred, e.Current))
{
list.Add(e.Current);
}
else
{
// otherwise return previous elements:
// if less than minCount elements,
// return each element separately
if (list.Count < minCount)
{
foreach (var i in list)
yield return new List<T> { i };
}
else
{
// otherwise return entire group
yield return list;
}
// create next group
list = new List<T> { e.Current };
}
pred = e.Current;
}
yield return list;
}
}
}
and change the criteria for GroupAdjacentBy to group on week transitions also:
// week starts with Monday, so this should
// represent: Wed, Fri, Sat, Sun, Mon
int[] array = new int[] { 1, 2, 4, 5, 6, 0 };
Func<int, int, bool> adjacentCriteria = (x, y) => (x+1==y) || (x==6 && y==0);
string result = string.Join(", ", array
.GroupAdjacentBy(adjacentCriteria, 3)
.Select(g => new int[] { g.First(), g.Last() }.Distinct())
.Select(g => string.Join("-", g)));
Console.WriteLine(result); // output: 1, 2, 4-0
I've finished my version of it. It's a bit longer than the other one, but then again it also handles the text representation and does exactly this task. How about that?
using System;
using System.Text;
namespace WeekMathTest
{
class Program
{
static void Main(string[] args)
{
string[] weekDayNames = new string[] {
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun"
};
WeekDays weekDays = WeekDays.Monday | WeekDays.Tuesday | WeekDays.Thursday | WeekDays.Saturday | WeekDays.Sunday;
Console.WriteLine(WeekDayGroup(weekDays, weekDayNames));
}
static string WeekDayGroup(WeekDays weekDays, string[] weekDayNames)
{
int groupStart = 0, groupEnd = 0, groupLength = 0;
int maxGroupStart = 0, maxGroupEnd = 0, maxGroupLength = 0;
// Iterate all days in a repeated range
// (Sat/Sun doesn't need to be repeated or it would be in the first group)
for (int day = 1; day <= 7 + 5; day++)
{
// Is this day set?
int bitValue = 1 << ((day - 1) % 7);
bool daySet = ((int) weekDays & bitValue) != 0;
if (daySet)
{
if (groupStart == 0)
{
// First day set, remember it as group start
groupStart = day;
groupEnd = day;
groupLength = 1;
}
else
{
// Group has already been started, set new end
groupEnd = day;
groupLength = groupEnd - groupStart + 1;
if (groupLength == 7)
{
// Seen every day of the week, stop here
break;
}
}
}
else
{
if (groupLength >= 3 && groupLength > maxGroupLength)
{
// Group was long enough and longer than the last one, save it
maxGroupStart = groupStart;
maxGroupEnd = groupEnd;
maxGroupLength = groupLength;
}
// Reset operation variables
groupStart = 0;
groupEnd = 0;
groupLength = 0;
}
}
// Final check
if (groupLength >= 3 && groupLength > maxGroupLength)
{
// Group was long enough and longer than the last one, save it
maxGroupStart = groupStart;
maxGroupEnd = groupEnd;
maxGroupLength = groupLength;
}
// Clear all group days from the original value
for (int day = maxGroupStart; day <= maxGroupEnd; day++)
{
int bitValue = 1 << ((day - 1) % 7);
weekDays = (WeekDays) ((int) weekDays & ~bitValue);
}
// Generate output string
StringBuilder sb = new StringBuilder();
for (int day = 1; day <= 7; day++)
{
int bitValue = 1 << ((day - 1) % 7);
bool daySet = ((int) weekDays & bitValue) != 0;
if (daySet)
{
if (sb.Length > 0) sb.Append(", ");
sb.Append(weekDayNames[day - 1]);
}
else if (day == maxGroupStart)
{
if (sb.Length > 0) sb.Append(", ");
sb.Append(weekDayNames[day - 1]);
sb.Append("-");
sb.Append(weekDayNames[(maxGroupEnd - 1) % 7]);
}
}
return sb.ToString();
}
[Flags]
enum WeekDays
{
Monday = 1,
Tuesday = 2,
Wednesday = 4,
Thursday = 8,
Friday = 16,
Saturday = 32,
Sunday = 64
}
}
}
I'm new to LINQ and I have this situation. I have this table:
ID Date Range
1 10/10/10 9-10
2 10/10/10 9-10
3 10/10/10 9-10
4 10/10/10 8-9
5 10/11/10 1-2
6 10/11/10 1-2
7 10/12/10 5-6
I just want to list the Maximun of rows per date by range, like this:
Date Range Total
10/10/10 9-10 3
10/11/10 1-2 2
10/12/10 5-6 1
I want to do this by using LINQ, do you have any ideas of how to do this?
I think something along these lines should work:
List<MyTable> items = GetItems();
var orderedByMax = from i in items
group i by i.Date into g
let q = g.GroupBy(i => i.Range)
.Select(g2 => new {Range = g2.Key, Count = g2.Count()})
.OrderByDescending(i => i.Count)
let max = q.FirstOrDefault()
select new {
Date = g.Key,
Range = max.Range,
Total = max.Count
};
Using extension methods:
List<MyTable> items = GetItems();
var rangeTotals = items.GroupBy(x => new { x.Date, x.Range }) // Group by Date + Range
.Select(g => new {
Date = g.Key.Date,
Range = g.Key.Range,
Total = g.Count() // Count total of identical ranges per date
});
var rangeMaxTotals = rangeTotals.Where(rt => !rangeTotals.Any(z => z.Date == rt.Date && z.Total > rt.Total)); // Get maximum totals for each date
unfortunately I can't test this at the moment but give this a try:
List<MyTable> items = GetItems();
items.Max(t=>t.Range.Distinct().Count());
This approach:
1) Groups by Date
2) For each Date, groups by Range and calculates the Total
3) For each Date, selects the item with the greatest Total
4) You end up with your result
public sealed class Program
{
public static void Main(string[] args)
{
var items = new[]
{
new { ID = 1, Date = new DateTime(10, 10, 10), Range = "9-10" },
new { ID = 2, Date = new DateTime(10, 10, 10), Range = "9-10" },
new { ID = 3, Date = new DateTime(10, 10, 10), Range = "9-10" },
new { ID = 4, Date = new DateTime(10, 10, 10), Range = "8-9" },
new { ID = 5, Date = new DateTime(10, 10, 11), Range = "1-2" },
new { ID = 6, Date = new DateTime(10, 10, 11), Range = "1-2" },
new { ID = 7, Date = new DateTime(10, 10, 12), Range = "5-6" },
};
var itemsWithTotals = items
.GroupBy(item => item.Date) // Group by Date.
.Select(groupByDate => groupByDate
.GroupBy(item => item.Range) // Group by Range.
.Select(groupByRange => new
{
Date = groupByDate.Key,
Range = groupByRange.Key,
Total = groupByRange.Count()
}) // Got the totals for each grouping.
.MaxElement(item => item.Total)); // For each Date, grab the item (grouped by Range) with the greatest Total.
foreach (var item in itemsWithTotals)
Console.WriteLine("{0} {1} {2}", item.Date.ToShortDateString(), item.Range, item.Total);
Console.Read();
}
}
/// <summary>
/// From the book LINQ in Action, Listing 5.35.
/// </summary>
static class ExtensionMethods
{
public static TElement MaxElement<TElement, TData>(this IEnumerable<TElement> source, Func<TElement, TData> selector) where TData : IComparable<TData>
{
if (source == null)
throw new ArgumentNullException("source");
if (selector == null)
throw new ArgumentNullException("selector");
bool firstElement = true;
TElement result = default(TElement);
TData maxValue = default(TData);
foreach (TElement element in source)
{
var candidate = selector(element);
if (firstElement || (candidate.CompareTo(maxValue) > 0))
{
firstElement = false;
maxValue = candidate;
result = element;
}
}
return result;
}
}
According to LINQ in Action (Chapter 5.3.3 - Will LINQ to Objects hurt the performance of my code?), using the MaxElement extension method is one of the most effecient approaches. I think the performance would be O(4n); one for the first GroupBy, two for the second GroupBy, three for the Count(), and four for loop within MaxElement.
DrDro's approach is going to be more like O(n^2) since it loops the entire list for each item in the list.
StriplingWarrior's approach is going to be closer to O(n log n) because it sorts the items. Though I'll admit, there may be some crazy magic in there that I don't understand.