【问题标题】:How to obtain index of a given LinkedHashSet element without iteration?如何在不迭代的情况下获取给定 LinkedHashSet 元素的索引?
【发布时间】:2011-10-27 11:56:13
【问题描述】:

有可能吗?

说你有

private Set<String> names = new LinkedHashSet<String>();

Strings 是“Mike”、“John”、“Karen”。

是否有可能在不迭代的情况下得到“1”来返回“John”的索引是多少?

以下工作正常..这个问题我想知道是否有更好的方法

for (String s : names) {
    ++i;
    if (s.equals(someRandomInputString)) {
        break;
    }
}

【问题讨论】:

  • 集合通常没有内部顺序,因此元素的“索引”实际上没有意义——它可能对集合实现有意义,但在一般情况下没有意义。跨度>
  • 是否有特定的 Set 实现允许这样做?
  • @Mat LinkedHashSet 是有序的,它做的比他的 Set 合约要求做的要多,所以这个问题很合理
  • 你被 Set 卡住是有原因的吗?如果您需要使用索引,显然您应该使用列表。
  • 我觉得这很奇怪,这是不可能的。 LinkedHashSet 甚至明确声明元素存储在链表中,为什么没有办法获取索引?

标签: java set iteration


【解决方案1】:

Set interface 没有类似indexOf() 的方法。您确实需要对其进行迭代或使用List interface 来代替它提供indexOf() method

如果您愿意,将Set 转换为List 非常简单,只需将Set 传递给List 实现的构造函数即可。例如

List<String> nameList = new ArrayList<String>(nameSet);
// ...

【讨论】:

  • 但是.. 如果您从一开始就使用Set 来达到List 不适合的目的,那么我宁愿只是循环它(如果有必要创建一些实用方法)。当您想了解索引时,您究竟为什么要使用Set?只是为了将物品放在订单中?您可能只想从一开始就使用List,然后事先使用Collections#sort()
  • 糟糕的答案。我必须创建一个新的数组列表才能做到这一点?想象一下,你一直这样做。 SO上这个问题没有很好的答案,所以我必须自己写。
  • @momo:对糟糕的功能需求的回答通常确实很糟糕。首先使用List 而不是Set
  • 列表和集合也会产生不同的结果。也许用户需要以有效的方式避免重复,为什么使用链接哈希,但希望能够向某人提供可用项目的列表,并让他们只选择一个。我已经在下面发布了我的答案。它使用 arraylist 作为备份,允许 o(1) get(index)
【解决方案2】:

我不这么认为,但是您可以创建一个 LinkedHashSetWithIndex 包装器类来为您执行迭代,或者如果性能下降对于您的用例是可以接受的,则可以为每个条目的索引保留一个单独的表.

【讨论】:

  • 漂亮而优雅的建议。谢谢
【解决方案3】:

Set 通常不可能返回索引,因为它不一定为特定的 Set 实现定义好。例如它在 HashSet 文档中说

它不保证集合的迭代顺序;特别是,它不保证订单会随着时间的推移保持不变。

因此,当您实际期望的是实现 som 顺序的 Set 时,您不应该说类型是 Set。

【讨论】:

  • 哇,人们的反应速度很快!这是我在 stackoverflow 上的第二个帖子,如果我做错了什么,请原谅:) 我看到 Mat 在我之前几分钟发布的内容基本相同。
  • @Aaron V.:LinkedHashSet 确实保持顺序“这个链表定义了迭代顺序,即元素插入集合的顺序(插入顺序)。”
【解决方案4】:

这是一个执行插入、删除、保留的实现,由 arraylist 支持以在 get(index) 上实现 o(1)。

/**
 * @Author Mo. Joseph
 *
 * Allows you to call get with o(1) instead of o(n) to get an instance by index
 */
public static final class $IndexLinkedHashSet<E> extends LinkedHashSet<E> {
        private final ArrayList<E> list = new ArrayList<>();

        public $IndexLinkedHashSet(int initialCapacity, float loadFactor) {
                super(initialCapacity, loadFactor);
        }
        public $IndexLinkedHashSet() {
                super();
        }
        public $IndexLinkedHashSet(int initialCapacity) {
                super(initialCapacity);
        }
        public $IndexLinkedHashSet(Collection<? extends E> c) {
                super(c);
        }

        @Override
        public synchronized boolean add(E e) {
                if ( super.add(e) ) {
                        return list.add(e);
                }
                return false;
        }

        @Override
        public synchronized boolean remove(Object o) {
                if ( super.remove(o) ) {
                        return list.remove(o);
                }
                return false;
        }

        @Override
        public synchronized void clear() {
                super.clear();
                list.clear();
        }

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

        @Override
        public synchronized boolean removeAll(Collection<?> c) {
                if ( super.removeAll(c) ) {
                        return list.removeAll(c);
                }
                return true;
        }

        @Override
        public synchronized boolean retainAll(Collection<?> c) {
                if ( super.retainAll(c) ) {
                        return list.retainAll(c);
                }
                return false;
        }

        /**
         * Copied from super class
         */
        @Override
        public synchronized boolean addAll(Collection<? extends E> c) {
                boolean modified = false;
                for (E e : c)
                        if (add(e))
                                modified = true;
                return modified;
        }

}

测试它:

public static void main(String[] args) {

        $IndexLinkedHashSet<String> abc = new $IndexLinkedHashSet<String>();
        abc.add("8");
        abc.add("8");
        abc.add("8");
        abc.add("2");
        abc.add("3");
        abc.add("4");
        abc.add("1");
        abc.add("5");
        abc.add("8");

        System.out.println("Size: " + abc.size());
        int i = 0;
        while ( i < abc.size()) {
                System.out.println( abc.get(i) );
                i++;
        }

        abc.remove("8");
        abc.remove("5");

        System.out.println("Size: " + abc.size());
        i = 0;
        while ( i < abc.size()) {
                System.out.println( abc.get(i) );
                i++;
        }

        abc.clear();

        System.out.println("Size: " + abc.size());
        i = 0;
        while ( i < abc.size()) {
                System.out.println( abc.get(i) );
                i++;
        }

}

哪些输出:

Size: 6
8
2
3
4
1
5
Size: 4
2
3
4
1
Size: 0

当然 remove、removeAll、retainAll 现在的性能与 ArrayList 相同或更差。但我不使用它们,所以我可以接受。

享受吧!

编辑:

这是另一个实现,它没有扩展 LinkedHashSet,因为那是多余的。相反,它使用 HashSet 和 ArrayList。

/**
 * @Author Mo. Joseph
 *
 * Allows you to call get with o(1) instead of o(n) to get an instance by index
 */
public static final class $IndexLinkedHashSet<E> implements Set<E> {
        private final ArrayList<E> list = new ArrayList<>( );
        private final HashSet<E>   set  = new HashSet<>  ( );

        public synchronized boolean add(E e) {
                if ( set.add(e) ) {
                        return list.add(e);
                }
                return false;
        }

        public synchronized boolean remove(Object o) {
                if ( set.remove(o) ) {
                        return list.remove(o);
                }
                return false;
        }

        @Override
        public boolean containsAll(Collection<?> c) {
                return set.containsAll(c);
        }

        public synchronized void clear() {
                set.clear();
                list.clear();
        }

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

        public synchronized boolean removeAll(Collection<?> c) {
                if ( set.removeAll(c) ) {
                        return list.removeAll(c);
                }
                return true;
        }

        public synchronized boolean retainAll(Collection<?> c) {
                if ( set.retainAll(c) ) {
                        return list.retainAll(c);
                }
                return false;
        }

        public synchronized boolean addAll(Collection<? extends E> c) {
                boolean modified = false;
                for (E e : c)
                        if (add(e))
                                modified = true;
                return modified;
        }

        @Override
        public synchronized int size() {
                return set.size();
        }

        @Override
        public synchronized boolean isEmpty() {
                return set.isEmpty();
        }

        @Override
        public synchronized boolean contains(Object o) {
                return set.contains(o);
        }

        @Override
        public synchronized Iterator<E> iterator() {
                return list.iterator();
        }

        @Override
        public synchronized Object[] toArray() {
                return list.toArray();
        }

        @Override
        public synchronized <T> T[] toArray(T[] a) {
                return list.toArray(a);
        }
}

现在你有两个实现,我更喜欢第二个。

【讨论】:

  • 只是注意到在迭代器上调用 remove() 会破坏两种实现中的东西。如果您不想实现删除,只需 return Collections.unmodifiableList(list).iterator(); 在 Iterator remove() 上抛出 UnsupportedOperationException 并且速度很快(不复制列表数据)。
  • @MatejSnoha 也许,也许还需要提供一个自定义迭代器。也许有一天会出现,但现在不需要。
  • 也许我很困惑,但我在您的实现中没有看到 indexOf 方法,这就是问题所在。
【解决方案5】:

虽然对机器来说效率不高,但这可以在一行中实现:

int index = new ArrayList<String>(names).indexOf("John");

【讨论】:

  • 不好,新的数组列表就是为了做这个。迭代集合更便宜。
  • @mmm 这对程序员和眼睛来说更便宜 - 只是一条短线。
【解决方案6】:

更好的方法没有,只有一个单行的(它也使用了迭代器,但隐含地):

new ArrayList(names).get(0)

【讨论】:

  • 不好,新的数组列表就是为了做这个。迭代集合更便宜。
【解决方案7】:

您可以将您的 Set 转换为 List,然后您可以进行任何索引操作。

示例:需要将 Set 列表裁剪为 5 个项目。

        Set<String> listAsLinkedHashSet = new LinkedHashSet<>();
        listAsLinkedHashSet.add("1");
        listAsLinkedHashSet.add("2");
        listAsLinkedHashSet.add("3");
        listAsLinkedHashSet.add("4");
        listAsLinkedHashSet.add("1");
        listAsLinkedHashSet.add("2");
        listAsLinkedHashSet.add("5");
        listAsLinkedHashSet.add("7");
        listAsLinkedHashSet.add("9");
        listAsLinkedHashSet.add("8");
        listAsLinkedHashSet.add("1");
        listAsLinkedHashSet.add("10");
        listAsLinkedHashSet.add("11");

        List<String> listAsArrayList = new ArrayList<>(listAsLinkedHashSet);
        //crop list to 5 elements
        if (listAsArrayList.size() > 5) {
            for (int i = 5; i < listAsArrayList.size(); i++) {
                listAsArrayList.remove(i);
                i--;
            }
        }
        listAsLinkedHashSet.clear();
        listAsLinkedHashSet.addAll(listAsArrayList);

【讨论】:

  • @dyno-chris 我确信提出问题的人知道如何从集合中创建 ArrayList,而问题是专门查找索引而无需迭代。您正在迭代 ArrayList 而不是 set。第二个问题是如果集合很大,那么您将浪费大量内存。
  • 提问者很清楚。 “以下工作正常......这个问题我想知道是否有更好的方法”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-27
  • 2021-09-15
相关资源
最近更新 更多