2016-04-15 14 views
12

Điều này đã gây phiền toái cho tôi trong một dự án gần đây và Google phoo của tôi không làm tôi tìm câu trả lời phù hợp.Bộ sưu tập sử dụng ListIterator và là một danh sách duy nhất

Có bộ sưu tập nào có quyền truy cập vào ListIterator nhưng cũng chỉ cho phép các giá trị duy nhất bên trong bộ sưu tập không?

Lý do cho điều này, tôi có một bộ sưu tập các mục trong bộ sưu tập này sẽ chỉ bao giờ là một trong mỗi phần tử. Tôi cũng muốn có thể duyệt bộ sưu tập này theo cả hai hướng. và tôi muốn nó thể là sắp xếp hoặc cho phép tôi để sắp xếp nó bằng cách sử Collections.Sort();

tôi đã không tìm thấy bất cứ điều gì phù hợp và phải viết lớp của riêng tôi bằng cách sử dụng đoạn mã sau:

public class UniqueArrayList<E> extends ArrayList<E> { 
    @Override 
    public boolean add(E element){ 
     if (this.contains(element)) 
      return false; 
     else 
      return super.add(element); 
    } 

    @Override 
    public void add(int index, E element){ 
     if (this.contains(element)) 
      return; 
     else 
      super.add(index, element); 
    } 

    @Override 
    public boolean addAll(Collection<? extends E> c){ 
     if (new HashSet<E>(c).size() < c.size()) 
      return false; 
     for(E element : c){ 
      if (this.contains(c)) 
       return false; 
     } 
     return super.addAll(c); 
    } 

    @Override 
    public boolean addAll(int index, Collection<? extends E> c) { 
     if (new HashSet<E>(c).size() < c.size()) 
      return false; 
     for(E element : c){ 
      if (this.contains(c)) 
       return false; 
     } 
     return super.addAll(index, c); 
    } 

    @Override 
    public ListIterator<E> listIterator(int index) { 
     if (index < 0 || index > this.size()) 
      throw new IndexOutOfBoundsException("Index: "+index); 
     return new ListItr(index); 
    } 

    @Override 
    public ListIterator<E> listIterator() { 
     return new ListItr(0); 
    } 

    @Override 
    public Iterator<E> iterator() { 
     return new Itr(); 
    } 

    private class Itr implements Iterator<E> { 
     int cursor;  // index of next element to return 
     int lastRet = -1; // index of last element returned; -1 if no such 
     int expectedModCount = modCount; 

     public boolean hasNext() { 
      return cursor != size(); 
     } 

     @SuppressWarnings("unchecked") 
     public E next() { 
      checkForComodification(); 
      int i = cursor; 
      if (i >= size()) 
       throw new NoSuchElementException(); 
      Object[] elementData = UniqueArrayList.this.toArray(); 
      if (i >= elementData.length) 
       throw new ConcurrentModificationException(); 
      cursor = i + 1; 
      return (E) elementData[lastRet = i]; 
     } 

     public void remove() { 
      if (lastRet < 0) 
       throw new IllegalStateException(); 
      checkForComodification(); 

      try { 
       UniqueArrayList.this.remove(lastRet); 
       cursor = lastRet; 
       lastRet = -1; 
       expectedModCount = modCount; 
      } catch (IndexOutOfBoundsException ex) { 
       throw new ConcurrentModificationException(); 
      } 
     } 

     final void checkForComodification() { 
      if (modCount != expectedModCount) 
       throw new ConcurrentModificationException(); 
     } 


    } 

    private class ListItr extends Itr implements ListIterator<E> { 
     ListItr(int index) { 
      super(); 
      cursor = index; 
     } 

     public boolean hasPrevious() { 
      return cursor != 0; 
     } 

     public int nextIndex() { 
      return cursor; 
     } 

     public int previousIndex() { 
      return cursor - 1; 
     } 

     @SuppressWarnings("unchecked") 
     public E previous() { 
      checkForComodification(); 
      int i = cursor - 1; 
      if (i < 0) 
       throw new NoSuchElementException(); 
      Object[] elementData = UniqueArrayList.this.toArray(); 
      if (i >= elementData.length) 
       throw new ConcurrentModificationException(); 
      cursor = i; 
      return (E) elementData[lastRet = i]; 
     } 

     public void set(E e) { 
      if (lastRet < 0) 
       throw new IllegalStateException(); 
      checkForComodification(); 
      try { 
       //Need to allow this for the collections sort to work! 
       //if (!UniqueArrayList.this.contains(e)) 
       UniqueArrayList.this.set(lastRet, e); 
      } catch (IndexOutOfBoundsException ex) { 
       throw new ConcurrentModificationException(); 
      } 
     } 

     public void add(E e) { 
      checkForComodification(); 

      try { 
       int i = cursor; 
       UniqueArrayList.this.add(i, e); 
       cursor = i + 1; 
       lastRet = -1; 
       expectedModCount = modCount; 
      } catch (IndexOutOfBoundsException ex) { 
       throw new ConcurrentModificationException(); 
      } 
     } 
    } 
} 

Tuy nhiên điều này là xa từ hoàn hảo, vì tôi không thể ghi đè lên ListIterator.set();Collections.sort(); sử dụng nó để di chuyển các mục trong danh sách. Nếu tôi cố gắng ngăn không cho các mục không độc đáo được thêm vào danh sách ở đây, sắp xếp không bao giờ xảy ra.

Vì vậy, có ai có phương pháp tốt hơn hoặc biết về một bộ sưu tập khác tuân theo các quy tắc mà tôi muốn không? Hay tôi chỉ cần sống với vấn đề khá khó chịu này?

[Chỉnh sửa] Đây là Collections.sort(); phương pháp:

public static <T extends Comparable<? super T>> void sort(List<T> list) { 
    Object[] a = list.toArray(); 
    Arrays.sort(a); 
    ListIterator<T> i = list.listIterator(); 
    for (int j=0; j<a.length; j++) { 
     i.next(); 
     i.set((T)a[j]); 
    } 
} 

Lý do họ đưa ra cho việc này là:

thi này bãi danh mục quy định vào một mảng, sắp xếp các mảng và lặp lại trên danh sách đặt lại từng phần tử từ vị trí tương ứng trong mảng. Điều này tránh được các hiệu suất n nhật ký (n) sẽ xuất phát từ việc cố gắng sắp xếp danh sách được liên kết tại chỗ.

+0

Phiên bản Java bạn đang sử dụng? – Kayaman

+0

Bạn đã đưa ra ý tưởng rằng 'Collections.sort();' sử dụng 'ListIterator.set();'? – Kayaman

+0

Java 1.7 và kiểm tra văn bản cập nhật của tôi trong các câu hỏi liên quan đến phương pháp sắp xếp – Draken

Trả lời

5

Thor Stan được đề cập, một TreeSet mang đến cho bạn hầu hết những gì bạn muốn. Nó đảm bảo các yếu tố là duy nhất, nó giữ chúng được sắp xếp, và bạn có thể lặp nó theo hai hướng bằng cách sử dụng iterator() hoặc descendingIterator().

Nó không hoàn toàn rõ ràng lý do tại sao bạn yêu cầu ListIterator mặc dù. Hầu hết mọi thứ về một ListIterator là rất vị trí: khái niệm về chỉ mục, hoặc thêm một cái gì đó ở vị trí hiện tại, hoặc thiết lập phần tử hiện tại. Những điều này không có ý nghĩa đối với một tập hợp được sắp xếp.

Một khía cạnh của ListIterator mà bạn có thể đang tìm kiếm, mặc dù, là khả năng đảo ngược chỉ đường ở giữa quá trình lặp lại. Bạn không thể thực hiện việc này trực tiếp với trình biến đổi TreeSet vì nó chỉ cung cấp quyền truy cập qua một số Iterator thông thường thay vì ListIterator.

Tuy nhiên, một TreeSet triển khai giao diện NavigableSet, cho phép bạn duyệt qua các phần tử theo thứ tự, theo một trong hai hướng. Giao diện NavigableSet là một giao diện con của SortedSet, cung cấp các phương thức first()last() để giúp bạn bắt đầu tại một trong "kết thúc" của tập hợp. Khi bạn có một phần tử trong tập hợp, bạn có thể bước theo một trong hai hướng bằng cách sử dụng các phương thức lower(E)higher(E). Hoặc, nếu bạn muốn bắt đầu từ đâu đó ở giữa, bạn không thể bắt đầu ở vị trí theo chỉ mục, nhưng bạn có thể bắt đầu bằng giá trị (không cần phải là thành viên của nhóm) và sau đó gọi lower(E) hoặc higher(E).

Ví dụ:

TreeSet<String> set = new TreeSet<>(
     Arrays.asList("a", "z", "b", "y")); 
    String cur; 

    cur = set.first();  // a 
    cur = set.higher(cur); // b 
    cur = set.higher(cur); // y 
    cur = set.higher(cur); // z 
    cur = set.lower(cur); // y 
    cur = set.lower(cur); // b 
    cur = set.lower(cur); // a 
    cur = set.lower(cur); // null 
+0

Đó thực sự là những gì tôi đang tìm kiếm, những phương pháp đó làm những gì tôi muốn làm và cho phép lặp lại theo một trong hai hướng. Tôi sẽ chấp nhận câu trả lời này. Cảm ơn các giải pháp! – Draken

7

Khi bạn cần giá trị duy nhất, bạn nên thử chuyển sang Bộ. Bạn có thể sử dụng một TreeSet cùng với một cá thể so sánh để sắp xếp các mục nhập. Phương thức descendingSet() của TreeSet sẽ cho bạn thứ tự ngược lại. Nếu bạn thực sự cần một ListIterator tại một số điểm bạn có thể tạo một danh sách tạm thời từ tập hợp.

+0

Liệu 'descendingSet()' có biết vị trí và sau đó có thể di chuyển về phía trước thông qua tập hợp hay tôi sẽ cần phải định vị lại vị trí đó và bắt đầu lại từ đó nếu tiến hành tiếp theo? – Draken

+0

descendingSet() trả về một Set, do đó không có vị trí nào. giảm dầnIterator() trả về một phiên bản Iterator mới, do đó vị trí của bất kỳ cuộc gọi trước đó không được biết. –

+0

Vì vậy, nó thực sự trông giống như đặt cược tốt nhất là để làm một TreeSet và sau đó tại thời điểm cần phải di chuyển thông qua các bộ sưu tập trong hai hướng, tôi cần phải tạo ra một danh sách tạm thời. Cảm thấy một chút xấu hổ không có một số nền tảng trung bình. Tôi sẽ để nó mở trong vài ngày nữa và nếu không ai khác có câu trả lời tốt hơn, tôi sẽ đánh dấu câu trả lời này là câu trả lời – Draken

1

Java xem xét List để cho phép các mục không độc đáo và Set không. Bộ rõ ràng không hỗ trợ ListIterators và do đó mã sử dụng ListIterator có thể giả định rằng bộ sưu tập cơ bản không phải là Set.

Java 8 không sử dụng ListIterator để sắp xếp nữa, nhưng nếu bạn đang mắc kẹt với Java 7 không thực sự hữu ích với bạn. Về cơ bản tùy thuộc rất nhiều vào ngữ cảnh và cách sử dụng của bạn, có thể hữu ích khi sử dụng một số Set như Thor Stan đã nói trong phản hồi của mình, tạo ra một yêu cầu List theo yêu cầu khi cần.

Một tùy chọn khác là chỉ cung cấp ListIterator của riêng bạn truy cập danh sách thông qua một phương pháp không kiểm tra trùng lặp. Điều này sẽ có lợi thế là không tạo ra các đối tượng không liên quan, nhưng tùy chọn Set rất có thể sẽ dẫn đến mã ngắn hơn và rất có khả năng không có hiệu suất đáng kể.

Cũng có thể có các tùy chọn khác, nhưng tôi không thể nghĩ ra bất kỳ tùy chọn thanh lịch nào.

1

Đây là vấn đề không có câu trả lời dễ dàng.

Vấn đề là bạn đã phá vỡ hợp đồng cho add(int, E). Phương thức này phải thêm phần tử hoặc ném một ngoại lệ - nó không được phép trả về mà không thêm phần tử.

Nếu bạn ghi đè set(int, E) để đôi khi nó không đặt thành phần, nó cũng sẽ phá vỡ hợp đồng cho phương thức đó và nó sẽ ngăn không cho Collections.sort() hoạt động như bạn đã xác định.

Tôi không khuyên bạn nên phá vỡ các hợp đồng này - điều này có thể khiến các phương pháp khác hoạt động trên danh sách hoạt động theo cách không thể đoán trước.

Những người khác đã gặp phải những khó khăn này - xem tài liệu java cho ví dụ SetUniqueList.

Một vấn đề khác với việc triển khai của bạn là nó sẽ rất chậm vì ArrayList.contains() là tìm kiếm tuyến tính.

Một giải pháp sẽ được để viết một lớp có sử dụng cả một ArrayListHashSet, và viết các phiên bản của riêng bạn addset, chứ không phải là phá vỡ hợp đồng cho các phiên bản bình thường. Đây không phải là thử nghiệm, nhưng bạn có được ý tưởng.

public final class MyList<E extends Comparable<? super E>> extends AbstractList<E> { 

    private final List<E> list = new ArrayList<>(); 
    private final Set<E> set = new HashSet<>(); 

    public E get(int i) { 
     return list.get(i); 
    } 

    public int size() { 
     return list.size(); 
    } 

    public boolean tryAdd(E e) { 
     return set.add(e) && list.add(e); 
    } 

    public boolean tryAdd(int i, E e) { 
     if (set.add(e)) { 
      list.add(i, e); 
      return true; 
     } 
     return false; 
    } 

    public boolean trySet(int i, E e) { 
     return set.add(e) && set.remove(list.set(i, e)); 
    } 

    public boolean remove(Object o) { 
     return set.remove(o) && list.remove(o); 
    } 

    public void sort() { 
     Collections.sort(list); 
    } 

    // One bonus of this approach is that contains() is now O(1) 
    public boolean contains(Object o) { 
     return set.contains(o); 
    } 

    // rest omitted. 
} 

Điều gì đó tương tự sẽ không vi phạm hợp đồng cho List. Lưu ý rằng với phiên bản này, addset ném một số UnsupportedOperationException, bởi vì đây là hành vi được kế thừa từ AbstractList. Bạn có thể sort bằng cách gọi myList.sort(). Ngoài ra, phương pháp listIterator sẽ hoạt động, nhưng bạn sẽ không thể sử dụng phương thức này để set hoặc add (mặc dù remove sẽ hoạt động).

Nếu bạn cần add hoặc set yếu tố trong khi lặp qua số List này, bạn sẽ cần phải sử dụng chỉ mục rõ ràng thay vì ListIterator. Cá nhân, tôi không coi đây là một vấn đề lớn. ListIterator là cần thiết cho các danh sách như LinkedList không có phương pháp liên tục get, nhưng đối với ArrayList thì tốt nhưng không cần thiết.

+1

Đó chỉ là một lớp nội bộ không nên được sử dụng ở nơi khác, ý tưởng tồi để phá vỡ hợp đồng. Tôi đang nghiêng về phía 'TreeSet' và sau đó chuyển nó tới một' ArrayList' nếu tôi cần lặp lại nó theo cả hai hướng. Tuy nhiên, cảm ơn bạn đã gợi ý và tư vấn. Tôi chắc chắn sẽ theo dõi hợp đồng hoàn thành vào lần tới khi tôi cố gắng ghi đè lên một lớp khác! – Draken

Các vấn đề liên quan