2016-04-26 2 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, так как значения между size и data.length могут быть мусором.

Ваша вторая проблема, касающаяся сравнения двух Bag s, раскрывает еще одну проблему. Ваш класс фактически не предоставляет инвариант (есть это слово снова), что массив будет когда-либо быть заказанным, поэтому его переупорядочение во время .remove() не делает вещи хуже, чем они были заранее. Что является проблемой является ваш класс не переопределяет .equals() и .hashcode() (вы должны сделать ни или оба), что означает, что никакие два Bag экземпляры никогда не могут считаться эквивалентными, независимо от порядка их элементов. Если вы собираетесь сравнить Bag s, вам необходимо правильно реализовать эти методы. Предполагая, что вы хотите это сделать, как определенно сложно. Вообще говоря, нет эффективного способа сравнить два неупорядоченных набора объектов - ваш единственный выбор - перебрать все элементы обеих коллекций, пока вы не проверите, что они содержат одни и те же элементы. Это O (n^2) или квадратичное по производительности (т. Е. Довольно медленно).

У вас есть два основных варианта; убедитесь, что ваш массив поддержки всегда отсортирован (это инвариант - в конце каждого метода будет отсортирован массив) или используйте более эффективную структуру данных для проверок равенства, таких как Set. Оба варианта имеют компромиссы. Правильное обеспечение массива всегда сортируется очень сложно; Java TreeMap делает это, и большинство людей считают это типом кода, который они никогда не захотят повторно реализовать. Использование Set позволяет эффективно проверять, существует ли элемент в вашей коллекции, но за счет предотвращения дублирования элементов. Структура данных «сумка» обычно допускает дубликаты, поэтому использование Set в качестве структуры данных резервного копирования может быть неприемлемым для вашего варианта использования. Использование Map<E, Integer>, где значение будет считать, будет работать, но это немного больше документов, чтобы отслеживать.

Тем не менее, в качестве отправной точки стандартная грубая сила .equals() может быть достаточно хорошей. Центральным арендатором хорошей разработки программного обеспечения является избежание чрезмерной оптимизации. Начните с чего-то, что работает, а затем сделайте его лучше, вместо того, чтобы пытаться сделать что-то совершенно эффективное с самого начала.

+0

Я добавил метод equals в самом конце моего кода, это то, что я планировал добавить. Я должен добавить, что мне необходимо реализовать эти последние 4 метода (содержит, addUnique, remove, equals) для существующего кода, который был предоставлен мне в моем классе структур данных (поэтому я не могу изменить ничего, что было дано мне). Рад слышать, что моя первая забота не должна быть одна. Я добавлю еще один фрагмент кода, чтобы показать, в чем я столкнулся. Спасибо, что нашли время, чтобы пройти через все это! – 23k

+0

Ваш метод '.equals()' является проблематичным по нескольким причинам. Самое главное, '.equals()' нарушается, если '.hashcode()' также реализуется одинаково (т. Е. Если 'a.equals (b)' then' a.hashcode() 'должен равняться' b.hashcode() '), но и хуже, чем реализация' Object.equals() 'по умолчанию, которая, по крайней мере, вернула бы« true »(правильно), если вы попытаетесь сравнить один и тот же объект (' a.equals (a) ') - ваша реализация вернет 'false', что неверно. – dimo414

+0

Метод '.equals()' все еще находится в моем списке задач, я знаю, что реализация в настоящее время неверна, я попрошу вас игнорировать это на данный момент. Я добавил больше примера кода к основному.В основном, удаление должно быть возвращено false, если элемент больше не находится в сумке, однако вызов дважды удаляется в одном элементе, дает неверные результаты, что вначале заставило меня поверить, что элемент фактически не удаляется, просто скрывается. – 23k

1

Iterator.remove() действительно хитрый способ, чтобы получить право, в частности, в условиях потенциальной параллельной модификации (см ConcurrentModificationException). Если вы посмотрите на реализацию некоторых стандартных коллекций, вы получите несколько хороших указателей: ArrayList.Itr, LinkedList.ListItr,, TreeMap.PrivateEntryIterator и ConcurrentSkipListMap.Iter - хорошие отправные точки.

Некоторые ключевые вещи, чтобы помнить:

  • Correct важнее быстрой - Вы всегда можете реализовать Iterator.remove() как призыв к коллекции бэк-х .remove() (наряду с обновлением состояния Iterator «s по мере необходимости) , Это может быть не самый эффективный способ решить проблему, но вы будете уверены, что это работает. Если вы не уверены, что эта наивная реализация является серьезной медленной точкой в ​​вашем приложении, это, вероятно, достаточно хорошо.
  • Iterator.remove() - необязательный метод - совершенно правильная реализация - это просто throw new UnsupportedOperationException();. Это не гламурно, но работает в подавляющем большинстве случаев. Если ваш прецедент не требует эффективной операции по удалению средней итерации, просто оставив его невыполненным, это может быть самый умный способ действий.
  • Расширение существующих абстрактных классов - JDK предоставляет ряд абстрактных классов в качестве отправных точек (например, AbstractCollection), которые обеспечивают разумные значения по умолчанию для ряда сложных методов. Guava также предоставляет число Forwarding* decorators, которое может быть полезно, если классы Abstract* не будут работать. Если вы можете избежать использования существующего поведения, сделайте это, не изобретайте велосипед.
  • Опираясь на совместное поведение с базовым классом - вы заметите, что глядя на JDK's Iterators, они стараются сделать как можно меньше работы, предпочитая аутсорсинг работы для класса поддержки. Например, предположим, что вы можете эффективно удалить элемент из своей коллекции, как только вы знаете его индекс; определите помощника private void removeByIndex(int), который вызовет как ваши методы Collection.remove(), так и Iterator.remove(). Чем больше у вас будет общее поведение, тем меньше будет случаев, связанных с вашим Iterator.
  • Обратите внимание на свои инварианты - ваша структура данных обеспечивает определенные гарантии о том, как он ведет себя, как и Java (.equals(), .hashcode()) и рамка Коллекции (Iterable, Collection и т.д.) определяют дополнительные требования. Очень важно учитывать эти требования при реализации вспомогательных классов, таких как Iterator. Если вы не можете быть уверены, что ваш оптимизированный Iterator по-прежнему уважает инварианты, предоставляемые классом поддержки, вы должны отказаться от оптимизации и просто вызвать общедоступные методы класса.
  • Помните о одновременных модификациях - очень полезной функцией коллекций JDK является их способность обнаруживать параллельные модификации (например, в середине итерации что-то еще, даже в том же потоке, вызывает Collection.remove()). В то время как ваш Iterator технически по-прежнему может быть правильным, не обнаруживая этих изменений, он намного менее полезен и, скорее всего, терпит тонкие ошибки. Если вы собираетесь реализовать свой собственный Iterator, вам стоит попытаться обнаружить изменения в промежуточной структуре структуры резервной копии.

И последнее, но не менее важное: Guava поставляется с Multiset и Multimap реализациями. Если это не школьное задание, нет оснований для реализации собственного типа Bag.

+0

Спасибо, очень полезная информация. К сожалению, некоторые из реализованных вами реализаций чрезвычайно над моей головой за то, что я студент первого курса. Я все еще запутался даже в том, где начать в моем конкретном примере. – 23k

+1

Да, классы JDK сложны; не чувствую, что вам нужно их полностью понять. Они все еще являются отличной ссылкой, чтобы взглянуть; не торопясь рассуждать о том, как даже небольшая часть их занятий будет очень ценным опытом обучения. Благодарим вас за опыт, добавив дополнительную информацию! – dimo414

Смежные вопросы