javamvvmtree

How can I compare a tree with different node types to a list of courses to see which courses have been completed and which haven't?


I'm working on a transcript analyzer personal project and I'm having an issue comparing the courses that are read in from the transcript with the required corses. The required courses are stored in a JSON file so when I read them in I read them in as a tree. I'll provide an example JSON of the core courses below:

{
    "CSCore": [
        "CS1073",
        "CS1083",
        "CS1103",
        {"exactlyOneOf": ["CS1303", "MATH2203"]},
        "CS2043",
        "CS2253",
        "CS2263",
        "CS2333",
        "CS2383",
        "CS3383",
        "CS3413",
        "CS3853",
        "CS3873",
        "CS3997"
    ],

    "MathAndStatsCore": [
        {"exactlyOneOf": ["MATH1003", "MATH1053"]},
        {"exactlyOneOf": ["MATH1013", "MATH1063"]},
        {"exactlyOneOf": ["MATH1503", "MATH2213"]},
        {
            "atLeastOneOf": [
                {
                    "allOf": [
                        "STAT2593",
                        {
                            "atLeastOneOf": [
                                "CS3113",
                                "MATH2003",
                                "MATH2513",
                                "STAT4083"
                            ]
                        }
                    ]
                },
                {"allOf": ["STAT3083", "STAT3093"]}
            ]
        }
    ]
}

So, when I read this in, my tree has a Map<String, Node> instance variable called groups. This maps the name of the group (ex. CSCore) to the root node of the tree representing that list of requirements. The Nodes have a type, COURSE, ALL_OF, and CHOICE.

When I read in a students transcript I have a Transcript object that contains a list of Term objects, each of which has a List of courses.

What I am looking for from this post is to choose a data type. Whether that is a List, Map, any combination of those, or even just a new Object such as a DTO that's okay, I'm having trouble seeing how to parse the data in such a way that it will work with a TabLeColumn in JavaFX.

Does anyone have any suggestions as to the datatype that I would return to my UI layer to be used as the data for the table? If you think there is a more efficient way to structure any of this I am open to that as well. Any suggestions or helpful hints or nudges in the right direction would be greatly appreciated.

I still think what I've tried so far might work but I am having trouble wrapping my head around parsing the tree to fill out these objects. I have a DAO called GroupEvaluation. This object contains a boolean stating if the whole group has been satisfied, a List containing the course codes that are completed for that group, a List of course codes that are incomplete, and a List which contains the DAO ChoicePick which has a List containing the course codes of all the options and a String stating which choice the student made.


Solution

  • The concept being represented by the example is a mathematical formula. Instead of the somewhat usual plus (+), minus (-), multiply (*) and divide (/), the 'operators' in this math are XOR, AND and OR.

    The representation used here obfuscates matters. This is what you really have, and it also shows how allOf feels like it has no purpose here. Because what's the difference between simply "CS1073", "CS1083" and allOf(CS1073, 1083)?

    MathAndStatsCore:
      (MATH1003 ^ MATH1053) &
      (MATH1013 ^ MATH1063) &
      (MATH1503 ^ MATH2213) & 
      (
        (STAT2593 & (CS3113 | MATH2003 | MATH2513 | STAT4083) |
        (STAT3083 & STAT3093)
      )
    

    The best way to represent such a thing is with a tree:

                                       AND
                                    /  / \   \
                                   /  /   \   ------------------\
                  ----------------/  /     ------------          \
                /                   /                  \          \
               XOR                 XOR                XOR         |
            /     \               /  \                /  \        |
      MATH1003   MATH1053  MATH1013  MATH1063  MATH1503  MATH2213 |
                                                                  /
                                            ---------------------/
                                           /
                                          OR
                                  /------/   \-------\
                               AND                   AND
                    /---------/   \                  /  \
                  OR               STAT2593     STAT3083 STAT3093
          /----/ / \  \-----\
         /      /   \        \
    CS3113 MATH2003 MATH2513 STAT4083
    

    Modelling that in java:

    sealed interface CurriculumRequirementNode {
      boolean valid(List<String> courses);
    }
    
    public class CurriculumAnd implements CurriculumRequirementNode {
      List<CurriculumRequirementNode> allRequired;
    
      @Override public boolean valid(List<String> courses) {
        return allRequired.stream().allMatch(v -> v.valid(courses));
      }
    }
    
    public class CurriculumOr implements CurriculumRequirementNode {
      List<CurriculumRequirementNode> anyRequired;
    
      @Override public boolean valid(List<String> courses) {
        return anyRequired.stream().anyMatch(v -> v.valid(courses));
      }
    }
    
    public class CurriculumXor implements CurriculumRequirementNode {
      List<CurriculumRequirementNode> exactlyOneRequired;
    
      @Override public boolean valid(List<String> courses) {
        return exactlyOneRequired.stream().filter(v -> v.valid(courses)).count() == 1;
      }
    }
    
    public class CurriculumCourse implements CurriculumRequirementNode {
      String courseId;
    
      @Override public boolean valid(List<String> courses) {
        return courses.contains(this);
      }
    }
    

    You'd then create this tree, ending up with a 'root' node of type CurriculumAnd. You can simply invoke valid(List.of("MATH1002", "list of all courses taken by a student")) and out rolls a boolean indicating whether the requirements are met or not.

    The above example needs fleshing out; I'm just guessing that CurriculumRequirement is a good name, for example. These classes need constructors and possibly equals and hashCode implementations (or use lombok's @Value, or try to make these records though I'm not sure it's a good fit here).

    Note how functionality that exists fundamentally at each level of this tree is now at the type the objects are which is how OO is supposed to work, and all is abstracted; generally you don't need to know what kind of requirement node you have. It's just a node. Any node. They all have a valid(List<String> courses) method.

    Somebody managed to write some code to take a tree structure and smush it into a somewhat ugly JSON version of it (a string containing the 'formula', such as MATH1003 ^ MATH1053 would have been vastly superior and simpler). You'd do the same thing in reverse: Write code that accepts a JSON file and spits out a CurriculumRequirementNode. At no point should there be a HashMap of any kind. Yes, JSON objects are hashmaps, but the structure of the JSON must not determine the structure of this data in your java code.