【问题标题】:Optimizing an algorithm [compile ready code]优化算法[编译就绪代码]
【发布时间】:2014-12-27 10:29:24
【问题描述】:

游戏角度的问题(扑克)

玩家有 2 个绿色筹码(5 分)和 1 个蓝色筹码(10 分)。这总分是 20 分。现在玩家想要购买一个花费 16 点的游戏图标。玩家有足够的钱购买该物品。所以玩家支付了 16 点,但是他会给商店多少点才能正确支付。

现在我已经编写了一个工作示例,所有工作都已完成。

代码

程序.java

import java.util.Arrays;

public class Program {

    public static void main(String[] args) {
        // Setting up test environment
        Player player = new Player("Borrie", new int[]{0,0,0,0, 230});
        int itemCost = 16626;
        // Pay for item
        System.out.printf("First we check if the player can pay with it's current ChipSet");
        if (!player.canPayWithChipSet(player.getChips(), 5)) {
            if (player.exchangeChips(5)) {
                System.out.printf("\n\nThe players ChipSet:" + Arrays.toString(player.getChips().chips));
                System.out.printf("\nThe players ChipSet has been succesfully exchanged.");
            } else {
                System.out.printf("\n\nThe players ChipSet:" + Arrays.toString(player.getChips().chips));
                System.out.printf("\nThe players ChipSet was not able to be exchanged.\n");
            }
        } else {
            System.out.printf("\n\nThe player can pay exact with it's original ChipSet. No need to exchange.");
        }

    }
}

Player.java

import java.util.ArrayList;
import java.util.Arrays;

public class Player {

    private String name;
    private ChipSet chips;
    private int points = 0;

    public Player(String name, int[] chips) {
        this.name = name;
        this.chips = new ChipSet(chips);
        this.points = this.chips.getSum();
    }

    public boolean exchangeChips(int cost) {
        ChipSet newChipSet = exchangePlayerChipSet(this.chips.getChips(), cost);
        if (newChipSet == null) {
            return false;
        }

        this.chips = newChipSet;
        return true;
    }

    public ChipSet exchangePlayerChipSet(int[] originalChipValues, int cost) {
        ChipSet newChipSet = null;

        // Create possible combinations to compare
        ArrayList<ChipSet> chipSetCombos = createCombinations(this.chips.getChips());

        // Filter the chipset based on if it's able to pay without changing chips
        System.out.printf("\n\n---- Filter which of these combinations are able to be payed with without changing chips ----");
        ArrayList<ChipSet> filteredCombos = filterCombinations(chipSetCombos, cost);

        // Compare the filtered chipsets to determine which one has changed the least
        if (!filteredCombos.isEmpty()) {
            newChipSet = compareChipSets(originalChipValues, filteredCombos);
        }
        return newChipSet;
    }

    private ArrayList<ChipSet> createCombinations(int[] array) {
        ArrayList<ChipSet> combos = new ArrayList<>();
        int[] startCombo = array;
        System.out.printf("Player has " + getTotalPoints(startCombo) + " points in chips.");
        System.out.printf("\nPlayer has these chips (WHITE,RED,GREEN,BLUE,BLACK): " + Arrays.toString(startCombo));

        while (startCombo[4] != 0) {
            startCombo = lowerBlack(startCombo);
            if (getTotalPoints(startCombo) == points) {
                combos.add(new ChipSet(startCombo));
            }
        }
        while (startCombo[3] != 0) {
            startCombo = lowerBlue(startCombo);
            if (getTotalPoints(startCombo) == points) {
                combos.add(new ChipSet(startCombo));
            }
        }
        while (startCombo[2] != 0) {
            startCombo = lowerGreen(startCombo);
            if (getTotalPoints(startCombo) == points) {
                combos.add(new ChipSet(startCombo));
            }
        }
        while (startCombo[1] != 0) {
            startCombo = lowerRed(startCombo);
            if (getTotalPoints(startCombo) == points) {
                combos.add(new ChipSet(startCombo));
            }
        }
        System.out.printf("\n\n---- Creating variations on the players chips ----");
        System.out.printf("\nVariation (all worth " + getTotalPoints(startCombo) + " points):\n");

        int counter = 1;
        for (ChipSet a : combos) {
            System.out.printf("\nCombo " + counter + ": " + Arrays.toString(a.getChips()));
            counter++;
        }
        return combos;
    }

    private ArrayList<ChipSet> filterCombinations(ArrayList<ChipSet> combinations, int cost) {
        ArrayList<ChipSet> filteredChipSet = new ArrayList<>();
        combinations.stream().filter((cs) -> (canPayWithChipSet(cs, cost))).forEach((cs) -> {
            filteredChipSet.add(cs);
        });
        return filteredChipSet;
    }

    // This method has be worked out
    public boolean canPayWithChipSet(ChipSet cs, int cost) {
        ChipSet csOrig = new ChipSet(cs.chips);
        ChipSet csCopy = new ChipSet(cs.chips);
        int counterWhite = 0, counterRed = 0, counterGreen = 0, counterBlue = 0, counterBlack = 0;

        while (20 <= cost && cost > 0 && csOrig.getChips()[4] != 0) {
            csOrig.getChips()[4] -= 1;
            cost -= 20;
            counterBlack++;
        }
        while (10 <= cost && cost > 0 && csOrig.getChips()[3] != 0) {
            csOrig.getChips()[3] -= 1;
            cost -= 10;
            counterBlue++;
        }
        while (5 <= cost && cost > 0 && csOrig.getChips()[2] != 0) {
            csOrig.getChips()[2] -= 1;
            cost -= 5;
            counterGreen++;
        }
        while (2 <= cost && cost > 0 && csOrig.getChips()[1] != 0) {
            csOrig.getChips()[1] -= 1;
            cost -= 2;
            counterRed++;
        }
        while (1 <= cost && cost > 0 && csOrig.getChips()[0] != 0) {
            csOrig.getChips()[0] -= 1;
            cost -= 1;
            counterWhite++;
        }

        if (cost == 0){
            System.out.printf("\nCombo: %s can pay exact. With %d white, %d red, %d green, %d blue an %d black chips", Arrays.toString(csCopy.chips),counterWhite,counterRed,counterGreen,counterBlue,counterBlack);
            return true;
        } else {
            System.out.printf("\nCombo: %s cannot pay exact.\n\n\n", Arrays.toString(csCopy.chips));
            return false;
        }    
    }

    private ChipSet compareChipSets(int[] originalChipValues, ArrayList<ChipSet> chipSetCombos) {
        ChipSet newChipSet;
        int[] chipSetWaardes = originalChipValues; // originele chipset aantal van kleur
        int[] chipSetCombosDifferenceValues = new int[chipSetCombos.size()];
        int counter = 1;

        System.out.printf("\n\n---- Calculate differences between players stack and it's variations ----");
        for (ChipSet cs : chipSetCombos) {
            int amountWhite = cs.getChips()[0];
            int amountRed = cs.getChips()[1];
            int amountGreen = cs.getChips()[2];
            int amountBlue = cs.getChips()[3];
            int amountBlack = cs.getChips()[4];
            int differenceWhite = Math.abs(chipSetWaardes[0] - amountWhite);
            int differenceRed = Math.abs(chipSetWaardes[1] - amountRed);
            int differenceGreen = Math.abs(chipSetWaardes[2] - amountGreen);
            int differenceBlue = Math.abs(chipSetWaardes[3] - amountBlue);
            int differenceBlack = Math.abs(chipSetWaardes[4] - amountBlack);
            int totalDifference = differenceWhite + differenceRed + differenceGreen + differenceBlue + differenceBlack;
            chipSetCombosDifferenceValues[counter - 1] = totalDifference;
            System.out.printf("\nCombo " + counter + ": " + Arrays.toString(cs.getChips()) + " = " + totalDifference);
            counter++;
        }
        newChipSet = chipSetCombos.get(smallestValueOfArrayIndex(chipSetCombosDifferenceValues));
        System.out.printf("\n\nThe least different ChipSet is: " + Arrays.toString(newChipSet.getChips()) + " ");

        return newChipSet;
    }

    private int smallestValueOfArrayIndex(int[] array) {
        int currentValue = array[0];
        int smallestIndex = 0;
        for (int j = 1; j < array.length; j++) {
            if (array[j] < currentValue) {
                currentValue = array[j];
                smallestIndex = j;
            }
        }
        return smallestIndex;
    }

    private int[] lowerBlack(int[] array) {
        return new int[]{array[0], array[1], array[2], array[3] + 2, array[4] - 1};
    }

    private int[] lowerBlue(int[] array) {
        return new int[]{array[0], array[1], array[2] + 2, array[3] - 1, array[4]};
    }

    private int[] lowerGreen(int[] array) {
        return new int[]{array[0] + 1, array[1] + 2, array[2] - 1, array[3], array[4]};
    }

    private int[] lowerRed(int[] array) {
        return new int[]{array[0] + 2, array[1] - 1, array[2], array[3], array[4]};
    }

    private int getTotalPoints(int[] array) {
        return ((array[0] * 1) + (array[1] * 2) + (array[2] * 5) + (array[3] * 10) + (array[4] * 20));
    }

    public String getName() {
        return this.name;
    }

    public int getPoints() {
        return this.points;
    }

    public ChipSet getChips() {
        return chips;
    }

}

ChipSet.java

public class ChipSet {

    public static final int WHITE_VALUE = 1;
    public static final int RED_VALUE = 2;
    public static final int GREEN_VALUE = 5;
    public static final int BLUE_VALUE = 10;
    public static final int BLACK_VALUE = 20;

    public static final int[] VALUES = new int[]{WHITE_VALUE, RED_VALUE, GREEN_VALUE, BLUE_VALUE, BLACK_VALUE};

    protected int[] chips;

    public ChipSet(int[] chips) {
        if (chips == null || chips.length != 5) {
            throw new IllegalArgumentException("ChipSets should contain exactly 5 integers!");
        }

        // store a copy of passed array
        this.chips = new int[5];
        for (int i = 0; i < this.chips.length; i++) {
            this.chips[i] = chips[i];
        }
    }

    public int getSum() {
        return chips[0] * WHITE_VALUE
                + chips[1] * RED_VALUE
                + chips[2] * GREEN_VALUE
                + chips[3] * BLUE_VALUE
                + chips[4] * BLACK_VALUE;
    }

    public int[] getChips() {
        return this.chips;
    }
}

一些解释:

  • 创建组合

我们创建了一些子方法,将筹码换成较低的筹码。所以 例如黑色 = 2 个蓝色。然后我们按顺序创建 5 个循环。这 第一个检查是否还有黑色筹码,如果有,减少 1 个黑色 添加2个蓝调。将此新组合保存在列表中,如果 新 ChipSet 中的筹码等于原始 ChipSets 值。环形 一直持续到没有黑人为止。然后它检查是否存在 是蓝色并重复相同的过程,直到没有红色 了。现在我们列出了 X 值的所有可能变化 筹码。

  • 过滤组合

您根据以下条件过滤 ChipSet 如果您可以在不交换的情况下与他们支付 X 积分。我们遍历所有 上一部分中创建的芯片组的可能组合。如果你 可以使用 ChipSet 支付而无需交换将其添加到过滤列表 芯片组。结果是一个仅包含有效芯片组的归档列表。

  • 计算差异

对于每个 ChipSet,我们计算一个中所有颜色的芯片数量 ChipSet 并用它减去原始芯片组的芯片数。 你把它的绝对值加起来 原始颜色和组合颜色的差异。现在我们有一个 代表与原始差异的数字。现在我们都 所要做的就是比较所有芯片组的“差异数”。唯一的那个 我们用来分配给播放器的差异最小。

所以它的基本作用是:它首先检查当前的 ChipSet 是否可以用来支付,并返回一个布尔值,就像你问的那样。如果可以,它什么也不做,否则它会通过 3 个子算法并定义最好的 ChipSet(一个可以用来支付,一个最少的不同)并改变玩家的 ChipSet

那么现在我的问题是,我将如何开始优化它?我问这个是因为当有更大的输入时,算法很容易使用几秒钟。

【问题讨论】:

  • 检查输入是否不大,让另一个算法处理;)if(input&lt;big){ alg0(); } else { alg1(); }
  • 大声笑查理,你在取笑我吗:p
  • 嗯,你说“简单吧”,是的,很简单
  • @Brendan 我查了有关它的理论,我认为你是对的。 “没有效率(优于与输入数量成正比的多项式时间)”。这让我不得不优化一些瓶颈。感谢所有输入的家伙,我可以用这个做很长一段时间。
  • @Brendan 我已经进行了一项重大改进,我将在明天发布。感谢您花时间帮助具有 1 年 Java 经验(一般编程)的人。

标签: java performance algorithm optimization


【解决方案1】:

对应用程序进行几次分析,以准确了解哪些方法花费的时间最多。例如:

尝试优化那些你知道是瓶颈的方法并重新配置,直到你的瓶颈消失。

【讨论】:

  • 这种总结对你没有帮助。这是要做的事情:让它运行并点击“暂停”。显示调用堆栈。点击每一层,它会显示一行代码,其中某个方法/函数 A 调用了某个 B,其原因从上下文中显而易见。将所有这些原因放在一起,您就会完全理解为什么要花费那个时间点。现在问问自己“有没有办法避免这样做,至少在某些时候?”现在,不要立即采取行动。再停顿几下,用同样的方法研究每一个……
  • ...现在,如果您看到这样的事情可以避免做,并且您在不止一个样本上看到它,那么你应该修复它,你看到一个显着的加速,保证。好消息来了:如果你再做一遍,你会发现你已经暴露了其他东西,这也可以让你加速。这种情况一直持续到它停止,然后您的代码几乎可以达到最佳状态。关于您发布的图片,我已经多次解释了为什么这不起作用。 Here's one example.
  • @MikeDunlavey 从我一直在阅读的内容来看,您似乎知道自己在说什么。也许您可以将上述文本复制粘贴到答案中,然后我可以删除此“错误答案”
  • 好的,但恐怕我“跑”得有点过头了。
【解决方案2】:

让我告诉你如何找到问题。这是做什么:

让它运行并点击“暂停”。显示调用堆栈。点击每一层,它会显示一行代码,其中某个方法/函数 A 调用了某个 B,其原因从上下文中显而易见。把所有这些原因放在一起,你就会完全理解为什么要花费那个时间点。现在问问自己“有没有办法避免这样做,至少在某些时候?”现在,不要立即采取行动。再停顿几下,以同样的方式研究每一个。

现在,如果您看到这样的事情是可以避免做的,而且您在不止一个样本上看到过,那么您应该修复它,您会看到明显的加速,保证。好消息来了:如果你再做一遍,你会发现你已经暴露了其他东西,这也可以让你加速。这种情况一直持续到它停止,然后您的代码几乎可以达到最佳状态。关于你发的图片,我已经解释过很多次了why that does not work

如果您这样做,您可以找到分析器可以找到的任何东西,以及他们无法找到的很多东西。 原因很简单——归根结底就是描述事物。

  • 什么是函数的包含时间百分比?它是包含该函数的调用堆栈样本的一部分。

  • 什么是函数的自身时间百分比?它是在末尾包含该函数的调用堆栈样本的一部分。

  • 一行代码(相对于函数)的包含时间百分比是多少?它是包含该行代码的调用堆栈样本的一部分。

  • 如果查看调用图,函数 A 和 B 之间的图中链接的时间百分比是多少?它是 A 直接调用 B 的调用堆栈样本的一部分。

  • 什么是 CPU 时间?如果您忽略在 I/O、睡眠或任何其他此类阻塞期间采集的任何样本,您该是时候了吗?

因此,如果您自己检查堆栈样本,您只需通过查看即可找到分析器可以找到的任何内容。 您还可以定位分析器无法定位的东西,例如:

  • 看到大部分时间都花在为对象分配内存上,而这些对象很快就会被删除。

  • 看到一个函数使用相同的参数被多次调用,只是因为程序员懒得声明一个变量来记住之前的结果。

  • 在 20 级堆栈示例中看到在第 10 级调用了一个看似无害的函数,程序员从未想过会在第 20 级执行文件 I/O,原因不明不能排除,但你知道没有必要。

  • 看到有十几个不同的函数都在做同样的事情,但它们是不同的函数,因为它们的所有者类已被模板化。

  • 看到函数 P 调用某些东西然后调用 R 的频繁模式,其中 P 从许多不同的地方调用,而 R 向下调用到许多不同的地方。

只需亲自检查样本,您就可以轻松查看这些内容以及更多内容。 现在,查看它们所需的平均样本数量取决于它们的大小。 如果某样东西需要 50% 的时间,那么看两次所需的平均样本数是 2/0.5 = 4 个样本,所以如果你取 10 个样本,你肯定会看到它。

假设有另一件事花费了 25% 的时间。 现在解决了第一个问题并将时间减半后,第二个问题需要 50%,而不是 25%,所以也很容易找到。

这就是修复一个加速会暴露下一个加速的方式。 但是,一旦您找不到真正存在的加速,整个过程就会停止,因为您停止暴露最初很小但在移除较大的加速后变得非常重要的加速。

Profilers 为您提供精确的测量值,但它们测量的是什么? (实际上,那个精度是假的。所有这些测量都有标准误差,你没有显示出来。) 他们测量的是什么?实际上,只有非常简单的东西。 因为您知道代码,所以他们无法识别您可以识别的那种东西。 我有学术分析器的粉丝坚持对我说,任何你能找到的问题,你都可以用分析器找到,但这不是一个定理。 理论上或实践上都没有任何理由。 有很多问题可以从分析器中逃脱。 这是一个“眼不见心不烦”的案例。

“哎呀,我在我的代码上运行了分析器,但我看不到任何瓶颈,所以我猜应该没有。”

如果您认真对待性能,那么您必须做得更好。

【讨论】:

  • 感谢有趣的阅读。以及您的其他“文章”。你写过书吗?如果没有,你应该这样做。
  • @ManyQuestions:谢谢。 I did, actually. 这个话题只花了几页。我没想到有那么多话要说。如果您有电子邮件,我会向您发送扫描件。大约 17mb。
  • 我如何给你我的电子邮件?
  • @ManyQuestions:例如,我的主页在我的 SO 主页上,所以你可以这样发送给我。您可以对其进行混淆处理以防止收到垃圾邮件。
  • 如果你能把它发给 dubparties (gmail.com) 我会很高兴:)
猜你喜欢
  • 1970-01-01
  • 2016-02-14
  • 2016-04-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-27
  • 1970-01-01
  • 2011-08-27
相关资源
最近更新 更多