如何使用java filter lambda对arraylist元素进行分组

zi8p0yeb  于 2021-06-27  发布在  Java
关注(0)|答案(3)|浏览(303)

我尝试按java数组列表元素分组。
我有一个jsonobjects数组,如下所示,我想按price和side字段分组,然后用new fileds 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"}]

我想按价格和侧面进行分组,得到如下结果:

[{"price":"500","buyShares":45, "sellShares":35}, {"sellShares":30,"price":"501"}, {"price":"505","buyShares":65}]

我使用以下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);

我有时会犯以下错误

Exception in thread "main" org.json.JSONException: JSONObject["side"] not found.
0s7z1bwu

0s7z1bwu1#

你要做的事情很复杂而且容易出错,因为你开始的时候走错了路。
你不应该有一个 JSONObject 首先。json输入中的数据是常规的,您希望对其内容进行操作。
正确的方法是创建一个表示这样一个记录的java对象,然后将json输入转换成一个适当的、惯用的java版本。你想要:

@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);
  }
}

注意:使用Lombok山的 @Value . 假设这个东西有一个构造函数,所有字段都是final,tostring和equals以及hashcode都在正确的位置,等等。
一旦有了上述两种类型,就可以将输入的json转换为 List<Record> . 用这个武装起来 List<Record> ,然后,也只有这样,你才能开始走Map、分组等道路。
当然,这个错误将永远不会发生,并且您的Map/组代码将更易于阅读。任何输入错误都会导致编译时错误。自动完成将工作良好,等等:所有的优势。
要将json转换为给定类型,请使用jackson。如果你只是不想添加依赖项(你应该,真的),那么写一个 public static Record fromJson(JSONObject) 方法,并使用 .map(Record::fromJson) 立即访问记录对象流,再也不返回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());

您的代码现在非常容易阅读,所有方法都有专有名称(它是 getBuyShares() ,不是 .getInt("buyShares") )可通过自动完成等方式发现,例如,您可以单独测试 merge 功能。

mpbci0fu

mpbci0fu2#

虽然每个人都建议使用对象Map值来简化计算,但这本身并不是必须的。我能想到的原因是:
您需要为每一种合理类型的json节点创建一个Map(虽然在某种程度上它可以自动化)——如果您的Map在您的代码库中被广泛使用,那么这是完全正确的,但是可能(!)不值得为临时做一次在用的东西;
直接操作json树可以保证绑定(反序列化)不会影响原始的json树结构(即在gson中(很可能是在jackson中),可以实现一个类型适配器,它不能保证数据Map的完美往返:in json->mappings->out json=>in json!=输出json;对json和back的Map也是如此);
一些json库(就像你的一样?)根本不提供对象Map/绑定特性。

@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
e3bfsja2

e3bfsja23#

要使用gson,可以创建一个类:

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;
    }
}

然后您可以使用这个从jsonMap对象:

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);

相关问题