Solved Looping through a HashMap

Discussion in 'Spigot Plugin Development' started by Xsm0deus, Apr 17, 2018.

  1. In my config file I have items, with int values on them, I'm trying to make it so it gets the keys and values from the config file, add them to a hashmap, then pick a random item name from the hashmap, this is what I have so far.
    Code (Text):
    for (String key : getDropPartyConfig().getConfigurationSection("items").getKeys(false)) {
                            droppartyitems.put(key, getDropPartyConfig().getInt("items."+ key));
                        }
     
  2. md_5

    Administrator Developer

  3. md_5

    Administrator Developer

    You have to make a list of keys and select from that. See the anove links or google.
     
  4. Code (Text):
                        for (String key : getDropPartyConfig().getConfigurationSection("items").getKeys(false)) {
                            droppartyitems.put(key, getDropPartyConfig().getInt("items."+ key));
                        }
                        Bukkit.broadcastMessage("Drop Party Task Started!");
                        droppartytask = Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
                            public void run() {
                                //randomizer
                                Random generator = new Random();
                                Object[] values = droppartyitems.keySet().toArray();
                                String randomValue = (String) values[generator.nextInt(values.length)].toString();
                                //value before change
                                Bukkit.broadcastMessage("Random Value: " + randomValue);
                                Bukkit.broadcastMessage("Value's Amount: " +  Integer.toString(droppartyitems.get(randomValue)));
                                if(droppartyitems.isEmpty() == false){
                                    droppartyitems.put(randomValue, droppartyitems.get(randomValue) - 1);
                                    //value after change
                                    Bukkit.broadcastMessage("Random Value 2: " + randomValue);
                                    Bukkit.broadcastMessage("Value's Amount 2: " +  Integer.toString(droppartyitems.get(randomValue)));
                                    if(droppartyitems.get(randomValue) <= 0){
                                        droppartyitems.remove(randomValue);
                                        Bukkit.broadcastMessage(randomValue + " was removed from list!");
                                        for(String id : droppartyitems.keySet()){
                                            Integer value = droppartyitems.get(id);
                                            Bukkit.broadcastMessage(id + " " + value);
                                        }
                                    }
                                }
                                else{
                                    //stop task dropparty
                                    Bukkit.broadcastMessage("Drop Party Task Ended!");
                                    Bukkit.getScheduler().cancelTask(droppartytask);
                                }
                            }
                        }, 20, 20);
    So I've got this now and everything works except 1 thing. When my hashmap is empty it doesn't run the else{ area (broadcast doesn't show and I assume it doesn't run cancelTask), and the scheduled task just stops (doesn't spam the 2 test broadcasts before the isEmpty)

    I already commented out the cancelTask to see if that work change anything and it doesn't, but it still breaks.
     
  5. You didn't even read what I wrote.

    "So I've got this now and everything works except 1 thing. When my hashmap is empty it doesn't run the else{ area (broadcast doesn't show and I assume it doesn't run cancelTask), and the scheduled task just stops (doesn't spam the 2 test broadcasts before the isEmpty)

    I already commented out the cancelTask to see if that work change anything and it doesn't, but it still breaks."
     
  6. I've been in this situation before. I'll tell you how I did it.

    Once you have your hashmap, I used a function that will sort my HashMap by it's values ascending.

    This method allows any object for the keys, but an object that must be comparable (e.i. numbers) for the values. This returns a hashmap
    Code (Text):

    @SuppressWarnings("unchecked")
    public static <K, V extends Comparable<? super V>> HashMap<K,V> entriesSortedByValues(Map<K, V> map, boolean descending) {
            return (HashMap<K,V>) ArrayUtils.toMap(map.entrySet()
                    .stream()
                    .sorted(comparingByValue())
                    .collect(
                            toMap(
                                    Map.Entry::getKey,
                                    Map.Entry::getValue,
                                    (e1, e2) -> e2, LinkedHashMap::new))
                    .entrySet().toArray());
        }
    Then what I did is I created a map that will store the chances they were given. For example...

    You have a map with 3 items: stone, grass, and bedrock. Each have the weight of 45, 35, 50. The COMBINED weight of all of these is 130.

    Now we have to get a random item from this list. We don't want to create a random yet. If we do, that will make grass have a weight of 35, stone with 10, and bedrock with 5. Which is obviously not what we want. We want each material to be the weight that was defined. In order to do this, we need to add all of the weights that came before it.

    Grass would stay 35. (26.9230769%)
    Stone would become 80. (34.6153846%)
    Bedrock would be come 130. (38.4615385%)

    Now we can create our random (new Random().nextInt(130)), don't add 1.
    This gives us a random number. For our sake, let's make it 85.

    We need to cycle our ascending map to find a value that is greater than (>) 85.

    35 < 85
    80 < 85
    130 > 85

    This works with all numbers (less than 130... but a random will never return a number greater than what we specify - 1 (129)) With 85, we got bedrock.

    Code (Text):
    public ItemStack pickRandom() {
        HashMap<ItemStack, Integer> unsortedHashMap = new HashMap<>() {{
            put(new ItemStack(Material.GRASS), 45);
            put(new ItemStack(Material.BEDROCK), 80);
            put(new ItemStack(Material.STONE), 35);
        }};

        HashMap<ItemStack, Integer> sortedMap = entriesSortedByValues(unsortedHashMap)

        int totalWeight = 0;
        for (Map.Entry<ItemStack, Integer> entry : sortedMap.entrySet()) {
            totalWeight += entry.getValue();

            sortedMap.put(entry.getKey(), totalWeight);
        }

        int number = new Random().nextInt(totalWeight);

        for (Map.Entry<ItemStack, Integer> entry : sortedMap.entrySet()) {
            if (entry.getValue() > number)
                return entry.getKey();
        }

        throw new IllegalStateException("A value is greater than the total weight.");
    }
     
    #9 Martoph, Apr 18, 2018
    Last edited: Apr 20, 2018
  7. idk I kinda want to fix the current code I got atm, it seems to just be breaking the bukkit thing and idk why.
     
  8. Code (Text):
                                Object[] values = droppartyitems.keySet().toArray();
                                String randomValue = (String) values[generator.nextInt(values.length)].toString();
    When droppartyitems is empty, values array is also length 0. You are then doing Random.nextInt(0), which isnt really possible because its bounded by 0 - len-1. To make matters worse, you're trying to get a random value at this index that doesn't exist.

    https://www.tutorialspoint.com/java/util/random_nextint_inc_exc.htm

    n = 0 yields no exclusive value, so I assume it's causing an error. An error will abort execution there, so your else won't be reached.

    Put all the above + those 2 broadcast lines showing the values under the if
    Code (Text):
    if(droppartyitems.isEmpty() == false){
        Object[] values = droppartyitems.keySet().toArray();
        String randomValue = (String) values[generator.nextInt(values.length)].toString();

        Bukkit.broadcastMessage("Random Value: " + randomValue);
        Bukkit.broadcastMessage("Value's Amount: " +  Integer.toString(droppartyitems.get(randomValue)));
       ... more
    There's more comments to be made on code quality, but this should hopefully answer at least why it's not working when your hashmap is empty.
     
    • Agree Agree x 1
  9. Wow I didn't even think about that, Thanks :)
     

Share This Page