Consider the following code for a class called SequenceToDnaMap, which maps String representations of DNA sequences to objects of the DNA class from Labs 2 & 3.

public class SequenceToDnaMap {

    private final List<String> keys = new ArrayList<>();
    private final List<DNA> values = new ArrayList<>();

    public SequenceToDnaMap() {
        
    }

    public void put(DNA dna) {
        if (!keys.contains(dna.sequence())) {
            this.keys.add(dna.sequence());
            this.values.add(dna);
        }
    }

    public Set<String> getSequenceSet() {
        return new HashSet<>(this.keys);
    }

    public Set<DNA> getDNASet() {
        return new HashSet<>(this.values);
    }


}

Which of the following statements are true for SequenceToDnaMap?
Checkbox options
1. It is not possible to mutate the object referred to by this.values from outside the class by accessing only the object at the reference returned by getDNASet().
2. Since the this.keys field is final, its reference can never change.
3. It is not possible to mutate the object referred to by this.keys from outside the class by accessing only the object at the reference returned by getSequenceSet().
4. Since this.keys is final and Strings which are immutable, its reference can never change and the underlying object is immutable.
5. The SequenceToDnaMap class is mutable.
6. This SequenceToDnaMap class implementation has rep exposure.


package arbitrarytree;

import java.util.*;

public class ArbitraryTree {
    public static final int ROOT = -1;

    public ArbitraryTree() {}

    /* ======= TASK 1 ======== */

    /**
     * Add the root node.
     * If no root node exists then this method adds the root and returns true.
     * If there is a root node already then this method does nothing and returns false.
     *
     * @param rootId is greater than or equal to 0.
     * @param rootName is not null and is not the empty string.
     * @return true if the root node was added and false otherwise.
     */
    public boolean addRoot(int rootId, String rootName) {
        if (ROOT == -1) {
            
        }
        return false;
        
    }

    /**
     * Add a node as a child to a specific parent node. This method returns true if
     * the child was successfully added. This method returns false if the parent does
     * not exist in this tree or if the child identifier is a duplicate.
     *
     * @param parentId the identifier of the parent node;
     *        requires {@code this.contains(parentId) == true}.
     * @param childId the identifier of the child node, >= 0, and should be unique to the tree.
     * @param childName is not null and is not the empty string.
     * @return true if the child was added successfully and false otherwise.
     */
    public boolean addChildOf(int parentId, int childId, String childName) {
        return false; // TODO: Implement this method
    }

    /**
     * Checks if a node with {@code nodeId} as identifier is present in this tree.
     * @param nodeId the node to check for, is >= 0.
     * @return true if there is a code with id as {@code nodeId} and false otherwise.
     */
    public boolean contains(int nodeId) {
        return false; // TODO: Implement this method
    }

    /**
     * Retrieves the name of the node associated with the specified identifier {@code nodeId}.
     *
     * @param nodeId the identifier of the node whose name is to be retrieved;
     *        requires {@code this.contains(nodeId) == true}.
     * @return the name of the node with the given {@code nodeId}.
     */
    public String getNodeName(int nodeId) {
        return null; // TODO: Implement this method
    }

    /**
     * Returns the parent id given a node id. If the given node id
     * is for the root of the tree then this method returns
     * {@code ArbiraryTree.ROOT}.
     *
     * @param nodeId the node to find the parent for;
     *      requires {@code this.contains(nodeId) == true}.
     * @return the id of the parent for the given node id and
     * {@code ArbiraryTree.ROOT} if the given id is that of the root node.
     */
    public int getParentId(int nodeId) {
        return -1; // TODO: Implement this method
    }

    /* ======= TASK 2 ======== */

    /**
     * Add a node as a child to a specific parent node. The parent is identified by
     * its name and not its identifier. If there is a unique node that matches the
     * specified name then treat that node as the parent node and add the child node.
     * If multiple nodes match {@code parentName}, this method returns false.
     * @param parentName the name of the parent; is not null and is not the empty string.
     * @param childId the identifier for the child, > 0.
     * @param childName the name for the child, is not null and is not the empty string.
     * @return true if the child node was successfully added and false otherwise.
     */
    public boolean addChildOf(String parentName, int childId, String childName) {
        return false; // TODO: Implement this method
    }

    /**
     * Checks if there are one or more nodes with {@code nodeName} as the name.
     * @param nodeName the name to check for, is not null and is not the empty string.
     * @return true if there is at least one node that matches {@code nodeName} and false otherwise.
     */
    public boolean contains(String nodeName) {
        return false; // TODO: Implement this method
    }

    /**
     * Returns the node identifier for the node associated with the name
     * {@code nodeName}. If there are multiple nodes with the same name then
     * this method throws an {@code AmbiguityException}.
     * @param nodeName the name to search for, is not null and is not the empty string.
     * @return the node identifier associated with {@code nodeName}.
     * @throws AmbiguityException if there are multiple nodes with the same name.
     */
    public int getNodeId(String nodeName) throws AmbiguityException {
        return -1; // TODO: Implement this method
    }

    /**
     * Return the nodeIds for all nodes that have the name given by
     * {@code nodeName}.
     * @param nodeName the name to search for, is not null and is not the empty string.
     * @return the nodeIds for all nodes that have the name given by {@code nodeName}.
     */
    public Set<Integer> getAllMatches(String nodeName) {
        return null; // TODO: Implement this method
    }

    /**
     * Get the name of the parent of the node identified by {@code nodeId}.
     *
     * @param nodeId the node for which the parent's name is needed, > 0.
     * @return the name of the parent of the node identified by {@code nodeId}.
     * @throws NoParentException if {@code nodeId} refers to the root node.
     */
    public String getParentName(int nodeId) throws NoParentException {
        return null; // TODO: Implement this method
    }

    /**
     * Retrieves all sibling nodes of the specified {@code nodeId}.
     *
     * @param nodeId the node identifier for which to find siblings; requires {@code nodeId > 0}.
     * @return a set of all sibling node identifiers that share the same parent as {@code nodeId},
     *         excluding {@code nodeId} itself. Returns an empty set if there are no siblings.
     */
    public Set<Integer> getSiblings(int nodeId) {
        return Collections.emptySet(); // TODO: Implement this method
    }

    /* ======= TASK 3 ======== */

    /**
     * Initializes an ArbitraryTree from an array structure and an array of node names.
     *
     * @param arbitraryTree an array defining the tree structure:
     *        each entry has a `nodeId`, the count of children, and
     *        references to the indices of the children in the same array.
     *        Parents are always listed before their children in this array representation.
     * @param nodeNames an array where each index represents the name of the node with the same id.
     * @throws IllegalArgumentException if the structure is invalid or the arrays are insufficient.
     */
    public ArbitraryTree(int[] arbitraryTree, String[] nodeNames) {
        // TODO: Implement this method
    }

    /**
     * Checks if the node identified by {@code ancestorId} is an ancestor of the
     * node identified by {@code childId}.
     *
     * @param ancestorId the identifier of the node that might be the ancestor, > 0.
     * @param childId the identifier of the node that might be a descendant of {@code ancestorId}, > 0.
     * @return true if the node with {@code ancestorId} is an ancestor of the node
     *         with {@code childId}, and false otherwise.
     */
    public boolean isAncestorOf(int ancestorId, int childId) {
        return false; // TODO: Implement this method
    }

    /**
     * Checks if the node identified by {@code descendantId} is a descendant of the
     * node identified by {@code ancestorId}.
     *
     * @param descendantId the identifier of the node that might be a descendant, > 0.
     * @param ancestorId the identifier of the node that might be the ancestor of {@code descendantId}, > 0.
     * @return true if the node with {@code descendantId} is a descendant of the node
     *         with {@code ancestorId}, and false otherwise.
     */
    public boolean isDescendantOf(int descendantId, int ancestorId) {
        return false; // TODO: Implement this method
    }


    /* ======= TASK 4 ======== */

    /**
     * Computes the total reward for this tree based on family sizes and rewards array.
     *
     * @param rewards is not null and rewards.size == number of nodes in the tree
     * @return the total reward computed from family sizes and corresponding rewards.
     */
    public double totalReward(double[] rewards) {
        return 0; // TODO: Implement this method
    }

    /**
     * Computes the maximum reward increase that is possible by adding a new leaf node.
     * If the total reward will not increase then this method will return 0.0 to
     * indicate that adding a new node is not beneficial.
     *
     * @param rewards is not null and rewards.size == (number of nodes in the tree) + 1
     * @return the maximum reward increase that is possible by adding a new leaf node.
     */
    public double rewardIncreaseWithSingleNodeAddition(double[] rewards) {
        return -1;
    }

    /* ======== TASK 5 ======== */

    /**
     * Computes the maximum possible reward for any tree with n nodes.
     *
     * @param rewards the rewards array corresponding to family sizes 0 to n-1.
     * @return the maximum possible reward achievable for any tree
     *         with rewards.size nodes.
     */
    public static double maxPossibleReward(double[] rewards) {
        return 0; // TODO: Implement this method
    }
}
implement the latter