Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/webpack/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 使用randomElement()方法编写集合实现_Java_Collections_Set_Time Complexity - Fatal编程技术网

Java 使用randomElement()方法编写集合实现

Java 使用randomElement()方法编写集合实现,java,collections,set,time-complexity,Java,Collections,Set,Time Complexity,我正在尝试编写一个Set的实现,它有一个额外的方法randomElement(),该方法从集中随机返回一个元素。我已经基于HashSet实现了一个快速contains()方法。我还使用了ArrayList,因此randomElement()方法也是O(1)——使用ArrayList只需选择一个随机索引 这是我的密码 public final class RandomChoiceSet<E> extends AbstractSet<E> { private fin

我正在尝试编写一个
Set
的实现,它有一个额外的方法
randomElement()
,该方法从
集中随机返回一个元素。我已经基于
HashSet
实现了一个快速
contains()
方法。我还使用了
ArrayList
,因此
randomElement()
方法也是
O(1)
——使用
ArrayList
只需选择一个随机索引

这是我的密码

public final class RandomChoiceSet<E> extends AbstractSet<E> {

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

    public RandomChoiceSet() {}

    public RandomChoiceSet(Collection<? extends E> collection) {
        addAll(collection);
    }

    public E randomElement() {
        return list.get(random.nextInt(list.size()));
    }

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

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

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

    @Override
    public boolean add(E e) {
        boolean result = set.add(e);
        if (result)
            list.add(e);
        return result;
    }

    @Override
    public boolean remove(Object o) {
        boolean result = set.remove(o);
        if (result)
            list.remove(o);
        return result;
    }

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

            private final Iterator<E> iterator = list.iterator();
            private E e;

            @Override
            public boolean hasNext() {
                return iterator.hasNext();
            }

            @Override
            public E next() {
                return e = iterator.next();
            }

            @Override
            public void remove() {
                iterator.remove();
                set.remove(e);
            }
        };
    }
}
公共最终类RandomChoiceSet扩展了抽象集{
私有最终列表=新的ArrayList();
私有最终集=新HashSet();
私有最终随机=新随机();
公共随机选择集(){}

public RandomChoiceSet(Collection我能想到的唯一一件事就是用一个HashMap替换你的集合,该HashMap将你的元素映射到它在arrayList中的位置。大小、包含、添加和随机将是相同的。对于移除,你将执行以下操作:

Find the element from the HashMap
Retrieve it's position in the arrayList
Remove the element from the HashMap
Swap the deleted element with the last element in the array
Modify the swapped element position in the HashMap
Delete the last element from the array //Now this is O(1)

这项工作的原因是,您不需要数组中的任何特定顺序,您只需要一个随机访问存储器,这样更改顺序或数据就不会引起任何问题,只要您将其与hashmap保持同步。

我唯一能想到的就是用一个从元素映射到其位置的hashmap替换您的集合在arrayList中的上。大小、包含、添加和随机将是相同的。对于删除,您将执行以下操作:

Find the element from the HashMap
Retrieve it's position in the arrayList
Remove the element from the HashMap
Swap the deleted element with the last element in the array
Modify the swapped element position in the HashMap
Delete the last element from the array //Now this is O(1)

这项工作的原因是,您不需要数组中的任何特定顺序,您只需要一个随机访问存储器,这样更改顺序或数据就不会导致任何问题,只要您将其与hashmap保持同步。

我认为您需要自定义实现
hashmap
,因为它需要细粒度控件。Ra选择一个存储桶非常简单,您可以在存储桶中使用
ArrayList
s进行随机访问

为了清楚起见,您可以实现一个经典的
HashMap
,但是在每个bucket中都会有一个
ArrayList
,而不是使用
LinkedList
。选择一个随机元素非常简单:

  • 随机选择一个介于0和
    nbbucket之间的索引
    rd0
  • 在0和桶[rd0]之间随机选取索引
    rd1
    。size()

我认为您需要一个自定义的
HashMap
实现,因为它需要一个细粒度的控件。随机选取一个bucket很容易,您可以在bucket中使用
ArrayList
来进行随机访问

为了清楚起见,您可以实现一个经典的
HashMap
,但是在每个bucket中都会有一个
ArrayList
,而不是使用
LinkedList
。选择一个随机元素非常简单:

  • 随机选择一个介于0和
    nbbucket之间的索引
    rd0
  • 在0和桶[rd0]之间随机选取索引
    rd1
    。size()

    这是一个功能性实现,遵循Amr的解决方案。即使是
    迭代器
    remove()
    方法也可以工作,因为元素总是从后面的位置进行交换

    public final class RandomChoiceSet<E> extends AbstractSet<E> {
    
        private final List<E> list = new ArrayList<>();
        private final Map<E, Integer> map = new HashMap<>();
        private final Random random = new Random();
    
        public RandomChoiceSet() {}
    
        public RandomChoiceSet(Collection<? extends E> collection) {
            addAll(collection);
        }
    
        public E randomElement() {
            return list.get(random.nextInt(list.size()));
        }
    
        @Override
        public int size() {
            return list.size();
        }
    
        @Override
        public boolean contains(Object o) {
            return map.containsKey(o);
        }
    
        @Override
        public void clear() {
            list.clear();
            map.clear();
        }
    
        @Override
        public boolean add(E e) {
            if (map.containsKey(e))
                return false;
            map.put(e, list.size());
            list.add(e);
            return true;
        }
    
        @Override
        public boolean remove(Object o) {
            Integer currentIndex = map.get(o);
            if (currentIndex == null)
                return false;
            int size = list.size();
            E lastE = list.get(size - 1);
            list.set(currentIndex, lastE);
            list.remove(size - 1);
            map.put(lastE, currentIndex);
            map.remove(o);
            return true;
        }
    
        @Override
        public Iterator<E> iterator() {
            return new Iterator<E>() {
    
                private int index = 0;
    
                @Override
                public boolean hasNext() {
                    return index < list.size();
                }
    
                @Override
                public E next() {
                    return list.get(index++);
                }
    
                @Override
                public void remove() {
                    RandomChoiceSet.this.remove(list.get(--index));
                }
            };
        }
    }
    

    我没有做过适当的基准测试,但是像这样迭代
    似乎比对每个
    循环使用
    迭代
    哈希集
    快好几倍。显然没有检查并发修改,这是一个
    实现,比
    哈希更需要内存Set
    (虽然不像
    LinkedHashSet
    那样需要内存),但总体上我认为它非常有趣。

    下面是一个功能实现,遵循Amr的解决方案。即使是
    迭代器
    remove()
    方法也可以工作,因为元素总是从后面的位置交换进来

    public final class RandomChoiceSet<E> extends AbstractSet<E> {
    
        private final List<E> list = new ArrayList<>();
        private final Map<E, Integer> map = new HashMap<>();
        private final Random random = new Random();
    
        public RandomChoiceSet() {}
    
        public RandomChoiceSet(Collection<? extends E> collection) {
            addAll(collection);
        }
    
        public E randomElement() {
            return list.get(random.nextInt(list.size()));
        }
    
        @Override
        public int size() {
            return list.size();
        }
    
        @Override
        public boolean contains(Object o) {
            return map.containsKey(o);
        }
    
        @Override
        public void clear() {
            list.clear();
            map.clear();
        }
    
        @Override
        public boolean add(E e) {
            if (map.containsKey(e))
                return false;
            map.put(e, list.size());
            list.add(e);
            return true;
        }
    
        @Override
        public boolean remove(Object o) {
            Integer currentIndex = map.get(o);
            if (currentIndex == null)
                return false;
            int size = list.size();
            E lastE = list.get(size - 1);
            list.set(currentIndex, lastE);
            list.remove(size - 1);
            map.put(lastE, currentIndex);
            map.remove(o);
            return true;
        }
    
        @Override
        public Iterator<E> iterator() {
            return new Iterator<E>() {
    
                private int index = 0;
    
                @Override
                public boolean hasNext() {
                    return index < list.size();
                }
    
                @Override
                public E next() {
                    return list.get(index++);
                }
    
                @Override
                public void remove() {
                    RandomChoiceSet.this.remove(list.get(--index));
                }
            };
        }
    }
    

    我没有做过适当的基准测试,但是像这样迭代
    似乎比对每个
    循环使用
    迭代
    哈希集
    快好几倍。显然没有检查并发修改,这是一个
    实现,比
    哈希更需要内存Set
    (虽然不像
    LinkedHashSet
    那样需要内存),但总体而言,我认为它非常有趣。

    这是一个完美的例子,说明了为什么集合接口应该有一个
    getRandom()
    方法,以及为什么它是API中缺少的基本功能。
    getRandom()的HashSet实现
    将只调用它的私有HashMap
    getRandom()
    方法,以此类推,直到数据表示是可索引的或可iterandom的,此时应该实现
    getRandom()
    逻辑

    基本上,这个
    getRandom()
    方法的复杂性会根据底层实现的不同而有所不同。但是,由于所有数据最终都必须存储为数组或链表,因此由于没有具有集合感知的getRandom(),许多优化都被抛在了一边

    想一想,什么是集合?想一想,我可以从现实世界中的集合中检索一个随机元素吗?是的,它应该在适当的OO代码中

    但事实并非如此,因此,如果无法构建自己的类,则必须迭代哈希集中的项并返回
    random.nextInt(size())
    元素

    如果您能够负担得起构建自己的实现(这似乎是您的情况),那么您的建议是一种公平的方法,但是我不明白为什么您要实现自己的匿名迭代器,这应该很好

    
    @凌驾
    公共迭代器迭代器(){
    返回set.iterator();
    }
    

    这是一个完美的例子,说明了为什么Collections接口应该有一个
    getRandom()
    方法,以及为什么它是API中缺少的基本功能