2009-09-02 37 views
35

Giả sử tôi có 2 bộ sưu tập song song, ví dụ: danh sách tên người trong số List<String> và danh sách tuổi của chúng theo số List<Int> theo cùng thứ tự (để bất kỳ chỉ mục cụ thể nào trong mỗi bộ sưu tập đều đề cập đến cùng một người).Cách lặp lại thanh lịch nhất thông qua các bộ sưu tập song song?

Tôi muốn lặp lại thông qua cả hai bộ sưu tập cùng một lúc và tìm nạp tên và tuổi của mỗi người và làm điều gì đó với nó. Với mảng này có thể dễ dàng thực hiện với:

for (int i = 0; i < names.length; i++) { 
    do something with names[i] .... 
    do something with ages[i]..... 
} 

Cách nào là cách thanh lịch nhất (về khả năng đọc và tốc độ) khi thực hiện việc này với bộ sưu tập?

+0

có thể trùng lặp của [Bes t cách để lặp qua hai danh sách cùng một lúc?] (http://stackoverflow.com/questions/3137944/best-way-to-iterate-over-two-lists-simaneously) –

+0

Câu hỏi đó là khác biệt một cách tinh tế: đó là về cách iterate _outside_ một lớp trên hai bộ sưu tập _inside_ một lớp, mà thay đổi giao diện một chút. –

Trả lời

30

Tôi sẽ tạo một đối tượng mới đóng gói cả hai. Ném nó vào mảng và lặp lại điều đó.

List<Person> 

đâu

public class Person { 
    public string name; 
    public int age; 
} 
+2

+1 - gợi ý rất hợp lý, vì nó là mở rộng nhất –

+1

Đây là giải pháp rõ ràng - xác định đối tượng và tên miền chính xác: Tuổi và Tên là thuộc tính của một người. Lập bản đồ và làm việc với họ. – Precipitous

+0

Tôi đồng ý đây là giải pháp thanh lịch nhất. –

49
it1 = coll1.iterator(); 
it2 = coll2.iterator(); 
while(it1.hasNext() && it2.hasNext()) { 
    value1 = it1.next(); 
    value2 = it2.next(); 
    do something with it1 and it2; 
} 

Phiên bản này chấm dứt khi bộ sưu tập ngắn hơn bị cạn kiệt; cách khác, bạn có thể tiếp tục cho đến khi cái còn lại cạn kiệt, thiết lập giá trị 1 resp. value2 đến null.

+0

cảm ơn câu trả lời nhưng tôi không hiểu bạn trong trường hợp chúng tôi muốn tiếp tục cho đến khi cái còn lại cạn kiệt – Youssef

+4

Đây là câu trả lời chính thức: "Sử dụng Iterator thay vì cấu trúc cho mỗi khi bạn cần: ... Lặp lại nhiều bộ sưu tập song song. " [Hướng dẫn Java ™: Giao diện Bộ sưu tập] (http://docs.oracle.com/javase/tutorial/collections/interfaces/collection.html) –

+1

Để xử lý một danh sách chạy trước tiên, bạn có thể sử dụng 'value1 = it1 .hasNext()? it1.next(): null; '(ditto cho' it2') để kiểm tra trước khi nhận phần tử tiếp theo. Ngoài ra, bạn có thể sử dụng try-catch với 'NoSuchElementException', dài hơn một chút. –

7
for (int i = 0; i < names.length; ++i) { 
    name = names.get(i); 
    age = ages.get(i); 
    // do your stuff 
} 

Nó không thực sự quan trọng. Mã của bạn sẽ không nhận được điểm cho sự thanh lịch. Chỉ cần làm điều đó để nó hoạt động. Và xin đừng sưng lên.

+8

Hãy cảnh báo ở đây rằng không phải tất cả Danh sách đều có triển khai get (int) hiệu quả. Trình lặp đôi ít có khả năng không hiệu quả. – TREE

9

Bạn có thể tạo một giao diện cho nó:

public interface ZipIterator<T,U> { 
    boolean each(T t, U u); 
} 

public class ZipUtils { 
    public static <T,U> boolean zip(Collection<T> ct, Collection<U> cu, ZipIterator<T,U> each) { 
    Iterator<T> it = ct.iterator(); 
    Iterator<U> iu = cu.iterator(); 
    while (it.hasNext() && iu.hasNext()) { 
     if (!each.each(it.next(), iu.next()) { 
     return false; 
     } 
    } 
    return !it.hasNext() && !iu.hasNext(); 
    } 
} 

Và sau đó bạn có:

Collection<String> c1 = ... 
Collection<Long> c2 = ... 
zip(c1, c2, new ZipIterator<String, Long>() { 
    public boolean each(String s, Long l) { 
    ... 
    } 
}); 
+2

Và với lambdas của Java 8 bạn thậm chí có thể sử dụng nó theo cách này: 'zip (c1, c2, (s, l) -> ...)'. (Ngoài ra, ZipIterator của bạn về cơ bản giống như một BiConsumer.) –

+0

có vẻ là cách thanh lịch nhất với java 8 lambdas. Lưu ý bạn sẽ thay đổi loại từ 'Bộ sưu tập' thành' Iterable' để làm cho nó trở nên tổng quát hơn – Sisyphus

0

Theo đề nghị của jeef3, mô hình hóa sự thật miền thay vì giữ sepa tỷ lệ, danh sách được ghép đôi hoàn toàn là đúng cách để đi ... khi đây là một tùy chọn.

Có nhiều lý do khiến bạn không thể áp dụng phương pháp này. Nếu vậy ...

A. Bạn có thể sử dụng phương pháp gọi lại, theo đề xuất của cletus.

B. Bạn vẫn có thể chọn để hiển thị Iterator hiển thị phần tử đối tượng miền cho từng trường hợp tổng hợp. Cách tiếp cận này không buộc bạn phải giữ một cấu trúc Danh sách song song xung quanh.

private List<String> _names = ...; 
private List<Integer> _ages = ...; 

Iterator<Person> allPeople() { 
    final Iterator<String> ni = _names.iterator(); 
    final Iterator<Integer> ai = _ages.iterator(); 
    return new Iterator() { 
    public boolean hasNext() { 
     return ni.hasNext(); 
    } 
    public Person next() { 
     return new Person(ni.next(), ai.next()); 
    } 
    public void remove() { 
     ni.remove(); 
     ai.remove(); 
    } 
    }; 
} 

C. Bạn có thể sử dụng biến thể này và sử dụng API con trỏ kiểu RowSet. Giả sử IPerson là giao diện mô tả Người. Sau đó, chúng ta có thể làm:

public interface IPerson { 
    String getName(); 
    void setName(String name); 
    ... 
} 

public interface ICursor<T> { 
    boolean next(); 
    T current(); 
} 

private static class PersonCursor implements IPerson, ICursor<IPerson> { 
    private final List<String> _names; 
    ... 
    private int _index = -1; 

    PersonCursor(List<String> names, List<Integer> ages) { 
    _names = names; 
    ... 
    } 

    public boolean next() { 
    return ++_index < _names.size(); 
    } 

    public Person current() { 
    return this; 
    } 

    public String getName() { 
    return _names.get(_index); 
    } 

    public void setName(String name) { 
    _names.set(0, name); 
    } 

    ... 
} 

private List<String> _names = ...; 
private List<Integer> _ages = ...; 

Cursor<Person> allPeople() { 
    return new PersonCursor(_names, _ages); 
} 

Lưu ý rằng phương pháp B cũng được thực hiện để hỗ trợ cập nhật vào danh sách bằng cách giới thiệu một giao diện Domain, và có sự trở lại Iterator 'sống' đối tượng.

0

Tôi chỉ đăng tải chức năng này trong similar question này (mà @Nils von Barth khẳng định không phải là một bản sao;)), nhưng nó không kém phần áp dụng ở đây:

public static <L,R,M> List<M> zipLists(
    BiFunction<L,R,M> factory, Iterable<L> left, Iterable<R> right) { 
    Iterator<L> lIter = left.iterator(); 
    Iterator<R> rIter = right.iterator(); 
    ImmutableList.Builder<M> builder = ImmutableList.builder(); 

    while (lIter.hasNext() && rIter.hasNext()) { 
    builder.add(factory.apply(lIter.next(), rIter.next())); 
    } 

    // Most of the existing solutions fail to enforce that the lists are the same 
    // size. That is a *classic* source of bugs. Always enforce your invariants! 
    checkArgument(!lIter.hasNext(), 
     "Unexpected extra left elements: %s", ImmutableList.copyOf(lIter)); 
    checkArgument(!rIter.hasNext(), 
     "Unexpected extra right elements: %s", ImmutableList.copyOf(rIter)); 
    return builder.build(); 
} 

Sau đó, bạn có thể cung cấp một hoạt động nhà máy cho BiFunction , chẳng hạn như một giá trị kiểu của constructor:

List<Person> people = zipLists(Person::new, names, ages); 

Nếu bạn thực sự chỉ muốn để lặp qua họ và làm một số hoạt động, chứ không phải là xây dựng một bộ sưu tập mới, bạn có thể hoán đổi số BiFunction cho số BiConsumer và có chức năng trả về void.

1

tôi đã bình luận @cletus và cải tiến nó ABIT, Và đó là những gì tôi sử dụng:

public static <T,U> void zip(Collection<T> ct, Collection<U> cu, BiConsumer<T, U> consumer) { 
    Iterator<T> it = ct.iterator(); 
    Iterator<U> iu = cu.iterator(); 
    while (it.hasNext() && iu.hasNext()) { 
     consumer.accept(it.next(), iu.next()); 
    } 
} 

Cách sử dụng:

zip(list1, list2, (v1, v2) -> { 
    // Do stuff 
}); 
0

Trong khi các giải pháp gửi là chính xác tôi thích một sau vì nó sau hướng dẫn từ mục java hiệu quả 57: giảm thiểu phạm vi biến cục bộ:

for (Iterator<String> i = lst1.iterator(), ii = lst2.iterator(); i.hasNext() && ii.hasNext();) { 
     String e1 = i.next(); 
     String e2 = ii.next(); 
     .... 
    } 
Các vấn đề liên quan