Item将工作表中的对象分组items为对象列表Group,每个组存储与一分钟相关的数据。
组内的数据value必须分组为给定间隔的算术平均值(以秒为单位)。如果源工作表中没有当前间隔 N 的数据,则重复上一个间隔的值。如果组中的第一个区间没有足够的数据,则复制前一组的最后一个区间。
间隔始终小于或等于 60 秒。并将一分钟分成相等的秒数,没有余数。
public class Item {
private int id;
private String time;
private double value;
// Getters, setters, constructors...
}
public class Group {
private int id;
private List<Item> items;
// Getters, setters, constructors...
}
对于原始列表,持续时间间隔 = 30:
List<Item> items = List.of(
new Item(1, "19/09/2020 1:03:00 AM", 1.0),
new Item(2, "19/09/2020 1:03:03 AM", 1.3),
new Item(3, "19/09/2020 1:03:15 AM", 1.1),
new Item(4, "19/09/2020 1:03:47 AM", 1.2),
new Item(5, "19/09/2020 1:03:57 AM", 1.6),
new Item(6, "19/09/2020 1:04:04 AM", 1.8),
new Item(7, "19/09/2020 1:04:43 AM", 1.9),
new Item(8, "19/09/2020 1:04:44 AM", 2.1),
new Item(9, "19/09/2020 1:05:30 AM", 1.8),
new Item(10, "19/09/2020 1:05:46 AM", 2.3)
);
结果应该是:
List.of(
new Group(1, List.of(
new Item(1, "19/09/2020 1:03:00 AM", 1.13), // первые 30 сек value = (1.0 + 1.3 + 1.1) / 3
new Item(2, "19/09/2020 1:03:30 AM", 1.4) // вторые 30 сек value = (1.2 + 1.6) / 2
)),
new Group(2, List.of(
new Item(1, "19/09/2020 1:04:00 AM", 1.8), // первые 30 сек
new Item(2, "19/09/2020 1:04:30 AM", 1.5) // вторые 30 сек
)),
new Group(2, List.of(
new Item(1, "19/09/2020 1:05:00 AM", 1.5), // для первых 30 сек данных нет, в результат пойдет предыдущее значение
new Item(2, "19/09/2020 1:05:30 AM", 2.05) // вторые 30 сек
)));
签名List<Group> transform(List<Item> src, int intervalSize)
到目前为止,我所做的只是创建一个空的组列表
public class Transformer {
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss a", Locale.ENGLISH);
@SneakyThrows
public List<Group> transform(List<Item> source, int intervalSize) {
List<Group> target = getEmptyGroups(source);
return target;
}
@SneakyThrows
private List<Group> getEmptyGroups(List<Item> source) {
Item start = source.get(0);
Calendar startTime = Calendar.getInstance();
startTime.setTime(formatter.parse(start.getTime()));
Item end = source.get(source.size() - 1);
Calendar endTime = Calendar.getInstance();
endTime.setTime(formatter.parse(end.getTime()));
long groupTotal = ChronoUnit.MINUTES.between(startTime.toInstant(), endTime.toInstant()) + 1;
List<Group> groups = new ArrayList<>();
IntStream.iterate(0, i -> i < groupTotal, i -> i + 1)
.forEachOrdered(i -> {
Group group = new Group();
group.setId(i + 1);
groups.add(group);
});
return groups;
}
}
首先,您的预期值有问题。对象 2.2 有一个时间戳
"19/09/2020 1:04:00 AM",它应该"19/09/2020 1:04:30 AM",对吧?对象 3.2 也是如此。顺便说一句,最后一个组号应该是 3,而不是 2,对吧?对象 2.2 的平均值尚不清楚。你
1.5,可是应该的(1.9+2.1)/2 -> 2.0。现在做决定。
如果您以一种简单的方式直接决定,而不进行内存优化,那么我会这样做。
将期间拆分为不重叠的窗口。例如,在您的情况下,60 秒的时间段分为两个窗口。进一步在一个无限循环中——如果对象属于当前区间,则将其添加到相应的窗口中,否则,关闭区间并开始下一个,直到对象在区间内。
关闭窗口意味着
Item使用与窗口开始相对应的时间戳和等于窗口时间平均值的值。窗口代码。
计算平均值的代码:
该类
Average由间隔的开始和时段内的窗口数参数化。Window.PERIOD周期的持续时间由以毫秒为单位的常数给出。如果需要,该方法public void add(Item it)会移动间距,然后将对象添加到适当的窗口。当间隔移动时,平均值被添加到列表中items。为简单起见,我将对象作为一个整体写在一个列表中,并且仅在必要时将它们分组收集,但可以在
finishPeriod.这个解决方案有什么问题。
可以通过在间隔内滑动的单个窗口来解决问题。
在数据中的时间戳之间存在较大差距的情况下,可以不保存 "empty"
Item,而只保存有数据的那些间隔。完整代码:https ://github.com/pakuula/StackOverflow/tree/main/java/1450111
一个周期内运行两个区间的示例:
结论: