【问题标题】:Java 8 Map of Collections remove element from collection and remove entry if emptyJava 8 Map of Collections 从集合中删除元素,如果为空则删除条目
【发布时间】:2019-05-18 19:34:12
【问题描述】:

我有一张地图,其中的值是一个集合。给定一个键,我想删除集合的一个元素并返回它,但如果集合为空,我也想删除该条目。有没有办法使用 Java 8 的众多新 Map 方法之一在短时间内完成此操作?

一个简单的例子(我使用堆栈,但它可以是列表、集合等)。举例来说,假设已经检查了映射是否包含键。

public static String removeOne(Map<Integer, Stack<String>> map, int key) {
    Stack<String> stack = map.get(key);
    String result = stack.pop();
    if(stack.isEmpty()){
        map.remove(key);
    }
    return result;
}

我尝试做类似的事情

map.compute(1, (k, v) -> {v.pop(); return v.size() == 0 ? null : v;});

但即使它确实删除了为空的条目,我也不知道如何获取pop() 返回的值。

【问题讨论】:

  • 看不出有什么理由在这里使用 streams/lambda
  • @NicholasK 唯一的好处是映射到 null 并自动删除条目 IMO,否则,我将不得不同意
  • 我不确定这会有多通用,但something like this 怎么样?

标签: java dictionary collections java-8 hashmap


【解决方案1】:
/* quite ugly
String rv = Optional.ofNullable(map.get(1)).map(stack -> {
            if (!stack.isEmpty()) {
                String v = stack.pop();
                if (stack.isEmpty()) {
                    map.remove(1);
                }
                return v;
            }
            return null;
        }).orElse(null);
*/ 

@Test
public void test() {
    {
        Map<Integer, Stack<String>> map = new HashMap<>();
        Stack<String> s = new Stack<String>();
        s.addAll(Arrays.asList("a", "b"));
        map.put(1, s);
        String rv = Optional.ofNullable(map.get(1)).map(stack -> {
            if (!stack.isEmpty()) {
                String v = stack.pop();
                if (stack.isEmpty()) {
                    map.remove(1);
                }
                return v;
            }
            return null;
        }).orElse(null);
        Assert.assertEquals("b", rv);
        Assert.assertEquals(1, map.get(1).size());
        Assert.assertEquals("a", map.get(1).iterator().next());
    }
    {
        Map<Integer, Stack<String>> map = new HashMap<>();
        Stack<String> s = new Stack<String>();
        s.add("a");
        map.put(1, s);
        String rv = Optional.ofNullable(map.get(1)).map(stack -> {
            if (!stack.isEmpty()) {
                String v = stack.pop();
                if (stack.isEmpty()) {
                    map.remove(1);
                }
                return v;
            }
            return null;
        }).orElse(null);
        Assert.assertEquals("a", rv);
        Assert.assertNull(map.get(1));
    }
}

【讨论】:

  • @JurgenDeLandsheer 你首先需要pop 然后然后检查它是否为空
  • 它完全按照他的要求做,它使用了所有新的 java 8 可能性
【解决方案2】:

嗯,它甚至比你已有的更难看,但我想有一种方法:

public static String removeOne(Map<Integer, Stack<String>> map, int key) {
    String[] removed = new String[1];
    map.compute(key, (k, v) -> {
        removed[0] = v.pop();
        return v.size() == 0 ? null : v;
    });
    return removed[0];
}

问题是merge/compute 等返回,在您的情况下是Stack/Set/List,而不是该集合中的单个元素。

【讨论】:

  • 哈哈哈我其实也想过这个解决方案,但正如你所说,它真的很难看。
【解决方案3】:

有没有一种方法可以使用众多新方法中的一种在短时间内完成此操作 Java 8 的映射方法?

从 JDK8 开始,没有任何新方法可以改进您的代码,无论是在可读性还是效率方面。

如果您将其作为自己的练习,那么我可以在一定程度上理解您为什么要缩短代码(如果可能),但是在生产代码打高尔夫球时应该避免,而是采用最易读和可维护的方法;再长也没关系。

你的方法很好。

【讨论】:

    【解决方案4】:

    我完全同意@NicholasK。这里没有理由使用任何流或 lambda。

    你的方法很不错。我唯一想补充的是使其通用:

    public static <K, E, C extends Collection<E>> E removeOne(Map<K, C> map, K key) {
        C col = map.get(key);
        Iterator<E> it = col.iterator();
        E e = it.next();
        it.remove();
        if (!it.hasNext()) {
            map.remove(key);
        }
        return e;
    }
    

    此方法将适用于任何返回有效迭代器的集合(映射值)。

    【讨论】:

    • 如果集合为空,我找不到删除条目的部分。另外,在所有情况下你都必须做it.remove(),为什么!it.hasNext()check?
    • !it.hasNext() 在我们删除一个元素后检查集合是否为空,因为它是由 OP 在他自己的示例中实现的。
    • 我是 OP,我 100% 确信这不会做同样的事情。我刚刚测试了它。正如我所说,您永远不会从地图中删除空集合。我想你的意思是:if (!it.hasNext()) { map.remove(key); } it.remove();
    • 好的,我的立场是正确的。现在更新的版本应该对任何集合类型作为地图值执行与您的示例相同的操作。
    【解决方案5】:

    Guava's Multimap 为您处理 remove-collection-if-empty 逻辑。您可以在两行中获得与您的方法等效的行为:

    public static String removeOne(ListMultimap<Integer, String> map, int key) {
        List<String> stack = map.get(key);
        return stack.remove(stack.size() - 1);
    }
    

    如果地图没有给定键的条目,您现有的解决方案和上述解决方案都会抛出异常。您可以选择更改代码来处理此问题:

    public static String removeOne(ListMultimap<Integer, String> map, int key) {
        List<String> stack = map.get(key);
        if (stack.isEmpty()) {
            return null;
        }
        return stack.remove(stack.size() - 1);
    }
    

    当然你也可以把它变成通用的:

    public static <K, V> V removeOne(ListMultimap<K, V> map, K key) {
        List<V> stack = map.get(key);
        if (stack.isEmpty()) {
            return null;
        }
        return stack.remove(stack.size() - 1);
    }
    

    【讨论】:

    • 感谢有关外部库的提示!顺便说一句,它适合一行:map.get(key).pop();
    【解决方案6】:

    或者您可以使用size 将其重写为:

    public static String removeOne(Map<Integer, Stack<String>> map, int key) {
        return map.get(key).size() == 1 ? map.remove(key).pop() : map.get(key).pop();
    }
    

    【讨论】:

    • 这种方法的遗憾是它只在地图中存在条目时才有效。我知道 OP 说过我们可以假设它已经存在,我只是说添加对密钥的检查会使代码更丑陋,因为在这种情况下该怎么办?返回空?也许......我看到的另一个缺点是您使用了两次get,尽管这很容易解决。无论如何 +1 以获得良好而简洁的答案
    • 嗯,是的,空值检查可能会使它更加混乱,但是回答在这种情况下要做什么的功能实现也需要更改 OP 的现有代码。因为这可能会导致此类情况下的 NPE。使用get 是我怀疑一般性能是否会受到损害的部分。但是后来O(1) 查找让我觉得疏忽了,再加上我在想,如果我存储这样的价值,在空间方面不会有类似的成本吗? @FedericoPeraltaSchaffner
    猜你喜欢
    • 1970-01-01
    • 2011-02-06
    • 2013-04-03
    • 2021-03-13
    • 1970-01-01
    • 1970-01-01
    • 2017-06-15
    • 1970-01-01
    • 2015-05-12
    相关资源
    最近更新 更多