How to Get Index of an Item in Java Set – 如何在 Java 集合中获取项的索引

最后修改: 2023年 9月 20日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.导言

In this tutorial, we’re going to look at how we can get the index of an item in a Java Set. Sets in Java don’t allow duplicate elements, and some important implementations of the Set interface include the HashSet, TreeSet and LinkedHashSet.

在本教程中,我们将了解如何获取 Java Set 中项的索引。Java 中的 Set 不允许元素重复,Set 接口的一些重要实现包括 哈希集合TreeSet链接哈希集合

2. Ordered, Unordered and Sorted Collections in Java

2.Java 中的有序、无序和排序集合

Before we look at the problem statement, let’s take a look at the difference between some of the types of collections we have in Java: 

在了解问题陈述之前,我们先来看看 Java 中一些集合类型之间的区别:

  • Ordered Collections
  • Unordered Collections
  • Sorted Collections

Ordered collections maintain the insertion order of its elements. The elements are stored in the order they were inserted and can be accessed by their position. These collections typically provide a get(index) interface to retrieve the element at a specific index. Examples of ordered collections include classes implementing the List interface, such as ArrayList, LinkedList, etc. 

有序集合保持元素的插入顺序。元素按其插入顺序存储,并可按其位置进行访问。这些集合通常提供一个 get(index) 接口来检索特定索引处的元素。有序集合的示例包括实现 List 接口的类,如 ArrayListLinkedList 等。

Unordered collections in Java, on the other hand, do not guarantee any specific order of traversal. The elements are stored in an order that depends on the underlying data structure that supports it. Elements in an unordered collection are typically accessed by their values, not indices. HashSet and HashMaps are some examples of Unordered collections. 

另一方面,Java 中的无序集合不保证任何特定的遍历顺序。元素的存储顺序取决于支持它的底层数据结构。无序集合中的元素通常通过其值而不是索引进行访问。哈希集合哈希映射是无序集合的一些示例。

Sorted collections are a special type of collection where traversing the collection will yield elements in their natural order or in accordance with a specified Comparator. TreeSets and TreeMaps are examples of Sorted collections. 

排序集合是一种特殊类型的集合,在这种集合中,遍历集合将按照自然顺序或指定的 比较器产生元素。TreeSetsTreeMaps 就是排序集合的示例。

3. Why Sets Do Not Provide an indexOf()

3.为什么 Sets 不提供 indexOf()?

Sets in Java are unordered collections. They have the following important characteristics: 

Java 中的 Sets 是无序集合。它们具有以下重要特征:

  • guarantees the uniqueness of its elements 
  • can confirm the existence of an element efficiently, in constant time

Sets come in different flavors. HashSet stores its elements based on a hash-based mechanism (uses a HashMap internally), while a TreeSet will use a default or custom comparator to store and order its elements.

有不同的类型。HashSet 基于哈希机制(内部使用 HashMap )存储其元素,而 TreeSet 将使用默认或自定义比较器来存储和排列其元素。

Sets also need to be efficient in guaranteeing uniqueness, which means storing elements efficiently is more important than preserving their order. It is not straightforward to get the index of an item in a Set, unlike a List.   

集合还需要高效地保证唯一性,这意味着高效地存储元素比保留元素的顺序更为重要。与 List 不同,在 Set 中获取项的索引并不简单。 <em

4. The Problem Statement

4.问题陈述

The problem that we want to solve here is to find the index of an element in a given Set. The index of the element should always be the same and should not change on each query. If the element is absent in the set, we should return -1. 

我们要解决的问题是查找给定 Set 中元素的索引。元素的索引应始终保持相同,并且在每次查询时都不会改变。如果集合中没有该元素,我们应该返回-1。

Example 1:
Input Set [10, 2, 9, 15, 0]
Query: getIndexOf(10)
Output: 0
Query: getIndexOf(0)
Output: 4
Example 2: 
Input Set ["Java", "Scala", "Python", "Ruby", "Go"]
Query: getIndexOf("Scala")
Output: 1

5. Writing a Utility Method To Get the Index

5.编写获取索引的实用方法

5.1. Using an Iterator

5.1.使用迭代器</span

An Iterator<E> is an object that allows us to traverse through a collection, such as a List, Set, or Map one element at a time. It’s an ordered approach to going through the elements and can be utilized to solve our problem.

Iterator<E> 是一个允许我们一次一个元素地遍历一个集合(如 列表集合Map )的对象。这是一种有序浏览元素的方法,可以用来解决我们的问题。

We first obtain an iterator instance from the Set and use it to iterate until we reach the element we are looking for. We keep track of the steps as well and break when we reach our desired element with the index: 

我们首先从 Set 中获取一个迭代器实例,然后使用它进行迭代,直到找到我们要找的元素。我们也会跟踪迭代的步骤,并在到达所需元素时中断迭代:

public int getIndexUsingIterator(Set<E> set, E element) {
    Iterator<E> iterator = set.iterator();
    int index = 0;
    while (iterator.hasNext()) {
        if (element.equals(iterator.next())) {
            return index;
        }
        index++;
    }
    return -1;
}

5.2. Using For-Each Loop

5.2.使用 For-Each 循环

We can alternatively apply the same solution using a for-each loop to traverse through the provided set: 

我们也可以使用 for-each 循环来遍历所提供的集合,从而应用相同的解决方案:

public int getIndexUsingForEach(Set<E> set, E element) {
    int index = 0;
    for (E current : set) {
        if (element.equals(current)) {
            return index;
        }
        index++;
    }
    return -1;
}

We use these utility methods in conjunction with the Set object we use. These methods run in O(n), or linear time, every time we invoke this method, where n is the size of the set. It does not require any additional space. 

我们将这些实用方法与我们使用的 Set 对象结合使用。每次调用该方法时,这些方法的运行时间为 O(n),即线性时间,其中 n 是集合的大小。它不需要任何额外的空间。

Our implementations here will always return the same index regardless of how many times we call the getIndexUsingIterator() or getIndexUsingForEach() methods. This verifies the correctness of the solution.

无论我们调用 getIndexUsingIterator() getIndexUsingForEach() 方法多少次,我们在这里的实现将始终返回相同的索引。这就验证了解决方案的正确性。

However, if there is a need to match the index output of this method to the order in which the element was inserted, we need to dig a little deeper. 

但是,如果需要将该方法输出的索引与插入元素的顺序相匹配,我们就需要深入研究一下。

5.3. Applying the Implementation on Different Types of Sets

5.3.在不同类型的集合上实施

It is important to note here that the index returned by traversing using an iterator might not match the insertion order, especially if we are using a HashSet as the source: 

这里需要注意的是,使用迭代器遍历返回的索引可能与插入顺序不匹配,尤其是当我们使用 HashSet 作为源时:

Set<Integer> set = new HashSet<>();
set.add(100);
set.add(20);
// add more elements
Assert.assertEquals(2, integerIndexOfElementsInSet.getIndexUsingIterator(set,100));

Although we inserted 100 as the first element, we see that the index we receive from our implementation is 2. The iterator will iterate through the elements in the order it is stored in the HashSet, not the order it was inserted. 

迭代器将按照元素在 HashSet 中的存储顺序而不是插入顺序遍历元素。

To solve this, we can swap out our HashSet with a LinkedHashSet

要解决这个问题,我们可以将 HashSet 换成 LinkedHashSet

Set<Integer> set = new LinkedHashSet<>();
set.add(100);
set.add(20);
// add more elements
Assert.assertEquals(0, integerIndexOfElementsInSet.getIndexUsingIterator(set, 100));

LinkedHashSet is backed by a LinkedList, which stores the elements and hence maintains the ordering of its elements. 

LinkedHashSetLinkedList支持,该列表存储元素并因此保持元素的排序。

Similarly, when we use a TreeSet, the index we get from our implementation is based on the natural ordering of the elements in the Set

同样,当我们使用 TreeSet 时,我们从实现中获得的索引是基于 Set 中元素的自然排序:

Set<Integer> set = new TreeSet<>();
set.add(0);
set.add(-1);
set.add(100);
// add more elements
Assert.assertEquals(0, integerIndexOfElementsInSet.getIndexUsingIterator(set, -1));
Assert.assertEquals(3, integerIndexOfElementsInSet.getIndexUsingIterator(set, 100));

In this section, we looked at how we can find the index of an element in a Set and how we can use LinkedHashSet to correctly find the index based on the insertion order. 

在本节中,我们了解了如何查找 Set 中元素的索引,以及如何使用 LinkedHashSet 根据插入顺序正确查找索引。

6. Writing a Custom LinkedHashSet Implementation

6.编写自定义 LinkedHashSet 实现

We can also write a custom variation of the LinkedHashSet class in Java to supplement its functionality to get the index of elements. Although it is highly unnecessary to create a subclass just for adding one utility method, this is still an option: 

我们还可以编写 Java 中 LinkedHashSet 类的自定义变体,以补充其获取元素索引的功能。虽然没有必要为了添加一个实用方法而创建一个子类,但这仍不失为一种选择:

public class InsertionIndexAwareSet<E> extends LinkedHashSet<E> {
    
    public int getIndexOf(E element) {
        int index = 0;
        for (E current : this) {
            if (current.equals(element)) {
                return index;
            }
            index++;
        }
        return -1;
    }
}

Finally, we can create an instance of our custom class and call the getIndexOf() method to get the index:

最后,我们可以创建自定义类的实例,并调用 getIndexOf() 方法获取索引:

@Test
public void givenIndexAwareSetWithStrings_whenIndexOfElement_thenGivesIndex() {
    InsertionIndexAwareSet<String> set = new InsertionIndexAwareSet<>();
    set.add("Go");
    set.add("Java");
    set.add("Scala");
    set.add("Python");
    Assert.assertEquals(0, set.getIndexOf("Go"));
    Assert.assertEquals(2, set.getIndexOf("Scala"));
    Assert.assertEquals(-1, set.getIndexOf("C++"));
}

7. Using Apache Commons Collections

7.使用 Apache Commons 集合</em

Finally, let’s also see how to use the Commons Collections library to solve our problem. The Apache Commons Collections library provides an extensive set of utility methods that help us tackle and extend the functionalities of the Java Collections APIs. 

最后,让我们看看如何使用 Commons Collections 库来解决我们的问题。Apache Commons Collections 库提供了大量实用方法,可帮助我们处理和扩展 Java Collections API 的功能。

First, we need to add the Maven dependency to use in our code:

首先,我们需要添加 Maven 依赖项,以便在代码中使用:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

We’ll take the help of the ListOrderedSet class here. ListOrderedSet implements the Set interface and uses the decorator pattern to provide the additional benefit of retaining the insertion order of the elements. If we add duplicate elements to the set, the element remains in its original position:

在此,我们将借助 ListOrderedSet 类。ListOrderedSet 实现了 Set 接口,并使用装饰器模式提供了保留元素插入顺序的额外好处。如果我们向集合中添加了重复的元素,元素将保持在其原来的位置:

@Test
public void givenListOrderedSet_whenIndexOfElement_thenGivesIndex() {
    ListOrderedSet<Integer> set = new ListOrderedSet<>();
    set.add(12);
    set.add(0);
    set.add(-1);
    set.add(50);
    Assert.assertEquals(0, set.indexOf(12));
    Assert.assertEquals(2, set.indexOf(-1));
}

8. Conclusion

8.结论

In this article, we looked at different ways we can find the index of an element in a Set. We first looked at why it is difficult to find the index of elements in a Set and how we can create our version of a LinkedHashSet to achieve the result. We finally looked at how to use the Apache libraries for the same outcome.

在本文中,我们探讨了查找 Set 中元素索引的不同方法。我们首先了解了查找 Set 中元素索引的困难原因,以及如何创建我们自己版本的 LinkedHashSet 来实现这一结果。最后,我们了解了如何使用 Apache 库来实现同样的结果。

As usual, all the code samples shown in this tutorial are available over on GitHub.

与往常一样,本教程中显示的所有代码示例均可在 GitHub 上获取