所以我有一个实现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 ]
}
}
答案 0 :(得分:1)
Iterator.remove()
是一个非常棘手的方法,尤其是面对潜在的并发修改(参见ConcurrentModificationException
)。如果你看一些标准集合的实现,你会得到一些好的指示:ArrayList.Itr
,LinkedList.ListItr
,HashMap.HashIterator
,TreeMap.PrivateEntryIterator
和ConcurrentSkipListMap.Iter
是很好的起点。
要记住的一些关键事项:
Iterator.remove()
作为对支持集.remove()
的调用(以及更新Iterator
根据需要说明。这可能不是解决问题的最有效方法,但你会对它最有信心。除非你确定这个天真的实现在你的应用程序中是一个严重的慢点,否则这很可能已经足够了。Iterator.remove()
是一种可选方法 - 完全正确的实现只是throw new UnsupportedOperationException();
。它并不富有魅力,但它适用于绝大多数情况。如果您的用例不需要有效的中间迭代删除操作,那么简单地将其保留为未实现可能是最明智的行动方式。AbstractCollection
),它们为许多棘手的方法提供了合理的默认值。 Guava还提供了一些Forwarding*
decorators,当Abstract*
类不能执行时,Multiset
会很有用。如果您可以使用现有行为,请不要重新发明轮子。Iterator
的一件事是,他们尝试尽可能少地工作,更喜欢外包工作到后备班。例如,假设您知道它的索引后可以有效地从集合中删除元素;定义private void removeByIndex(int)
和Collection.remove()
方法都可以调用的Iterator.remove()
帮助器。您拥有的共享行为越多,Iterator
引入的边缘情况就越少。.equals()
,.hashcode()
)和集合框架({{ 1}},Iterable
等)定义其他要求。在实现诸如Collection
实现之类的辅助类时,请务必牢记这些要求。如果您无法确定优化的Iterator
是否仍然尊重支持类提供的不变量,则应该废弃优化并调用类的公共方法。Iterator
技术上仍然可以在没有检测到这些修改的情况下保持正确,但它的用处要小得多,而且更容易忍受细微的错误。如果您要实现自己的Collection.remove()
,那么您将尝试在迭代过程中检测对后备数据结构的更改。最后但并非最不重要的是,Guava带有Multimap
和Bag
实现。除非这是学校作业,否则没有理由实施您自己的https://jsfiddle.net/qe1862ju/类型。
答案 1 :(得分: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
以删除参考,但在正确性方面不是必需的。相反,您应该查看其他方法,例如contains()
,并确保他们尊重size
。您应该只查看小于size
而非data.length
的指标,因为size
和data.length
之间的值可能是垃圾。
关于比较两个Bag
的第二个问题,暴露了另一个问题。你的课程实际上并没有提供一个不变的(那个词再次出现)数组将永远订购,所以在.remove()
期间重新排序它不会#39}使事情变得比以前更糟糕。 的问题是,您的班级不会覆盖.equals()
和.hashcode()
(您必须同时执行这两项操作),这意味着 no two {{无论元素的顺序如何,实例都可以被认为是等价的。如果您打算比较Bag
s,则需要正确实现这些方法。假设你想这样做,如何绝对是棘手的。一般来说,没有有效的方法来比较两个无序的对象集合 - 您唯一的选择是迭代两个集合的所有元素,直到您验证它们包含相同的元素。这是O(n ^ 2)或性能的二次方(即很慢)。
你有两个基本选择;确保您的后备数组始终排序(这是一个不变量 - 在数组将被排序的每个方法的末尾)或使用更有效的数据结构进行相等性检查,例如Bag
。两种选择都有权衡。正确地确保数组总是排序是非常棘手的; Java的TreeMap
做到了这一点,大多数人认为这是他们从未想过重新实现自己的代码类型。使用Set
可以有效地检查集合中是否存在元素,但代价是防止重复元素。 A&#34;包&#34;数据结构通常允许重复,因此使用Set
作为后备数据结构可能不适用于您的用例。使用值为计数的Set
可以起作用,但需要更多的文书工作来跟踪。
然而,作为一个起点,标准蛮力Map<E, Integer>
的实施可能已经足够好了。良好的软件工程的中心租户是避免过度优化。从有效的东西开始,然后让它变得更好,而不是试图从一开始就使某些东西变得非常有效。