简体   繁体   English

如何使用 JAVA 过滤器 lambda 对 Arraylist 元素进行分组

[英]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数组,如下所示,我想按字段priceside分组,然后将份额与新字段 buySharessellShares 相加

    [{"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:我能想到的原因是:

  • you need to create a mapping for every reasonable type of JSON node (it can be automated to some extent though) -- this is totally fine if your mappings are widely used in your codebase, but probably (;) not worth doing it for ad-hoc one-time-in-use stuff;您需要为每种合理类型的 JSON 节点创建一个映射(尽管它可以在某种程度上自动化)——如果您的映射在您的代码库中被广泛使用,这完全没问题,但可能 (;) 不值得为广告做- 一次性使用的东西;
  • manipulating a JSON tree directly guarantees that bindings (de/serialization) do not affect the original JSON tree structure (ie in Gson (and most likely in Jackson) it is possible to implement a type adapter that does not guarantee a perfect round-trip for data mappings: in-JSON -> mappings -> out-JSON => in-JSON;= out-JSON; the same for mappings to JSON and back);操作 JSON 树直接保证绑定(反序列化)不会影响原始JSON 树结构(即在 Gson 中)对于杰克逊可能实现的类型适配器,它很可能不能保证完美的往返数据映射:in-JSON -> 映射 -> out-JSON => in-JSON;= out-JSON;与 JSON 的映射相同);
  • some JSON libraries (just like yours?) do not provide the object mapping/binding feature at all.一些 JSON 库(就像你的一样?)根本不提供 object 映射/绑定功能。
@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

如何使用jdk8对ArrayList进行分组过滤<object><div id="text_translate"><p>这是周一的 Object 电话。 它有两个属性,开始和结束,都是字符串。 结束属性可以包括“;” 像这样</p><pre>Mon m1 = new Mon(); m1.setStart("A"); m1.setEnd("a;b"); Mon m2 = new Mon(); m2.setStart("A"); m2.setEnd("b"); Mon m3 = new Mon(); m3.setStart("C"); m3.setEnd("c"); Mon m4 = new Mon(); m4.setStart("A"); m4.setEnd("c");</pre><p> 这是 ArrayList。</p><pre> List&lt;Mon&gt; list = new ArrayList(); list.add(m1);list.add(m2);list.add(m3);list.add(m4);</pre><p> 现在我想按起始属性和结束属性对列表进行分组,但基于末尾包含“;”,所以我也想将 m1 和 m2 放入同一组,因为 m1.end 包含 m2.end ,但 m4 在另一组中,因为 m4 开始和结束与 m1,m2 不同。</p><p> 我尝试使用 Collectors.groupingBy 按开始分组</p><pre>Map&lt;String, List&lt;Mon&gt;&gt; map = list.stream().collect(Collectors.groupingBy(d -&gt; d.getStart()));</pre><p> 但这不是我想要的 Output 的结果,如下所示:</p><pre> key: Aa;b OR Ab, value: m1, m2 key: Cc, value: m3 key: Ac, value: m4</pre><p> 有什么方法可以实现它,jdk8 stream function 更好</p><p>非常感谢</p></div></object> - How to use jdk8 to group by and filter the ArrayList<Object>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Java 8 Lambda-筛选两个ArrayList - Java 8 Lambda - filter two ArrayList 如何通过Java 8 Lambda在列表中使用地图过滤器 - How to use Map filter in list by Java 8 lambda 如何使用 lambda 在 Java 中过滤布尔数组 - How to use a lambda to filter a boolean array in Java 如何使用jdk8对ArrayList进行分组过滤<object><div id="text_translate"><p>这是周一的 Object 电话。 它有两个属性,开始和结束,都是字符串。 结束属性可以包括“;” 像这样</p><pre>Mon m1 = new Mon(); m1.setStart("A"); m1.setEnd("a;b"); Mon m2 = new Mon(); m2.setStart("A"); m2.setEnd("b"); Mon m3 = new Mon(); m3.setStart("C"); m3.setEnd("c"); Mon m4 = new Mon(); m4.setStart("A"); m4.setEnd("c");</pre><p> 这是 ArrayList。</p><pre> List&lt;Mon&gt; list = new ArrayList(); list.add(m1);list.add(m2);list.add(m3);list.add(m4);</pre><p> 现在我想按起始属性和结束属性对列表进行分组,但基于末尾包含“;”,所以我也想将 m1 和 m2 放入同一组,因为 m1.end 包含 m2.end ,但 m4 在另一组中,因为 m4 开始和结束与 m1,m2 不同。</p><p> 我尝试使用 Collectors.groupingBy 按开始分组</p><pre>Map&lt;String, List&lt;Mon&gt;&gt; map = list.stream().collect(Collectors.groupingBy(d -&gt; d.getStart()));</pre><p> 但这不是我想要的 Output 的结果,如下所示:</p><pre> key: Aa;b OR Ab, value: m1, m2 key: Cc, value: m3 key: Ac, value: m4</pre><p> 有什么方法可以实现它,jdk8 stream function 更好</p><p>非常感谢</p></div></object> - How to use jdk8 to group by and filter the ArrayList<Object> 如何对ArrayList中的元素进行分组<CustomObject> - How to group elements in ArrayList<CustomObject> 如何在Java中使用ArrayList - How to use ArrayList in Java 如何在 ArrayList 中搜索元素? - Java - How to search for elements in an ArrayList? - Java 如何将59个元素分组为arraylist中的一个元素? - how to group 59 elements into one element in arraylist? 如何分组重复? Lambda 8 Java - How to group duplicates? Lambda 8 Java 如何使用Java Lambda创建/初始化ArrayList的ArrayList - How to create/Initialize ArrayList of ArrayList using java Lambda
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM