按Java中的时间戳分组对象数组列表

时间:2017-09-14 02:18:41

标签: java list datetime timestamp grouping

我有一个List<对象[]> Object []中的一列是LocalDateTime。其他列是位置(String)和项目价格(Double)。

基本上,我的列表如下所示:

2017-01-01 02:05:00   NEWYORK   26.89
2017-01-01 02:10:00   NEWYORK   72.00
2017-01-01 02:15:00   NEWYORK   73.10
2017-01-01 02:20:00   NEWYORK   70.11
2017-01-01 02:25:00   NEWYORK   79.90
2017-01-01 02:30:00   NEWYORK   72.33
2017-01-01 02:35:00   NEWYORK   75.69
2017-01-01 02:40:00   NEWYORK   72.12
2017-01-01 02:45:00   NEWYORK   73.09
2017-01-01 02:50:00   NEWYORK   72.67
2017-01-01 02:55:00   NEWYORK   72.56
2017-01-01 03:00:00   NEWYORK   72.76

2017-01-01 02:05:00   BOSTON    26.89
2017-01-01 02:10:00   BOSTON    42.00
2017-01-01 02:15:00   BOSTON    23.10
2017-01-01 02:20:00   BOSTON    77.11
2017-01-01 02:25:00   BOSTON    49.92
2017-01-01 02:30:00   BOSTON    72.63
2017-01-01 02:35:00   BOSTON    73.19
2017-01-01 02:40:00   BOSTON    76.18
2017-01-01 02:45:00   BOSTON    83.59
2017-01-01 02:50:00   BOSTON    76.67
2017-01-01 02:55:00   BOSTON    52.06
2017-01-01 03:00:00   BOSTON    76.06

我需要做的是每个城市15分钟间隔的价格的时间加权平均值。与间隔关联的DateTime是最新的。因此,在上面的列表中运行我的算法会产生另一个看起来像这样的List:

01-01-2017 02:15:00   NEWYORK   57.33 (average of 2:05, 2:10 and 2:15)
01-01-2017 02:30:00   NEWYORK   74.11 (average of 2:20, 2:25 and 2:30)
01-01-2017 02:45:00   NEWYORK   73.63 (...)
01-01-2017 03:00:00   NEWYORK   72.60
01-01-2017 02:15:00   BOSTON    30.66 (average of 2:05, 2:10 and 2:15)
01-01-2017 02:30:00   BOSTON    66.55 (average of 2:20, 2:25 and 2:30)
01-01-2017 02:45:00   BOSTON    77.65 (...)
01-01-2017 03:00:00   BOSTON    68.26

我认为这样做的第一步是按15分钟的间隔和按城市划分记录。其余的只是迭代群体并得到平均值,我可以自己弄清楚。

我不知道如何对每个LocalDateTime进行分组,甚至在15分钟内进行分组。最后要提到的是可能缺少行。一些间隔可能是空的,在这种情况下我们可以完全忽略该间隔。非常感谢任何帮助。

UPDATE1 :我假设有一种更好的方法来对它们进行分组,而不是对它们进行排序,然后迭代每一个并比较时间戳。像这篇文章的第一个答案: How to Group Objects in a List into other Lists by Attribute using streams & Java 8?

UPDATE2 :此外,时间戳不一定每5分钟一次。它们可能是随机的,有些间隔可能有3或5行。

UPDATE3 :这不是重复,因为这个问题是关于分组而不是四舍五入。我知道如何向下舍入到15分钟是一种方法,但之后,我必须保持实时时间戳以执行时间加权平均值。这绝对不是这样做的方式。

4 个答案:

答案 0 :(得分:1)

设计一个类来保存数据而不是Object数组。您班级的每个对象都会包含时间戳,位置和商品价格。它也可以将时间戳四舍五入到整个15分钟;或者只是一种方法来实现这一目标。据我所知,你可以进行四舍五入(否则有this question: Round time by seconds中的灵感。)

使用此类,您可以使用the answer you linked to中的流进行分组和平均。如果您愿意,您甚至可以从List <Object[]>启动流,并在进一步处理之前将每个数组映射到一个对象中。

编辑我似乎明白,您更喜欢在没有行的类的情况下执行此操作。当然可以这样做:

private static List<Object[]> averageByQuarterOfHour(final int indexOfTime, 
        int otherGroupingIndex, int indexToAverage, List<Object[]> myList) {
    return myList.stream()
            .collect(Collectors.groupingBy(arr 
                    -> Arrays.asList(roundUpToWholeQuarterOfHour((LocalDateTime) arr[indexOfTime]), 
                            arr[otherGroupingIndex])))
            .entrySet()
            .stream()
            .map(e -> new Object[] { e.getKey().get(0), 
                    e.getKey().get(1), 
                    e.getValue().stream()
                            .map(arr -> (Number) arr[indexToAverage])
                            .mapToDouble(Number::doubleValue)
                            .average()
                            .getAsDouble() })
            .collect(Collectors.toList());
}

static LocalDateTime roundUpToWholeQuarterOfHour(LocalDateTime timeToRound) {
    LocalDateTime truncated = timeToRound.truncatedTo(ChronoUnit.MINUTES);
    int minute = truncated.getMinute();
    if (truncated.isEqual(timeToRound) && minute % 15 == 0) { // already on a whole quarter
        return timeToRound;
    }
    int minutesToAdd = 15 - (minute % 15);
    return truncated.plusMinutes(minutesToAdd);
}

将您的列表提交到averageByQuarterOfHour()会给出:

[2017-01-01T03:00, NEWYORK, 72.66333333333334]
[2017-01-01T02:15, NEWYORK, 57.330000000000005]
[2017-01-01T02:30, NEWYORK, 74.11333333333333]
[2017-01-01T02:45, NEWYORK, 73.63333333333334]
[2017-01-01T02:45, BOSTON, 77.65333333333334]
[2017-01-01T03:00, BOSTON, 68.26333333333334]
[2017-01-01T02:15, BOSTON, 30.663333333333338]
[2017-01-01T02:30, BOSTON, 66.55333333333333]

我会把排序留给你。

你可能想要三思而后行。行的类可能具有更好的建模优势,这会影响整个应用程序,而不仅仅是平均计算,但另一方面,它可能需要您分别为每个实体建模,以及实体的数据成立。在本地,一个类,甚至只是一个包含数组的辅助类和一个获取四分之一小时的方法,可能仍然使上面的代码更具可读性。

答案 1 :(得分:1)

这就是我最终做到的方式:

    HashMap<LocalDateTime, HashMap<Object, ArrayList<Object[]>>> map = new HashMap<LocalDateTime, HashMap<Object, ArrayList<Object[]>>>();

    for(int i = 0; i < data.size(); i++) {
        Object[] row = data.get(i);

        int minutes = ((LocalDateTime) row[timeFieldIndex]).getMinute();
        int minutesToAdd = ( newIntervalMinutes - minutes % newIntervalMinutes) % newIntervalMinutes;

        LocalDateTime roundedTime = ((LocalDateTime)row[timeFieldIndex]).plusMinutes(minutesToAdd).withSecond(0);

        HashMap<Object, ArrayList<Object[]>> sortFieldMap = map.get(roundedTime);
        if(sortFieldMap == null) {
            sortFieldMap = new HashMap<Object, ArrayList<Object[]>>();
            map.put(roundedTime, sortFieldMap);
        }

        ArrayList<Object[]> rowList = sortFieldMap.get(row[sortFieldIndex]);
        if(rowList == null) {
            rowList = new ArrayList<Object[]>();
            sortFieldMap.put(row[sortFieldIndex], rowList);
        }

        rowList.add(row);
    }

所以步骤是:

  1. 获取当前行
  2. 计算要添加的分钟数,向上舍入到最接近的15分钟
  3. 获取间隔的时间戳(variable roundedTime)
  4. 查看我们是否已有此间隔的条目。如果不这样做,请创建一个新的地图以按位置排序(sortFieldMap)。
  5. 查看我们是否已有此特定时间间隔和位置的行列表。如果不这样做,请在地图中创建一个新的行列表。
  6. 结构在那里,因此将行添加到组中。
  7. 现在,变量映射包含我的所有行,按时间间隔和位置分组。

答案 2 :(得分:0)

虽然有这个问题的解决方案,我试着自己写。 也许是另一种解决方案?

public static void main(String[] args) {

    List<Demo> list = new ArrayList<>();
    LocalDateTime now = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES);
    Demo demo = new Demo(Timestamp.valueOf(now),"Beijing",66.40);
    list.add(demo);

    demo = new Demo(Timestamp.valueOf(now.plusMinutes(10)),"Beijing",60.40);
    list.add(demo);

    demo = new Demo(Timestamp.valueOf(now.plusMinutes(16)),"Beijing",80.40);
    list.add(demo);

    Map map = list.stream().collect(Collectors.groupingBy(e -> roundUp(e.timestamp.toLocalDateTime())));

    System.out.println(map);
}


public static String roundUp(LocalDateTime time){
    LocalDateTime temp;
    int minutesSinceLastWhole15 = time.getMinute() % 15;
    if (minutesSinceLastWhole15 >= 8) { // round up
        temp = time.plusMinutes(15 - minutesSinceLastWhole15);
    } else { // round down
        temp = time.minusMinutes(minutesSinceLastWhole15);
    }
    return temp.toString();
}

static class Demo{
    Timestamp timestamp;
    String city;
    Double price;

    public Demo(Timestamp timestamp, String city, Double price){
        this.timestamp = timestamp;
        this.city = city;
        this.price = price;
    }

    @Override
    public String toString() {
        return "{ timestamp:"+timestamp+" city:"+city+" price:"+price+" }";
    }
}

答案 3 :(得分:-1)

为了简化演示,我将你的Object []转换为CityValue

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class HelloWorld{

     public static void main(String []args) throws Exception {
        makeList(900).values().stream().forEach(cityValues -> {
            cityValues.forEach(cityValue -> {
                System.out.println(cityValue.toString());
            });
            System.out.println(" ------- ");
        });
     }


     private static Map<Double, List<CityValue>>  makeList(int second) throws ParseException {
        List<CityValue> CityValues = new ArrayList<>();
        CityValues.add(new CityValue("2017-01-01 02:05:00", "NEWYORK", 26.89));
        CityValues.add(new CityValue("2017-01-01 02:10:00", "NEWYORK", 72.00));
        CityValues.add(new CityValue("2017-01-01 02:15:00", "NEWYORK", 73.10));
        CityValues.add(new CityValue("2017-01-01 02:20:00", "NEWYORK", 70.11));
        CityValues.add(new CityValue("2017-01-01 02:25:00", "NEWYORK", 79.90));
        CityValues.add(new CityValue("2017-01-01 02:30:00", "NEWYORK", 72.33));
        CityValues.add(new CityValue("2017-01-01 02:35:00", "NEWYORK", 75.69));
        CityValues.add(new CityValue("2017-01-01 02:40:00", "NEWYORK", 72.12));
        CityValues.add(new CityValue("2017-01-01 02:45:00", "NEWYORK", 73.09));
        CityValues.add(new CityValue("2017-01-01 02:50:00", "NEWYORK", 72.67));
        CityValues.add(new CityValue("2017-01-01 02:55:00", "NEWYORK", 72.56));
        CityValues.add(new CityValue("2017-01-01 03:00:00", "NEWYORK", 72.76));

        CityValues.add(new CityValue("2017-01-01 02:05:00", "BOSTON",  26.89));
        CityValues.add(new CityValue("2017-01-01 02:10:00", "BOSTON",  42.00));
        CityValues.add(new CityValue("2017-01-01 02:15:00", "BOSTON",  23.10));
        CityValues.add(new CityValue("2017-01-01 02:20:00", "BOSTON",  77.11));
        CityValues.add(new CityValue("2017-01-01 02:25:00", "BOSTON",  49.92));
        CityValues.add(new CityValue("2017-01-01 02:30:00", "BOSTON",  72.63));
        CityValues.add(new CityValue("2017-01-01 02:35:00", "BOSTON",  73.19));
        CityValues.add(new CityValue("2017-01-01 02:40:00", "BOSTON",  76.18));
        CityValues.add(new CityValue("2017-01-01 02:45:00", "BOSTON",  83.59));
        CityValues.add(new CityValue("2017-01-01 02:50:00", "BOSTON",  76.67));
        CityValues.add(new CityValue("2017-01-01 02:55:00", "BOSTON",  52.06));
        CityValues.add(new CityValue("2017-01-01 03:00:00", "BOSTON",  76.06));
        return CityValues.stream().collect(Collectors.groupingBy(cityValue -> Math.ceil(cityValue.getDate().getTime() / (second * 1000))));
    }
}

class CityValue {
    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-DD hh:mm:ss");
    Date date;
    String cityName;
    float price;

    public CityValue(String date, String cityName, double price) throws ParseException {
        this.cityName = cityName;
        this.price = (float) price;
        this.date = sdf.parse(date);
    }

    public Date getDate() {
        return this.date;
    }

    @Override
    public String toString() {
        return sdf.format(this.date) + " " + this.cityName + "  " + this.price;
    }
}