Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Curve-Tool

介绍

概述

Curve-Tool 是一个简易的数据曲线计算工具,使用函数式编程的方式实现自定义公式计算、多曲线叠加计算、分组计算��,一些场景可提高开发效率,简化代码,使开发者专注核心计算而非维护数据关系。

使用场景

  • 轻量级数据可视化,不使用 Prometheus & Grafana 等专业工具。
  • 数据曲线的处理计算或不同类型数据曲线的叠加计算。
  • 关注核心计算,避免大量维护数据关系(如遍历、分组、条件)。

工具缺陷

  • 多曲线计算需要严格对齐数据,无法应对复杂处理场景。
  • 大数据量计算效率较低,后续考虑使用 Fork/Join 框架优化。
  • 大量函数式编程会造成更多调试分析的障碍。

快速开始

构建与依赖

本项目已工程化为 Maven 工程,可直接运行:

mvn test

如需以依赖方式使用(示例坐标,版本请按实际发布调整):

<dependency>
  <groupId>io.github.chocohql</groupId>
  <artifactId>curve-tool</artifactId>
  <version>0.1.0-SNAPSHOT</version>
</dependency>

创建曲线

ICurve 接口定义了曲线计算方法,它��承了 List 。Curve 类是该接口的通用实现类,它本身是对 ArrayList 的增强。

@Data
public class Data {
    private Double val;
    private Long timestamp;
}
// 获取数据集
List<Data> data = getData();
// 创建曲线,ICurve<T, V> 泛型1表示数据本身,泛型2表示用于计算的属性类型
ICurve<Data, Double> curve2 = new Curve<>(data);

单曲线处理

process() 方法针对的是曲线每一个元素,第一个表达式产生结果,第二个表达式对第一个表达式产生的结果进行消费,这样设计实际上是分离了处理和消费的逻辑,因此你可以传递 setter 方法引用对结果赋值,当然也可以自定义其他消费逻辑。

// 集合中每个数据的 val 属性 x2 后赋值 
curve.process(d -> d.getVal() * 2, Data1::setVal);
// 集合中每个数据的 val 属性 x2 后打印
curve.process(d -> d.getVal() * 2, (d, v) -> System.out.println(v));

三个参数的 process() 方法为需要传递条件断言,满足条件的才会执行后续定义的计算逻辑

// 集合中数据的 val 属性满足 > 0.5 这个条件的 x2 后赋值
curve.process(d -> d.getVal() > 0.5, d -> d.getVal() * 2, Data1::setVal)

如果你不想要拆分条件、处理和消费逻辑,那么你也可以直接使用单一参数的 process() 方法,类似于 peek() 方法

curve.process(d -> {
        // TODO ...
        d.setVal(d.getVal() * 2);
        // TODO ...
    });

多曲线叠加处理

如果你需要对两条曲线进行叠加处理,并且它们的在曲线集合中的数据一一对应(长度相同、下标对应),那么可以使用 biProcess() 方法,你可以传递不同类型的曲线集合,只需要保证泛型2类型一致(用于计算)。

@Data
public class Data1 {
    private Double val;
    private Long timestamp;
}

@Data
public class Data2 {
    private Double val;
    private Long timestamp;
}
// 获取数据集
ArrayList<Data1> data1 = getData1List();
ArrayList<Data2> data2 = getData2List();
// 创建曲线
ICurve<Data2, Double> curve2 = new Curve<>(data2);
ICurve<Data1, Double> curve1 = new Curve<>(data1);

curve1
        // 叠加计算
        .biProcess(curve2, (d1, d2) -> d1.getVal() + d2.getVal(), Data1::setVal)
        // 条件叠加计算
        .biProcess(curve2,
                (d1, d2) -> 1 <= d1.getVal() * d2.getVal(),
                (d1, d2) -> d1.getVal() + d2.getVal(),
                Data1::setVal)

对齐失败策略(更可控)

原有 biProcess 在长度不一致时会默认跳过计算(保持兼容)。如需显式策略,可使用带 IndexAlignmentPolicy 的重载:

curve1.biProcess(curve2,
        IndexAlignmentPolicy.TRUNCATE,
        (d1, d2) -> d1.getVal() + d2.getVal(),
        Data1::setVal);

可选策略:

  • SKIP_IF_MISMATCH:默认行为,长度不一致直接跳过
  • THROW:长度不一致直接抛异常
  • TRUNCATE:按最短长度截断计算
  • PAD_NULL:右侧不足时按 null 补齐(多余右侧忽略)

按 timestamp/key 对齐叠加(解决“严格下标对齐”限制)

当两条曲线的下标无法严格对齐,但可以通过 timestamp(或其他 key)匹配时,可使用 joinByKeyProcess:

curve1.joinByKeyProcess(curve2,
        Data1::getTimestamp,
        Data2::getTimestamp,
        KeyJoinType.LEFT,
        DuplicateKeyPolicy.THROW,
        MissingPointPolicy.SKIP,
        (d1, d2) -> d1.getVal() + d2.getVal(),
        Data1::setVal);

如果你希望拿到 join 结果用于进一步处理(而不是原地写回),可使用:

ICurve<KeyJoinRow<Long, Data1, Data2>, Object> joined =
        CurveJoins.joinByKey(curve1, curve2, Data1::getTimestamp, Data2::getTimestamp, KeyJoinType.FULL, DuplicateKeyPolicy.THROW);

常用算子(更贴近真实处理需求)

在 ICurve 上提供了一批常用、非冷门的算子,方便更偏“结果导向”的处理:

ICurve<Integer, Object> out = new Curve<Integer, Integer>(Arrays.asList(1,2,3,4))
        .filter(x -> x % 2 == 0)
        .map(x -> x * 10);

double ma = curve.movingAverage(Data::getVal, 10).get(curve.size() - 1);
ICurve<Double, Double> diff = curve.diff(Data::getVal);

创建分组曲线

如果你需要将数据分为多条曲线(分组/维度),且需要对不同组的曲线进行独立的处理,那么可以使用分组曲线,它可以帮你划分数据集隔离执行相同的 ICurve 操作。

ICurveGroup 接口继承了 Map ,它有三个泛型,第一个为分组 Key 的类型,后两个与 Curve 一致,CurveGroup 为通用实现类,提供了创建分组曲线的方法,需要传入数据集合和进行分组的规则。需要注意的是分组并不是指一个 key 对应了多条曲线,分组是针对的数据,它实际上是 Map<K, ICurve<T, V>> 的形式,在两个 CurveGroup 进行叠加计算时的分组才是多条曲线。

@Data
public class Data {
    private Double val;
    private Long timestamp;
    private String tag;
}
ArrayList<Data> data = data1();
// 根据 Data 的 tag 属性进行分组
ICurveGroup<String, Data, Double> curveGroup = CurveGroup.create(data, Data::getTag);

分组曲线处理

ICurveGroup 的处理逻辑和 ICurve 类似,它会在表达式中多提供一个 key 辅助你的处理,如果是两个分组曲线进行计算,会自动匹配相同分组的两条曲线,于是你可以将他当成普通的 ICurve 处理。

@Data
public class Data1 {
    private Double val;
    private Long timestamp;
    private String tag;
}

@Data
public class Data2 {
    private Double val;
    private Long timestamp;
    private String tag;
}
curveGroup1
        // 分组计算
        .process((tag, d) -> d.getVal() + 0.1, Data1::setVal)
        // 分组条件计算
        .process((tag, d) -> d.getVal() > 0.5, (tag, d) -> d.getVal() + 0.1, Data1::setVal)
        // 分组叠加计算
        .biProcess(curveGroup2, (tag, d1, d2) -> d1.getVal() + d2.getVal(), Data1::setVal)
        // 分组条件叠加计算
        .biProcess(curveGroup2,
                (tag, d1, d2) -> tag.equals("tagA") || d1.getVal() + d2.getVal() > 0.5,
                (tag, d1, d2) -> d1.getVal() * 2,
                Data1::setVal)

有时候你也许并不想直接对具体数据进行操作,而是想操作不同分组下的曲线,那么你也可以使用 forCurve 方法调出对应分组的曲线来自定义处理过程。

curveGroup1.forCurve((key, curve1) -> {/*TODO*/})

curveGroup1.forCurve(curveGroup2, (tag, curve1, curve2) -> {
            curve1.process(d ->{/*TODO*/});
            curve2.process(d ->{/*TODO*/});
        });

分组 join 策略(更安全)

分组叠加默认按左侧 key 遍历(保持兼容)。如需 INNER/FULL 或缺失 key 的处理策略,可使用重载:

curveGroup1.biProcess(curveGroup2,
        GroupJoinType.INNER,
        MissingCurvePolicy.THROW,
        (tag, d1, d2) -> d1.getVal() + d2.getVal(),
        Data1::setVal);

关系图

About

一个基于函数式编程的数据曲线计算工具。

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages