2015-06-29 52 views
9

Giả sử tôi có danh sách với các thành phần (34, 11, 98, 56, 43).Java 8: Tìm chỉ mục giá trị tối thiểu từ Danh sách

Sử dụng luồng Java 8, làm cách nào để tìm chỉ mục của phần tử tối thiểu trong danh sách (ví dụ 1 trong trường hợp này)?

Tôi biết điều này có thể được thực hiện dễ dàng trong Java bằng cách sử dụng list.indexOf(Collections.min(list)). Tuy nhiên, tôi đang xem xét một giải pháp giống như Scala, nơi chúng ta có thể chỉ cần nói List(34, 11, 98, 56, 43).zipWithIndex.min._2 để có được chỉ mục giá trị tối thiểu.

Có điều gì có thể được thực hiện bằng cách sử dụng các dòng hoặc biểu thức lambda (nói các tính năng Java 8 cụ thể) để đạt được cùng một kết quả.

Lưu ý: Đây chỉ là mục đích học tập. Tôi không gặp vấn đề gì trong việc sử dụng các phương thức tiện ích Collections.

+0

bản sao có thể có của [Cách lấy chỉ mục và giá trị tối đa của một mảng trong một lần chụp?] (Http://stackoverflow.com/questions/30730861/how-to-get-the-index-and-max- giá trị-of-an-array-in-one-shot) – CKing

Trả lời

14
import static java.util.Comparator.comparingInt; 

int minIndex = IntStream.range(0,list.size()).boxed() 
      .min(comparingInt(list::get)) 
      .get(); // or throw if empty list 

Như @TagirValeev đề cập trong his answer, bạn có thể tránh đấm bốc bằng cách sử dụng IntStream#reduce thay vì Stream#min, nhưng với chi phí che khuất mục đích:

int minIdx = IntStream.range(0,list.size()) 
      .reduce((i,j) -> list.get(i) > list.get(j) ? j : i) 
      .getAsInt(); // or throw 
+1

Vâng, đây là một +1 tuyệt vời. Tôi đã tập trung vào trường hợp 'zipWithIndex', nhưng đây có thể là cách thành ngữ để giải quyết vấn đề này. Tôi sẽ chỉ sử dụng 'orElse (-1)' thay thế. –

2

Bạn có thể làm điều đó như thế này:

int indexMin = IntStream.range(0, list.size()) 
       .mapToObj(i -> new SimpleEntry<>(i, list.get(i))) 
       .min(comparingInt(SimpleEntry::getValue)) 
       .map(SimpleEntry::getKey) 
       .orElse(-1); 

Nếu danh sách là một danh sách truy cập ngẫu nhiên, get là một hoạt động thời gian liên tục. API thiếu một lớp tuple tiêu chuẩn, vì vậy tôi đã sử dụng SimpleEntry từ lớp AbstractMap để thay thế.

Vì vậy, IntStream.range tạo luồng chỉ mục từ danh sách mà từ đó bạn ánh xạ từng chỉ mục đến giá trị tương ứng của nó. Sau đó, bạn nhận được phần tử tối thiểu bằng cách cung cấp một bộ so sánh về các giá trị (những cái trong danh sách). Từ đó bạn lập bản đồ số Optional<SimpleEntry<Integer, Integer>> đến một số Optional<Integer> mà từ đó bạn nhận được chỉ mục (hoặc -1 nếu tùy chọn trống). Là một sang một bên, tôi có lẽ sẽ sử dụng một vòng lặp đơn giản để có được chỉ số của giá trị tối thiểu, vì sự kết hợp của bạn là min/indexOf có 2 vượt qua danh sách.

Bạn cũng có thể quan tâm để kiểm tra Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip)

+0

Thanks @ Alexis, có vẻ phức tạp hơn việc sử dụng đơn giản của 'Collections' hoặc 'Scala's implementation. – Mubin

1

Dưới đây là hai giải pháp có thể sử dụng thư viện StreamEx của tôi:

int idx = IntStreamEx.ofIndices(list).minBy(list::get).getAsInt(); 

Hoặc:

int idx = EntryStream.of(list).minBy(Entry::getValue).get().getKey(); 

Giải pháp thứ hai trong nội bộ là rất gần gũi với từng người @AlexisC đề xuất. Việc đầu tiên có lẽ là nhanh nhất vì nó không sử dụng boxing (internally nó là một hoạt động giảm).

Không sử dụng mã của bên thứ ba @ Câu trả lời của Misha có vẻ phù hợp nhất với tôi.

2

Vì mục đích này là dành cho mục đích học tập, hãy cố gắng tìm giải pháp không chỉ bằng cách nào đó sử dụng luồng mà thực sự hoạt động trên luồng danh sách của chúng tôi. Chúng tôi cũng không muốn giả sử truy cập ngẫu nhiên.

Vì vậy, có hai cách để có được kết quả không tầm thường từ một luồng: collectreduce.Here là một giải pháp mà sử dụng một nhà sưu tập:

class Minimum { 
    int index = -1; 
    int range = 0; 
    int value; 

    public void accept(int value) { 
     if (range == 0 || value < this.value) { 
      index = range; 
      this.value = value; 
     } 
     range++; 
    } 

    public Minimum combine(Minimum other) { 
     if (value > other.value) { 
      index = range + other.index; 
      value = other.value; 
     } 
     range += other.range; 
     return this; 
    } 

    public int getIndex() { 
     return index; 
    } 
} 

static Collector<Integer, Minimum, Integer> MIN_INDEX = new Collector<Integer, Minimum, Integer>() { 
     @Override 
     public Supplier<Minimum> supplier() { 
      return Minimum::new; 
     } 
     @Override 
     public BiConsumer<Minimum, Integer> accumulator() { 
      return Minimum::accept; 
     } 
     @Override 
     public BinaryOperator<Minimum> combiner() { 
      return Minimum::combine; 
     } 
     @Override 
     public Function<Minimum, Integer> finisher() { 
      return Minimum::getIndex; 
     } 
     @Override 
     public Set<Collector.Characteristics> characteristics() { 
      return Collections.emptySet(); 
     } 
    }; 

Viết một nhà sưu tập tạo ra một lượng gây phiền nhiễu mã, nhưng nó có thể được dễ dàng khái quát hóa để hỗ trợ bất kỳ giá trị tương đương. Ngoài ra, kêu gọi các nhà sưu tập trông rất thành ngữ:

List<Integer> list = Arrays.asList(4,3,7,1,5,2,9); 
int minIndex = list.stream().collect(MIN_INDEX); 

Nếu chúng ta thay đổi phương pháp acceptcombine luôn trả về một Minimum dụ mới (. Tức là nếu chúng ta làm Minimum bất biến), chúng ta cũng có thể sử dụng reduce:

int minIndex = list.stream().reduce(new Minimum(), Minimum::accept, Minimum::combine).getIndex(); 

Tôi cảm thấy tiềm năng lớn để song song trong trường hợp này.

+0

Giải pháp khá thú vị. Đầu tiên tôi nghĩ rằng nó sẽ không hoạt động cho các luồng song song, nhưng có vẻ như nó sẽ xảy ra. Lưu ý rằng bạn có thể sử dụng 'Collector.of (tối thiểu :: mới, tối thiểu :: chấp nhận, tối thiểu :: kết hợp, tối thiểu :: getIndex)' thay vì định nghĩa một lớp ẩn danh. –

+1

Cách tiếp cận này có lợi thế là không yêu cầu danh sách truy cập ngẫu nhiên thân thiện. Lưu ý rằng phương thức 'kết hợp' phải xử lý chính xác trường hợp' khác' đang trống. 'Bộ sưu tập javadoc đặc biệt yêu cầu rằng đối với sự thân thiện song song là" ràng buộc bản sắc ". Nó không nói bất cứ điều gì về việc xử lý trường hợp của 'này' đang trống rỗng và' khác' không trống rỗng, nhưng có vẻ như thận trọng để xử lý điều đó. – Misha

+0

@Misha, vâng, nó có một số vấn đề nhỏ, nhưng tôi thích ý tưởng. Btw nó hoạt động khá nhanh, trên một số thử nghiệm nó thậm chí còn tốt hơn 'IntStreamEx.minBy' của tôi. Tôi [cam kết] (https://github.com/amaembo/streamex/commit/86703a8b787e4769766c99112db5ba42d75ac3c4) một phiên bản cố định, có thể sẽ bao gồm nó trong lib của tôi. –

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