Related
I want to cluster the list of DateTimes in the groups.
Every times that are near each other in interval of 30 sec for ins.
12:00:05,
12:00:10,
12:00:15,
12:30:15,
12:30:25
I have a list of the times- MainBookmarksList
MainBookmarksList.Add(dt1);
MainBookmarksList.Add(dt2);
MainBookmarksList.Add(dt3);
MainBookmarksList.Add(dt4);
MainBookmarksList.Add(dt5);
now I expect to have a groups like this
12:00:05,
12:00:10,
12:00:15,
********
12:30:15,
12:30:25
I tried this :
TimeSpan interval = new TimeSpan(0, 0, 15);
var groupedTimes = from dt in MainBookmarksList
group dt by dt.Ticks / interval.Ticks
into g
select new { Begin = new DateTime(g.Key * interval.Ticks), Values = g.ToList() };
but it doesn't return the correct results.
One possible solution would be this:
static void Main(string[] args)
{
var MainBookmarksList = new List<DateTime>();
MainBookmarksList.Add(new DateTime(1900, 1, 1, 12, 0, 5));
MainBookmarksList.Add(new DateTime(1900, 1, 1, 12, 0, 10));
MainBookmarksList.Add(new DateTime(1900, 1, 1, 12, 0, 15));
MainBookmarksList.Add(new DateTime(1900, 1, 1, 12, 30, 15));
MainBookmarksList.Add(new DateTime(1900, 1, 1, 12, 30, 25));
var interval = new TimeSpan(0, 0, 15);
var groupedTimes = new List<TimeGroup>();
var currentTimeGroup = new TimeGroup(MainBookmarksList[0]);
groupedTimes.Add(currentTimeGroup);
for (var i = 1; i < MainBookmarksList.Count; i++)
{
var time = MainBookmarksList[i];
if (time-currentTimeGroup.Begin > interval)
{
currentTimeGroup = new TimeGroup(time);
groupedTimes.Add(currentTimeGroup);
}
else
{
currentTimeGroup.Values.Add(time);
}
}
}
class TimeGroup
{
public TimeGroup(DateTime dateTime)
{
Begin = dateTime;
Values = new List<DateTime>() { dateTime };
}
public DateTime Begin { get; }
public List<DateTime> Values { get; }
}
I have a really ghetto implementation of this, but I imagine there is some clean way to do this with Linq. I have a list of objects with a start and stop date.
class Thing
{
int ID;
...
DateTime? StartDate;
DateTime EndDate;
}
In some cases there is no start date, its null. What I want the algorithm to do is remove items from the list until there is no overlapping of dates; I'm just using years to illustrate the concept:
List<Thing> x = new List<Thing>()
{
{1, 2012, 2014}
{2, 2013, 2015}
{3, 2014, 2016}
{4, <null>, 2015}
{5, 2016, 2017}
}
Running this list through the algo should yield:
var y = Do(x);
y =
{
{1, 2012, 2014}
{2, <null>, 2015}
{3, 2016, 2017}
}
There is the possibilities for cycles where, where there are different optimal solutions. My data doesn't have these edge cases.
I think this should work for you:
List<Thing> x = new List<Thing>()
{
new Thing () { ID = 1, StartDate = new DateTime(2012, 1, 1), EndDate = new DateTime(2014, 1, 1) },
new Thing () { ID = 2, StartDate = new DateTime(2013, 1, 1), EndDate = new DateTime(2015, 1, 1) },
new Thing () { ID = 3, StartDate = new DateTime(2014, 1, 1), EndDate = new DateTime(2016, 1, 1) },
new Thing () { ID = 4, StartDate = null, EndDate = new DateTime(2015, 1, 1) },
new Thing () { ID = 5, StartDate = new DateTime(2016, 1, 1), EndDate = new DateTime(2017, 1, 1) },
};
Func<Thing, Thing, bool> overlaps =
(t0, t1) =>
(t0.StartDate.HasValue ? t0.StartDate.Value <= t1.EndDate : false)
&& (t1.StartDate.HasValue ? t0.EndDate >= t1.StartDate : false);
var y = x.Skip(1).Aggregate(x.Take(1).ToList(), (a, b) =>
{
if (a.All(c => !overlaps(b, c)))
{
a.Add(b);
}
return a;
});
This gives me:
If your class Thing implements IEquatable<Thing> and you properly fill out the Equals, and GetHashCode methods you can then just do
var results = x.Distinct();
Data:
I want to order data like the image above:
var expiredDate = DateTime.Now.AddDays(-6);
var query=*;
var first=query.Search(o=>o.PreorderTime<expiredDate&&(o.TotalMoney-o.MoneyPaid)>0); // this on the top
var second=query.Search(o=>o.PreorderTime>=expiredDate&&(o.TotalMoney-o.MoneyPaid)>0);
var third=query.Search(o=>(o.TotalMoney-o.MoneyPaid)<=0);
var left= query.Search(o=>!first.Contains(o)&&!second.Contains(o)&&!third.Contains(o));
var all = first.Concat(second).Concat(third).Concat(left);
var result=all.AsEnumerable().Select((item, index) => new{...,Index=index}).Take(pagesize).OrderBy(o=>o.Index).Skip(pagesize*(pageindex-1));
I test the result ,I can get first page, but after second page,no data.I don't know why.
Is there a smart way to order this data?
Something like this should work (hope you can manage the actual projection)
var query = new[]
{
new { Id = 1, PreorderTime = new DateTime(2015, 11, 11), Name = "Jim", MoneyPaid = 0, TotalMoney = 5000 },
new { Id = 2, PreorderTime = new DateTime(2015, 11, 09), Name = "Sim", MoneyPaid = 500, TotalMoney = 5000 },
new { Id = 3, PreorderTime = new DateTime(2015, 11, 10), Name = "Sim", MoneyPaid = 1100, TotalMoney = 5000 },
new { Id = 4, PreorderTime = new DateTime(2015, 11, 19), Name = "Long", MoneyPaid = 3200, TotalMoney = 5000 },
new { Id = 5, PreorderTime = new DateTime(2015, 11, 29), Name = "Long", MoneyPaid = 200, TotalMoney = 5000 },
new { Id = 6, PreorderTime = new DateTime(2015, 11, 08), Name = "Long", MoneyPaid = 5000, TotalMoney = 5000 },
new { Id = 7, PreorderTime = new DateTime(2015, 11, 28), Name = "Long", MoneyPaid = 5000, TotalMoney = 5000 },
};
var expiredDate = DateTime.Now.AddDays(-6);
int pageSize = 3;
int pageCount = (query.Length + pageSize - 1) / pageSize;
for (int pageIndex = 1; pageIndex <= pageCount; pageIndex++)
{
var page = query
.OrderBy(o => o.TotalMoney - o.MoneyPaid > 0 ? o.PreorderTime < expiredDate ? 0 : 1 : 2)
.ThenByDescending(o => o.PreorderTime)
.Skip(pageSize * (pageIndex - 1))
.Take(pageSize)
//.Select(...)
.ToList();
}
I have this problem where a null datetime in a list is not being serialized/deserialized properly.
see this sample code:
internal class WrapperNullableDateTimeList
{
public List<DateTime?> MyList { get; set; }
}
public void T14_Should_skip_output_nullable_datetime_in_list_TODO_THIS_IS_WRONG()
{
WrapperNullableDateTimeList input = new WrapperNullableDateTimeList();
input.MyList = new List<DateTime?>()
{
new DateTime(2000, 01, 01, 0, 0, 0, 0, DateTimeKind.Utc),
null,
new DateTime(2000, 12, 31, 0, 0, 0, 0, DateTimeKind.Utc),
};
JsConfig.IncludeNullValues = true;
var serializer = new JsonSerializer<WrapperNullableDateTimeList>();
string serializedInput = serializer.SerializeToString(input);
WrapperNullableDateTimeList output = serializer.DeserializeFromString(serializedInput);
output.MyList.Count.Should().Be(3); // Fails here! The 'null' DateTime in the list is dropped
}
Any ideas?
BTW, I printed the serializedInput (json), and it looks like this:
{"MyList":["2000-01-01T00:00:00.0000000Z",null,"2000-12-31T00:00:00.0000000Z"]}
I have my JsConfig to include null values... so what gives?
This works in the latest version of ServiceStack:
using (JsConfig.With(new Config { includeNullValues = true }))
{
var dto = new WrapperNullableDateTimeList
{
MyList = new List<DateTime?>
{
new DateTime(2000, 01, 01, 0, 0, 0, 0, DateTimeKind.Utc),
null,
new DateTime(2000, 12, 31, 0, 0, 0, 0, DateTimeKind.Utc),
}
};
var json = dto.ToJson();
json.Print();
Assert.That(json, Is.EqualTo(
#"{""MyList"":[""\/Date(946684800000)\/"",null,""\/Date(978220800000)\/""]}"));
var fromJson = json.FromJson<WrapperNullableDateTimeList>();
Assert.That(fromJson.MyList.Count, Is.EqualTo(dto.MyList.Count));
}
I am trying to work out if the following can be done in a LINQ to Objects statement.
I have a dictionary with the key as a DateTime (keys are values that are on multiple days) and a double value. I have too much data to plot on a graph so would like to the average value of each 5 minutes.
Sample Input
01/01/2012 23:53 5
01/01/2012 23:54 2
01/01/2012 23:55 1
01/01/2012 23:56 3
01/01/2012 23:57 4
01/01/2012 23:58 5
01/01/2012 23:59 6
02/01/2012 00:00 2
02/01/2012 00:01 4
02/01/2012 00:02 5
Expected Output
01/01/2012 23:55 3
02/01/2012 00:00 4.4
Using this helper method:
static DateTime RoundToNearestInterval(DateTime dt, TimeSpan d)
{
int f=0;
double m = (double)(dt.Ticks % d.Ticks) / d.Ticks;
if (m >= 0.5)
f=1;
return new DateTime(((dt.Ticks/ d.Ticks)+f) * d.Ticks);
}
it's as simple as
var result = from kvp in data
let key = RoundToNearestInterval(kvp.Key, TimeSpan.FromMinutes(5))
group kvp by key into g
select new { g.Key, Value = g.Average(x => x.Value) };
or
var result = data.GroupBy(kvp => RoundToNearestInterval(kvp.Key, TimeSpan.FromMinutes(5)), kvp => kvp.Value)
.Select(g => new { g.Key, Value = g.Average() });
LINQPad example:
void Main()
{
var tmp = new Dictionary<string, int>
{
{"01/01/2012 23:53", 5},
{"01/01/2012 23:54", 2},
{"01/01/2012 23:55", 1},
{"01/01/2012 23:56", 3},
{"01/01/2012 23:57", 4},
{"01/01/2012 23:58", 5},
{"01/01/2012 23:59", 6},
{"02/01/2012 00:00", 2},
{"02/01/2012 00:01", 4},
{"02/01/2012 00:02", 5}
};
var data = tmp.ToDictionary(d => DateTime.Parse(d.Key), d=>d.Value);
var result = from kvp in data
let key = RoundToNearestInterval(kvp.Key, TimeSpan.FromMinutes(5))
group kvp by key into g
select new {g.Key, Value = g.Average (x => x.Value) };
result.ToDictionary(r => r.Key, v => v.Value).Dump();
}
Here's a LINQ query that will do what you want, you can test this in LINQPad:
void Main()
{
var points = new[]
{
new { dt = new DateTime(2012, 1, 1, 23, 53, 00), value = 5 },
new { dt = new DateTime(2012, 1, 1, 23, 54, 00), value = 2 },
new { dt = new DateTime(2012, 1, 1, 23, 55, 00), value = 1 },
new { dt = new DateTime(2012, 1, 1, 23, 56, 00), value = 3 },
new { dt = new DateTime(2012, 1, 1, 23, 57, 00), value = 4 },
new { dt = new DateTime(2012, 1, 1, 23, 58, 00), value = 5 },
new { dt = new DateTime(2012, 1, 1, 23, 59, 00), value = 6 },
new { dt = new DateTime(2012, 1, 2, 00, 00, 00), value = 2 },
new { dt = new DateTime(2012, 1, 2, 00, 01, 00), value = 4 },
new { dt = new DateTime(2012, 1, 2, 00, 01, 00), value = 5 }
};
var interval = TimeSpan.FromMinutes(5);
var averageByInterval =
from point in points
let intervalStart = new DateTime(((int)((point.dt.Ticks + interval.Ticks / 2) / interval.Ticks)) * interval.Ticks)
group point.value by intervalStart into g
select new { g.Key, average = g.Average() };
averageByInterval.Dump();
}
Output:
Looks like your dictionary contains the ordered elements so we can do something like this:
var firstDate = yourDict.First().Key;
var output = yourDict.GroupBy(e=> (int)(e.Key - firstDate).TotalMinutes / 5)
.ToDictionary(g => g.First().Key
.AddMinutes(g.Average(e=>(e.Key - g.First().Key).TotalMinutes)),
g => g.Average(e=>e.Value));
NOTE: The input data of the OP uses a different cutlure than en-US, the month goes after the day. That's the noticeable point to take some test. otherwise the test won't be correct.
Try this:
var results =
data
.GroupBy(
x => (x.Key.Ticks / TimeSpan.TicksPerMinute + 2) / 5,
x => x.Value)
.Select(x => new
{
Key = new DateTime(x.Key * TimeSpan.TicksPerMinute * 5),
Value = x.Average()
});
var data = new Dictionary<DateTime, double>();
data.Add(new DateTime(2012, 1, 1, 23, 53, 0), 5);
data.Add(new DateTime(2012, 1, 1, 23, 54, 0), 2);
data.Add(new DateTime(2012, 1, 1, 23, 55, 0), 1);
data.Add(new DateTime(2012, 1, 1, 23, 56, 0), 3);
data.Add(new DateTime(2012, 1, 1, 23, 57, 0), 4);
data.Add(new DateTime(2012, 1, 1, 23, 58, 0), 5);
data.Add(new DateTime(2012, 1, 1, 23, 59, 0), 6);
data.Add(new DateTime(2012, 1, 2, 0, 0, 0), 2);
data.Add(new DateTime(2012, 1, 2, 0, 1, 0), 4);
data.Add(new DateTime(2012, 1, 2, 0, 2, 0), 5);
var result = data.GroupBy(kvp =>
{
var dt = kvp.Key;
var nearest5 = (int)Math.Round(dt.Minute / 5.0) * 5;
//Add the minutes after inital date creation to deal with minutes=60
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0).AddMinutes(nearest5);
})
.Select(g =>
{
return new KeyValuePair<DateTime, double>(g.Key, g.Average(row => row.Value));
});
foreach (var r in result)
{
Console.WriteLine(r.Key + " " + r.Value);
// 1/01/2012 11:55:00 PM 3
// 2/01/2012 12:00:00 AM 4.4
}