2016-04-26 77 views
0

因此,我有一个实现Iterable的类来编写一组方法。他们中的大多数都很简单,但是,我很难为该课程写一个删除方法。为实现Iterable的类编写移除方法

import java.util.Iterator; 

public class Bag<Item> implements Iterable<Item> { 

    private Item[] data = (Item[]) new Object[5]; 

    // The size variable keeps track of how many items 
    private int size = 0; 

    public String toString() { 
     StringBuilder b = new StringBuilder("["); 
     for (Item i : this) 
      b.append(i + " "); 
     return b.toString() + "]"; 
    } 

    public void expandArray() { 
     int capacity = data.length * 2; 
     Item[] newData = (Item[]) new Object[capacity]; 
     for (int i = 0; i < data.length; i++) 
      newData[i] = data[i]; 
     data = newData; 
    } 

    public boolean add(Item x) { 
     if (size == data.length) 
      expandArray(); 
     data[size++] = x; 
     return true; 
    } 

    // return an Iterator for the bag 
    public Iterator<Item> iterator() { 
     return new BagIterator<Item>(); 
    } 

    // Iterator class 
    public class BagIterator<Item> implements Iterator<Item> { 

     private int i = 0; 

     public boolean hasNext() { 
      return i < size; 
     } 

     public Item next() { 
      return (Item) data[i++]; 
     } 

    } 

    public boolean contains(Item x) { 
     for (int i = 0; i < data.length; i++) { 
      if (data[i] == x) 
       return true; 
     } 
     return false; 
    } 

    public boolean addUnique(Item x) { 
     for (int i = 0; i < data.length; i++) { 
      if (data[i] == x) 
       return false; 
     } 
     this.size++; 
     this.add(x); 
     return true; 
    } 

    public boolean remove(Item x) { 

     Item lastItem = x; // holds x item 
     Item swap; // holds item to swap 
     int swapIndex; // holds index of item to swap 

     for (int i = 0; i < data.length; i++) { 
      if (data[i] == x) { 
       // Save the last item 
       lastItem = data[3]; 
       // Save the swapped item 
       swap = data[i]; 
       // Save the index of swapped item 
       swapIndex = i; 
       // move swap item to end of list 
       data[3] = swap; 
       // move last item to swap pos 
       data[swapIndex] = lastItem; 
       // remove last item in list 
       this.size--; 
       return true; 
      } 
     } 
     return false; 
    } 

    public boolean equals(Object o) { 
     Bag<Item> b = (Bag<Item>) o; 
     return false; 
    } 
} 

我的想法背后的删除方法如下;穿过袋子,找到要移除的物品,拿起同样的物品并移动到袋子的末端(与袋子中的最后一个物品交换位置),然后减小袋子的尺寸(认为它会移除它)。

现在很清楚,我的思维存在一些问题。 1)包仍然是它的原始大小。 2)袋子现在是无序的,这将在比较两个袋子时导致问题。

所以我的问题是,我怎样才能有效地编写一个删除方法,从我的包类中取出一个项目,而不会遇到前面提到的问题。

主要

public class Main { 

    public static void main (String[] args) { 

     Bag<Integer> bag = new Bag<>(); 

     bag.add(1); 
     bag.add(2); 
     bag.add(3); 
     bag.add(4); 

     System.out.println(bag); // [1, 2, 3, 4] 

     System.out.println(bag.remove(4)); // should remove 4 and return true **WORKING 
     System.out.println(bag.remove(1)); // should remove 1 and return true **WORKING 
     System.out.println(bag.remove(1)); // should NOT remove 1 and return false **NOT WORKING 

     System.out.println(bag); // [4 ] 

    } 
} 

回答

1

不知何故,我第一次完全误解了你的问题;我现在看到你甚至没有实施Iterator.remove()Iterable.remove()棘手,让我们来谈谈!您可能不想直接执行Iterable。它只有只有可以让你遍历一系列元素(也可以调用Iterator.remove()),没有别的。您的Bag类应该实现Collection(或扩展AbstractCollection),这是大多数Java数据结构实现(它指定.add(),.remove(),.size()等)的通用接口。

创建数据结构(例如bag)时要记住的关键事项是强制执行invariants。粗略地说,不变量是关于数据结构或方法如何表现的保证。例如,每当您在空数据结构上调用.size()时,它将返回0。一旦您致电.add(),所有未来呼叫.size()将返回1(直到您进一步修改数据结构)。这是一个不变的。这看起来很明显,但随着数据结构变得更加复杂,这些简单的保证可以使您的代码推理变得更加简单。

对你的具体问题。首先,将物品移动到最后的想法是一个很好的直觉。这比将其余元素复制到新索引中效率更高。

您的第一个担心,即Bag仍然是相同的大小,实际上不是问题。由于您递减size,数组末尾的项目被有效地删除。您可以将其设置为null以删除引用,但在正确性方面没有必要。相反,你应该看看你的其他方法,如,并确保他们尊重size。您应该只查看小于size的标记,而不是data.length,因为sizedata.length之间的值可能是垃圾。

第二个问题是关于比较两个Bag的问题,这暴露了另一个问题。你的班级实际上并没有提供一个不变量(再次有这个词),有史以来被订购,所以在.remove()重新排序它不会让事情变得比预先更糟糕。什么一个问题是你的类没有重载.equals().hashcode()(你所要做的既不或两者),这意味着没有两个Bag情况下都不能不顾自己的元素的顺序被视为等同。如果您打算对Bag进行比较,则需要正确实施这些方法。假设你想这样做,如何是绝对棘手。一般来说,没有有效的方法来比较两个无序的对象集合 - 唯一的选择是遍历这两个集合的所有元素,直到您验证它们包含相同的元素。这是O(n^2)或性能的二次方(即很慢)。

你有两个基本选择;确保你的后备数组总是被排序(这是一个不变的 - 在数组将被排序的每个方法的末尾)或者使用更高效的数据结构进行相等性检查,如Set。两种选择都有折衷。正确确保数组总是排序非常棘手; Java的TreeMap这样做,大多数人认为这是他们永远不想重新实现自己的代码类型。使用Set可让您高效地检查集合中是否存在元素,但是以防止重复元素为代价。 “包”数据结构通常允许重复,因此使用Set作为后备数据结构可能不适用于您的用例。使用Map<E, Integer>这个值是一个计数值可以工作,但是要记录更多的文件。

尽管如此,作为一个起点,标准蛮力.equals()的实现可能已经足够好了。良好软件工程的核心承租人是避免过度优化。从一些可行的事情开始,然后让它变得更好,而不是从一开始就试图使某些事情变得完美高效。

+0

我在代码的最底部添加了一个equals方法,这是我打算添加的东西。我应该补充一点,我需要在数据结构类中向我提供的现有代码实现最后4个方法(包含,addUnique,remove,equals)(所以我不能改变任何给我的东西)。很高兴听到我的第一个担忧不应该是one.I将添加更多的代码片段,以具体显示我正在遇到什么。感谢您花时间完成所有这些工作! – 23k

+0

您的'.equals()'方法出于几个原因是有问题的。最重要的是'.equals()'被破坏,除非'.hashcode()'也以相同的方式实现(即如果'a.equals(b)'然后'a.hashcode()'必须等于'b.hashcode ()'),但它也比默认的Object.equals()实现更差,如果你试图比较同一个对象('a.equals(a)'),它至少会返回'true' ) - 你的实现将返回false,这是不正确的。 – dimo414

+0

'.equals()'方法仍在我的待办事项列表中,我知道该实现当前不正确,现在我要求您忽略该方法。我在main中添加了更多的代码示例。基本上,如果物品不在包中,则remove应该返回false,但是,在同一个物品上调用remove两次会提供不正确的结果,这最初导致我相信该物品实际上并未被移除,只是隐藏起来。 – 23k

1

Iterator.remove()是一个真正棘手的方法来得到正确的,特别是在潜在的并发修改的面(见ConcurrentModificationException)。如果你看一些标准集合的实现,你会得到一些好的指针:ArrayList.Itr,LinkedList.ListItr,HashMap.HashIterator,TreeMap.PrivateEntryIteratorConcurrentSkipListMap.Iter是很好的起点。

一些关键的事情要记住:

  • 正确比速度快更重要 - 你总是可以实现Iterator.remove()为后盾集合的.remove()通话(与更新的需要Iterator的状态) 。这可能不是解决问题的最有效的方式,但是你会最有信心的。除非你确定这个天真的实现在你的应用程序中是一个严重的慢点,否则这可能就足够了。
  • Iterator.remove()是一个可选的方法 - 一个完全正确的实现只是throw new UnsupportedOperationException();。它不是迷人的,但它在绝大多数情况下都有效。如果你的用例不需要一个有效的中间迭代去除操作,那么简单地将它实现就可以是最明智的做法。
  • 扩展现有的抽象类 - JDK提供了许多抽象类作为起点(例如AbstractCollection),它们为许多棘手的方法提供合理的默认值。 Guava也提供了一些Forwarding* decorators,当Abstract*类不会这样做时可能会有用。如果您可以避免使用现有的行为,请不要重新发明轮子。
  • 依赖于支持类的共享行为 - 您会发现查看JDK的Iterator s的一件事是,他们尽可能少做工,宁愿将工作外包给支持类。例如,假设您知道它是索引后就可以有效地从集合中移除元素;定义一个private void removeByIndex(int)帮手,您的Collection.remove()Iterator.remove()方法都可以调用。您拥有的共享行为越多,您的Iterator即将推出的边缘案例越少。
  • 注意你的不变量 - 你的数据结构提供了一定的保证它的行为,并包括Java(.equals().hashcode())和集合框架(IterableCollection等)定义的附加要求。在实现像Iterator这样的帮助类时,请记住这些要求至关重要。如果您不能确定您的优化Iterator仍然尊重支持类提供的不变量,则应废弃优化并调用该类的公共方法。
  • 注意并发修改 - JDK集合的一个非常有用的特性是它们能够检测到并发修改(例如,即使在同一线程中,中间迭代也是如此,请致电Collection.remove())。虽然你的Iterator在技术上仍然是正确的,但没有检测到这些修改,它就不太有用,并且更容易容忍细微的错误。如果您要实现自己的Iterator,那么您应该尝试检测中间迭代中支持数据结构的更改。

最后但并非最不重要的是,番石榴自带MultisetMultimap实现。除非这是一份学校作业,否则没有理由实施您自己的Bag类型。

+0

谢谢,真的很有用的信息。不幸的是,您提供的一些实施方案对于成为大学一年级学生而言是极其沉重的。即使从我的具体示例开始,我仍然感到困惑。 – 23k

+1

是的,JDK类很复杂;不要觉得你需要完全理解它们。他们仍然是一个很好的参考只是看一眼;花时间去理解他们的一小部分课程是如何工作的,这将是一次非常有价值的学习经历。感谢您的经验背景,额外的信息传入! – dimo414