[英]How to use JAVA filter lambda to group Arraylist elements
I'm trying to group by JAVA array list elements.我正在尝试按 JAVA 数组列表元素进行分组。
I have an array of JSONObjects like the following, I want to group by the fields price and side and then sum the shares with new fileds buyShares and sellShares我有一个JSONObjects数组,如下所示,我想按字段price和side分组,然后将份额与新字段 buyShares和sellShares 相加
[{"shares":20,"side":"B","orderId":"001","price":"500"},
{"shares":20,"side":"S","orderId":"002","price":"501"},
{"shares":25,"side":"B","orderId":"003","price":"500"},
{"shares":10,"side":"S","orderId":"004","price":"501"},
{"shares":30,"side":"B","orderId":"005","price":"505"},
{"shares":35,"side":"B","orderId":"006","price":"505"},
{"shares":35,"side":"S","orderId":"007","price":"500"}]
and I want to group by price and by side to have something like the following:我想按价格和并排分组以获得以下内容:
[{"price":"500","buyShares":45, "sellShares":35}, {"sellShares":30,"price":"501"}, {"price":"505","buyShares":65}]
I'm using the following java code:我正在使用以下 java 代码:
ArrayList<JSONObject> aOrdersArray = new ArrayList<>(aOrders.values());
System.out.println(aOrdersArray);
List<JSONObject> test = aOrdersArray.stream()
.distinct()
.collect(Collectors.groupingBy(jsonObject -> jsonObject.getInt("price")))
.entrySet().stream()
.map(e -> e.getValue().stream()
.reduce((f1,f2) -> {
JSONObject h = new JSONObject();
h.put("price",f1.get("price"));
System.out.println(f1);
if (f1.get("side").equals("B") && f2.get("side").equals("B")) {
h.put("buyShares", f1.getInt("shares") + f2.getInt("shares"));
}
else if (f1.get("side").equals("S") && f2.get("side").equals("S")) {
h.put("sellShares", f1.getInt("shares") + f2.getInt("shares"));
}
else if (f1.get("side").equals("S")) {
h.put("sellShares", f1.get("shares"));
}
else if (f1.get("side").equals("B")) {
h.put("buyShares",f1.get("shares"));
}
else if (f2.get("side").equals("S")) {
h.put("sellShares", f2.get("shares"));
}
else if (f2.get("side").equals("B")) {
h.put("buyShares",f2.get("shares"));
}
return h;
}))
.map(f -> f.get())
.collect(Collectors.toList());
System.out.println(test);
and I get sometimes the following error我有时会收到以下错误
Exception in thread "main" org.json.JSONException: JSONObject["side"] not found.
What you're trying to do is compicated and errorprone because you've started on the wrong foot.你试图做的事情很复杂而且容易出错,因为你的出发点是错误的。
You shouldn't be having a JSONObject
in the first place.首先,您不应该拥有JSONObject
。 The data you do have in your JSON input is regular, and you want to operate on its contents.您在 JSON 输入中确实拥有的数据是常规的,您希望对其内容进行操作。
The right way to start is to make a java object that represents such a record, and then turn your JSON input into a proper, idiomatic java version of that. The right way to start is to make a java object that represents such a record, and then turn your JSON input into a proper, idiomatic java version of that. You want:你要:
@Value class Record {
int shares;
RecordKind kind;
int orderId;
int price;
public PriceGroup toGroup() {
return new PriceGroup(price,
kind == BUY ? shares : 0,
kind == SELL ? shares : 0);
}
}
@Value class PriceGroup {
int price;
int buyShares;
int sellShares;
public PriceGroup merge(PriceGroup other) {
if (other.price != this.price) throw new IllegalArgumentException();
return new PriceGroup(price,
this.buyShares + other.buyShares,
this.sellShares + other.sellShares);
}
}
NB: Uses Lombok's @Value
.注意:使用Lombok 的@Value
。 Assume this thing has a constructor, all fields are final, toString and equals and hashCode are in their right place, etc.假设这个东西有一个构造函数,所有的字段都是最终的,toString 和 equals 和 hashCode 都在正确的位置,等等。
Once you have the above two types, then you can convert your input JSON into a List<Record>
.一旦您拥有上述两种类型,您就可以将您的输入 JSON 转换为List<Record>
。 Armed with this List<Record>
, then and only then should you start down the path of mapping, grouping, etc.有了这个List<Record>
,你才应该开始映射、分组等的路径。
That error will then, of course, never occur, and your map/group code will be significantly easier to read.当然,该错误将永远不会发生,并且您的地图/组代码将更容易阅读。 Any typos you make will result in compile time errors.您所做的任何拼写错误都会导致编译时错误。 auto complete will work fine, etcetera: All the advantages.自动完成可以正常工作,等等:所有优点。
To turn JSON into a given type, use Jackson .要将 JSON 转换为给定类型,请使用Jackson 。 If you somehow just don't want to add that dependency (you should, really), then write a public static Record fromJson(JSONObject)
method in your Record class, and use .map(Record::fromJson)
to get to a stream of Record objects right away, and never go back to JSONObject.如果您只是不想添加该依赖项(您应该,真的),那么在您的 Record class 中编写一个public static Record fromJson(JSONObject)
方法,并使用.map(Record::fromJson)
来获取 Z28A27B44CFZAFD5E立即记录对象,并且永远不会将 go 返回 JSONObject。
List<Record> orders = ...;
List<PriceGroup> test = orders.stream()
.distinct()
.map(Record::toGroup)
.collect(Collectors.groupingBy(PriceGroup::getPrice))
.entrySet().stream()
.map(e -> e.getValue().stream()
.reduce((f1, f2) -> f1.merge(f2))
.collect(Collectors.toList());
Your code is now vastly simpler to read, all methods have proper names (it is getBuyShares()
, not .getInt("buyShares")
) and are discoverable via eg auto-complete, and you can for example individually test your merge
functionality.您的代码现在非常易于阅读,所有方法都有正确的名称(它是getBuyShares()
,而不是.getInt("buyShares")
)并且可以通过例如自动完成来发现,并且您可以例如单独测试您的merge
功能。
To use gson you can create a class:要使用gson ,您可以创建 class:
public class Share{
private int shares;
private String side;
private String orderId;
private String price;
//getters and setters
public int getShares() {
return shares;
}
public void setShares(int shares) {
this.shares = shares;
}
public String getSide() {
return side;
}
public void setSide(String side) {
this.side = side;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
then you can use this one to map object from json:然后你可以使用这个 map object 从 json :
String yourJson = "[{\"shares\":20,\"side\":\"B\",\"orderId\":\"001\",\"price\":\"500\"},{\"shares\":35,\"side\":\"S\",\"orderId\":\"007\",\"price\":\"500\"}]";
Gson gson = new Gson();
Type shareListType = new TypeToken<ArrayList<Share>>(){}.getType();
ArrayList<Share> userArray = gson.fromJson(yourJson, shareListType);
Whilst everybody is suggesting to use object mapped values to simplify your calculations, it is not a must per se.虽然每个人都建议使用 object 映射值来简化计算,但这本身并不是必须的。 The reasons I can think of are:我能想到的原因是:
@SuppressWarnings("unchecked")
final Iterable<JSONObject> input = (Iterable<JSONObject>) new JSONTokener(inputReader).nextValue();
final JSONArray actual = StreamSupport.stream(input.spliterator(), false)
.collect(Collectors.groupingBy(jsonObject -> jsonObject.getString("price")))
.entrySet()
.stream()
.map(entry -> entry.getValue()
.stream()
.collect(Collector.of(
() -> {
final JSONObject jsonObject = new JSONObject();
jsonObject.put("price", entry.getKey());
return jsonObject;
},
(a, e) -> {
final String side = e.getString("side");
final String key;
final BigDecimal shares;
switch ( side ) {
case "B":
key = "buyShares";
shares = e.getBigDecimal("shares");
break;
case "S":
key = "sellShares";
shares = e.getBigDecimal("shares");
break;
default:
throw new AssertionError(side);
}
if ( !a.has(key) ) {
a.put(key, shares);
} else {
a.put(key, a.getBigDecimal(key).add(shares));
}
},
(a1, a2) -> {
throw new UnsupportedOperationException();
},
Function.identity()
))
)
.collect(Collector.of(
JSONArray::new,
JSONArray::put,
(a1, a2) -> {
throw new UnsupportedOperationException();
},
Function.identity()
));
final JSONArray expected = (JSONArray) new JSONTokener(expectedReader).nextValue();
Assertions.assertEquals(expected.toString(), actual.toString()); // why? because neither JSONArray nor JSONObject implement equals/hashCode
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.