有没有办法使用流或其他方式对包含两个类的ArrayList进行排序?

huangapple go评论65阅读模式
英文:

Is there any way to sort an arraylist which have 2 classes by stream or whatever?

问题

我知道这个问题可能相当复杂,但这是我必须实现的内容...
假设我有一个'entities'(实体)列表:

  //它们都在PlayGround类中
  public static interface Entity{
    void turn(PlayGround g);
    int x();
    int y();
    int radius();
  }
 
  public ArrayList<Entity> entities(){return entities;}
  ArrayList<Entity> entities=new ArrayList<>();

然后我有两个子类,Rock(岩石)和Miner(矿工),它们都继承自Entity:

//Record是Java 14中的新功能,类似于使用字段创建新类
@SuppressWarnings("preview")
record Rock(int x, int y, int radius) implements Entity, Comparable<Rock>{
  ...
  public int compareTo(Rock o) {return Integer.compare(radius, o.radius);}
  public int getRadius() {return radius;};
}

class Miner implements Entity{
  int x = 100;
  int y = 100;
  public int x(){return x;}
  public int y(){return y;}
  public int radius(){return 15;}
  ...
}

在这种情况下,我们可以像这样添加元素:

    var mg = new PlayGround();
    mg.entities().add(new Rock(200, 200, 50));
    mg.entities().add(new Rock(250, 100, 40));
    mg.entities().add(new Rock(200, 350, 80));
    mg.entities().add(new Miner()); 

然而,我想要对ArrayList中的Rock类按半径进行排序,在示例中,ArrayList将从[Rock(50),Rock(40),Rock(80),Miner()]变为[Rock(80),Rock(50),Rock(40),Miner()]。

根据您之前的尝试,我尝试了以下方法:

    var es = mg.entities();
	@SuppressWarnings("unchecked")
	ArrayList<Entity> ess = es.stream()
     .filter(Rock.class::isInstance)
     .map(Rock.class::cast)
     .sorted(Comparator.comparingInt(Rock::getRadius))
     .collect(Collectors 
             .toCollection(ArrayList::new));

但是它只比较了Rock类并在结果中过滤掉了Miner。有没有方法可以实现这一点?谢谢!

英文:

I know this question might be ridiculous complicated but this is something I must achieve...
Assume I have a list of 'entities':

  //they are all in the PlayGround class
  public static interface Entity{
    void turn(PlayGround g);
    int x();
    int y();
    int radius();
    }
 
  public ArrayList&lt;Entity&gt; entities(){return entities;}
  ArrayList&lt;Entity&gt; entities=new ArrayList&lt;&gt;();

Then I have two subclasses rock and miner extends from Entity:

//Record is a new feature in Java14 which similar to create a new class with fields
@SuppressWarnings(&quot;preview&quot;)
record Rock(int x,int y, int radius) implements Entity, Comparable&lt;Rock&gt;{
  ...
  public int compareTo(Rock o) {return Integer.compare(radius,o.radius);}
  public int getRadius() {return radius;};
}

class Miner implements Entity{
  int x=100;int y=100;
  public int x(){return x;}
  public int y(){return y;}
  public int radius(){return 15;}
...
}

In that case, we can add elements like this:

    var mg=new PlayGround();
    mg.entities().add(new Rock(200,200,50));
    mg.entities().add(new Rock(250,100,40));
    mg.entities().add(new Rock(200,350,80));
    mg.entities().add(new Miner()); 

However, I want to sort the Rock class in the Arraylist<Entity> by the number of radius, in the example, the ArrayList will from[Rock(50), Rock(40), Rock(80), Miner()] to [Rock(80), Rock(50), Rock(40), Miner()].

From the former query I tried:

    var es = mg.entities();
	@SuppressWarnings(&quot;unchecked&quot;)
	ArrayList&lt;Entity&gt; ess = es.stream()
     .filter(Rock.class::isInstance)
     .map(Rock.class::cast)
     .sorted(Comparator.comparingInt(Rock::getRadius))
     .collect(Collectors 
             .toCollection(ArrayList::new));

But it only compares the rock class and filter Miner out in the result.
Is there any way to achieve this? Thanks!

答案1

得分: 3

不需要涉及流API。

你对问题的描述不清楚。对于不清楚的任务,按照预期,无法将不清楚的任务写成代码,因为计算机需要非常具体的指令。

不清楚的部分是:在你的排序算法中,矿工是如何相关的?

对于给定的 [rock5,rock2,miner1,rock10],有许多可能的答案:

  • 将矿工保持在原位,围绕他们对岩石进行排序,变为 [rock2,rock5,miner1,rock10]。这无法在短时间内用少量代码完成。这强烈暗示您不想要一个实体的单一列表,而是两个列表(一个是矿工,一个是实体)。
  • 将所有矿工移到末尾:[rock2,rock5,rock10,miner1]
  • 将所有矿工移到开头:[miner1,rock2,rock5,rock10]

它们都相对复杂;你不能通过链接几个 Comparator::comparingInt 或类似的方法来实现。您可以编写自己的比较器来完成这项工作,但请注意,从技术上讲,比较器应保持一致。如果比较器表示“a在b下方”,那么如果稍后询问“b和a”,它必须回答“b在a上方”,否则事情会变得非常奇怪。这使事情变得复杂,实际上没有一个“嗯,随便了,不要尝试对这些东西进行排序”的选项。对于对ArrayList进行排序,您可以返回“我认为这些在同一级别”,但请注意,TreeMapTreeSet 不能这样做(任何两个相同级别的事物被视为相同的键)。

这种一致性在这里确实是一个问题。如果你说所有矿工在所有岩石的同一级别,你不能这样做。如果 rock1 在 rock2 下方,但 rock1 与 miner1 处于同一级别,而 miner1 与 miner2 处于同一级别,破裂,不一致。

因此,你的比较器必须变得相当复杂:

es.sort((a, b) -> {
    // 矿工彼此处于同一级别。
    if (a instanceof Miner && b instanceof Miner) return 0;
    // 矿工位于所有岩石下方
    if (a instanceof Miner) return -1;
    if (b instanceof Miner) return +1;
    // 它们都是岩石。
    return Integer.compare(((Rock) a).getRadius(), ((Rock) b).getRadius());
});

这个比较器是一致的,并将所有矿工排序到前面。

如果你想围绕矿工进行排序,我认为 list.sort(或 Collections.sort)无法做到这一点,所以打开你喜欢的搜索引擎,重新创建TIMsort或quicksort或其他类似的算法吧。

英文:

There's no need to involve stream API.

Your description of the problem is unclear. As is supposed to happen with unclear tasks, it is not possible to write an unclear task in code, as computers require very specific instructions.

The part that is unclear is: How do miners relate in this sorting algorithm of yours?

Given [rock5,rock2,miner1,rock10], there are many answers possible:

  • Leave the miners precisely where they are, and sort the rocks 'around' them. becomes [rock2,rock5,miner1,rock10]. This cannot be done in a short amount of code. It strongly suggests you don't want a single list of entities, but 2 lists (one of miners, one of entities).
  • Shift all the miners to the end: [rock2,rock5,rock10,miner1]
  • Shift all miners to the beginning: [miner1,rock2,rock5,rock10].

They're really all relatively tricky; you can't do this by chaining a few Comparator::comparingInt and friends together. You can write your own comparator that does the job, but note that technically comparators should remain consistent. If a comparator says that 'a is below b', then if later asked about 'b and a', it MUST answer that 'b is above a', or things break in bizarre ways. That complicates matters, there isn't really a 'eh, whatever, don't try to order these things' option. For sorting arraylists you can return 'I consider these at the same level', but note that TreeMap and TreeSet can't do that (any 2 things at the same level are considered the same key).

That consistency is really a problem here. If you say that all miners are at the same level of all rocks, you can't do that. If rock1 is below rock2, but rock1 is at the same level as miner1, and miner1 is at the same level as miner2, boom, inconsistent.

So, your comparator has to get pretty complicated:

es.sort((a, b) -&gt; {
    // miners are at the same level as each other.
    if (a instanceof Miner &amp;&amp; b instanceof Miner) return 0;
    // miners are below all rocks
    if (a instanceof Miner) return -1;
    if (b instanceof Miner) return +1;
    // they&#39;re both rocks.
    return Integer.compare(((Rock) a).getRadius(), ((Rock b).getRadius());
});

that one would be consistent, and sorts all miners to the front.

If you want to sort 'around' the miners, I don't think list.sort (or Collections.sort) is capable of that, so fire up your favorite search engine and recreate TIMsort or quicksort or whatnot.

huangapple
  • 本文由 发表于 2020年9月27日 11:09:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/64084407.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定