removeAll 似乎影响了它的论点

发布于 2024-10-13 10:52:38 字数 4782 浏览 6 评论 0 原文

我编写了一个通用的 Partition 类(分区是将一个集合划分为不相交的子集,称为部分)。在内部,这是一个 Map 和一个 Map>,其中 Integers 是部件的标签。例如 partition.getLabel(T t) 给出 t 所在部分的标签,partition.move(T t, Integer label) 将 t 移动到分区由标签标记(在内部,它更新两个地图)。

但是我将对象集合移动到新部分的方法不起作用。看来 Set.removeAll() 正在影响它的参数。我认为问题类似于 ConcurrentModificationException,但我无法解决。抱歉,代码相当长,但我已经标记了问题所在(大约在中间),底部的输出应该清楚地表明问题是什么 - 最后分区处于非法状态。

    import java.util.*;

    public class Partition<T> {
  private Map<T,Integer> objToLabel = new HashMap<T,Integer>();
     private Map<Integer,Set<T>> labelToObjs = 
       new HashMap<Integer,Set<T>>();
     private List<Integer> unusedLabels;
     private int size;  // = number of elements

     public Partition(Collection<T> objects) {
      size = objects.size();
      unusedLabels = new ArrayList<Integer>();
      for (int i = 1; i < size; i++)
       unusedLabels.add(i);
      // Put all the objects in part 0. 
      Set<T> part = new HashSet<T>(objects);
      for (T t : objects)
       objToLabel.put(t,0);
      labelToObjs.put(0,part);
     }

     public Integer getLabel(T t) {
      return objToLabel.get(t);
     }
     public Set<T> getPart(Integer label) {
      return labelToObjs.get(label);
     }
     public Set<T> getPart(T t) {
      return getPart(getLabel(t));
     }

     public Integer newPart(T t) { 
      // Move t to a new part. 
      Integer newLabel = unusedLabels.remove(0); 
      labelToObjs.put(newLabel,new HashSet<T>()); 
      move(t, newLabel); 
      return newLabel; 
     } 
     public Integer newPart(Collection<T> things) {
      // Move things to a new part. (This assumes that 
      // they are all in the same part to start with.)
      Integer newLabel = unusedLabels.remove(0);
      labelToObjs.put(newLabel,new HashSet<T>());
      moveAll(things, newLabel); 
      return newLabel; 
     }
     public void move(T t, Integer label) {
      // Move t to the part "label". 
      Integer oldLabel = getLabel(t);
      getPart(oldLabel).remove(t);
      if (getPart(oldLabel).isEmpty())  // if the old part is 
       labelToObjs.remove(oldLabel);  // empty, remove it
      getPart(label).add(t);
      objToLabel.put(t,label);
     }
     public void moveAll(Collection<T> things, Integer label) {
      // Move all the things from their current part to label. 
      // (This assumes all the things are in the same part.)
      if (things.size()==0)  return;

      T arbitraryThing = new ArrayList<T>(things).get(0);
      Set<T> oldPart = getPart(arbitraryThing);

   // THIS IS WHERE IT SEEMS TO GO WRONG //////////////////////////
      System.out.println(" oldPart = " + oldPart);
      System.out.println(" things = " + things);
      System.out.println("Now doing oldPart.removeAll(things) ...");
      oldPart.removeAll(things);
      System.out.println(" oldPart = " + oldPart);
      System.out.println(" things = " + things);

      if (oldPart.isEmpty())
       labelToObjs.remove(objToLabel.get(arbitraryThing));

      for (T t : things)
       objToLabel.put(t,label);
      getPart(label).addAll(things);
     }

     public String toString() {
      StringBuilder result = new StringBuilder();
      result.append("\nPARTITION OF " + size + " ELEMENTS INTO " + 
        labelToObjs.size() + " PART");
      result.append((labelToObjs.size()==1 ? "" : "S") + "\n");
      for (Map.Entry<Integer,Set<T>> mapEntry : 
        labelToObjs.entrySet()) {
       result.append("PART " + mapEntry.getKey() + ": ");
       result.append(mapEntry.getValue() + "\n");
      }
      return result.toString();
     }

     public static void main(String[] args) {
      List<String> strings = 
        Arrays.asList("zero one two three".split(" "));

      Partition<String> p = new Partition<String>(strings);
      p.newPart(strings.get(3));  // move "three" to a new part
      System.out.println(p);

      System.out.println("Now moving all of three's part to the " + 
        "same part as zero.\n");
      Collection<String> oldPart = p.getPart(strings.get(3));
      //oldPart = Arrays.asList(new String[]{"three"});  // works fine!
      p.moveAll(oldPart, p.getLabel(strings.get(0)));
      System.out.println(p);
     }
    }

/* OUTPUT
PARTITION OF 4 ELEMENTS INTO 2 PARTS
PART 0: [two, one, zero]
PART 1: [three]

Now moving all of three's part to the same part as zero.

 oldPart = [three]
 things = [three]
Now doing oldPart.removeAll(things) ...
 oldPart = []
 things = []

PARTITION OF 4 ELEMENTS INTO 1 PART
PART 0: [two, one, zero]
*/

I have written a generic Partition class (a partition is a division of a set into disjoint subsets, called parts). Internally this is a Map<T,Integer> and a Map<Integer,Set<T>>, where the Integers are the labels of the parts. For example partition.getLabel(T t) gives the label of the part that t is in, and partition.move(T t, Integer label) moves t to the partition labelled by label (internally, it updates both the Maps).

But my method for moving a Collection of objects to a new part does not work. It seems that Set.removeAll() is affecting its argument. I think the problem is something like a ConcurrentModificationException, but I can't work it out. Sorry the code is rather long, but I have marked where the problem is (about half-way down), and the output at the bottom should make it clear what the problem is - at the end the partition is in an illegal state.

    import java.util.*;

    public class Partition<T> {
  private Map<T,Integer> objToLabel = new HashMap<T,Integer>();
     private Map<Integer,Set<T>> labelToObjs = 
       new HashMap<Integer,Set<T>>();
     private List<Integer> unusedLabels;
     private int size;  // = number of elements

     public Partition(Collection<T> objects) {
      size = objects.size();
      unusedLabels = new ArrayList<Integer>();
      for (int i = 1; i < size; i++)
       unusedLabels.add(i);
      // Put all the objects in part 0. 
      Set<T> part = new HashSet<T>(objects);
      for (T t : objects)
       objToLabel.put(t,0);
      labelToObjs.put(0,part);
     }

     public Integer getLabel(T t) {
      return objToLabel.get(t);
     }
     public Set<T> getPart(Integer label) {
      return labelToObjs.get(label);
     }
     public Set<T> getPart(T t) {
      return getPart(getLabel(t));
     }

     public Integer newPart(T t) { 
      // Move t to a new part. 
      Integer newLabel = unusedLabels.remove(0); 
      labelToObjs.put(newLabel,new HashSet<T>()); 
      move(t, newLabel); 
      return newLabel; 
     } 
     public Integer newPart(Collection<T> things) {
      // Move things to a new part. (This assumes that 
      // they are all in the same part to start with.)
      Integer newLabel = unusedLabels.remove(0);
      labelToObjs.put(newLabel,new HashSet<T>());
      moveAll(things, newLabel); 
      return newLabel; 
     }
     public void move(T t, Integer label) {
      // Move t to the part "label". 
      Integer oldLabel = getLabel(t);
      getPart(oldLabel).remove(t);
      if (getPart(oldLabel).isEmpty())  // if the old part is 
       labelToObjs.remove(oldLabel);  // empty, remove it
      getPart(label).add(t);
      objToLabel.put(t,label);
     }
     public void moveAll(Collection<T> things, Integer label) {
      // Move all the things from their current part to label. 
      // (This assumes all the things are in the same part.)
      if (things.size()==0)  return;

      T arbitraryThing = new ArrayList<T>(things).get(0);
      Set<T> oldPart = getPart(arbitraryThing);

   // THIS IS WHERE IT SEEMS TO GO WRONG //////////////////////////
      System.out.println(" oldPart = " + oldPart);
      System.out.println(" things = " + things);
      System.out.println("Now doing oldPart.removeAll(things) ...");
      oldPart.removeAll(things);
      System.out.println(" oldPart = " + oldPart);
      System.out.println(" things = " + things);

      if (oldPart.isEmpty())
       labelToObjs.remove(objToLabel.get(arbitraryThing));

      for (T t : things)
       objToLabel.put(t,label);
      getPart(label).addAll(things);
     }

     public String toString() {
      StringBuilder result = new StringBuilder();
      result.append("\nPARTITION OF " + size + " ELEMENTS INTO " + 
        labelToObjs.size() + " PART");
      result.append((labelToObjs.size()==1 ? "" : "S") + "\n");
      for (Map.Entry<Integer,Set<T>> mapEntry : 
        labelToObjs.entrySet()) {
       result.append("PART " + mapEntry.getKey() + ": ");
       result.append(mapEntry.getValue() + "\n");
      }
      return result.toString();
     }

     public static void main(String[] args) {
      List<String> strings = 
        Arrays.asList("zero one two three".split(" "));

      Partition<String> p = new Partition<String>(strings);
      p.newPart(strings.get(3));  // move "three" to a new part
      System.out.println(p);

      System.out.println("Now moving all of three's part to the " + 
        "same part as zero.\n");
      Collection<String> oldPart = p.getPart(strings.get(3));
      //oldPart = Arrays.asList(new String[]{"three"});  // works fine!
      p.moveAll(oldPart, p.getLabel(strings.get(0)));
      System.out.println(p);
     }
    }

/* OUTPUT
PARTITION OF 4 ELEMENTS INTO 2 PARTS
PART 0: [two, one, zero]
PART 1: [three]

Now moving all of three's part to the same part as zero.

 oldPart = [three]
 things = [three]
Now doing oldPart.removeAll(things) ...
 oldPart = []
 things = []

PARTITION OF 4 ELEMENTS INTO 1 PART
PART 0: [two, one, zero]
*/

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

烦人精 2024-10-20 10:52:38

使用我的调试器,我在removeAll之前放置了一个断点,我可以看到(正如我怀疑的那样)oldPart和things是同一个集合,因此删除所有元素会清除该集合。

Using my debugger I place a breakpoint before the removeAll and I can see (as I suspected) that oldPart and things as the same collection so removing all elements clears that collection.

我的奇迹 2024-10-20 10:52:38

您的代码非常混乱,但据我所知,oldPartthings 实际上是同一个对象。 Set.removeAll() 当然不会影响它的参数,除非它与调用它的对象是同一个对象:

public boolean removeAll(Collection<?> c) {
    boolean modified = false;

    if (size() > c.size()) {
        for (Iterator<?> i = c.iterator(); i.hasNext(); )
            modified |= remove(i.next());
    } else {
        for (Iterator<?> i = iterator(); i.hasNext(); ) {
            if (c.contains(i.next())) {
                i.remove();
                modified = true;
            }
        }
    }
    return modified;
}

更新:

消除这种情况的简单方法是使用副本moveAll() 方法中的 things 。事实上,这样的副本已经存在。

T arbitraryThing = new ArrayList<T>(things).get(0);

该行创建了 things 的副本,但随后立即丢弃了该副本。因此,我建议将其替换为:

ArrayList<T> thingsToRemove = new ArrayList<T>(things)
T arbitraryThing = thingsToRemove.get(0);

在该方法的其余部分中,将对 things 的所有引用替换为 thingsToRemove

Your code is extremely confusing but as far as I can work out, oldPart and things are actually the same object. Set.removeAll() certainly doesn't affect its argument unless it is the same object as it's invoked on:

public boolean removeAll(Collection<?> c) {
    boolean modified = false;

    if (size() > c.size()) {
        for (Iterator<?> i = c.iterator(); i.hasNext(); )
            modified |= remove(i.next());
    } else {
        for (Iterator<?> i = iterator(); i.hasNext(); ) {
            if (c.contains(i.next())) {
                i.remove();
                modified = true;
            }
        }
    }
    return modified;
}

Update:

The easy way to eliminate this is to use a copy of things in the moveAll() method. Indeed such a copy already exists.

T arbitraryThing = new ArrayList<T>(things).get(0);

This line creates but then instantly discards a copy of things. So I'd suggest replacing it with:

ArrayList<T> thingsToRemove = new ArrayList<T>(things)
T arbitraryThing = thingsToRemove.get(0);

And in the rest of the method, replace all references to things to thingsToRemove.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文