我已经做了一些代码,在多个实体上操作,并为它们创建“时间窗口”。基本上,实体将包含状态,通常以布尔或int的形式。它们将被时间戳,为了减少在大日期范围内创建图表时的行数,我正在计算5分钟、15分钟和60分钟的时间窗口。如果我正在计算5分钟的时间窗口,并且状态从00:00:00到00:02:59为真,从00:03:00开始为false,则一天的前5分钟的时间窗口为真,因为状态为3/5分钟。
我使用了自定义属性、泛型和反射,以检查实体具有哪些属性以及需要处理哪些属性。
代码可以工作,但我相信它可以改进。
有时,代码对16个实体中的数千行快速工作,即对CalculateTimeWindows<T>()方法的16个调用(不到1秒),但有时非常慢(大约需要45秒)。有什么办法优化这段代码吗?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace Services.Helpers
{
#region Custom Attributes
[AttributeUsage(AttributeTargets.Property)]
public class DoNotCopyIntoTimeWindow : System.Attribute { } // leave default
[AttributeUsage(AttributeTargets.Property)]
public class IsProcessedIntoTimeWindow : System.Attribute { } // calculate time window for this property
[AttributeUsage(AttributeTargets.Property)]
public class IsTimeWindowDate : Attribute { } // attribute to mark property as the datetime
[AttributeUsage(AttributeTargets.Property)]
public class IsTimeWindowIdentifier : Attribute { } // this is the time window property
#endregion
public class TimeWindow
{
#region Structs
public struct TimeWindowDictionary
{
public string Name { get; set; }
public Dictionary<NullObject<dynamic>, int> Dictionary { get; set; }
}
public struct NullObject<T>
{
[DefaultValue(true)]
private readonly bool isnull;// default property initializers are not supported for structs
private NullObject(T item, bool isnull) : this()
{
this.isnull = isnull;
Item = item;
}
public NullObject(T item) : this(item, item == null)
{
}
public static NullObject<T> Null()
{
return new NullObject<T>();
}
public T Item { get; private set; }
public bool IsNull()
{
return isnull;
}
public static implicit operator T(NullObject<T> nullObject)
{
return nullObject.Item;
}
public static implicit operator NullObject<T>(T item)
{
return new NullObject<T>(item);
}
public override string ToString()
{
return (Item != null) ? Item.ToString() : "NULL";
}
public override bool Equals(object obj)
{
if (obj == null)
return IsNull();
if (!(obj is NullObject<T>))
return false;
var no = (NullObject<T>)obj;
if (IsNull())
return no.IsNull();
if (no.IsNull())
return false;
return Item.Equals(no.Item);
}
public override int GetHashCode()
{
if (IsNull())
return 0;
var result = Item.GetHashCode();
if (result >= 0)
result++;
return result;
}
}
#endregion
public static IEnumerable<T> CalculateTimeWindows<T>(DateTime dateFrom, DateTime dateTo, List<T> stateModels) where T : new()
{
if (stateModels.Count() == 0)
return new List<T>();
dateFrom = GetPropertiesAndDictionaries(dateFrom, stateModels, out PropertyInfo datePropertyInfo, out List<PropertyInfo> copyProperties, out PropertyInfo timeWindowIdentifier, out int size, out TimeWindowDictionary[] dictionaries, out int i);
return CalculateTimeWindow(dateFrom, dateTo, stateModels, 5, datePropertyInfo, copyProperties, timeWindowIdentifier, size, dictionaries, i)
.Concat(CalculateTimeWindow(dateFrom, dateTo, stateModels, 15, datePropertyInfo, copyProperties, timeWindowIdentifier, size, dictionaries, i))
.Concat(CalculateTimeWindow(dateFrom, dateTo, stateModels, 60, datePropertyInfo, copyProperties, timeWindowIdentifier, size, dictionaries, i));
}
public static IEnumerable<T> CalculateTimeWindow<T>(DateTime dateFrom, DateTime dateTo, List<T> stateModels, byte timeWindowMinutes, PropertyInfo datePropertyInfo, List<PropertyInfo> copyProperties, PropertyInfo timeWindowIdentifier, int size, TimeWindowDictionary[] dictionaries, int i) where T : new()
{
if (stateModels.Count() > 0)
{
DateTime currentWindowFrom, currentWindowTo, nextWindowFrom;
nextWindowFrom = dateFrom;
int itemPointer = 0;
T prevItem = default;
T prevTimeWindow = default;
KeyValuePair<NullObject<dynamic>, int> maxValue = new KeyValuePair<NullObject<dynamic>, int>();
int j = 1;
do // one time window
{
for (i = 0; i < size; i++)
dictionaries[i].Dictionary = new Dictionary<NullObject<dynamic>, int>();
currentWindowFrom = nextWindowFrom;
nextWindowFrom = currentWindowFrom.AddMinutes(timeWindowMinutes);
currentWindowTo = nextWindowFrom.AddSeconds(-1);
var calculateTime = currentWindowFrom;
for (itemPointer = itemPointer; itemPointer < stateModels.Count(); itemPointer++)
{
var item = stateModels.ElementAt(itemPointer);
var date = (DateTime)datePropertyInfo.GetValue(item);
if (date >= currentWindowTo)
break;
var endDate = (date > currentWindowTo) ? nextWindowFrom : date; // state might extend more than the end of the time window
CalculateStateSeconds(prevItem, dictionaries, calculateTime, endDate);
prevItem = item;
calculateTime = (date < currentWindowFrom) ? currentWindowFrom : date; // to fix the 'yesterday' date
}
if (calculateTime < currentWindowTo)
CalculateStateSeconds(prevItem, dictionaries, calculateTime, nextWindowFrom);
if (dictionaries[0].Dictionary.Count > 0)
{
bool sameAsPrevious = (prevTimeWindow != null);
var output = new T();
foreach (var dictionary in dictionaries)
{
//var valToSet = dictionary.Dictionary.FirstOrDefault(x => x.Value.Equals(dictionary.Dictionary.Values.Max())).Key.Item;
for (i = 0; i < dictionary.Dictionary.Count; i++)
{
maxValue = dictionary.Dictionary.First();
for (j = 1; j < dictionary.Dictionary.Count; j++)
{
var valuePair = dictionary.Dictionary.ElementAt(j);
if (valuePair.Value > maxValue.Value)
maxValue = valuePair;
}
}
var valToSet = maxValue.Key.Item;
if (sameAsPrevious)
{
var prevVal = GetValue(prevTimeWindow, dictionary.Name);
if (valToSet == null && prevVal == null)
{ }
else if ((valToSet == null && prevVal != null) || (valToSet != null && prevVal == null) || !valToSet.Equals(prevVal))
sameAsPrevious = false;
}
SetValue(output, dictionary.Name, valToSet);
}
if (!sameAsPrevious)
{
foreach (var copyProperty in copyProperties)
SetValue(output, copyProperty.Name, copyProperty.GetValue(prevItem));
timeWindowIdentifier.SetValue(output, timeWindowMinutes);
datePropertyInfo.SetValue(output, currentWindowFrom);
prevTimeWindow = output;
yield return output;
}
}
}
while (nextWindowFrom <= dateTo);
}
}
private static DateTime GetPropertiesAndDictionaries<T>(DateTime dateFrom, List<T> stateModels, out PropertyInfo datePropertyInfo, out List<PropertyInfo> copyProperties, out PropertyInfo timeWindowIdentifier, out int size, out TimeWindowDictionary[] dictionaries, out int i) where T : new()
{
Type tType = typeof(T);
var propInfos = tType.GetProperties();
datePropertyInfo = propInfos.Single(p => p.GetCustomAttributes(typeof(IsTimeWindowDate), true).Any());
var firstDate = (DateTime)datePropertyInfo.GetValue(stateModels.First());
if (firstDate < dateFrom)
dateFrom = new DateTime(firstDate.Year, firstDate.Month, firstDate.Day, firstDate.Hour, 0, 0, DateTimeKind.Utc);
var properties = propInfos.Where(p => p.GetCustomAttributes(typeof(IsProcessedIntoTimeWindow), true).Any()).Select(x => x.Name);
copyProperties = propInfos.Where(p => !p.GetCustomAttributes(typeof(IsTimeWindowIdentifier), true).Any() && !p.GetCustomAttributes(typeof(DoNotCopyIntoTimeWindow), true).Any() && !p.GetCustomAttributes(typeof(IsTimeWindowDate), true).Any() && !p.GetCustomAttributes(typeof(IsProcessedIntoTimeWindow), true).Any() && p.CanWrite && !p.GetMethod.IsVirtual).ToList();
timeWindowIdentifier = propInfos.Single(p => p.GetCustomAttributes(typeof(IsTimeWindowIdentifier), true).Any());
size = properties.Count();
dictionaries = new TimeWindowDictionary[size];
i = 0;
foreach (var property in properties)
{
dictionaries[i] = new TimeWindowDictionary() { Name = property };
i++;
}
return dateFrom;
}
private static dynamic GetValue(object inputObject, string propertyName)
{
Type type = inputObject.GetType();
PropertyInfo propertyInfo = type.GetProperty(propertyName);
return propertyInfo.GetValue(inputObject);
}
private static void SetValue(object inputObject, string propertyName, object propertyVal)
{
//find out the type
Type type = inputObject.GetType();
//get the property information based on the type
PropertyInfo propertyInfo = type.GetProperty(propertyName);
if (propertyVal != null)
{
//find the property type
Type propertyType = propertyInfo.PropertyType;
//Convert.ChangeType does not handle conversion to nullable types
//if the property type is nullable, we need to get the underlying type of the property
var targetType = IsNullableType(propertyType) ? Nullable.GetUnderlyingType(propertyType) : propertyType;
//Returns an System.Object with the specified System.Type and whose value is
//equivalent to the specified object.
propertyVal = Convert.ChangeType(propertyVal, targetType);
}
//Set the value of the property
propertyInfo.SetValue(inputObject, propertyVal, null);
}
private static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
}
private static void CalculateStateSeconds<T>(T prevItem, IEnumerable<TimeWindowDictionary> dictionaries, DateTime calculateTime, DateTime endDate)
{
if (prevItem != null)
{
var seconds = Convert.ToInt32(endDate.Subtract(calculateTime).TotalSeconds);
Type tType = typeof(T);
foreach (var dictionary in dictionaries)
{
PropertyInfo propertyInfo = tType.GetProperty(dictionary.Name);
var key = propertyInfo.GetValue(prevItem);
dictionary.Dictionary.TryGetValue(key, out int existingSeconds);
dictionary.Dictionary[key] = existingSeconds + seconds;
}
}
}
}
}下面是一个示例类和单元测试:
public abstract class MyBaseModel : ICloneable
{
[DoNotCopyIntoTimeWindow]
public int Id { get; set; }
public short fk_TenantId { get; set; }
[IsTimeWindowIdentifier]
public byte TimeWindow { get; set; }
[IsTimeWindowDate]
public DateTime Date { get; set; }
[IsProcessedIntoTimeWindow]
public byte ActuallyAnEnum { get; set; }
public abstract bool CalculatorOn { get; }
public object Clone()
{
return this.MemberwiseClone();
}
}
public class MyModel : MyBaseModel
{
public short ShortId { get; set; }
public short AnotherShortId { get; set; }
[IsProcessedIntoTimeWindow]
public decimal Value { get; set; }
public override bool CalculatorOn
{
get
{
throw new NotImplementedException();
}
}
}
[TestMethod]
public void TestLowerResolution()
{
/* generate test data */
DateTime dateFrom = new DateTime(2018, 1, 28, 15, 0, 0, DateTimeKind.Utc);
var dateTo = dateFrom.AddDays(1).AddSeconds(-1);
var myValues = new List<MyModel>();
myValues.Add(new MyModel()
{
Date = new DateTime(2018, 1, 9, 15, 48, 46, DateTimeKind.Utc),
ShortId = 5,
AnotherShortId = 0,
TimeWindow = 0,
Value = 30,
fk_TenantId = 1006
});
myValues.Add(new MyModel()
{
Date = new DateTime(2018, 1, 29, 10, 11, 31, DateTimeKind.Utc),
ShortId = 5,
AnotherShortId = 0,
TimeWindow = 0,
Value = 14336,
fk_TenantId = 1006
});
myValues.Add(new MyModel()
{
Date = new DateTime(2018, 1, 29, 10, 11, 59, DateTimeKind.Utc),
ShortId = 5,
AnotherShortId = 0,
TimeWindow = 0,
Value = 30,
fk_TenantId = 1006
});
var windows = TimeWindow.CalculateTimeWindows(dateFrom, dateTo, myValues).ToList();
}编辑:空列表的性能问题上更新的代码
发布于 2019-06-14 12:12:12
var firstDate = (DateTime)datePropertyInfo.GetValue(stateModels.First()); if (firstDate < dateFrom) dateFrom = new DateTime(firstDate.Year, firstDate.Month, firstDate.Day, firstDate.Hour, 0, 0, DateTimeKind.Utc);
您接受了第一个stateModel,但是如果没有按日期订购呢?
for (itemPointer = itemPointer;itemPointer < stateModels.Count();itemPointer++)
itemPointer = itemPointer不是死胡同:
for (; itemPointer < stateModels.Count(); itemPointer++)CalculateTimeWindow<T>(..., ..., int i)的最后一个参数是用来自GetPropertiesAndDictionaries()的out int i参数初始化的,但是实际上没有使用这些值,而且第一次使用i作为迭代器索引设置为0,那么为什么要将它作为参数而不仅仅是局部变量呢?如果它不被使用的话,把它处理掉。
这是:
public static IEnumerable<T> CalculateTimeWindows<T>(DateTime dateFrom, DateTime dateTo, List<T> stateModels) where T : new()
{
if (stateModels.Count() == 0)
return new List<T>();
dateFrom = GetPropertiesAndDictionaries(dateFrom, stateModels, out PropertyInfo datePropertyInfo, out List<PropertyInfo> copyProperties, out PropertyInfo timeWindowIdentifier, out int size, out TimeWindowDictionary[] dictionaries, out int i);
return CalculateTimeWindow(dateFrom, dateTo, stateModels, 5, datePropertyInfo, copyProperties, timeWindowIdentifier, size, dictionaries, i)
.Concat(CalculateTimeWindow(dateFrom, dateTo, stateModels, 15, datePropertyInfo, copyProperties, timeWindowIdentifier, size, dictionaries, i))
.Concat(CalculateTimeWindow(dateFrom, dateTo, stateModels, 60, datePropertyInfo, copyProperties, timeWindowIdentifier, size, dictionaries, i));
}我会写成:
public static IEnumerable<T> CalculateTimeWindows<T>(DateTime dateFrom, DateTime dateTo, List<T> stateModels) where T : new()
{
if (stateModels == null || stateModels.Count() == 0)
yield break; // return new List<T>();
dateFrom = GetPropertiesAndDictionaries(
dateFrom,
stateModels,
out PropertyInfo datePropertyInfo,
out List<PropertyInfo> copyProperties,
out PropertyInfo timeWindowIdentifier,
out int size,
out TimeWindowDictionary[] dictionaries,
out int i);
byte[] windowDurations = { 5, 15, 60 };
foreach (byte duration in windowDurations)
{
foreach (T result in CalculateTimeWindow(dateFrom, dateTo, stateModels, duration, datePropertyInfo, copyProperties, timeWindowIdentifier, size, dictionaries, i))
{
yield return result;
}
}或作为:
public static IEnumerable<T> CalculateTimeWindows<T>(DateTime dateFrom, DateTime dateTo, List<T> stateModels) where T : new()
{
if (stateModels.Count() == 0)
return new List<T>();
dateFrom = GetPropertiesAndDictionaries(
dateFrom,
stateModels,
out PropertyInfo datePropertyInfo,
out List<PropertyInfo> copyProperties,
out PropertyInfo timeWindowIdentifier,
out int size,
out TimeWindowDictionary[] dictionaries,
out int i);
byte[] windowDurations = { 5, 15, 60 };
return windowDurations.SelectMany(wd =>
CalculateTimeWindow(
dateFrom,
dateTo,
stateModels,
wd,
datePropertyInfo,
copyProperties,
timeWindowIdentifier,
size,
dictionaries,
i));
}如果您想要添加一个新的时间窗口,它更易读和易于维护。
copyProperties = propInfos
.Where(
p =>
!p.GetCustomAttributes(typeof(IsTimeWindowIdentifier), true).Any()
&& !p.GetCustomAttributes(typeof(DoNotCopyIntoTimeWindow), true).Any()
&& !p.GetCustomAttributes(typeof(IsTimeWindowDate), true).Any()
&& !p.GetCustomAttributes(typeof(IsProcessedIntoTimeWindow), true).Any()
&& p.CanWrite
&& !p.GetMethod.IsVirtual).ToList();在上面,您可以通过检查数据不是什么属性来确定从哪些属性中获取负面数据。我想,我应该定义一个ValuePropertyAttribute来标记要从其中获取数据的属性。维护起来更容易,因为您将始终能够从该属性中看出哪些属性值是从对象上复制的。
在处理属性(设置和获取)时,PropertyInfos和strings (名称)之间的转换有点过大。
您可以通过更改以下内容的定义来减少这种情况:
public struct TimeWindowDictionary
{
public string Name { get; set; }
public Dictionary<NullObject<dynamic>, int> Dictionary { get; set; }
}至
public struct TimeWindowDictionary
{
public PropertyInfo PropertyInfo { get; set; }
public Dictionary<NullObject<dynamic>, int> Dictionary { get; set; }
}然后改变一切以适应这一点--例如:
var properties = propInfos.Where(p => p.GetCustomAttributes(typeof(IsProcessedIntoTimeWindow), true).Any()).Select(x => x.Name);
size = properties.Count();
dictionaries = new TimeWindowDictionary[size];
i = 0;
foreach (var property in properties)
{
dictionaries[i] = new TimeWindowDictionary() { Name = property };
i++;
}可以是:
dictionaries =
propInfos
.Where(p => p.GetCustomAttributes(typeof(IsProcessedIntoTimeWindow), true).Any())
.Select(p => new TimeWindowDictionary { PropertyInfo = p })
.ToArray();
size = dictionaries.Length;GetValue(...)可以是:
private static dynamic GetValue(object inputObject, PropertyInfo propertyInfo)
{
return propertyInfo.GetValue(inputObject);
//Type type = inputObject.GetType();
//System.Reflection.PropertyInfo propertyInfo = type.GetProperty(propertyName);
//return propertyInfo.GetValue(inputObject);
}SetValue(...)也可以具有以下签名:
private static void SetValue(object inputObject, PropertyInfo propertyInfo, object propertyVal)在CalculateStateSeconds<T>(...)中:
//PropertyInfo propertyInfo = tType.GetProperty(dictionary.Name);
var key = dictionary.PropertyInfo.GetValue(prevItem);
dictionary.Dictionary.TryGetValue(key, out int existingSeconds);
dictionary.Dictionary[key] = existingSeconds + seconds; private static DateTime GetPropertiesAndDictionaries<T>(
DateTime dateFrom,
List<T> stateModels,
out PropertyInfo datePropertyInfo,
out List<PropertyInfo> copyProperties,
out PropertyInfo timeWindowIdentifier,
out int size,
out TimeWindowDictionary[] dictionaries,
out int i) where T : new()
{当所有这些参数从一个方法传递到另一个方法时,我想我会为它们创建一个容器对象,并将其传递给它们。或者,您可以使用这些参数作为成员来使类状态化,这样您就可以完全避免使用方法的参数。当然,这些方法应该是非静态成员。您唯一的静态方法可能是这样的:
public static IEnumerable<T> GetTimeWindows<T>(DateTime dateFrom, DateTime dateTo, List<T> stateModels) where T : new()
{
TimeWindow timeWindow = new TimeWindow(dateFrom, dateTo, stateModels);
return timeWindow.Calculate();
}其中,TimeWindow类应该有一个适当的泛型类型参数。
发布于 2019-06-15 18:23:45
我不使用属性和反射,而是选择一组使用类型特定日期和值选择器方法作为参数的泛型方法。
首先,返回按时间段分组的项的方法:
public static IEnumerable<TimeSegmentItems<T>> GetItemsPerTimeSegment(
IEnumerable<T> items,
DateTime from,
DateTime to,
TimeSpan segmentDuration,
Func<T, DateTime> getTimestamp) { ... }
// Contains all items within a given time segment,
// as well as the last item from the preceding time segment
// (that item determines the initial state):
public class TimeSegmentItems<T>
{
public DateTime Start { get; }
public DateTime End { get; }
public IEnumerable<T> Items { get; }
}然后,获取给定时间段的主导值的实用方法:
public static TValue GetDominantValue<TItem, TValue>(
IEnumerable<TItem> items,
DateTime from,
DateTime to,
Func<TItem, DateTime> getTimestamp,
Func<TItem, TValue> getValue) { ... }它们结合在一起,可以让你做以下事情:
GetItemsPerTimeSegment(myValues, fromDate, toDate, TimeSpan.FromMinutes(5), m => m.Date)
.Select(segment => new MyModel {
Value = GetDominantValue(segment.Items, segment.Start, segment.End, m => m.Date, m => m.Value),
ActuallyAnEnum = GetDominantValue(segment.Items, segment.Start, segment.End, m => m.Date, m => m.ActuallyAnEnum),
});这是可以进一步简化的,但这是它的主旨。
将其与最初的方法进行比较:
dynamic都比较慢,特别是在使用dynamic时会丢失编译时检查。这种方法应该更简洁、更健壮和(更快)。发布于 2019-06-13 20:41:18
我发现了一些我觉得很奇怪的东西
for (i = 0; i < dictionary.Dictionary.Count; i++) { maxValue = dictionary.Dictionary.First(); for (j = 1; j < dictionary.Dictionary.Count; j++) { var valuePair = dictionary.Dictionary.ElementAt(j); if (valuePair.Value > maxValue.Value) maxValue = valuePair; } } var valToSet = maxValue.Key.Item;
这个看起来可以用
var valToSet = dictionary.Values.Max();如果您正在尝试获取带有最大值的键,则可以使用以下内容
var valToSet = dictionary.OrderByDescending(x => x.Value).First().Key;在https://stackoverflow.com/a/42508315/1214743上找到的,一定要对相同的值进行测试。
我还发现了一些我也会做不同的事情
if ( sameAsPrevious ) { var prevVal = GetValue(prevTimeWindow,dictionary.Name);if (valToSet == null & prevVal == null) {} else ((valToSet == null & prevVal != null != null& prevVal == null!valToSet!=null&prevVal== null) x!valToSet.Equals(PrevVal)=== false;}
您的if/else语句可以这样编写:
if (valToSet == null && prevVal == null)
{
}
else
{
sameAsPrevious = (valToSet == preval);
}因为
valToSet是空的,而另一个值是不一样的,那么它的工作方式也会相反。这将使sameAsPrevious == falsesameAsPrevious值是true。如果valToSet == preval将sameAsPrevious设置为true,则应该是在这种情况下。https://codereview.stackexchange.com/questions/222072
复制相似问题