Java:如何在支持每组中最小,最大,平均,最后一种聚合的列表上进行聚合

时间:2014-07-09 10:37:46

标签: java collections guava lambdaj

我之前在MySQL本身做过这个,因为这似乎是正确的方法,但我必须做一些业务逻辑计算,然后需要在结果列表中应用组,任何建议在Java中执行此操作而不妥协性能(看过lambdaj,看起来由于代理的大量使用而放慢了速度,但是尝试过避风港)。

List<Item>包含name,value,unixtimestamp作为属性,并由数据库返回。 每条记录相隔5分钟。

我应该能够按动态采样时间分组,比如1小时,这意味着必须将每12条记录分组到一条记录中,然后在每组上应用最小值,最大值,平均值,最后一张。

任何建议表示赞赏。

[更新]让以下工作,但尚未对索引的地图值上的每个列表元素进行聚合。如您所见,我创建了一个列表映射,其中key是请求的整数表示示例时间(30是此处请求的示例)。

private List<Item> performConsolidation(List<Item> items) {
        ListMultimap<Integer, Item> groupByTimestamp = ArrayListMultimap.create();
        List<Item> consolidatedItems = new ArrayList<>();
        for (Item item : items) {
            groupByTimestamp.put((int)floor(((Double.valueOf(item.getItem()[2])) / 1000) / (60 * 30)), item);
        }
        return consolidatedItems;
    }

3 个答案:

答案 0 :(得分:1)

这是一个建议:

public Map<Long,List<Item>> group_items(List<Item> items,long sample_period) {
  Map<Long,List<Item>> grouped_result = new HashMap<Long,List<Item>>();
  long group_key;

  for (Item item: items) {
    group_key = item.timestamp / sample_period;
    if (grouped_result.containsKey(group_key)) {  
      grouped_result.get(group_key).add(item);
    }
    else {
      grouped_result.put(group_key, new ArrayList<Item>());
      grouped_result.get(group_key).add(item);
    }
  }
  return grouped_result;
}

sample_period是分组的秒数:3600 =小时,900 = 15分钟

地图中的键当然可以是相当大的数字(取决于采样周期),但此分组将保留组的内部时间顺序,即较低的键是按时间顺序排在第一位的键。如果我们假设原始列表中的数据按时间顺序排序,我们当然可以得到第一个键的值,然后从键中减去该值。这样我们就会得到键0,1等。在这种情况下,在for循环开始之前我们需要:

int subtract = items.get(0).timestamp / sample_period; //注意,因为两个数字一个整数/长整数我们有一个整数除法

然后在for循环中:

group_key = items.timestamp / sample_period - subtract;

这些行中的某些内容将起作用,即按照您的描述对数据集进行分组。然后,您可以将最小平均avg等应用于结果列表。但由于这些函数当然必须再次迭代单个组列表,最好将这些计算结合到此解决方案中,并使函数返回类似于Map的位置,其中Aggregates是包含avg,min,max字段的新类型,然后是组中的项目列表?至于表现,我认为这是可以接受的。这是一个简单的O(N)解决方案。 编辑:

确定只想添加更完整的解决方案/建议,同时计算最小值,最大值和平均值:

public class Aggregate {
  public double avg;
  public double min;
  public double max;

  public List<Item> items = new ArrayList<Item>();

  public Aggregate(Item item) {
    min = item.value;
    max = item.value;
    avg = item.value;
    items.add(item);
  }

  public void addItem(Item item) {
    items.add(item);
    if (item.value < this.min) {
      this.min = item.value;
    }
    else if (item.value > this.max) {
      this.max = item.value;
    }
    this.avg = (this.avg * (this.items.size() - 1) + item.value) / this.items.size(); 
  }
}

public Map<Long,Aggregate> group_items(List<Item> items,long sample_period) {

  Map<Long,Aggregate> grouped_result = new HashMap<Long,Aggregate>();
  long group_key;

  long subtract = items.get(0).timestamp / sample_period;
  for (Item item: items) {
    group_key = items.timestamp / sample_period - subtract;
    if (grouped_result.containsKey(group_key)) {  
      grouped_result.get(group_key).addItem(item);
    }
    else {
      grouped_result.put(group_key, new Aggregate(item));
    }
  }
  return grouped_result;
}

这只是一个粗略的解决方案。我们可能想要为聚合添加更多属性等。

答案 1 :(得分:0)

不考虑min / max / etc的计算,我注意到你的performConsolidation方法看起来可以使用Multimaps.index。只需传递项目和计算所需值的Function<Item, Integer>

return (int) floor(((Double.valueOf(item.getItem()[2])) / 1000) / (60 * 30));

这不会节省大量代码,但可以让您更容易看到一目了然的事情:index(items, timeBucketer)

答案 2 :(得分:0)

如果您可以使用我的xpresso项目,您可以执行以下操作:

让您的输入列表为:

list<tuple> items = x.list(x.tuple("name1",1d,100),x.tuple("name2",3d,105),x.tuple("name1",4d,210));

首先解压缩元组列表以获得列表元组:

tuple3<list<String>,list<Double>,list<Integer>> unzipped = x.unzip(items, String.class, Double.class, Integer.class);

然后你可以按照你想要的方式进行聚合:

x.print(x.tuple(x.last(unzipped.value0), x.avg(unzipped.value1), x.max(unzipped.value2)));

前面的内容将产生:

(name1,2.67,210)