Search code examples
javaandroid-studiolibgdx

How can I pick almost random questions from a list depending on which level you are on?


I'm working on a project and I want to pick up different questions depending on which level you are on.

For example, the first 25 levels I want my system to pick easy questions for example in a 80% chance and medium questions in a 20%. At the end on level 80 I want the difficulty to slowly increase and the system will 100% only pick hard questions. How can I write a working slowly increasing graph matematically to implement to my arraypicker?

I have tried to shuffle ArrayList objects with:

Collections.shuffle(random);

for example in order to get percent, but I there should be an easier workaround this.

Hard to explain but I hope you understand, ask if you need more information. I'm working on a libgdx project on Android studio


Solution

  • You have several problems wrapped up in one question, making your question a little broad for this site, but let's break down your issues:

    • Create a question class with varying levels of difficulty. Let's call this class Question and level of difficulty, Difficulty
    • Create a class that holds these questions in a collection or collections of some sort, and allow the user to request a random question from this class. Let's call this class QuestionCollection and the method used to request a random question, public Question getRandomQuestion(...).
    • Allow the user to have their own level of advancement. This can be the User class
    • The distribution of the questions possibly received from the request getRandomQuestion(...) will depend on the User's level of advancement

    So first to encapsulate level of difficulty, let's create an enum, one that has 3 (or more) levels:

    public enum Difficulty {
        EASY, MEDIUM, HARD
    }
    

    Then the Question class can have a private Difficulty difficulty; field, one set in its constructor, and with a public getter method, public Difficulty getDifficulty(). A simplified Question class could look something like this:

    public class Question {
        private String question;
        private String answer;
        private Difficulty difficulty;
    
        public Question(String question, String answer, Difficulty difficulty) {
            this.question = question;
            this.answer = answer;
            this.difficulty = difficulty;
        }
    
        public String getQuestion() {
            return question;
        }
    
        public String getAnswer() {
            return answer;
        }
    
        public Difficulty getDifficulty() {
            return difficulty;
        }
    
    }
    

    Again this is all an over simplification, but it can be used to help illustrate the problem and a possible solution. If desired, you could have this class implement Comparable<Question>, and using Difficulty to help do the comparison, and allow you to sort a List<Question> by difficulty.

    Then the key to all of this would be the QuestionCollection class, the one that held collection(s) of questions and that had the getRandomQuestion(...) method -- how to implement this.

    One way, is to rather than worry about User level of advancement at this point, give the getRandomQuestion(...) method some parameters that allow the QuestionCollection to know what distribution to use. Easiest in my mind is to give it a relative frequency of difficulty, a percentage of Difficulty.EASY, Difficulty.MEDIUM, and Difficulty.HARD, something like:

    public Question getRandomQuestion(int percentEasy, int percentMedium, int percentHard) {
        // ... code here to get the random question
    }
    

    OK, so now we're at how to build the internal workings of the QuestionCollection class, and then use this to get a proper distribution of random questions based on the parameter percentages. Probably the simplest is put a question into its own List, such as an ArrayList based on its level of Difficulty -- so here 3 Lists, one for EASY, one for MEDIUM and one for HARD questions.

    So:

    private List<Question> easyQuestions = new ArrayList<>();
    private List<Question> mediumQuestions = new ArrayList<>();
    private List<Question> hardQuestions = new ArrayList<>();
    

    Or another possibly cleaner solution is to use a Map<Difficulty, List<Question>> rather than separate Lists, but I'll keep things simple for now and leave it at 3 Lists.

    Then the class would have an public void addQuestion(Question q) method that would add questions to the correct list based on difficulty level:

    public void addQuestion(Question q) {
        switch (q.getDifficulty()) {
        case EASY:
            easyQuestions.add(q);
            break;
        case MEDIUM:
            mediumQuestions.add(q);
            break;
        case HARD:
            hardQuestions.add(q);
        }
    }
    

    OK, so we've got our lists filled with questions, we're now at the core issue of how to get the right distribution of randomization? I would recommend a 2 step process -- first use Math.random() or an instance of the Random class to choose which list to get a question from, and then use use randomization to select a random question from within the chosen list.

    So the first step, getting the random List could look like so:

    // declare variable before the if blocks
    List<Question> randomList = null;
    
    // get a random int from 0 to 99
    int rand = (int) (100 * Math.random());
    
    // get the random list using basic math and if blocks
    if (rand < percentEasy) {
        randomList = easyQuestions;
    } else if (rand < percentEasy + percentMedium) {
        randomList = mediumQuestions;
    } else {
        randomList = hardQuestions;
    }
    

    OK once the randomList has been obtained, then get a random question from it:

    // first get a random index to the list from 0 to < size
    int size = randomList.size();
    int listIndex = (int)(size * Math.random());
    Question randomQuestion = randomList.get(listIndex);
    return randomQuestion;
    

    The whole QuestionCollection class (simplified version) could look like so:

    // imports here
    
    public class QuestionCollection {
        private List<Question> easyQuestions = new ArrayList<>();
        private List<Question> mediumQuestions = new ArrayList<>();
        private List<Question> hardQuestions = new ArrayList<>();
    
        public void addQuestion(Question q) {
            switch (q.getDifficulty()) {
            case EASY:
                easyQuestions.add(q);
                break;
            case MEDIUM:
                mediumQuestions.add(q);
                break;
            case HARD:
                hardQuestions.add(q);
            }
        }
    
        public Question getRandomQuestion(int percentEasy, int percentMedium, int percentHard) {
            // if the numbers don't add up to 100, the distribution is broken -- throw an exception
            if (percentEasy + percentMedium + percentHard != 100) {
                String format = "For percentEasy: %d, percentMedium: %d, percentHard: %d";
                String text = String.format(format, percentEasy, percentMedium, percentHard);
                throw new IllegalArgumentException(text);
            }
    
            List<Question> randomList = null;
            int rand = (int) (100 * Math.random());
            if (rand < percentEasy) {
                randomList = easyQuestions;
            } else if (rand < percentEasy + percentMedium) {
                randomList = mediumQuestions;
            } else {
                randomList = hardQuestions;
            }
    
            // we've now selected the correct List
            // now get a random question from the list:
    
            // first get a random index to the list from 0 to < size
            int size = randomList.size();
            int listIndex = (int)(size * Math.random());
            Question randomQuestion = randomList.get(listIndex);
            return randomQuestion;
        }
    }
    

    So to test proof of concept, a test program shows that the distribution works:

    // imports
    
    public class QuestionFun {
        public static void main(String[] args) {
            // create QuestionCollection object
            QuestionCollection questionCollection = new QuestionCollection();
    
            // fill it with questions with random difficulty
            for (int i = 0; i < 1000; i++) {
                String question = "Question #" + i;
                String answer = "Answer #" + i;
                int randomIndex = (int) (Difficulty.values().length * Math.random());
                Difficulty difficulty = Difficulty.values()[randomIndex];
                Question q = new Question(question, answer, difficulty);
                questionCollection.addQuestion(q);
            }
    
            Map<Difficulty, Integer> frequencyDistMap = new EnumMap<>(Difficulty.class);
            for (Difficulty diff : Difficulty.values()) {
                frequencyDistMap.put(diff, 0);
            }
    
            int easyPercent = 20;
            int mediumPercent = 70;
            int hardPercent = 10;
    
            int questionCount = 10000;
            for (int i = 0; i < questionCount; i++) {
                Question q = questionCollection.getRandomQuestion(easyPercent, mediumPercent, hardPercent);
                Difficulty difficulty = q.getDifficulty();
                int currentCount = frequencyDistMap.get(difficulty);
                currentCount++;
                frequencyDistMap.put(difficulty, currentCount);
            }
    
            System.out.println("Difficulty: Count (Percent)");
            String format = "%-12s %4d   (%02d)%n";
            for (Difficulty difficulty : Difficulty.values()) {
                int number = frequencyDistMap.get(difficulty);
                int percent = (int) Math.round((100.0 * number) / questionCount);
                System.out.printf(format, difficulty + ":", number, percent);
            }
        }
    }
    

    Which returns the original distribution percentages:

    Difficulty: Count (Percent)
    EASY:      200325   (20)
    MEDIUM:    699341   (70)
    HARD:      100334   (10)