【问题标题】:Efficiently adding element to the top of the list有效地将元素添加到列表顶部
【发布时间】:2015-08-18 10:18:56
【问题描述】:

我有一个这样的 ENUM,我总能从中得到我的 localFruit,它可以是 APPLEORANGEBANANA

public enum Fruits {
    // it can have more elements here
    APPLE, ORANGE, BANANA;

    // some code
}

假设 APPLE 是我的localFruit,那么ORANGEBANANA 就是我的remoteFruits。我需要洗牌我的remoteFruits,然后确保我的localFruit 在我的列表顶部,然后是remoteFruits

下面是我的代码,我在其中进行改组并将其添加到原始 result 列表中:在下面的代码中,CURRENT_FRUIT 可以是 APPLEORANGEBANANA

private static List<Fruits> getFruitsInOrder() {
    EnumSet<Fruits> localFruit = EnumSet.of(CURRENT_FRUIT);
    EnumSet<Fruits> remoteFruits = EnumSet.complementOf(localFruit);

    List<Fruits> result = new ArrayList<Fruits>(remoteFruits);
    Collections.shuffle(result);

    // first element in the list will always be the local fruit
    result.addAll(0, new ArrayList<Fruits>(localFruit));
    return result;
}

由于这段代码会被调用很多次,所以想看看我做错了什么,这可能是性能方面的瓶颈?我的代码在性能方面还可以吗?

我的主要目标是将localFruit 放在列表顶部,然后是remoteFruits(但我需要在添加到我的结果列表之前将它们洗牌)。

【问题讨论】:

  • 为什么不result.add(0, CURRENT_FRUIT)
  • 那么remoteFruits呢?对于remoteFruits 的改组,我需要转换为列表以便我可以轻松地做到这一点。

标签: java list arraylist enums linked-list


【解决方案1】:

所有这些解决方案都太难了。 Java 集合非常有效,但简单的数组访问更是如此。此外,构建两个集合(一个具有补码操作),复制到 ArrayList,然后使用 addAll 创建一个新的 ArrayList 是很多无用的工作和内存垃圾。

枚举按顺序为您提供其值的数组。用它!只需将本地元素交换到零位置,然后对数组的其余部分进行洗牌。这样,您就可以创建一个数据结构:要返回的 ArrayList。其余的只是对其元素进行重新排序。

当然,除非你有一个巨大的枚举或者数百万次调用这个函数,否则这个讨论是学术性的。您不会注意到性能差异。

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

enum Fruit { APPLE, PEAR, PEACH, PLUM, BANANA }

public class Hack {

    static List<Fruit> getFruitInOrder(Fruit local) {
        List<Fruit> list = Arrays.asList(Fruit.values());
        Collections.swap(list, 0, local.ordinal());
        Collections.shuffle(list.subList(1, list.size()));
        return list;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(getFruitInOrder(Fruit.PLUM));
        }
    }
}

在我的 MacBook 上:

run:
[PLUM, BANANA, PEAR, APPLE, PEACH]
[PLUM, PEACH, PEAR, APPLE, BANANA]
[PLUM, PEACH, BANANA, PEAR, APPLE]
[PLUM, PEAR, BANANA, APPLE, PEACH]
[PLUM, PEAR, APPLE, BANANA, PEACH]
[PLUM, BANANA, APPLE, PEACH, PEAR]
[PLUM, APPLE, BANANA, PEACH, PEAR]
[PLUM, APPLE, PEACH, PEAR, BANANA]
[PLUM, APPLE, PEAR, PEACH, BANANA]
[PLUM, PEACH, APPLE, PEAR, BANANA]
BUILD SUCCESSFUL (total time: 0 seconds)

【讨论】:

  • 感谢您的建议。您能否添加一些描述,例如您的示例与我的示例之间有什么区别以及为什么它更好。这将有助于我更好地理解。
  • @user1950349 集合操作比访问数组元素成本更高。您正在构建两套,一套带有补码。然后将补码转换为数组。洗牌后,将元素向上移动一个位置,为第一个位置腾出空间。您可以通过构建一个长度为 1 的数组并使用 addAll 来分配它。这是一大堆无用的复制。上面的代码在对 values() 的调用中创建了一个数组。其余的将该数组的元素移动到位。当然,如果枚举很小,它几乎没有什么区别。主要的一点是,对于这个问题,集合是矫枉过正的。
  • @user1950349 一句老话(我不记得是谁说的了)写 300 行代码需要一天的时间,再写 100 行代码需要 3 天。
【解决方案2】:

这个呢?它通过第一次正确调整大小来避免任何ArrayList resize 性能命中,并通过插入列表来避免你受到的影响。它还干净地支持多个localFruit。我认为您的版本已经是 O(n),所以这不是什么大问题。

private static List<Fruits> getFruitsInOrder() {
    EnumSet<Fruits> localFruit = EnumSet.of(CURRENT_FRUIT);
    EnumSet<Fruits> remoteFruits = EnumSet.complementOf(localFruit);

    List<Fruits> result = new ArrayList<Fruits>(localFruit.size() + remoteFruits.size());
    result.addAll(localFruit);
    result.addAll(remoteFruit);
    Collections.shuffle(result.subList(localFruit.size(), result.size()));
    return result;
}

【讨论】:

    【解决方案3】:

    假设集合的大小相当小,比如 3 或 4 个值,这可能没问题。与优化一样,最好先分析您的代码,然后再进行优化。话虽如此,代码看起来相当合理,但是您正在制作大量列表副本,其中大部分是可以避免的。这是您的代码,并带有注释,表明您每次制作副本:

    private static List<Fruits> getFruitsInOrder() {
        EnumSet<Fruits> localFruit = EnumSet.of(CURRENT_FRUIT);
        // Creates a new set copying all the fruits from the enum
        EnumSet<Fruits> remoteFruits = EnumSet.complementOf(localFruit);
    
        // Copies the set above
        List<Fruits> result = new ArrayList<Fruits>(remoteFruits);
        Collections.shuffle(result);
    
        // Will allocate a new array, copy the remote fruits, then add
        // another copy of the local fruit
        result.addAll(0, new ArrayList<Fruits>(localFruit));
        return result;
    }
    

    这些副本都是O(n),其中n 是水果的数量。如果 n 小于 10 左右(甚至可能小于 100 左右),这还不错。还有分配和释放所有这些对象的成本。再次,不可怕,但也不是很好。这是一种只制作​​一个副本的替代实现:

    private static List<Fruits> getFruitsInOrder() {
        // Makes one copy of all the fruits, that's it.
        ArrayList<Fruits> allFruits = new ArrayList<>(Fruits.values());
        // move the current fruit to the front. Assumes that the value of the
        // fruit is its array index: if not, you'll have to maintain that 
        // explicitly in a Map<Fruits, int>
        int curFruitIdx = CURRENT_FRUIT.value();
        Fruits curFruit = allFruits.get(curFruitIdx);
        allFruits.set(curFruitIdx, allFruits.get(0));
        allFruits.set(0, curFruit);
        // Now get a **view**, not a copy, of the rest of the list and shuffle it
        List<Fruits> toShuffle = allFruits.subList(1, allFruits.size());
        Collections.suffle(toShuffle);
        return allFruits;
    }
    

    如果不需要线程安全,即使方法顶部的单个副本也可以删除:您可以将副本粘贴到静态某处并继续对其进行操作。同样,如果 n 很小,那么上面的内容实际上可能比你得到的要慢。

    基本上,以上是避免在这种情况下复制长列表的一种方法。对于大列表,这很有帮助。小的,没那么多。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-18
      • 1970-01-01
      • 1970-01-01
      • 2014-10-28
      • 2021-12-18
      • 1970-01-01
      相关资源
      最近更新 更多