From 2940f6594615e56f09cf726bfa0927999f62e02c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E9=87=8E=E8=A3=95=E5=93=89?= Date: Fri, 28 Feb 2020 14:36:28 -0500 Subject: [PATCH 01/13] resolved dependency --- HintEvaluation/.classpath | 1 + HintEvaluation/pom.xml | 4 +- .../src/edu/isnap/eval/java/JavaImport.java | 159 ++++++++++-------- 3 files changed, 88 insertions(+), 76 deletions(-) diff --git a/HintEvaluation/.classpath b/HintEvaluation/.classpath index ad2d3213..c95ea44e 100644 --- a/HintEvaluation/.classpath +++ b/HintEvaluation/.classpath @@ -24,5 +24,6 @@ + diff --git a/HintEvaluation/pom.xml b/HintEvaluation/pom.xml index 641fde6c..ded0dd4b 100644 --- a/HintEvaluation/pom.xml +++ b/HintEvaluation/pom.xml @@ -28,11 +28,11 @@ - + de.cit-ec.tcs.alignment sequences diff --git a/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java b/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java index 322e66b0..4953e68f 100644 --- a/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java +++ b/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java @@ -14,6 +14,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Optional; import java.util.Set; @@ -21,10 +22,11 @@ import org.json.JSONObject; -import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.io.Output; +//import com.esotericsoftware.kryo.Kryo; +//import com.esotericsoftware.kryo.io.Output; import com.opencsv.CSVReader; import com.opencsv.CSVReaderBuilder; +import com.github.javaparser.javaparser_symbol_solver_core.ASTParserJSON; import edu.isnap.hint.HintData; import edu.isnap.hint.SnapHintBuilder; @@ -35,18 +37,18 @@ import edu.isnap.python.SourceCodeHighlighter; import edu.isnap.sourcecheck.HintHighlighter; import edu.isnap.sourcecheck.edit.EditHint; -import edu.isnap.util.map.ListMap; +//import edu.isnap.util.map.ListMap; public class JavaImport { - static String DATA_DIR = "../data/blackbox/ClockDisplay/"; + static String DATA_DIR = "../data/project3_2/task1/"; public static void main(String[] args) throws IOException { // Run generate hints to load data, generate hints for each student and print them out // You need to update the file path to wherever you unzipped the data -// Map> nodes = loadAssignment( +// Map>> nodes = loadAssignment( // DATA_DIR + "1__output_clock-display-ast.csv"); // GrammarBuilder builder = new GrammarBuilder("java", new HashMap<>()); // nodes.values().forEach(listMap -> listMap.values() @@ -56,17 +58,17 @@ public static void main(String[] args) throws IOException { PrintStream fileOut = new PrintStream(DATA_DIR + "output_hints.txt"); // System.setOut(fileOut); - generateHintsForGS(DATA_DIR + "1__output_clock-display-ast-gs.csv", "ClockDisplay"); + generateHintsForGS(DATA_DIR + "input.csv", "ProfileServlet"); // serializeHintData(DATA_DIR + "1__output_clock-display-ast.csv", "ClockDisplay", // "../HintServer/WebContent/WEB-INF/data/ClockDisplay.hdata"); } /*static HintData createHintData(String inputCSV, String assignment) throws IOException { - ListMap attempts = loadAssignment(inputCSV); + LinkedHashMap> attempts = loadAssignment(inputCSV); return createHintData(assignment, attempts); }*/ - static HintData createHintData(String assignmentID, ListMap attempts) { + static HintData createHintData(String assignmentID, LinkedHashMap> attempts) { JavaHintConfig config = new JavaHintConfig(); HintData hintData = new HintData(assignmentID, config, 1, HintHighlighter.DataConsumer); for (String attemptID : attempts.keySet()) { @@ -82,7 +84,8 @@ static HintData createHintData(String assignmentID, ListMap at // Don't worry about this method for now static void serializeHintData(String inputCSV, String assignment, String outputPath) throws IOException { - ListMap attempts = loadAssignment(inputCSV, false).get("ClockDisplay.java"); + LinkedHashMap> attempts = loadAssignment(inputCSV, false, + assignment).get("ClockDisplay.java"); List toRemove = new ArrayList(); // Remove incorrect attempts before serializing for (String attemptID : attempts.keySet()) { @@ -93,14 +96,16 @@ static void serializeHintData(String inputCSV, String assignment, String outputP } toRemove.forEach(attempts::remove); HintData hintData = createHintData(assignment, attempts); - Kryo kryo = SnapHintBuilder.getKryo(); - Output output = new Output(new FileOutputStream(outputPath)); - kryo.writeObject(output, hintData); - output.close(); + /* + * Kryo kryo = SnapHintBuilder.getKryo(); Output output = new Output(new + * FileOutputStream(outputPath)); kryo.writeObject(output, hintData); + * output.close(); + */ } static void generateHints(String inputCSV, String assignment) throws IOException { - HashMap> filePathToattempts = loadAssignment(inputCSV, false); + HashMap>> filePathToattempts = loadAssignment( + inputCSV, false, assignment); for (String filePath : filePathToattempts.keySet()) { File startSourceFile = new File(DATA_DIR + "Start/" + filePath); @@ -111,15 +116,15 @@ static void generateHints(String inputCSV, String assignment) throws IOException } // For now, just look at ClockDisplay, since NumberDisplay didn't have to be edited - if (!filePath.equals("ClockDisplay.java")) continue; - ListMap attempts = filePathToattempts.get(filePath); + //if (!filePath.equals("ClockDisplay.java")) continue; + LinkedHashMap> attempts = filePathToattempts.get(filePath); for (String student : attempts.keySet()) { // May want to change this to a random attempt, not just the first one, but you can // start with the first one JavaNode firstAttempt = attempts.get(student).get(0); if (firstAttempt.correct.orElse(false)) continue; - ListMap subset = new ListMap<>(); + LinkedHashMap> subset = new LinkedHashMap<>(); for (String attemptID : attempts.keySet()) { // Don't add this student to their own hint generation data if (attemptID.equals(student)) continue; @@ -153,12 +158,12 @@ static void generateHints(String inputCSV, String assignment) throws IOException } static void generateHintsForGS(String inputCSV, String assignment) throws IOException { - ListMap attempts = - loadAssignment(inputCSV, true).get("ClockDisplay.java"); + LinkedHashMap> attempts = + loadAssignment(inputCSV, true, assignment).get(assignment + ".java"); //System.out.println(attempts); - ListMap correct = new ListMap<>(); + LinkedHashMap> correct = new LinkedHashMap<>(); for (String attemptID : attempts.keySet()) { - if(attemptID.startsWith("s") || attemptID.startsWith("r")) { + if(true) {//attemptID.startsWith("s") || attemptID.startsWith("r")) { // Get the sequence of snapshots over time List trace = attempts.get(attemptID); // If it was correct, then add it to the subset @@ -168,32 +173,33 @@ static void generateHintsForGS(String inputCSV, String assignment) throws IOExce } } HintData hintData = createHintData(assignment, correct); - HintHighlighter highlighter = hintData.hintHighlighter(); + //HintHighlighter highlighter = hintData.hintHighlighter(); // TODO: Get the actual list from a .csv file, map project_id to the hint request - ListMap goldStandardHintRequests = attempts; - new File(DATA_DIR+"GS").mkdirs(); - for (String student : goldStandardHintRequests.keySet()) { - if(student.startsWith("s") || student.startsWith("r")) { - continue; - } - JavaNode hintRequest = goldStandardHintRequests.get(student).get(0); - List edits = highlighter.highlightWithPriorities(hintRequest); - int i = 0; - for (EditHint hint : edits) { - Node copy = hintRequest.copy(); - EditHint.applyEdits(copy, Collections.singletonList(hint)); - double priority = hint.priority.consensus(); - JSONObject json = copy.toJSON(); - json.put("priority", priority); - String file = String.format("%s_%02d.json", student, i); - PrintWriter out = new PrintWriter(DATA_DIR+"GS/"+file); - out.println(json.toString()); - out.close(); - System.out.println(file + ": " + json.toString()); - i++; - } + //LinkedHashMap> goldStandardHintRequests = attempts; + //new File(DATA_DIR+"GS").mkdirs(); + for (String student : attempts.keySet()) { + JavaNode hintRequest = attempts.get(student).get(0); + String highlightedCode = SourceCodeHighlighter.highlightSourceCode( + hintData, hintRequest); + PrintWriter out = new PrintWriter(DATA_DIR + student + "/output_hints.html"); + out.println(highlightedCode); + out.close(); + } + /* + * for (String student : goldStandardHintRequests.keySet()) { + * if(student.startsWith("s") || student.startsWith("r")) { continue; } JavaNode + * hintRequest = goldStandardHintRequests.get(student).get(0); List + * edits = highlighter.highlightWithPriorities(hintRequest); int i = 0; for + * (EditHint hint : edits) { Node copy = hintRequest.copy(); + * EditHint.applyEdits(copy, Collections.singletonList(hint)); double priority = + * hint.priority.consensus(); JSONObject json = copy.toJSON(); + * json.put("priority", priority); String file = String.format("%s_%02d.json", + * student, i); PrintWriter out = new PrintWriter(DATA_DIR+"GS/"+file); + * out.println(json.toString()); out.close(); System.out.println(file + ": " + + * json.toString()); i++; } } + */ } private static String stripComments(String source) { @@ -250,11 +256,11 @@ private static String removeComments(String code) { ); } - static HashMap> loadAssignment(String inputCSV, boolean GS) - throws IOException { - HashMap> filePathToNodes = new HashMap<>(); + static HashMap>> loadAssignment(String inputCSV, boolean GS, + String assignment) throws IOException { + HashMap>> filePathToNodes = new HashMap<>(); List csvRecords= readCSV(inputCSV); - Set numberDisplayProjects = new HashSet<>();; + Set numberDisplayProjects = new HashSet<>(); String numberDisplayStartSource = null; if(!GS) { numberDisplayProjects = new HashSet<>(); @@ -267,40 +273,45 @@ static HashMap> loadAssignment(String inputCSV for(String[] record: csvRecords) { String projectID = record[0]; - String sourceCode = record[4]; - String sourceCodeJSON = record[12]; + String sourceFileID = record[1]; + //String sourceCode = record[4]; + //String sourceCodeJSON = record[12]; String isCorrect = record[10]; String filePath = record[3]; - if(!GS) { - filePath = filePath.split("/")[1]; - } - else { - filePath = "ClockDisplay.java"; - } - if(filePath.equals("ClockDisplay.java") || filePath.equals("NumberDisplay.java")) { - File testJson = new File(DATA_DIR + "ASTs/" + sourceCodeJSON); - if(GS) { - testJson = new File(DATA_DIR + "ASTsGS/" + sourceCodeJSON); - } - String json = new String(Files.readAllBytes(testJson.toPath())); + filePath = filePath.split("/")[1]; + /* + * if(!GS) { filePath = filePath.split("/")[1]; } else { filePath = + * "ClockDisplay.java"; } + */ + if(true) {//filePath.equals("ClockDisplay.java") || filePath.equals("NumberDisplay.java")) { + /* + * File testJson = new File(DATA_DIR + "ASTs/" + sourceCodeJSON); if(GS) { + * testJson = new File(DATA_DIR + "ASTsGS/" + sourceCodeJSON); } String json = + * new String(Files.readAllBytes(testJson.toPath())); + * + * if (!GS && filePath.contentEquals("NumberDisplay.java") && + * isCorrect.equals("True")) { String source = removeComments(sourceCode); if + * (!source.equals(numberDisplayStartSource)) { + * numberDisplayProjects.add(projectID); } } + */ - if (!GS && filePath.contentEquals("NumberDisplay.java") && isCorrect.equals("True")) { - String source = removeComments(sourceCode); - if (!source.equals(numberDisplayStartSource)) { - numberDisplayProjects.add(projectID); - } - } - - JSONObject obj = new JSONObject(json); - JavaNode node = (JavaNode) TextualNode.fromJSON(obj, sourceCode, JavaNode::new); - if (isCorrect.toLowerCase().equals("true") || GS) { + //JSONObject obj = new JSONObject(json); + File originalCode = new File(DATA_DIR + sourceFileID + "/" + assignment + ".java"); + String originalSourceCode = new String(Files.readAllBytes(originalCode.toPath())); + JSONObject parsedTree = new JSONObject(ASTParserJSON.toJSON(originalSourceCode)); + //JavaNode node = (JavaNode) TextualNode.fromJSON(obj, sourceCode, JavaNode::new); + JavaNode node = (JavaNode) JavaNode.fromJSON(parsedTree, originalSourceCode, JavaNode::new); + if (isCorrect.toLowerCase().equals("true")) {// || GS) { node.correct = Optional.of(true); } if(filePathToNodes.get(filePath) == null) { - filePathToNodes.put(filePath, new ListMap()); + filePathToNodes.put(filePath, new LinkedHashMap>()); + } + if(filePathToNodes.get(filePath).get(projectID) == null) { + filePathToNodes.get(filePath).put(projectID, new ArrayList()); } - filePathToNodes.get(filePath).add(projectID, node); + filePathToNodes.get(filePath).get(projectID).add(node); } } if(!GS) { From d29809ca58e28dc30854a41b19afc69b8239d911 Mon Sep 17 00:00:00 2001 From: akounoroushi Date: Wed, 4 Mar 2020 14:22:44 -0500 Subject: [PATCH 02/13] Fixed overlapping span tags --- HintEvaluation/src/edu/isnap/eval/java/JavaImport.java | 5 +++++ iSnap/src/edu/isnap/python/SourceCodeHighlighter.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java b/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java index 4953e68f..138356f0 100644 --- a/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java +++ b/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java @@ -182,6 +182,11 @@ static void generateHintsForGS(String inputCSV, String assignment) throws IOExce JavaNode hintRequest = attempts.get(student).get(0); String highlightedCode = SourceCodeHighlighter.highlightSourceCode( hintData, hintRequest); + highlightedCode = highlightedCode.replace("\n", "
\n"); + highlightedCode = "\n" + + "\n" + highlightedCode; PrintWriter out = new PrintWriter(DATA_DIR + student + "/output_hints.html"); out.println(highlightedCode); out.close(); diff --git a/iSnap/src/edu/isnap/python/SourceCodeHighlighter.java b/iSnap/src/edu/isnap/python/SourceCodeHighlighter.java index bbf79609..ed82797a 100644 --- a/iSnap/src/edu/isnap/python/SourceCodeHighlighter.java +++ b/iSnap/src/edu/isnap/python/SourceCodeHighlighter.java @@ -110,7 +110,7 @@ private static List getSuggestions(List edits) { for (EditHint hint : edits) { hint.addSuggestions(suggestions); } - Collections.sort(suggestions); + Collections.sort(suggestions, Collections.reverseOrder()); return suggestions; } From 71e1a80a5305919a258344db436f1a362528e874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E9=87=8E=E8=A3=95=E5=93=89?= Date: Sat, 27 Jun 2020 14:33:49 -0400 Subject: [PATCH 03/13] fliped compareTo to fix the ordering --- CTD/src/edu/isnap/sourcecheck/edit/Suggestion.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CTD/src/edu/isnap/sourcecheck/edit/Suggestion.java b/CTD/src/edu/isnap/sourcecheck/edit/Suggestion.java index cb1f8b2c..bf5961ef 100644 --- a/CTD/src/edu/isnap/sourcecheck/edit/Suggestion.java +++ b/CTD/src/edu/isnap/sourcecheck/edit/Suggestion.java @@ -27,11 +27,11 @@ public Suggestion(EditHint hint, SourceLocation location, SuggestionType type, b @Override public int compareTo(Suggestion s) { - if (location == null) return -1; - if (s == null) return 1; + if (location == null) return 1; + if (s == null) return -1; int comp = location.compareTo(s.location); // If the source locations are different, return the later one (we work backwards) - if (comp != 0) return -comp; + if (comp != 0) return comp; // Otherwise, process starts before ends, so spans don't overlap if (start != s.start) return start ? 1 : -1; // Otherwise use the type order (reversed so the first is processed last) From 262d9d6fef7cdcc810dbd06ff570a86f0ab2263f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E9=87=8E=E8=A3=95=E5=93=89?= Date: Sat, 27 Jun 2020 14:41:05 -0400 Subject: [PATCH 04/13] high-level hints --- CTD/src/edu/isnap/hint/HintConfig.java | 3 + CTD/src/edu/isnap/hint/HintData.java | 18 + CTD/src/edu/isnap/hint/util/Alignment.java | 25 +- CTD/src/edu/isnap/node/Node.java | 27 + CTD/src/edu/isnap/node/TextualNode.java | 1 + .../sourcecheck/ConsensusHintHighlighter.java | 2 +- .../isnap/sourcecheck/HintHighlighter.java | 33 +- .../edu/isnap/sourcecheck/NodeAlignment.java | 72 ++- HighLevelHints/.classpath | 18 + HighLevelHints/.project | 17 + .../src/alignment/JavaSolutionConfig.java | 16 + .../src/alignment/SolutionAlignment.java | 227 ++++++++ .../alignment/SolutionDistanceMeasure.java | 48 ++ HighLevelHints/src/clustering/DBSCAN.java | 22 + .../src/clustering/PreComputed.java | 59 ++ .../src/clustering/SolutionClusterer.java | 153 ++++++ .../src/edu/isnap/eval/java/JavaImport.java | 510 ++++++++++-------- .../isnap/python/SourceCodeHighlighter.java | 147 +++-- 18 files changed, 1105 insertions(+), 293 deletions(-) create mode 100644 HighLevelHints/.classpath create mode 100644 HighLevelHints/.project create mode 100644 HighLevelHints/src/alignment/JavaSolutionConfig.java create mode 100644 HighLevelHints/src/alignment/SolutionAlignment.java create mode 100644 HighLevelHints/src/alignment/SolutionDistanceMeasure.java create mode 100644 HighLevelHints/src/clustering/DBSCAN.java create mode 100644 HighLevelHints/src/clustering/PreComputed.java create mode 100644 HighLevelHints/src/clustering/SolutionClusterer.java diff --git a/CTD/src/edu/isnap/hint/HintConfig.java b/CTD/src/edu/isnap/hint/HintConfig.java index 8c5e320b..39b48ef7 100644 --- a/CTD/src/edu/isnap/hint/HintConfig.java +++ b/CTD/src/edu/isnap/hint/HintConfig.java @@ -11,6 +11,9 @@ public abstract class HintConfig implements Serializable { /** If true, uses new version of SourceCheck with more global alignment */ public boolean sourceCheckV2 = false; + + /** If true, uses reference solutions to get high-level hints */ + public boolean useAnnotation = true; /** * Should return true if the hint generator can expect traces to keep consistent node IDs diff --git a/CTD/src/edu/isnap/hint/HintData.java b/CTD/src/edu/isnap/hint/HintData.java index 961fda3e..0454700d 100644 --- a/CTD/src/edu/isnap/hint/HintData.java +++ b/CTD/src/edu/isnap/hint/HintData.java @@ -1,6 +1,7 @@ package edu.isnap.hint; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import edu.isnap.ctd.hint.CTDHintGenerator; @@ -17,7 +18,15 @@ public class HintData { public final double minGrade; public final HintConfig config; private final List dataModels; + private LinkedHashMap referenceSolutions; + /** + * @param assignment the name of java file used in this assignment. Omit ".java". + * @param config + * @param minGrade + * @param consumer + * @param additionalConsumers + */ public HintData(String assignment, HintConfig config, double minGrade, IDataConsumer consumer, IDataConsumer... additionalConsumers) { this.assignment = assignment; @@ -29,6 +38,7 @@ public HintData(String assignment, HintConfig config, double minGrade, for (IDataConsumer con : additionalConsumers) { addDataModels(con.getRequiredData(this)); } + referenceSolutions = new LinkedHashMap<>(); } private void addDataModels(IDataModel[] models) { @@ -73,4 +83,12 @@ public CTDHintGenerator hintGenerator() { public HintHighlighter hintHighlighter() { return new HintHighlighter(this); } + + public LinkedHashMap getReferenceSolutions() { + return referenceSolutions; + } + + public Node addReferenceSoltion(int clusterID, Node solution) { + return referenceSolutions.put(clusterID, solution); + } } diff --git a/CTD/src/edu/isnap/hint/util/Alignment.java b/CTD/src/edu/isnap/hint/util/Alignment.java index 59807c39..463d90ba 100644 --- a/CTD/src/edu/isnap/hint/util/Alignment.java +++ b/CTD/src/edu/isnap/hint/util/Alignment.java @@ -25,6 +25,14 @@ public static double normAlignCost(String[] sequenceA, String[] sequenceB, int i } // Credit: http://introcs.cs.princeton.edu/java/96optimization/Diff.java.html + /** + * @param sequenceA + * @param sequenceB + * @param insCost + * @param delCost + * @param subCost + * @return Levenshtein distance between "sequenceA" and "sequenceB"?? + */ public static int alignCost(String[] sequenceA, String[] sequenceB, int insCost, int delCost, int subCost) { int[][] opt = createAlignmentMatrix(sequenceA, sequenceB, insCost, delCost, subCost, false); @@ -300,7 +308,7 @@ private static int correspondingIndexBefore(List pairs, List sequ return closestA; } - private static int[][] createAlignmentMatrix(String[] sequenceA, String[] sequenceB, + public static int[][] createAlignmentMatrix(String[] sequenceA, String[] sequenceB, int insCost, int delCost, int subCost, boolean flipInvalid) { // The penalties to apply int matchCost = 0; @@ -397,7 +405,7 @@ public static int[] reorderIndices(String[] from, String[] to, int[] toOrderGrou return toIndices; } - public static int getProgress(String[] from, String[] to, int orderReward, int unorderReward) { + public static double getProgress(String[] from, String[] to, int orderReward, int unorderReward) { return (int) Math.round(getProgress(from, to, orderReward, unorderReward, 0)); } @@ -417,14 +425,19 @@ public static double getProgress(String[] from, String[] to, int[] toOrderGroups private static double getProgress(String[] from, String[] to, int[] toOrderGroups, int orderReward, int unorderReward, double skipCost, int[] toIndices) { // TODO: This can and should be much more efficient + // toList[k] = "\0" if the k-th node in c(b_{rj}) is in c(a_{ri}) List toList = new LinkedList<>(Arrays.asList(to)); + // If indices[k] = l, k-th node in c(a_{ri}) is l-th node in c(b_{rj}) + // If indices[k] = -1, k-th node in c(a_{ri}) is not in c(b_{rj}) int[] indices = new int[from.length]; for (int i = 0; i < from.length; i++) { String item = from[i]; int index = toList.indexOf(item); - if (index >= 0) { - toList.set(index, "\0"); + if (index >= 0) { // i-th node in c(a_{ri}) is in c(b_{rj}) +// if (orderReward == 1 && unorderReward == 1) { + toList.set(index, "\0"); // prevents multiple matches +// } indices[i] = index; } else { indices[i] = -1; @@ -437,7 +450,7 @@ private static double getProgress(String[] from, String[] to, int[] toOrderGroup int lastIndex = -1; int maxIndex = -1; for (Integer index : indices) { - if (index < 0) continue; + if (index < 0) continue; // don't change reward if k-th node in c(a_{ri}) is not in c(b_{rj}) int adjIndex = index; int group; // System.out.println(index); @@ -487,7 +500,7 @@ private static double getProgress(String[] from, String[] to, int[] toOrderGroup * including duplicates */ public static int getMissingNodeCount(String[] from, String[] to) { - return to.length - getProgress(to, from, 1, 1); + return to.length - (int) getProgress(to, from, 1, 1); } public static void main(String[] args) { diff --git a/CTD/src/edu/isnap/node/Node.java b/CTD/src/edu/isnap/node/Node.java index e5e20a19..16da6f63 100644 --- a/CTD/src/edu/isnap/node/Node.java +++ b/CTD/src/edu/isnap/node/Node.java @@ -37,6 +37,8 @@ public abstract class Node extends StringHashable implements INode { public final String value; public final Node parent; public final List children = new ArrayList<>(); + private String studentID; + private String submissionTime; public transient Object tag; public final transient List canonicalizations = new ArrayList<>(); @@ -501,6 +503,10 @@ public String prettyPrintWithIDs() { return prettyPrint(false, new HashMap()); } + /** + * @return a list of strings of types obtained by depth-first iteration of + * the AST rooted at this node + */ public String[] depthFirstIteration() { String[] array = new String[treeSize()]; depthFirstIteration(array, 0); @@ -683,6 +689,11 @@ public static Node fromASTNode(ASTNode astNode, Node parent, NodeConstructor con Node node = constructor.constructNode(parent, type, astNode.value, astNode.id); node.readFromASTNode(astNode); node.tag = astNode; + if (astNode.annotation != null) { + node.writableAnnotations(); + TextHint hint = new TextHint(astNode.annotation, 1); + node.addTextHint(hint); + } for (ASTNode child : astNode.children()) { node.children.add(fromASTNode(child, node, constructor)); } @@ -697,4 +708,20 @@ protected void readFromASTNode(ASTNode node) { public String toString() { return prettyPrint(true); } + + public String getStudentID() { + return studentID; + } + + public void setStudentID(String studentID) { + this.studentID = studentID; + } + + public String getSubmissionTime() { + return submissionTime; + } + + public void setSubmissionTime(String submissionTime) { + this.submissionTime = submissionTime; + } } diff --git a/CTD/src/edu/isnap/node/TextualNode.java b/CTD/src/edu/isnap/node/TextualNode.java index 8a63604e..aa20c468 100644 --- a/CTD/src/edu/isnap/node/TextualNode.java +++ b/CTD/src/edu/isnap/node/TextualNode.java @@ -10,6 +10,7 @@ public abstract class TextualNode extends Node { public Optional correct = Optional.empty(); + public Optional cluster = Optional.empty(); private String source; // TODO: Protect diff --git a/CTD/src/edu/isnap/sourcecheck/ConsensusHintHighlighter.java b/CTD/src/edu/isnap/sourcecheck/ConsensusHintHighlighter.java index 9a29f5a3..08e06108 100644 --- a/CTD/src/edu/isnap/sourcecheck/ConsensusHintHighlighter.java +++ b/CTD/src/edu/isnap/sourcecheck/ConsensusHintHighlighter.java @@ -38,7 +38,7 @@ public List highlightConsensus(Node node) { double consensusThreshold = 0.6; List matches = NodeAlignment.findBestMatches( - node, solutions, getDistanceMeasure(config), config, nMatches); + node, getSolutions(), getDistanceMeasure(config), config, nMatches); final int n = matches.size(); final int thresh = (int) Math.ceil(n * consensusThreshold); diff --git a/CTD/src/edu/isnap/sourcecheck/HintHighlighter.java b/CTD/src/edu/isnap/sourcecheck/HintHighlighter.java index 799a0c90..cf80db02 100644 --- a/CTD/src/edu/isnap/sourcecheck/HintHighlighter.java +++ b/CTD/src/edu/isnap/sourcecheck/HintHighlighter.java @@ -50,7 +50,7 @@ public static enum Highlight { public final HintConfig config; public final HintData hintData; - final List solutions; + private final List solutions; public final static IDataConsumer DataConsumer = new IDataConsumer() { @Override @@ -106,12 +106,16 @@ public List highlight(Node node) { return highlight(node, mapping); } + /** + * @param node current student's code for which we show hints + * @return a list of edits with priorities + */ public List highlightWithPriorities(Node node) { Mapping mapping = findSolutionMapping(node); return highlightWithPriorities(node, mapping); } - private List highlightWithPriorities(Node node, Mapping mapping) { + public List highlightWithPriorities(Node node, Mapping mapping) { List hints = highlight(node, mapping); new HintPrioritizer(this).assignPriorities(mapping, hints); return hints; @@ -121,6 +125,12 @@ public List highlight(Node node, final Mapping mapping) { return highlight(node, mapping, true); } + /** + * @param node current student's code for which we show hints + * @param mapping + * @param reuseDeletedBlocks + * @return + */ public List highlight(Node node, final Mapping mapping, boolean reuseDeletedBlocks) { final List edits = new ArrayList<>(); @@ -555,6 +565,10 @@ public static DistanceMeasure getDistanceMeasure(HintConfig config) { return new ProgressDistanceMeasure(config); } + /** + * @param node current student's code for which we show hints + * @return the mapping from "node" to the closest solution + */ public Mapping findSolutionMapping(Node node) { long startTime = System.currentTimeMillis(); @@ -574,16 +588,21 @@ public Mapping findSolutionMapping(Node node) { return bestMatch; } + /** + * @param node current student's code for which we show hints + * @param maxReturned the number of the closest codes to return + * @return a mapping from "node" to maxReturned number of closest "solutions" + */ public List findBestMappings(Node node, int maxReturned) { DistanceMeasure dm = getDistanceMeasure(config); - List filteredSolutions = solutions; + List filteredSolutions = getSolutions(); RulesModel rulesModel = hintData.getModel(RulesModel.class); RuleSet ruleSet = rulesModel == null ? null : rulesModel.getRuleSet(); if (ruleSet != null) { RuleSet.trace = trace; - filteredSolutions = ruleSet.filterSolutions(solutions, node); + filteredSolutions = ruleSet.filterSolutions(getSolutions(), node); if (filteredSolutions.size() == 0) throw new RuntimeException("No solutions!"); } return NodeAlignment.findBestMatches(node, filteredSolutions, dm, config, maxReturned); @@ -775,7 +794,7 @@ public List highlightStringEdit(Node node) { double minDis = Double.MAX_VALUE; Node best = null; - for (Node solution : solutions) { + for (Node solution : getSolutions()) { double dis = Alignment.alignCost(nodeSeq, solution.depthFirstIteration()); if (dis < minDis) { best = solution; @@ -796,4 +815,8 @@ public List highlightStringEdit(Node node) { return highlight(node, mapping); } + + public List getSolutions() { + return solutions; + } } diff --git a/CTD/src/edu/isnap/sourcecheck/NodeAlignment.java b/CTD/src/edu/isnap/sourcecheck/NodeAlignment.java index 6238973f..ce268fae 100644 --- a/CTD/src/edu/isnap/sourcecheck/NodeAlignment.java +++ b/CTD/src/edu/isnap/sourcecheck/NodeAlignment.java @@ -72,6 +72,9 @@ public Mapping(Node from, Node to, HintConfig config) { } } + /** + * @return distance between "from" and "to"?? + */ public double cost() { return cost; } @@ -300,21 +303,22 @@ public Mapping calculateMapping(DistanceMeasure distanceMeasure) { return calculateMapping(distanceMeasure, null); } - private Mapping calculateMapping(DistanceMeasure distanceMeasure, Mapping previousMapping) { + public Mapping calculateMapping(DistanceMeasure distanceMeasure, Mapping previousMapping) { mapping = new Mapping(from, to, config); // If we're given a previous mapping, we use it to calculate value mappings if (previousMapping != null) mapping.calculateValueMappings(previousMapping); + // Behaves differently depending on previousMapping to.resetAnnotations(); ListMap fromMap = getChildMap(from, true); ListMap toMap = getChildMap(to, false); - for (String key : fromMap.keySet()) { + for (String key : fromMap.keySet()) { // Iterate over root paths from shortest to longest List fromNodes = fromMap.get(key); List toNodes = toMap.get(key); if (toNodes == null) { - // Continue if we have no toNodes to match + // Continue if we have no toNodes to match because the same root path isn't in "to" (i.e. B) continue; } @@ -322,8 +326,8 @@ private Mapping calculateMapping(DistanceMeasure distanceMeasure, Mapping previo // other containers e.g. Sprites, so we make sure only descendants of a given container // are aligned // TODO: Why do String IDs work so much faster than IDHashMap - ListMap fromContainers = new ListMap<>(); - ListMap toContainers = new ListMap<>(); + ListMap fromContainers = new ListMap<>(); // map root node or null to "from"? + ListMap toContainers = new ListMap<>(); // map root node or null to "to"? for (Node from : fromNodes) { Node container = getContainer(distanceMeasure, from); fromContainers.add(container == null ? null : container.id, from); @@ -334,6 +338,7 @@ private Mapping calculateMapping(DistanceMeasure distanceMeasure, Mapping previo } for (String containerID : fromContainers.keySet()) { + // Get child sequences? List containedFrom = fromContainers.get(containerID); List containedTo = toContainers.get(containerID); if (containedTo == null) continue; @@ -430,6 +435,13 @@ public double matchedOrphanReward(String type) { } }; + /** + * + * @param fromNodes child sequence of "from" node (i.e. a_{ri} in the paper) + * @param toNodes child sequence of "to" node (i.e. b_{ri} in the paper) + * @param distanceMeasure + * @param fromMap + */ private void align(List fromNodes, List toNodes, DistanceMeasure distanceMeasure, final ListMap fromMap) { String[][] fromStates = stateArray(fromNodes, true); @@ -439,7 +451,7 @@ private void align(List fromNodes, List toNodes, String type = fromNodes.get(0).type(); double minCost = Integer.MAX_VALUE; - double[][] costMatrix = new double[fromStates.length][toStates.length]; + double[][] costMatrix = new double[fromStates.length][toStates.length]; // pairwise distance CountMap costCounts = new CountMap<>(); for (int i = 0; i < fromStates.length; i++) { for (int j = 0; j < toStates.length; j++) { @@ -452,7 +464,7 @@ private void align(List fromNodes, List toNodes, minCost = Math.min(minCost, cost); } } - for (int i = 0; i < fromStates.length; i++) { + for (int i = 0; i < fromStates.length; i++) { // Break ties for (int j = 0; j < toStates.length; j++) { Node from = fromNodes.get(i), to = toNodes.get(j); double cost = costMatrix[i][j]; @@ -490,7 +502,7 @@ private void align(List fromNodes, List toNodes, } HungarianAlgorithm alg = new HungarianAlgorithm(costMatrix); - int[] matching = alg.execute(); + int[] matching = alg.execute(); // matching[k] = l means the k-th node in a_{ri} is matched with the l-th node in b_{rj} Set matchedTo = new HashSet<>(); // We pre-compute whether each of the from and to nodes have been previous put in the @@ -658,6 +670,14 @@ private boolean needsReorder(int[] reorders) { return needsReorder; } + /** + * @param a + * @param b + * @param config + * @param dm + * @return the difference between string representations of a and b obtained by + * depth-first iteration + */ public static double getSubCostEsitmate(Node a, Node b, HintConfig config, DistanceMeasure dm) { String[] aDFI = a.depthFirstIteration(); String[] bDFI = b.depthFirstIteration(); @@ -678,6 +698,11 @@ private String[][] stateArray(List nodes, boolean isFrom) { return states; } + /** + * + * @param nodes + * @return relative positions of annotations? + */ private int[][] orderGroups(List nodes) { int[][] orderGroups = new int[nodes.size()][]; for (int i = 0; i < orderGroups.length; i++) { @@ -717,6 +742,14 @@ private String parentNodeKey(Node node, boolean isFrom) { return Arrays.toString(list); } + /** + * @param from node of current student's code for which we show hints + * @param matches lists from which we find the closest + * @param distanceMeasure the definition of distance between source codes + * @param config + * @param maxReturned the number of the closest codes to return + * @return a list of the mappings from "from" to codes in "matches" + */ public static List findBestMatches(Node from, List matches, DistanceMeasure distanceMeasure, HintConfig config, int maxReturned) { List best = new LinkedList<>(); @@ -825,15 +858,17 @@ private Mapping align(DistanceMeasure dm, Mapping previousMapping) { } HungarianAlgorithm alg = new HungarianAlgorithm(costMatrix); - int[] matching = alg.execute(); + // i-th node in fromNodes is assigned to matching[i]-th node in toNodes + int[] matching = alg.execute(); for (int i = 0; i < fromNodes.size(); i++) { int j = matching[i]; - if (j < 0) continue; + if (j < 0) continue; // i-th node in fromNodes is unassigned // TODO: determine/config - if (costMatrix[i][j] > 1000) continue; - mapping.put(fromNodes.get(i), toNodes.get(j)); + // too large. Leave j-th node in toNodes unassigned + if (costMatrix[i][j] > 1000) continue; + mapping.put(fromNodes.get(i), toNodes.get(j)); // add assignment to mapping } } @@ -923,6 +958,12 @@ private void calculateMappingReward() { }); } + /** + * + * @param node + * @param isFrom + * @return a list of strings of types of the parent nodes staring from the closest parent + */ private String[] getRootPathArray(Node node, boolean isFrom) { String[] rp = new String[node.rootPathLength()]; int i = rp.length - 1; @@ -933,6 +974,13 @@ private String[] getRootPathArray(Node node, boolean isFrom) { return rp; } + /** + * + * @param node + * @param isFrom true if node is the current student's code + * @return ListMap (extends LinkedHashMap) that maps type or value strings in the AST + * to "node" + */ private ListMap getNodesByType(Node node, boolean isFrom) { final ListMap map = new ListMap<>(MapFactory.LinkedHashMapFactory); node.recurse(new Action() { diff --git a/HighLevelHints/.classpath b/HighLevelHints/.classpath new file mode 100644 index 00000000..07bdc197 --- /dev/null +++ b/HighLevelHints/.classpath @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/HighLevelHints/.project b/HighLevelHints/.project new file mode 100644 index 00000000..3313da1d --- /dev/null +++ b/HighLevelHints/.project @@ -0,0 +1,17 @@ + + + HighLevelHints + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/HighLevelHints/src/alignment/JavaSolutionConfig.java b/HighLevelHints/src/alignment/JavaSolutionConfig.java new file mode 100644 index 00000000..3a9359c7 --- /dev/null +++ b/HighLevelHints/src/alignment/JavaSolutionConfig.java @@ -0,0 +1,16 @@ +package alignment; + +import edu.isnap.hint.HintConfig.ValuesPolicy; +import edu.isnap.java.JavaHintConfig; + +public class JavaSolutionConfig extends JavaHintConfig { + public double baseReward = 2; + public double factor = 0.5; + public double outOfOrderReward = 1; + + public JavaSolutionConfig() { + super(); + progressMissingFactor = 0; + valuesPolicy = ValuesPolicy.MatchAllExactly; + } +} diff --git a/HighLevelHints/src/alignment/SolutionAlignment.java b/HighLevelHints/src/alignment/SolutionAlignment.java new file mode 100644 index 00000000..fffc5162 --- /dev/null +++ b/HighLevelHints/src/alignment/SolutionAlignment.java @@ -0,0 +1,227 @@ +package alignment; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import edu.isnap.hint.HintConfig; +import edu.isnap.hint.util.Alignment; +import edu.isnap.node.Node; +import edu.isnap.sourcecheck.NodeAlignment; + +public class SolutionAlignment extends Alignment { + + public static double getFullOrderReward(int length, double baseReward, double factor) { + double reward = 0.0; + for (int i = 0; i < length; i++) { + reward += Math.pow(baseReward, Math.pow(factor, i)); + } + return reward; + } + + public static int getMissingNodeCount(String[] from, String[] to) { + return (int) (to.length - getProgress(to, from, 1, 1, true)); + } + +// public static double getProgress(String[] from, String[] to, int orderReward, int unorderReward) { +// return getProgress(from, to, orderReward, unorderReward, false); +// } + + public static double getProgress(String[] from, String[] to, int[] toOrderGroups, int orderReward, int unorderReward) { + return getProgress(from, to, toOrderGroups, orderReward, unorderReward, new int[to.length], false); + } + + private static double getProgress(String[] from, String[] to, int orderReward, + int unorderReward, boolean missing) { + return getProgress(from, to, null, orderReward, unorderReward, new int[to.length], missing); + } + + private static double getProgress(String[] from, String[] to, int[] toOrderGroups, + int orderReward, int unorderReward, int[] toIndices, boolean missing) { + // TODO: This can and should be much more efficient + // toList[k] = "\0" if the k-th node in c(b_{rj}) is in c(a_{ri}) + List toList = new LinkedList<>(Arrays.asList(to)); + + // If indices[k] = l, k-th node in c(a_{ri}) is l-th node in c(b_{rj}) + // If indices[k] = -1, k-th node in c(a_{ri}) is not in c(b_{rj}) + //int[] indices = new int[from.length]; + List indices = new ArrayList<>(from.length); + for (int i = 0; i < from.length; i++) { + String item = from[i]; + int index = toList.indexOf(item); + if (index >= 0) { // i-th node in c(a_{ri}) is in c(b_{rj}) + if (missing) { + toList.set(index, "\0"); // prevents multiple matches + } + //indices[i] = index; + indices.add(i, index); + } else { + //indices[i] = -1; + indices.add(i, index); + } + } + + Arrays.fill(toIndices, -1); + + Queue> oneToOneIndices = new LinkedList<>(); + oneToOneIndices.add(indices); + if (!missing) { + for (int i = 0; i < to.length; i++) { + List fromIndices = new ArrayList<>(); + List example = oneToOneIndices.peek(); + for (int j = 0; j < example.size(); j++) { + if (example.get(j) == i) { + fromIndices.add(j); + } + } + if (fromIndices.size() < 2) { + continue; // no duplicates + } + // Multiple elements in "from" point to one element in "to" + int queueSize = oneToOneIndices.size(); + for (int j = 0; j < queueSize; j++) { + ArrayList polled = (ArrayList) oneToOneIndices.poll(); + for (int index : fromIndices) { + List temp = new ArrayList<>(polled.size()); + for (int h = 0; h < polled.size(); h++) { + if (fromIndices.indexOf(polled.get(h)) != -1 && h != index) { + temp.add(h, -1); + }else { + temp.add(h, polled.get(h)); + } + } + oneToOneIndices.add(new ArrayList(temp)); + } + } + } + } + + double maxReward = Double.NEGATIVE_INFINITY; + for (List filteredIndices : oneToOneIndices) { + + double reward = 0; + int lastIndex = -1; + int maxIndex = -1; + for (Integer index : filteredIndices) { + if (index < 0) continue; // don't change reward if k-th node in c(a_{ri}) is not in c(b_{rj}) + int adjIndex = index; + int group; +// System.out.println(index); + if (toOrderGroups != null && (group = toOrderGroups[adjIndex]) > 0) { // always false for Java SourceCheck + // If the matched "to" item is in an order group (for which all items in the group + // are unordered), we should match i to the index of the earliest item in this group + // which comes after the last index, since the actual match could have been + // reordered to that index without loss of meaning + + // First check if the index can be decreased within the order group without going + // <= the max seen index (to avoid duplicate adjusted indices) + while (adjIndex > 0 && adjIndex - 1 > maxIndex && + toOrderGroups[adjIndex - 1] == group) { + adjIndex--; +// System.out.println("m-> " + adjIndex); + } + // Next check if the index is out of order and increasing it to maxIndex + 1 will + // make in order + int nextIndex = maxIndex + 1; + if (nextIndex < toOrderGroups.length && adjIndex <= lastIndex && + toOrderGroups[nextIndex] == group) { + adjIndex = nextIndex; +// System.out.println("p-> " + adjIndex); + } + + // Set the actual to-index used after adjustments above + if (index != adjIndex) { + toIndices[index] = adjIndex; + } + } + + if (to[adjIndex] != null) { + reward += adjIndex > lastIndex ? orderReward : unorderReward; + } + lastIndex = adjIndex; + maxIndex = Math.max(maxIndex, adjIndex); + } + if (reward > maxReward) maxReward = reward; + } + + return maxReward; + } + + public static double getNormalizedMissingNodeCount(String[] from, String[] to, + double baseReward, double factor) { + return getFullOrderReward(to.length, baseReward, factor) + - getNormalizedProgress(to, from, null, baseReward, 1, factor, new int[to.length]); + } + + public static double getNormalizedProgress(String[] from, String[] to, int[] toOrderGroups, + double baseReward, double unorderReward, double factor, int[] toIndices) { + // TODO: This can and should be much more efficient + // toList[k] = "\0" if the k-th node in c(b_{rj}) is in c(a_{ri}) + List toList = new LinkedList<>(Arrays.asList(to)); + + // If indices[k] = l, k-th node in c(a_{ri}) is l-th node in c(b_{rj}) + // If indices[k] = -1, k-th node in c(a_{ri}) is not in c(b_{rj}) + int[] indices = new int[from.length]; + for (int i = 0; i < from.length; i++) { + String item = from[i]; + int index = toList.indexOf(item); + if (index >= 0) { // i-th node in c(a_{ri}) is in c(b_{rj}) + toList.set(index, "\0"); + indices[i] = index; + } else { + indices[i] = -1; + } + } + + Arrays.fill(toIndices, -1); + + double reward = 0; + int lastIndex = -1; + int maxIndex = -1; + for (Integer index : indices) { + if (index < 0) continue; // don't change reward if k-th node in c(a_{ri}) is not in c(b_{rj}) + int adjIndex = index; + int group; +// System.out.println(index); + if (toOrderGroups != null && (group = toOrderGroups[adjIndex]) > 0) { // always false for Java SourceCheck + // If the matched "to" item is in an order group (for which all items in the group + // are unordered), we should match i to the index of the earliest item in this group + // which comes after the last index, since the actual match could have been + // reordered to that index without loss of meaning + + // First check if the index can be decreased within the order group without going + // <= the max seen index (to avoid duplicate adjusted indices) + while (adjIndex > 0 && adjIndex - 1 > maxIndex && + toOrderGroups[adjIndex - 1] == group) { + adjIndex--; +// System.out.println("m-> " + adjIndex); + } + // Next check if the index is out of order and increasing it to maxIndex + 1 will + // make in order + int nextIndex = maxIndex + 1; + if (nextIndex < toOrderGroups.length && adjIndex <= lastIndex && + toOrderGroups[nextIndex] == group) { + adjIndex = nextIndex; +// System.out.println("p-> " + adjIndex); + } + + // Set the actual to-index used after adjustments above + if (index != adjIndex) { + toIndices[index] = adjIndex; + } + } + + if (to[adjIndex] != null) { + reward += adjIndex > lastIndex ? Math.pow(baseReward, Math.pow(factor, adjIndex)) : + Math.pow(unorderReward, Math.pow(factor, adjIndex)); + } + lastIndex = adjIndex; + maxIndex = Math.max(maxIndex, adjIndex); + } + + return reward; + } + +} diff --git a/HighLevelHints/src/alignment/SolutionDistanceMeasure.java b/HighLevelHints/src/alignment/SolutionDistanceMeasure.java new file mode 100644 index 00000000..4af9c01f --- /dev/null +++ b/HighLevelHints/src/alignment/SolutionDistanceMeasure.java @@ -0,0 +1,48 @@ +package alignment; + +import edu.isnap.hint.HintConfig; +import edu.isnap.hint.util.Alignment; +import edu.isnap.node.Node; +import edu.isnap.sourcecheck.NodeAlignment.DistanceMeasure; +import edu.isnap.sourcecheck.NodeAlignment.ProgressDistanceMeasure; + +public class SolutionDistanceMeasure extends ProgressDistanceMeasure { + + public SolutionDistanceMeasure(HintConfig config) { + super(config); + // TODO Auto-generated constructor stub + } + + @Override + public double measure(Node from, String[] a, String[] b, int[] bOrderGroups) { +// if (this.config instanceof JavaSolutionConfig) { +// JavaSolutionConfig config = (JavaSolutionConfig) this.config; +// return SolutionAlignment.getNormalizedMissingNodeCount(a, b, config.baseReward, config.factor) +// + SolutionAlignment.getNormalizedMissingNodeCount(b, a, config.baseReward, config.factor) +// + SolutionAlignment.getFullOrderReward( +// SolutionAlignment.getProgress(a, b, 1, 1), config.baseReward, config.factor) +// - (SolutionAlignment.getNormalizedProgress( +// a, b, bOrderGroups, config.baseReward, config.outOfOrderReward, config.factor, new int[b.length]) +// + SolutionAlignment.getNormalizedProgress(b, a, null, config.baseReward, config.outOfOrderReward, config.factor, new int[a.length]) +// ) / 2; +// } + return SolutionAlignment.getMissingNodeCount(a, b) + SolutionAlignment.getMissingNodeCount(b, a) + + inOrderReward * SolutionAlignment.getProgress(a, b, 1, 1) + - (SolutionAlignment.getProgress(a, b, bOrderGroups, inOrderReward, outOfOrderReward, 0) + + SolutionAlignment.getProgress(b, a, null, inOrderReward, outOfOrderReward, 0)) / 2; + } + + @Override + public double matchedOrphanReward(String type) { + return 0; + } + public static void main(String[] args) { + HintConfig temp = new JavaSolutionConfig(); + DistanceMeasure sol = new SolutionDistanceMeasure(temp); + String[] a = {"1", "2", "3", "4", "5", "3"}; + String[] b = {"1", "2", "4", "4", "3", "5"}; + System.out.println(sol.measure(null, a, b, new int[b.length])); + System.out.println(sol.measure(null, b, a, new int[a.length])); + } + +} diff --git a/HighLevelHints/src/clustering/DBSCAN.java b/HighLevelHints/src/clustering/DBSCAN.java new file mode 100644 index 00000000..3c57bc70 --- /dev/null +++ b/HighLevelHints/src/clustering/DBSCAN.java @@ -0,0 +1,22 @@ +package clustering; + +import net.sf.javaml.core.*; +import net.sf.javaml.clustering.*; +import net.sf.javaml.distance.*; + +public class DBSCAN { + public static Dataset[] fit_predict(Dataset data) { + Clusterer dbscan = new DensityBasedSpatialClustering(); + return dbscan.cluster(data); + } + + public static Dataset[] fit_predict(Dataset data, double epsilon, int minPoints) { + Clusterer dbscan = new DensityBasedSpatialClustering(epsilon, minPoints); + return dbscan.cluster(data); + } + + public static Dataset[] fit_predict(Dataset data, double epsilon, int minPoints, DistanceMeasure dm) { + Clusterer dbscan = new DensityBasedSpatialClustering(epsilon, minPoints, dm); + return dbscan.cluster(data); + } +} diff --git a/HighLevelHints/src/clustering/PreComputed.java b/HighLevelHints/src/clustering/PreComputed.java new file mode 100644 index 00000000..45bd3166 --- /dev/null +++ b/HighLevelHints/src/clustering/PreComputed.java @@ -0,0 +1,59 @@ +package clustering; + +import net.sf.javaml.distance.DistanceMeasure; + +import java.util.Iterator; + +import net.sf.javaml.core.*; + +public class PreComputed implements DistanceMeasure { + private double max; + private double min; + private Dataset data; + + public PreComputed(Dataset data) { + this.data = data; + this.max = Double.NEGATIVE_INFINITY; + this.min = Double.POSITIVE_INFINITY; + for (Instance i : data) { + Iterator itr = i.iterator(); + while (itr.hasNext()) { + double value = itr.next(); + if (value > this.max) this.max = value; + if (value < this.min) this.min = value; + } + } + } + + @Override + public boolean compare(double arg0, double arg1) { + return arg0 >= arg1; + } + + @Override + public double getMaxValue() { + return this.max; + } + + @Override + public double getMinValue() { + return this.min; + } + + @Override + public double measure(Instance arg0, Instance arg1) { + int arg0Idx = data.indexOf(arg0); + int arg1Idx = data.indexOf(arg1); + + // Check if the distance matrix is symmetric + double arg0toarg1 = arg0.value(arg1Idx); + double arg1toarg0 = arg1.value(arg0Idx); + if (arg0toarg1 != arg1toarg0) { + return (arg0toarg1 + arg1toarg0) / 2; + //throw new RuntimeException("The precomputed distance matrix is not symmetric."); + } + return arg0toarg1; + } + +} + diff --git a/HighLevelHints/src/clustering/SolutionClusterer.java b/HighLevelHints/src/clustering/SolutionClusterer.java new file mode 100644 index 00000000..ed1dbacc --- /dev/null +++ b/HighLevelHints/src/clustering/SolutionClusterer.java @@ -0,0 +1,153 @@ +package clustering; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import alignment.JavaSolutionConfig; +import alignment.SolutionAlignment; +import alignment.SolutionDistanceMeasure; +import edu.isnap.node.JavaNode; +import edu.isnap.node.Node; +import edu.isnap.node.TextualNode; +import edu.isnap.python.SourceCodeHighlighter; +import edu.isnap.hint.HintConfig; +import edu.isnap.hint.HintData; +import edu.isnap.hint.util.NullStream; +import edu.isnap.java.JavaHintConfig; +import edu.isnap.sourcecheck.HintHighlighter; +import edu.isnap.sourcecheck.NodeAlignment; +import edu.isnap.sourcecheck.NodeAlignment.DistanceMeasure; +import edu.isnap.sourcecheck.NodeAlignment.Mapping; +import edu.isnap.sourcecheck.edit.EditHint; +import edu.isnap.sourcecheck.edit.Suggestion; +import net.sf.javaml.core.*; + +public class SolutionClusterer { + private List solutions; + private HintConfig solConfig = new JavaSolutionConfig(); + private String assignment; + + public SolutionClusterer(List solutions, String assignment) { + this.solutions = solutions; + this.assignment = assignment; + } + + public int getNumSolutions() { + return solutions.size(); + } + + public HashMap> clusterSolutions() { + // Sort solutions by studentID and timestamp + Collections.sort(solutions, new Comparator() { + @Override + public int compare(Node o1, Node o2) { + int studentID = o1.getStudentID().compareTo(o2.getStudentID()); + if (studentID != 0) return studentID; + return o1.getSubmissionTime().compareTo(o2.getSubmissionTime()); + } + }); + + // Map student ID and timestamp to a distance vector + HashMap> solutionMaps = new HashMap<>(); + + // Construct a distance matrix + double max = Double.NEGATIVE_INFINITY; + double min = Double.POSITIVE_INFINITY; + JavaHintConfig config = new JavaHintConfig(); + double[][] distanceMatrix = new double[solutions.size()][]; + for (int i = 0; i < solutions.size(); i++) { + TextualNode solution = (TextualNode) solutions.get(i); + double[] distances = getDistances(solution).stream().mapToDouble(d -> d).toArray(); +// double[] distances = new double[solutions.size()]; +// for (int j = 0; j < solutions.size(); j++) { +// List trace = new ArrayList<>(); +// trace.add(solutions.get(j)); +// HintData hintData = new HintData(assignment, config, 0, HintHighlighter.DataConsumer); +// hintData.addTrace(solutions.get(j).getStudentID(), trace); +// HintHighlighter highlighter = hintData.hintHighlighter(); +// +// highlighter.trace = NullStream.instance; +// +// List edits = highlighter.highlightWithPriorities(solution); +// List suggestions = SourceCodeHighlighter.getSuggestions(edits); +// int dist = suggestions.size(); +// distances[j] = dist; +// if (dist > max) max = dist; +// if (dist < min) min = dist; +// } + for (double dist : distances) { + if (dist > max) max = dist; + if (dist < min) min = dist; + } + distanceMatrix[i] = distances; + } + + // Check if the matrix is symmetric + for (int i = 0; i < distanceMatrix.length; i++) { + for (int j = 0; j < i; j++) { + if (distanceMatrix[i][j] != distanceMatrix[j][i]) { + System.err.println(distanceMatrix[i][j] + " is not " + distanceMatrix[j][i] + + " when i = " + i + ", j = " + j); + } + double smaller = Math.min(distanceMatrix[i][j], distanceMatrix[j][i]); + distanceMatrix[i][j] = smaller; + distanceMatrix[j][i] = smaller; + } + } + + // Normalize dataset + Dataset normalizedData = new DefaultDataset(); + for (int i = 0; i < solutions.size(); i++) { + TextualNode solution = (TextualNode) solutions.get(i); + Instance data = new SparseInstance(distanceMatrix[i]); + Instance normalized = data.minus(min).divide(max - min); + if (solutionMaps.get(solution.getStudentID()) == null) { + solutionMaps.put(solution.getStudentID(), new HashMap()); + } + solutionMaps.get(solution.getStudentID()).put(solution.getSubmissionTime(), normalized); + normalizedData.add(normalized); + } + + PreComputed precomputed = new PreComputed(normalizedData); + Dataset[] clusters = DBSCAN.fit_predict(normalizedData, 0.12, 6, precomputed); + for (int i = 0; i < clusters.length; i ++) { + for (Instance inst : clusters[i]) { + inst.setClassValue(String.valueOf(i)); + } + } + System.out.println("Finished Clustering!"); + return solutionMaps; + } + + private List getDistances(Node node) { + DistanceMeasure dm = new SolutionDistanceMeasure(solConfig); + + Mapping[] mappings = getMappingsFrom(node, solutions, dm, solConfig); + List distances = new ArrayList(); + for (Mapping map : mappings) { + distances.add(map.cost()); + } + + return distances; + } + + private static Mapping[] getMappingsFrom(Node from, List matches, + DistanceMeasure distanceMeasure, HintConfig config) { + boolean v2 = config.sourceCheckV2; + + Mapping[] mappings = new Mapping[matches.size()]; + for (int i = 0; i < mappings.length; i++) { + Node to = matches.get(i); + NodeAlignment align = new NodeAlignment(from, to, config); + Mapping mapping = v2 ? align.align(distanceMeasure) : + align.calculateMapping(distanceMeasure); + mappings[i] = mapping; + } + + return mappings; + } + +} \ No newline at end of file diff --git a/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java b/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java index 138356f0..d998c721 100644 --- a/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java +++ b/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; @@ -14,6 +15,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Optional; @@ -22,10 +24,12 @@ import org.json.JSONObject; -//import com.esotericsoftware.kryo.Kryo; -//import com.esotericsoftware.kryo.io.Output; import com.opencsv.CSVReader; import com.opencsv.CSVReaderBuilder; +import com.opencsv.CSVWriter; + +import clustering.SolutionClusterer; + import com.github.javaparser.javaparser_symbol_solver_core.ASTParserJSON; import edu.isnap.hint.HintData; @@ -34,177 +38,218 @@ import edu.isnap.node.JavaNode; import edu.isnap.node.Node; import edu.isnap.node.TextualNode; +import edu.isnap.node.Node.Annotations; import edu.isnap.python.SourceCodeHighlighter; import edu.isnap.sourcecheck.HintHighlighter; import edu.isnap.sourcecheck.edit.EditHint; //import edu.isnap.util.map.ListMap; +import net.sf.javaml.core.*; + public class JavaImport { - static String DATA_DIR = "../data/project3_2/task1/"; + static String DATA_DIR = "../data/F19_Project_3_2/task4/"; + static String CMU_CS = "@andrew.cmu.edu_social-network_p32-task4_"; + static boolean clustered = false; public static void main(String[] args) throws IOException { - // Run generate hints to load data, generate hints for each student and print them out + // Run generate hints to load data, generate hints for each student and print + // them out // You need to update the file path to wherever you unzipped the data -// Map>> nodes = loadAssignment( -// DATA_DIR + "1__output_clock-display-ast.csv"); -// GrammarBuilder builder = new GrammarBuilder("java", new HashMap<>()); -// nodes.values().forEach(listMap -> listMap.values() -// .forEach(list -> list.forEach(n -> builder.add(n)))); -// System.out.println(builder.toJSON()); - - - PrintStream fileOut = new PrintStream(DATA_DIR + "output_hints.txt"); -// System.setOut(fileOut); - generateHintsForGS(DATA_DIR + "input.csv", "ProfileServlet"); -// serializeHintData(DATA_DIR + "1__output_clock-display-ast.csv", "ClockDisplay", -// "../HintServer/WebContent/WEB-INF/data/ClockDisplay.hdata"); + generateHintsForGS(DATA_DIR + "input.csv", "TimelineServlet"); } - /*static HintData createHintData(String inputCSV, String assignment) throws IOException { - LinkedHashMap> attempts = loadAssignment(inputCSV); - return createHintData(assignment, attempts); - }*/ + /* + * static HintData createHintData(String inputCSV, String assignment) throws + * IOException { LinkedHashMap> attempts = + * loadAssignment(inputCSV); return createHintData(assignment, attempts); } + */ - static HintData createHintData(String assignmentID, LinkedHashMap> attempts) { + /** + * + * @param assignment the name of java file used in this assignment. Omit + * ".java". + * @param attempts Hashmap that maps student id's to LinkedHashMap's (i.e. + * each student has their own LinkedHashMap). Each + * LinkedHashMap maps timestamps to parsed JavaNodes + * + * @return + */ + static HintData createHintData(String assignment, LinkedHashMap> attempts) { JavaHintConfig config = new JavaHintConfig(); - HintData hintData = new HintData(assignmentID, config, 1, HintHighlighter.DataConsumer); - for (String attemptID : attempts.keySet()) { - List trace = attempts.get(attemptID).stream() - .map(node -> (Node) node) - .collect(Collectors.toList()); + HintData hintData = new HintData(assignment, config, 0, HintHighlighter.DataConsumer); + for (String studentID : attempts.keySet()) { + List trace = attempts.get(studentID).stream().map(node -> (Node) node).collect(Collectors.toList()); // Only needed for LOOCV - hintData.addTrace(attemptID, trace); + hintData.addTrace(studentID, trace); } return hintData; } - // Don't worry about this method for now - static void serializeHintData(String inputCSV, String assignment, String outputPath) - throws IOException { - LinkedHashMap> attempts = loadAssignment(inputCSV, false, - assignment).get("ClockDisplay.java"); - List toRemove = new ArrayList(); - // Remove incorrect attempts before serializing - for (String attemptID : attempts.keySet()) { - List attempt = attempts.get(attemptID); - if (attempt.size() == 0 || !attempt.get(attempt.size() - 1).correct.orElse(false)) { - toRemove.add(attemptID); - } - } - toRemove.forEach(attempts::remove); - HintData hintData = createHintData(assignment, attempts); - /* - * Kryo kryo = SnapHintBuilder.getKryo(); Output output = new Output(new - * FileOutputStream(outputPath)); kryo.writeObject(output, hintData); - * output.close(); - */ + /* + * static void generateHints(String inputCSV, String assignment) throws + * IOException { HashMap>> + * filePathToattempts = loadAssignment( inputCSV, false, assignment); + * + * for (String filePath : filePathToattempts.keySet()) { File startSourceFile = + * new File(DATA_DIR + "Start/" + filePath); String startSource = ""; if + * (startSourceFile.exists()) { startSource = new + * String(Files.readAllBytes(startSourceFile.toPath())); startSource = + * stripComments(startSource); } + * + * // For now, just look at ClockDisplay, since NumberDisplay didn't have to be + * edited //if (!filePath.equals("ClockDisplay.java")) continue; + * LinkedHashMap> attempts = + * filePathToattempts.get(filePath); for (String student : attempts.keySet()) { + * // May want to change this to a random attempt, not just the first one, but + * you can // start with the first one JavaNode firstAttempt = + * attempts.get(student).get(0); if (firstAttempt.correct.orElse(false)) + * continue; + * + * LinkedHashMap> subset = new LinkedHashMap<>(); for + * (String attemptID : attempts.keySet()) { // Don't add this student to their + * own hint generation data if (attemptID.equals(student)) continue; // Get the + * sequence of snapshots over time List trace = + * attempts.get(attemptID); // If it was correct, then add it to the subset if + * (trace.get(trace.size() - 1).correct.orElse(false)) { // String + * solutionSource = stripComments( // trace.get(trace.size() - 1).getSource()); + * // System.out.println("Solution #: " + subset.size()); // + * System.out.println(Diff.diff(startSource, solutionSource, 2)); // + * System.out.println("--------------------------"); subset.put(attemptID, + * attempts.get(attemptID)); } } // if (1==1) break; + * System.out.println("Student: " + student); + * System.out.println("Building with: " + subset.size()); // We create a + * "HintData" object, which represents the data from which we generate // all + * hints HintData hintData = createHintData(assignment, subset); + * + * // Then we use this method to "highlight" the java source code using the + * SourceCheck // hints System.out.println( + * SourceCodeHighlighter.highlightSourceCode(hintData, firstAttempt)); + * + * break; } } } + */ + + static void clusterSolutions(List correct, String assignment) throws IOException { + SolutionClusterer clusterer = new SolutionClusterer(correct, assignment); + HashMap> solutionMaps = clusterer.clusterSolutions(); + List students = new ArrayList(solutionMaps.keySet()); + Collections.sort(students); + + // Open a csv file + File clusterCSV = new File(DATA_DIR + "cluster_info.csv"); + FileWriter outputfile = new FileWriter(clusterCSV); + + // create CSVWriter object filewriter object as parameter + CSVWriter writer = new CSVWriter(outputfile); + + // adding header to csv + List header = new ArrayList(); + header.add("StudentID"); + header.add("Timestamp"); + header.add("ClusterID"); + for (int i = 1; i <= clusterer.getNumSolutions(); i++) { + header.add("Dist_" + i); + } + String[] tempHeader = new String[header.size()]; + writer.writeNext(header.toArray(tempHeader)); + + // add data to csv + for (String student : students) { + HashMap timeMap = solutionMaps.get(student); + List timestamps = new ArrayList(timeMap.keySet()); + String timestamp = timestamps.get(0); +// Collections.sort(timestamps); +// for (String timestamp : timestamps) { + List data = new ArrayList(); + Instance inst = timeMap.get(timestamp); + String clusterID = (String) inst.classValue(); + data.add(student); // student id + data.add(timestamp); // timestamp + data.add(clusterID); // cluster id + + // Add distances + Iterator itr = inst.iterator(); + while (itr.hasNext()) { + data.add(itr.next().toString()); + } + String[] tempData = new String[data.size()]; + writer.writeNext(data.toArray(tempData)); +// } + } + + // closing writer connection + writer.close(); } - static void generateHints(String inputCSV, String assignment) throws IOException { - HashMap>> filePathToattempts = loadAssignment( - inputCSV, false, assignment); - - for (String filePath : filePathToattempts.keySet()) { - File startSourceFile = new File(DATA_DIR + "Start/" + filePath); - String startSource = ""; - if (startSourceFile.exists()) { - startSource = new String(Files.readAllBytes(startSourceFile.toPath())); - startSource = stripComments(startSource); - } - - // For now, just look at ClockDisplay, since NumberDisplay didn't have to be edited - //if (!filePath.equals("ClockDisplay.java")) continue; - LinkedHashMap> attempts = filePathToattempts.get(filePath); - for (String student : attempts.keySet()) { - // May want to change this to a random attempt, not just the first one, but you can - // start with the first one - JavaNode firstAttempt = attempts.get(student).get(0); - if (firstAttempt.correct.orElse(false)) continue; + /** + * Exports html files highlighting hints + * + * @param inputCSV path to your input csv containing student id's, timestamps, + * correctness, etc + * @param assignment the name of java file used in this assignment. Omit + * ".java". + * @throws IOException + */ + static void generateHintsForGS(String inputCSV, String assignment) throws IOException { + HashMap> attempts = loadAssignment(inputCSV, true, assignment); - LinkedHashMap> subset = new LinkedHashMap<>(); - for (String attemptID : attempts.keySet()) { - // Don't add this student to their own hint generation data - if (attemptID.equals(student)) continue; - // Get the sequence of snapshots over time - List trace = attempts.get(attemptID); - // If it was correct, then add it to the subset - if (trace.get(trace.size() - 1).correct.orElse(false)) { -// String solutionSource = stripComments( -// trace.get(trace.size() - 1).getSource()); -// System.out.println("Solution #: " + subset.size()); -// System.out.println(Diff.diff(startSource, solutionSource, 2)); -// System.out.println("--------------------------"); - subset.put(attemptID, attempts.get(attemptID)); - } + // Maps student id's to their history of submissions. Only students + // who eventually got correct are considered + LinkedHashMap> correctTraces = new LinkedHashMap<>(); + // List of correct submissions + List correct = new ArrayList<>(); + LinkedHashMap annotated = new LinkedHashMap<>(); + for (String studentID : attempts.keySet()) { + List trace = new ArrayList<>(); + List timestamps = new ArrayList<>(attempts.get(studentID).keySet()); + Collections.sort(timestamps); + for (String timestamp : timestamps) { + // Get the sequence of snapshots over time + JavaNode node = attempts.get(studentID).get(timestamp); +// if (node.correct.orElse(false)) { +// correct.add(node); +// } + trace.add(node); + if (!node.readOnlyAnnotations().equals(Annotations.EMPTY)) { + annotated.put(node.cluster.get(), node); } -// if (1==1) break; - System.out.println("Student: " + student); - System.out.println("Building with: " + subset.size()); - // We create a "HintData" object, which represents the data from which we generate - // all hints - HintData hintData = createHintData(assignment, subset); - - // Then we use this method to "highlight" the java source code using the SourceCheck - // hints - System.out.println( - SourceCodeHighlighter.highlightSourceCode(hintData, firstAttempt)); - - break; } - } - } - static void generateHintsForGS(String inputCSV, String assignment) throws IOException { - LinkedHashMap> attempts = - loadAssignment(inputCSV, true, assignment).get(assignment + ".java"); - //System.out.println(attempts); - LinkedHashMap> correct = new LinkedHashMap<>(); - for (String attemptID : attempts.keySet()) { - if(true) {//attemptID.startsWith("s") || attemptID.startsWith("r")) { - // Get the sequence of snapshots over time - List trace = attempts.get(attemptID); - // If it was correct, then add it to the subset - if (trace.get(trace.size() - 1).correct.orElse(false)) { - correct.put(attemptID, attempts.get(attemptID)); - } + // If it was correct, then add it to the subset + if (trace.get(trace.size() - 1).correct.orElse(false)) { + correctTraces.put(studentID, trace); + correct.add(trace.get(trace.size() - 1)); } } - HintData hintData = createHintData(assignment, correct); - //HintHighlighter highlighter = hintData.hintHighlighter(); + HintData hintData = createHintData(assignment, correctTraces); + if (!clustered) { + clusterSolutions(correct, assignment); + return; + } + + for (int clusterID : annotated.keySet()) { + hintData.addReferenceSoltion(clusterID, annotated.get(clusterID)); + } - // TODO: Get the actual list from a .csv file, map project_id to the hint request - //LinkedHashMap> goldStandardHintRequests = attempts; - //new File(DATA_DIR+"GS").mkdirs(); - for (String student : attempts.keySet()) { - JavaNode hintRequest = attempts.get(student).get(0); - String highlightedCode = SourceCodeHighlighter.highlightSourceCode( - hintData, hintRequest); + // TODO: Get the actual list from a .csv file, map project_id to the hint + // request + List csvRecords = readCSV(inputCSV); + for (String[] record : csvRecords) { + // for (String student : attempts.keySet()) { + String timestamp = record[0]; + String student = record[1]; + JavaNode hintRequest = attempts.get(student).get(timestamp); + String highlightedCode = SourceCodeHighlighter.highlightSourceCode(hintData, hintRequest); highlightedCode = highlightedCode.replace("\n", "
\n"); - highlightedCode = "\n" + - "\n" + highlightedCode; - PrintWriter out = new PrintWriter(DATA_DIR + student + "/output_hints.html"); + highlightedCode = "\n" + "\n" + highlightedCode; + PrintWriter out = new PrintWriter(DATA_DIR + student + CMU_CS + timestamp + "/output_hints.html"); out.println(highlightedCode); out.close(); - + } - /* - * for (String student : goldStandardHintRequests.keySet()) { - * if(student.startsWith("s") || student.startsWith("r")) { continue; } JavaNode - * hintRequest = goldStandardHintRequests.get(student).get(0); List - * edits = highlighter.highlightWithPriorities(hintRequest); int i = 0; for - * (EditHint hint : edits) { Node copy = hintRequest.copy(); - * EditHint.applyEdits(copy, Collections.singletonList(hint)); double priority = - * hint.priority.consensus(); JSONObject json = copy.toJSON(); - * json.put("priority", priority); String file = String.format("%s_%02d.json", - * student, i); PrintWriter out = new PrintWriter(DATA_DIR+"GS/"+file); - * out.println(json.toString()); out.close(); System.out.println(file + ": " + - * json.toString()); i++; } } - */ } private static String stripComments(String source) { @@ -220,115 +265,114 @@ private static String stripComments(String source) { return String.join("\n", l); } - public static List readCSV(String fileName){ + public static List readCSV(String fileName) { List csvRecords = new ArrayList<>(); - try ( - Reader reader = Files.newBufferedReader(Paths.get(fileName)); - CSVReader csvReader = new CSVReaderBuilder(reader).withSkipLines(1).build() - ) { - String[] nextLine; - while ((nextLine = csvReader.readNext()) != null) { - String projectID = nextLine[0]; - String sourceFileID = nextLine[1]; - String compileTime = nextLine[2]; - String filePath = nextLine[3]; - String sourceCode = nextLine[4]; - String diff = nextLine[5]; - String isLastDiff = nextLine[6]; - String sessionID = nextLine[7]; - String compileID = nextLine[8]; - String originalFileID = nextLine[9]; - String isCorrect = nextLine[10]; - String doesCompile = nextLine[11]; - String sourceCodeJSON = nextLine[12]; - String[] record = {projectID, sourceFileID, compileTime, filePath, sourceCode, diff, - isLastDiff, sessionID, - compileID, originalFileID, isCorrect, doesCompile, sourceCodeJSON}; - csvRecords.add(record); - } - }catch (IOException e) { - e.printStackTrace(); - } + try (Reader reader = Files.newBufferedReader(Paths.get(fileName)); + CSVReader csvReader = new CSVReaderBuilder(reader).withSkipLines(1).build()) { + String[] nextLine; + while ((nextLine = csvReader.readNext()) != null) { + String projectID = nextLine[0]; + String sourceFileID = nextLine[1]; + String compileTime = nextLine[2]; + String filePath = nextLine[3]; + String sourceCode = nextLine[4]; + String diff = nextLine[5]; + String isLastDiff = nextLine[6]; + String sessionID = nextLine[7]; + String compileID = nextLine[8]; + String originalFileID = nextLine[9]; + String isCorrect = nextLine[10]; + String doesCompile = nextLine[11]; + String sourceCodeJSON = nextLine[12]; + String isAnnotated = nextLine[13]; + String clusterID = nextLine[14]; + String[] record = { projectID, sourceFileID, compileTime, filePath, sourceCode, diff, isLastDiff, + sessionID, compileID, originalFileID, isCorrect, doesCompile, sourceCodeJSON, isAnnotated, clusterID }; + csvRecords.add(record); + } + } catch (IOException e) { + e.printStackTrace(); + } return csvRecords; } private static String removeComments(String code) { - return String.join("\n", - Arrays.stream(code.split("\n")) - .filter(l -> !(l.trim().startsWith("*") || l.trim().startsWith("/**"))) - .collect(Collectors.toList()) - ); + return String.join("\n", Arrays.stream(code.split("\n")) + .filter(l -> !(l.trim().startsWith("*") || l.trim().startsWith("/**"))).collect(Collectors.toList())); } - static HashMap>> loadAssignment(String inputCSV, boolean GS, + /** + * @param inputCSV path to your input csv containing student id's, timestamps, + * correctness, etc + * @param GS ignored + * @param assignment the name of java file used in this assignment. Omit + * ".java". + * + * @return Hashmap that maps student id's to LinkedHashMap's (i.e. each student + * has their own LinkedHashMap). Each LinkedHashMap maps timestamps to + * parsed JavaNodes + * + */ + static HashMap> loadAssignment(String inputCSV, boolean GS, String assignment) throws IOException { - HashMap>> filePathToNodes = new HashMap<>(); - List csvRecords= readCSV(inputCSV); - Set numberDisplayProjects = new HashSet<>(); - String numberDisplayStartSource = null; - if(!GS) { - numberDisplayProjects = new HashSet<>(); - File startSourceFile = new File(DATA_DIR + "Start/NumberDisplay.java"); - numberDisplayStartSource = new String(Files.readAllBytes(startSourceFile.toPath()), - Charset.forName("UTF-8")); - numberDisplayStartSource = numberDisplayStartSource.replaceAll("\r", ""); - numberDisplayStartSource = removeComments(numberDisplayStartSource); - } + HashMap> filePathToNodes = new HashMap<>(); + List csvRecords = readCSV(inputCSV); - for(String[] record: csvRecords) { - String projectID = record[0]; - String sourceFileID = record[1]; - //String sourceCode = record[4]; - //String sourceCodeJSON = record[12]; + clustered = false; + for (String[] record : csvRecords) { + String timestamp = record[0]; + String studentID = record[1]; String isCorrect = record[10]; - String filePath = record[3]; - filePath = filePath.split("/")[1]; - /* - * if(!GS) { filePath = filePath.split("/")[1]; } else { filePath = - * "ClockDisplay.java"; } - */ - if(true) {//filePath.equals("ClockDisplay.java") || filePath.equals("NumberDisplay.java")) { - /* - * File testJson = new File(DATA_DIR + "ASTs/" + sourceCodeJSON); if(GS) { - * testJson = new File(DATA_DIR + "ASTsGS/" + sourceCodeJSON); } String json = - * new String(Files.readAllBytes(testJson.toPath())); - * - * if (!GS && filePath.contentEquals("NumberDisplay.java") && - * isCorrect.equals("True")) { String source = removeComments(sourceCode); if - * (!source.equals(numberDisplayStartSource)) { - * numberDisplayProjects.add(projectID); } } - */ - - //JSONObject obj = new JSONObject(json); - File originalCode = new File(DATA_DIR + sourceFileID + "/" + assignment + ".java"); - String originalSourceCode = new String(Files.readAllBytes(originalCode.toPath())); - JSONObject parsedTree = new JSONObject(ASTParserJSON.toJSON(originalSourceCode)); - //JavaNode node = (JavaNode) TextualNode.fromJSON(obj, sourceCode, JavaNode::new); - JavaNode node = (JavaNode) JavaNode.fromJSON(parsedTree, originalSourceCode, JavaNode::new); - if (isCorrect.toLowerCase().equals("true")) {// || GS) { - node.correct = Optional.of(true); - } + String isAnnotated = record[13]; + String clusterID = record[14]; - if(filePathToNodes.get(filePath) == null) { - filePathToNodes.put(filePath, new LinkedHashMap>()); - } - if(filePathToNodes.get(filePath).get(projectID) == null) { - filePathToNodes.get(filePath).put(projectID, new ArrayList()); - } - filePathToNodes.get(filePath).get(projectID).add(node); + // JSONObject obj = new JSONObject(json); + File originalCode = new File(DATA_DIR + studentID + CMU_CS + timestamp + "/" + assignment + ".java"); + String originalSourceCode = new String(Files.readAllBytes(originalCode.toPath())); + String jsonString; + if (isAnnotated.toLowerCase().equals("true")) { + File annotatedCode = new File(DATA_DIR + studentID + CMU_CS + timestamp + "/" + assignment + ".json"); + jsonString = new String(Files.readAllBytes(annotatedCode.toPath())); + }else { + jsonString = ASTParserJSON.toJSON(originalSourceCode); } - } - if(!GS) { - System.out.println("NDPs: " + numberDisplayProjects.size()); - } - // Remove all solutions that changed the NumberDisplay class (for now) - // TODO: At some point, we need to use both source files in hint generation... - for (String filePath : filePathToNodes.keySet()) { - for (String project : numberDisplayProjects) { - filePathToNodes.get(filePath).remove(project); + + // TODO has to be commented out +// if ((studentID + CMU_CS + timestamp).equals("84895@andrew.cmu.edu_social-network_p32-task4_20191003042705") || +// (studentID + CMU_CS + timestamp).equals("69641@andrew.cmu.edu_social-network_p32-task4_20191013030137") || +// (studentID + CMU_CS + timestamp).equals("27319@andrew.cmu.edu_social-network_p32-task4_20191011031955")) { +// PrintWriter out = new PrintWriter(DATA_DIR + studentID + CMU_CS + timestamp + "/" + assignment + ".json"); +// out.println(jsonString); +// out.close(); +// } + + JSONObject parsedTree = new JSONObject(jsonString); + // JavaNode node = (JavaNode) TextualNode.fromJSON(obj, sourceCode, + // JavaNode::new); + JavaNode node = (JavaNode) JavaNode.fromJSON(parsedTree, originalSourceCode, JavaNode::new); + if (isCorrect.toLowerCase().equals("true")) {// || GS) { + node.correct = Optional.of(true); + } + if (!clusterID.equals("")) { + node.cluster = Optional.of(Integer.parseInt(clusterID)); + clustered = true; } + + node.setStudentID(studentID); + node.setSubmissionTime(timestamp); + + if (filePathToNodes.get(studentID) == null) { + filePathToNodes.put(studentID, new LinkedHashMap()); + } + /* + * if(filePathToNodes.get(studentID).get(timestamp) == null) { + * filePathToNodes.get(studentID).put(timestamp, node); } + */ + filePathToNodes.get(studentID).put(timestamp, node); + // filePathToNodes.get(studentID).get(timestamp).add(node); } + return filePathToNodes; } } diff --git a/iSnap/src/edu/isnap/python/SourceCodeHighlighter.java b/iSnap/src/edu/isnap/python/SourceCodeHighlighter.java index ed82797a..50e0815d 100644 --- a/iSnap/src/edu/isnap/python/SourceCodeHighlighter.java +++ b/iSnap/src/edu/isnap/python/SourceCodeHighlighter.java @@ -8,15 +8,20 @@ import edu.isnap.hint.HintConfig; import edu.isnap.hint.HintData; +import edu.isnap.hint.TextHint; import edu.isnap.hint.util.NullStream; import edu.isnap.node.ASTNode.SourceLocation; +import edu.isnap.node.Node.Annotations; import edu.isnap.node.Node; import edu.isnap.node.TextualNode; import edu.isnap.sourcecheck.HintHighlighter; +import edu.isnap.sourcecheck.NodeAlignment; +import edu.isnap.sourcecheck.NodeAlignment.DistanceMeasure; import edu.isnap.sourcecheck.NodeAlignment.Mapping; import edu.isnap.sourcecheck.edit.EditHint; import edu.isnap.sourcecheck.edit.Insertion; import edu.isnap.sourcecheck.edit.Suggestion; +import edu.isnap.sourcecheck.edit.Suggestion.SuggestionType; import edu.isnap.util.Diff; public class SourceCodeHighlighter { @@ -33,7 +38,17 @@ public class SourceCodeHighlighter { // public static String REORDER_START = ""; public static String SPAN_END = ""; - + + public static String DELETE_ANNOTATION = " System.out.println(((TextualNode) n).startSourceLocation)); - -// System.out.println("From:"); -// System.out.println(from); -// System.out.println("Target:"); -// System.out.println(target); - System.out.println("Node Diff:"); - System.out.println(Diff.diff(studentCode.prettyPrint(), target.prettyPrint(), 2)); -// System.out.println("Student source:"); -// System.out.println(studentCode.source); -// System.out.println("Target source:"); -// System.out.println(target.source); - System.out.println("Source Diff:"); - System.out.println(Diff.diff(studentCode.getSource(), target.getSource(), 2)); - mapping.printValueMappings(System.out); - edits.forEach(e -> System.out.printf("%.02f: %s\n", e.priority.consensus(), e)); - System.out.println(); - String marked = studentCode.getSource(); List suggestions = getSuggestions(edits); List missing = new ArrayList<>(); + + Mapping referenceMapping = null; + if (hintData.config.useAnnotation) { + int clusterID = 0; + boolean v2 = hintData.config.sourceCheckV2; + DistanceMeasure distanceMeasure = highlighter.getDistanceMeasure(); + + if (target.cluster.isPresent()) { + clusterID = target.cluster.get(); + } + TextualNode reference = (TextualNode) hintData.getReferenceSolutions().get(clusterID); + NodeAlignment align = new NodeAlignment(target, reference, hintData.config); + referenceMapping = v2 ? align.align(distanceMeasure) : align.calculateMapping(distanceMeasure); + } for (Suggestion suggestion : suggestions) { SourceLocation location = suggestion.location; - EditHint hint = suggestion.hint; + EditHint hint = suggestion.hint; if (!suggestion.start) { marked = location.markSource(marked, SPAN_END); continue; } - switch (suggestion.type) { - case DELETE: - marked = location.markSource(marked, DELETE_START); - break; - case MOVE: - marked = location.markSource(marked, CANDIDATE_START); - break; - case REPLACE: - marked = location.markSource(marked, REPLACE_START); - break; - case INSERT: - String insertionCode = getInsertHTML(mapping, hint); + if (referenceMapping != null) { + TextualNode ref = null; + switch (suggestion.type) { + case DELETE: // If reorder or deletion, hint.node to get "from" in studentCode and hint.parent to get its parent. hint.parent is from EditHint + TextualNode parent = (TextualNode) hint.parent; + ref = (TextualNode) referenceMapping.getFromMap().get(parent); // get corresponding parent node in reference + break; + case MOVE: // If insert, hint.candidate to get "from" in studentCode and hint.pair to get "to" + case REPLACE: // If insert, hint.candidate to get "from" in studentCode and hint.pair to get "to" + case INSERT: + Insertion ins = (Insertion) hint; // hint.candidate to get "from" in studentCode. If null, it's insertion. hint.pair to get "to" + TextualNode inserted = (TextualNode) ins.pair; // get node to be inserted + ref = (TextualNode) referenceMapping.getFromMap().get(inserted); // get corresponding node in reference + missing.add(getHumanReadableName((Insertion) hint, mapping.config)); + } + String insertionCode; + if (ref != null) { + Annotations annotations = ref.readOnlyAnnotations(); + if (!annotations.equals(Annotations.EMPTY)) { + List hints = annotations.getHints(); + insertionCode = getHTML(hints.get(0).text, suggestion.type); + }else { + insertionCode = getHTML(mapping, hint, suggestion.type); + } + }else { + insertionCode = getHTML(mapping, hint, suggestion.type); + } marked = location.markSource(marked, insertionCode); - missing.add(getHumanReadableName((Insertion) hint, mapping.config)); + }else { + switch (suggestion.type) { + case DELETE: + marked = location.markSource(marked, DELETE_START); + break; + case MOVE: + marked = location.markSource(marked, CANDIDATE_START); + break; + case REPLACE: + marked = location.markSource(marked, REPLACE_START); + break; + case INSERT: + String insertionCode = getInsertHTML(mapping, hint); + marked = location.markSource(marked, insertionCode); + missing.add(getHumanReadableName((Insertion) hint, mapping.config)); + } } + System.out.println(suggestion.type + ": " + suggestion.location); // System.out.println(marked); } @@ -101,25 +143,58 @@ public static String highlightSourceCode(HintData hintData, TextualNode studentC marked += ""; } - System.out.println(marked); + //System.out.println(marked); return marked; } - private static List getSuggestions(List edits) { + public static List getSuggestions(List edits) { List suggestions = new ArrayList<>(); for (EditHint hint : edits) { hint.addSuggestions(suggestions); } - Collections.sort(suggestions, Collections.reverseOrder()); + Collections.sort(suggestions); return suggestions; } private static String getInsertHTML(Mapping mapping, EditHint editHint) { String hint = getInsertHint((Insertion)editHint, mapping.config); + return getInsertHTML(hint); + } + + private static String getInsertHTML(String hint) { String insertionCode = String.format("%s data-tooltip=\"%s\">%s%s", INSERT_START, hint, "\u2795", SPAN_END); return insertionCode; } + + private static String getHTML(Mapping mapping, EditHint editHint, SuggestionType type) { + switch (type) { + case DELETE: // If reorder or deletion, hint.node to get "from" in studentCode and hint.parent to get its parent. hint.parent is from EditHint + return DELETE_START; + case MOVE: // If insert, hint.candidate to get "from" in studentCode and hint.pair to get "to" + return CANDIDATE_START; + case REPLACE: // If insert, hint.candidate to get "from" in studentCode and hint.pair to get "to" + return REPLACE_START; + case INSERT: + String hint = getInsertHint((Insertion)editHint, mapping.config); + return String.format("%s data-tooltip=\"%s\">%s%s", INSERT_START, hint, "\u2795", SPAN_END); + } + return null; + } + + private static String getHTML(String hint, SuggestionType type) { + switch (type) { + case DELETE: // If reorder or deletion, hint.node to get "from" in studentCode and hint.parent to get its parent. hint.parent is from EditHint + return String.format("%s data-tooltip=\"%s\">%s", REPLACE_ANNOTATION, hint, "\u2795"); + case MOVE: // If insert, hint.candidate to get "from" in studentCode and hint.pair to get "to" + return String.format("%s data-tooltip=\"%s\">%s", CANDIDATE_ANNOTATION, hint, "\u2795"); + case REPLACE: // If insert, hint.candidate to get "from" in studentCode and hint.pair to get "to" + return String.format("%s data-tooltip=\"%s\">%s", REPLACE_ANNOTATION, hint, "\u2795"); + case INSERT: + return String.format("%s data-tooltip=\"%s\">%s%s", INSERT_START, hint, "\u2795", SPAN_END); + } + return null; + } private static String getInsertHint(Insertion insertion, HintConfig config) { String hrName = getHumanReadableName(insertion, config); From 5186fcf19915e51ac7ea8f4bc689a406d9b6641c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E9=87=8E=E8=A3=95=E5=93=89?= Date: Wed, 1 Jul 2020 10:44:35 -0400 Subject: [PATCH 05/13] configured server --- .classpath | 32 +++++++++++++++++++++++++++ .project | 17 ++++++++++++++ CTD/.classpath | 3 ++- HighLevelHints/.classpath | 1 + HighLevelHints/.project | 13 +++++++++++ HintEvaluation/.classpath | 7 ++++++ HintServer/.classpath | 8 ++++--- HintServer/.project | 1 + HintServer/WebContent/WEB-INF/web.xml | 8 +++++++ SnapParser/.classpath | 1 + iSnap/.classpath | 1 + 11 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 .classpath create mode 100644 .project diff --git a/.classpath b/.classpath new file mode 100644 index 00000000..9fe03ad8 --- /dev/null +++ b/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 00000000..a73ac803 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + Clustering + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/CTD/.classpath b/CTD/.classpath index 50ec62a5..d5cd6b1c 100644 --- a/CTD/.classpath +++ b/CTD/.classpath @@ -6,17 +6,18 @@
+ - + diff --git a/HighLevelHints/.classpath b/HighLevelHints/.classpath index 07bdc197..064a38d2 100644 --- a/HighLevelHints/.classpath +++ b/HighLevelHints/.classpath @@ -14,5 +14,6 @@ + diff --git a/HighLevelHints/.project b/HighLevelHints/.project index 3313da1d..988f4ee4 100644 --- a/HighLevelHints/.project +++ b/HighLevelHints/.project @@ -5,13 +5,26 @@ + + org.eclipse.wst.common.project.facet.core.builder + + + org.eclipse.jdt.core.javabuilder + + org.eclipse.wst.validation.validationbuilder + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature org.eclipse.jdt.core.javanature + org.eclipse.wst.common.project.facet.core.nature diff --git a/HintEvaluation/.classpath b/HintEvaluation/.classpath index c95ea44e..e2815455 100644 --- a/HintEvaluation/.classpath +++ b/HintEvaluation/.classpath @@ -25,5 +25,12 @@ + + + + + + + diff --git a/HintServer/.classpath b/HintServer/.classpath index 5f181e88..c653da6f 100644 --- a/HintServer/.classpath +++ b/HintServer/.classpath @@ -6,9 +6,6 @@ - - - @@ -22,6 +19,11 @@ + + + + + diff --git a/HintServer/.project b/HintServer/.project index 6347ce75..14cdfdd7 100644 --- a/HintServer/.project +++ b/HintServer/.project @@ -8,6 +8,7 @@ QualityScore CTD JavaParser + HighLevelHints diff --git a/HintServer/WebContent/WEB-INF/web.xml b/HintServer/WebContent/WEB-INF/web.xml index 33fddd5a..28261090 100644 --- a/HintServer/WebContent/WEB-INF/web.xml +++ b/HintServer/WebContent/WEB-INF/web.xml @@ -3,5 +3,13 @@ HintServer hints + java.html + java.htm + java.jsp + + +HighLelelHintServlet +HintServlet2 + \ No newline at end of file diff --git a/SnapParser/.classpath b/SnapParser/.classpath index f6be6232..0b8e865d 100644 --- a/SnapParser/.classpath +++ b/SnapParser/.classpath @@ -17,5 +17,6 @@ + diff --git a/iSnap/.classpath b/iSnap/.classpath index b97f49f4..72d03158 100644 --- a/iSnap/.classpath +++ b/iSnap/.classpath @@ -18,5 +18,6 @@ + From 0f1bc1bf33962d1721d984a0380222511fce193a Mon Sep 17 00:00:00 2001 From: Siddharth Kandimalla Date: Thu, 23 Jul 2020 15:28:52 -0400 Subject: [PATCH 06/13] Initial commit --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..4106c9a9 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# SourceCheck +SourceCheck Modified Version From 9bff2f6dcee0c64fef6cf9a633d65ce9b185ae05 Mon Sep 17 00:00:00 2001 From: Yuya Asano Date: Wed, 29 Jul 2020 21:46:27 -0400 Subject: [PATCH 07/13] Delete README.md To resolve conflict with the original README --- README.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 4106c9a9..00000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# SourceCheck -SourceCheck Modified Version From a75f02c701d2ca139713657e3ab3b309b01215e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E9=87=8E=E8=A3=95=E5=93=89?= Date: Sat, 10 Oct 2020 14:30:16 -0400 Subject: [PATCH 08/13] cluster solutions --- .../src/alignment/SolutionAlignment.java | 88 ++++++++++------- .../alignment/SolutionDistanceMeasure.java | 10 +- .../src/edu/isnap/eval/java/GetClusters.java | 99 +++++++++++++++++++ .../src/edu/isnap/eval/java/JavaImport.java | 99 +++---------------- 4 files changed, 170 insertions(+), 126 deletions(-) create mode 100644 HintEvaluation/src/edu/isnap/eval/java/GetClusters.java diff --git a/HighLevelHints/src/alignment/SolutionAlignment.java b/HighLevelHints/src/alignment/SolutionAlignment.java index fffc5162..faa7a868 100644 --- a/HighLevelHints/src/alignment/SolutionAlignment.java +++ b/HighLevelHints/src/alignment/SolutionAlignment.java @@ -2,9 +2,11 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; +import java.util.Set; import edu.isnap.hint.HintConfig; import edu.isnap.hint.util.Alignment; @@ -13,9 +15,9 @@ public class SolutionAlignment extends Alignment { - public static double getFullOrderReward(int length, double baseReward, double factor) { + public static double getFullOrderReward(double d, double baseReward, double factor) { double reward = 0.0; - for (int i = 0; i < length; i++) { + for (int i = 0; i < d; i++) { reward += Math.pow(baseReward, Math.pow(factor, i)); } return reward; @@ -38,7 +40,7 @@ private static double getProgress(String[] from, String[] to, int orderReward, return getProgress(from, to, null, orderReward, unorderReward, new int[to.length], missing); } - private static double getProgress(String[] from, String[] to, int[] toOrderGroups, + static double getProgress(String[] from, String[] to, int[] toOrderGroups, int orderReward, int unorderReward, int[] toIndices, boolean missing) { // TODO: This can and should be much more efficient // toList[k] = "\0" if the k-th node in c(b_{rj}) is in c(a_{ri}) @@ -65,46 +67,48 @@ private static double getProgress(String[] from, String[] to, int[] toOrderGroup Arrays.fill(toIndices, -1); - Queue> oneToOneIndices = new LinkedList<>(); - oneToOneIndices.add(indices); - if (!missing) { - for (int i = 0; i < to.length; i++) { - List fromIndices = new ArrayList<>(); - List example = oneToOneIndices.peek(); - for (int j = 0; j < example.size(); j++) { - if (example.get(j) == i) { - fromIndices.add(j); - } - } - if (fromIndices.size() < 2) { - continue; // no duplicates - } - // Multiple elements in "from" point to one element in "to" - int queueSize = oneToOneIndices.size(); - for (int j = 0; j < queueSize; j++) { - ArrayList polled = (ArrayList) oneToOneIndices.poll(); - for (int index : fromIndices) { - List temp = new ArrayList<>(polled.size()); - for (int h = 0; h < polled.size(); h++) { - if (fromIndices.indexOf(polled.get(h)) != -1 && h != index) { - temp.add(h, -1); - }else { - temp.add(h, polled.get(h)); - } - } - oneToOneIndices.add(new ArrayList(temp)); - } - } - } - } +// Queue> oneToOneIndices = new LinkedList<>(); +// oneToOneIndices.add(indices); +// if (!missing) { +// for (int i = 0; i < to.length; i++) { +// List fromIndices = new ArrayList<>(); +// List example = oneToOneIndices.peek(); +// for (int j = 0; j < example.size(); j++) { +// if (example.get(j) == i) { +// fromIndices.add(j); +// } +// } +// if (fromIndices.size() < 2) { +// continue; // no duplicates +// } +// // Multiple elements in "from" point to one element in "to" +// int queueSize = oneToOneIndices.size(); +// for (int j = 0; j < queueSize; j++) { +// ArrayList polled = (ArrayList) oneToOneIndices.poll(); +// for (int index : fromIndices) { +// List temp = new ArrayList<>(polled.size()); +// for (int h = 0; h < polled.size(); h++) { +// if (fromIndices.indexOf(polled.get(h)) != -1 && h != index) { +// temp.add(h, -1); +// }else { +// temp.add(h, polled.get(h)); +// } +// } +// oneToOneIndices.add(new ArrayList(temp)); +// } +// } +// } +// } double maxReward = Double.NEGATIVE_INFINITY; - for (List filteredIndices : oneToOneIndices) { +// for (List filteredIndices : oneToOneIndices) { + + Set usedIndices = new HashSet<>(to.length); double reward = 0; int lastIndex = -1; int maxIndex = -1; - for (Integer index : filteredIndices) { + for (Integer index : indices) {//filteredIndices) { if (index < 0) continue; // don't change reward if k-th node in c(a_{ri}) is not in c(b_{rj}) int adjIndex = index; int group; @@ -139,12 +143,20 @@ private static double getProgress(String[] from, String[] to, int[] toOrderGroup if (to[adjIndex] != null) { reward += adjIndex > lastIndex ? orderReward : unorderReward; + if (!missing && adjIndex - lastIndex > 1 && !to[adjIndex].equals(from[indices.indexOf(index)])) { + boolean skipped = true;// && !usedIndices.isEmpty(); + for (int idx = lastIndex + 1; idx < adjIndex; idx++) { + skipped = skipped && usedIndices.contains(idx); + } + if (skipped) reward -= 1; + } } + usedIndices.add(adjIndex); lastIndex = adjIndex; maxIndex = Math.max(maxIndex, adjIndex); } if (reward > maxReward) maxReward = reward; - } +// } return maxReward; } diff --git a/HighLevelHints/src/alignment/SolutionDistanceMeasure.java b/HighLevelHints/src/alignment/SolutionDistanceMeasure.java index 4af9c01f..ade47cce 100644 --- a/HighLevelHints/src/alignment/SolutionDistanceMeasure.java +++ b/HighLevelHints/src/alignment/SolutionDistanceMeasure.java @@ -22,14 +22,14 @@ public double measure(Node from, String[] a, String[] b, int[] bOrderGroups) { // + SolutionAlignment.getFullOrderReward( // SolutionAlignment.getProgress(a, b, 1, 1), config.baseReward, config.factor) // - (SolutionAlignment.getNormalizedProgress( -// a, b, bOrderGroups, config.baseReward, config.outOfOrderReward, config.factor, new int[b.length]) +// a, b, bOrderGroups, config.baseReward, config.outOfOrderReward, config.factor, new int[b.length])); // + SolutionAlignment.getNormalizedProgress(b, a, null, config.baseReward, config.outOfOrderReward, config.factor, new int[a.length]) // ) / 2; // } return SolutionAlignment.getMissingNodeCount(a, b) + SolutionAlignment.getMissingNodeCount(b, a) + inOrderReward * SolutionAlignment.getProgress(a, b, 1, 1) - - (SolutionAlignment.getProgress(a, b, bOrderGroups, inOrderReward, outOfOrderReward, 0) - + SolutionAlignment.getProgress(b, a, null, inOrderReward, outOfOrderReward, 0)) / 2; + - Math.max(SolutionAlignment.getProgress(a, b, bOrderGroups, inOrderReward, outOfOrderReward, 0), + SolutionAlignment.getProgress(b, a, null, inOrderReward, outOfOrderReward, 0)); } @Override @@ -39,8 +39,8 @@ public double matchedOrphanReward(String type) { public static void main(String[] args) { HintConfig temp = new JavaSolutionConfig(); DistanceMeasure sol = new SolutionDistanceMeasure(temp); - String[] a = {"1", "2", "3", "4", "5", "3"}; - String[] b = {"1", "2", "4", "4", "3", "5"}; + String[] a = {"1", "2", "3", "4", "5"}; + String[] b = {"1", "2", "4", "3", "3", "5"}; System.out.println(sol.measure(null, a, b, new int[b.length])); System.out.println(sol.measure(null, b, a, new int[a.length])); } diff --git a/HintEvaluation/src/edu/isnap/eval/java/GetClusters.java b/HintEvaluation/src/edu/isnap/eval/java/GetClusters.java new file mode 100644 index 00000000..065c24c2 --- /dev/null +++ b/HintEvaluation/src/edu/isnap/eval/java/GetClusters.java @@ -0,0 +1,99 @@ +package edu.isnap.eval.java; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; + +import com.opencsv.CSVWriter; + +import clustering.SolutionClusterer; +import edu.isnap.node.JavaNode; +import edu.isnap.node.Node; +import edu.isnap.node.Node.Annotations; +import net.sf.javaml.core.Instance; + +public class GetClusters { + + public static void main(String[] args) throws IOException { + int task = 4; + String dataDir = "../data/F19_Project_3_2/task" + task + "/"; + String separator = "@andrew.cmu.edu_social-network_p32-task" + task + "_"; + String[] assignments = {"ProfileServlet", "FollowerServlet", "HomepageServlet", "TimelineServlet"}; + String assignment = assignments[task - 1]; + String outFile = dataDir + "cluster_info_v2.csv"; + + HashMap> attempts = JavaImport.loadAssignment( + dataDir + "input.csv", true, assignment, dataDir, separator); + + List correct = new ArrayList<>(); + LinkedHashMap annotated = new LinkedHashMap<>(); + for (String studentID : attempts.keySet()) { + List timestamps = new ArrayList<>(attempts.get(studentID).keySet()); + Collections.sort(timestamps); + JavaNode node = attempts.get(studentID).get(timestamps.get(timestamps.size() - 1)); + if (!node.readOnlyAnnotations().equals(Annotations.EMPTY)) { + annotated.put(node.cluster.get(), node); + } + + // If it was correct, then add it to the subset + if (node.correct.orElse(false)) { + correct.add(node); + } + } + + SolutionClusterer clusterer = new SolutionClusterer(correct, assignment); + HashMap> solutionMaps = clusterer.clusterSolutions(); + List students = new ArrayList(solutionMaps.keySet()); + Collections.sort(students); + + // Open a csv file + File clusterCSV = new File(outFile); + FileWriter outputfile = new FileWriter(clusterCSV); + + // create CSVWriter object filewriter object as parameter + CSVWriter writer = new CSVWriter(outputfile); + + // adding header to csv + List header = new ArrayList(); + header.add("StudentID"); + header.add("Timestamp"); + header.add("ClusterID"); + for (int i = 1; i <= clusterer.getNumSolutions(); i++) { + header.add("Dist_" + i); + } + String[] tempHeader = new String[header.size()]; + writer.writeNext(header.toArray(tempHeader)); + + // add data to csv + for (String student : students) { + HashMap timeMap = solutionMaps.get(student); + List timestamps = new ArrayList(timeMap.keySet()); + String timestamp = timestamps.get(0); + List data = new ArrayList(); + Instance inst = timeMap.get(timestamp); + String clusterID = (String) inst.classValue(); + data.add(student); // student id + data.add(timestamp); // timestamp + data.add(clusterID); // cluster id + + // Add distances + Iterator itr = inst.iterator(); + while (itr.hasNext()) { + data.add(itr.next().toString()); + } + String[] tempData = new String[data.size()]; + writer.writeNext(data.toArray(tempData)); + + } + + // closing writer connection + writer.close(); + } + +} diff --git a/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java b/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java index d998c721..3b378a78 100644 --- a/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java +++ b/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java @@ -48,17 +48,18 @@ public class JavaImport { - static String DATA_DIR = "../data/F19_Project_3_2/task4/"; - static String CMU_CS = "@andrew.cmu.edu_social-network_p32-task4_"; - static boolean clustered = false; - public static void main(String[] args) throws IOException { // Run generate hints to load data, generate hints for each student and print // them out // You need to update the file path to wherever you unzipped the data + int task = 4; + String dataDir = "../data/F19_Project_3_2/task" + task + "/"; + String separator = "@andrew.cmu.edu_social-network_p32-task" + task + "_"; + String[] assignments = {"ProfileServlet", "FollowerServlet", "HomepageServlet", "TimelineServlet"}; + String assignment = assignments[task - 1]; - generateHintsForGS(DATA_DIR + "input.csv", "TimelineServlet"); + generateHintsForGS(dataDir + "input.csv", assignment, dataDir, separator); } /* @@ -130,58 +131,6 @@ static HintData createHintData(String assignment, LinkedHashMap correct, String assignment) throws IOException { - SolutionClusterer clusterer = new SolutionClusterer(correct, assignment); - HashMap> solutionMaps = clusterer.clusterSolutions(); - List students = new ArrayList(solutionMaps.keySet()); - Collections.sort(students); - - // Open a csv file - File clusterCSV = new File(DATA_DIR + "cluster_info.csv"); - FileWriter outputfile = new FileWriter(clusterCSV); - - // create CSVWriter object filewriter object as parameter - CSVWriter writer = new CSVWriter(outputfile); - - // adding header to csv - List header = new ArrayList(); - header.add("StudentID"); - header.add("Timestamp"); - header.add("ClusterID"); - for (int i = 1; i <= clusterer.getNumSolutions(); i++) { - header.add("Dist_" + i); - } - String[] tempHeader = new String[header.size()]; - writer.writeNext(header.toArray(tempHeader)); - - // add data to csv - for (String student : students) { - HashMap timeMap = solutionMaps.get(student); - List timestamps = new ArrayList(timeMap.keySet()); - String timestamp = timestamps.get(0); -// Collections.sort(timestamps); -// for (String timestamp : timestamps) { - List data = new ArrayList(); - Instance inst = timeMap.get(timestamp); - String clusterID = (String) inst.classValue(); - data.add(student); // student id - data.add(timestamp); // timestamp - data.add(clusterID); // cluster id - - // Add distances - Iterator itr = inst.iterator(); - while (itr.hasNext()) { - data.add(itr.next().toString()); - } - String[] tempData = new String[data.size()]; - writer.writeNext(data.toArray(tempData)); -// } - } - - // closing writer connection - writer.close(); - } /** * Exports html files highlighting hints @@ -192,14 +141,13 @@ static void clusterSolutions(List correct, String assignment) throws IOExc * ".java". * @throws IOException */ - static void generateHintsForGS(String inputCSV, String assignment) throws IOException { - HashMap> attempts = loadAssignment(inputCSV, true, assignment); + static void generateHintsForGS(String inputCSV, String assignment, String dataDir, String separator) throws IOException { + HashMap> attempts = loadAssignment(inputCSV, true, assignment, dataDir, separator); // Maps student id's to their history of submissions. Only students // who eventually got correct are considered LinkedHashMap> correctTraces = new LinkedHashMap<>(); // List of correct submissions - List correct = new ArrayList<>(); LinkedHashMap annotated = new LinkedHashMap<>(); for (String studentID : attempts.keySet()) { List trace = new ArrayList<>(); @@ -220,14 +168,9 @@ static void generateHintsForGS(String inputCSV, String assignment) throws IOExce // If it was correct, then add it to the subset if (trace.get(trace.size() - 1).correct.orElse(false)) { correctTraces.put(studentID, trace); - correct.add(trace.get(trace.size() - 1)); } } HintData hintData = createHintData(assignment, correctTraces); - if (!clustered) { - clusterSolutions(correct, assignment); - return; - } for (int clusterID : annotated.keySet()) { hintData.addReferenceSoltion(clusterID, annotated.get(clusterID)); @@ -245,7 +188,7 @@ static void generateHintsForGS(String inputCSV, String assignment) throws IOExce highlightedCode = highlightedCode.replace("\n", "
\n"); highlightedCode = "\n" + "\n" + highlightedCode; - PrintWriter out = new PrintWriter(DATA_DIR + student + CMU_CS + timestamp + "/output_hints.html"); + PrintWriter out = new PrintWriter(dataDir + student + separator + timestamp + "/output_hints.html"); out.println(highlightedCode); out.close(); @@ -315,11 +258,10 @@ private static String removeComments(String code) { * */ static HashMap> loadAssignment(String inputCSV, boolean GS, - String assignment) throws IOException { + String assignment, String dataDir, String separator) throws IOException { HashMap> filePathToNodes = new HashMap<>(); List csvRecords = readCSV(inputCSV); - clustered = false; for (String[] record : csvRecords) { String timestamp = record[0]; String studentID = record[1]; @@ -327,36 +269,32 @@ static HashMap> loadAssignment(String in String isAnnotated = record[13]; String clusterID = record[14]; - // JSONObject obj = new JSONObject(json); - File originalCode = new File(DATA_DIR + studentID + CMU_CS + timestamp + "/" + assignment + ".java"); + File originalCode = new File(dataDir + studentID + separator + timestamp + "/" + assignment + ".java"); String originalSourceCode = new String(Files.readAllBytes(originalCode.toPath())); String jsonString; if (isAnnotated.toLowerCase().equals("true")) { - File annotatedCode = new File(DATA_DIR + studentID + CMU_CS + timestamp + "/" + assignment + ".json"); + File annotatedCode = new File(dataDir + studentID + separator + timestamp + "/" + assignment + ".json"); jsonString = new String(Files.readAllBytes(annotatedCode.toPath())); }else { jsonString = ASTParserJSON.toJSON(originalSourceCode); } // TODO has to be commented out -// if ((studentID + CMU_CS + timestamp).equals("84895@andrew.cmu.edu_social-network_p32-task4_20191003042705") || -// (studentID + CMU_CS + timestamp).equals("69641@andrew.cmu.edu_social-network_p32-task4_20191013030137") || -// (studentID + CMU_CS + timestamp).equals("27319@andrew.cmu.edu_social-network_p32-task4_20191011031955")) { -// PrintWriter out = new PrintWriter(DATA_DIR + studentID + CMU_CS + timestamp + "/" + assignment + ".json"); +// if ((studentID + separator + timestamp).equals("84895@andrew.cmu.edu_social-network_p32-task4_20191003042705") || +// (studentID + separator + timestamp).equals("69641@andrew.cmu.edu_social-network_p32-task4_20191013030137") || +// (studentID + separator + timestamp).equals("27319@andrew.cmu.edu_social-network_p32-task4_20191011031955")) { +// PrintWriter out = new PrintWriter(dataDir + studentID + separator + timestamp + "/" + assignment + ".json"); // out.println(jsonString); // out.close(); // } JSONObject parsedTree = new JSONObject(jsonString); - // JavaNode node = (JavaNode) TextualNode.fromJSON(obj, sourceCode, - // JavaNode::new); JavaNode node = (JavaNode) JavaNode.fromJSON(parsedTree, originalSourceCode, JavaNode::new); if (isCorrect.toLowerCase().equals("true")) {// || GS) { node.correct = Optional.of(true); } if (!clusterID.equals("")) { node.cluster = Optional.of(Integer.parseInt(clusterID)); - clustered = true; } node.setStudentID(studentID); @@ -365,12 +303,7 @@ static HashMap> loadAssignment(String in if (filePathToNodes.get(studentID) == null) { filePathToNodes.put(studentID, new LinkedHashMap()); } - /* - * if(filePathToNodes.get(studentID).get(timestamp) == null) { - * filePathToNodes.get(studentID).put(timestamp, node); } - */ filePathToNodes.get(studentID).put(timestamp, node); - // filePathToNodes.get(studentID).get(timestamp).add(node); } return filePathToNodes; From 88d9865f4784adca79d99138301412d210333137 Mon Sep 17 00:00:00 2001 From: Yuya Asano Date: Sat, 17 Oct 2020 17:59:31 -0400 Subject: [PATCH 09/13] Cleaned and added comments --- .../src/edu/isnap/eval/java/GetClusters.java | 18 ++++++++----- .../src/edu/isnap/eval/java/JavaImport.java | 26 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/HintEvaluation/src/edu/isnap/eval/java/GetClusters.java b/HintEvaluation/src/edu/isnap/eval/java/GetClusters.java index 065c24c2..eda24716 100644 --- a/HintEvaluation/src/edu/isnap/eval/java/GetClusters.java +++ b/HintEvaluation/src/edu/isnap/eval/java/GetClusters.java @@ -21,15 +21,19 @@ public class GetClusters { public static void main(String[] args) throws IOException { - int task = 4; - String dataDir = "../data/F19_Project_3_2/task" + task + "/"; - String separator = "@andrew.cmu.edu_social-network_p32-task" + task + "_"; - String[] assignments = {"ProfileServlet", "FollowerServlet", "HomepageServlet", "TimelineServlet"}; - String assignment = assignments[task - 1]; - String outFile = dataDir + "cluster_info_v2.csv"; + int task = 1; + // String dataDir = "../data/F19_Project_3_2/task" + task + "/"; + String dataDir = "../data/S20_3.3_OPE_Grading_Anon/3.3_OPE_Submissions-anonymized/"; // The path to the folder containing different students' source files + // String separator = "@andrew.cmu.edu_social-network_p32-task" + task + "_"; + String separator = "@andrew.cmu.edu_data-consistency-ope_consistency-ope-task_"; // You may not need this. This is useful when the names of folders for different students share separator string. + // String[] assignments = {"ProfileServlet", "FollowerServlet", "HomepageServlet", "TimelineServlet"}; + String[] assignments = {"BankUserConcurrentGet", "BankUserConcurrentPut", "BankUserMultiThreaded", "BankUserStrongConsistency"}; + String assignment = assignments[task - 1]; // The name of the source file + String sourcePath = "/src/main/java/Project_OMP/BankUserSystem/"; // The path to the source file folder for each student + String outFile = dataDir + "cluster_info_task" + task + ".csv"; HashMap> attempts = JavaImport.loadAssignment( - dataDir + "input.csv", true, assignment, dataDir, separator); + dataDir + "input_task" + task + ".csv", true, assignment, dataDir, separator, sourcePath); List correct = new ArrayList<>(); LinkedHashMap annotated = new LinkedHashMap<>(); diff --git a/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java b/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java index 3b378a78..f9c771ab 100644 --- a/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java +++ b/HintEvaluation/src/edu/isnap/eval/java/JavaImport.java @@ -54,12 +54,16 @@ public static void main(String[] args) throws IOException { // them out // You need to update the file path to wherever you unzipped the data int task = 4; - String dataDir = "../data/F19_Project_3_2/task" + task + "/"; - String separator = "@andrew.cmu.edu_social-network_p32-task" + task + "_"; - String[] assignments = {"ProfileServlet", "FollowerServlet", "HomepageServlet", "TimelineServlet"}; + // String dataDir = "../data/F19_Project_3_2/task" + task + "/"; + String dataDir = "../data/S20_3.3_OPE_Grading_Anon/3.3_OPE_Submissions-anonymized/"; + // String separator = "@andrew.cmu.edu_social-network_p32-task" + task + "_"; + String separator = "@andrew.cmu.edu_data-consistency-ope_consistency-ope-task_"; + // String[] assignments = {"ProfileServlet", "FollowerServlet", "HomepageServlet", "TimelineServlet"}; + String[] assignments = {"BankUserConcurrentGet", "BankUserConcurrentPut", "BankUserMultiThreaded", "BankUserStrongConsistency"}; String assignment = assignments[task - 1]; - - generateHintsForGS(dataDir + "input.csv", assignment, dataDir, separator); + String sourcePath = "/src/main/java/Project_OMP/BankUserSystem/"; // The path to the source file folder for each student + + generateHintsForGS(dataDir + "input_task" + task + ".csv", assignment, dataDir, separator, sourcePath); } /* @@ -141,8 +145,8 @@ static HintData createHintData(String assignment, LinkedHashMap> attempts = loadAssignment(inputCSV, true, assignment, dataDir, separator); + static void generateHintsForGS(String inputCSV, String assignment, String dataDir, String separator, String sourcePath) throws IOException { + HashMap> attempts = loadAssignment(inputCSV, true, assignment, dataDir, separator, sourcePath); // Maps student id's to their history of submissions. Only students // who eventually got correct are considered @@ -258,7 +262,7 @@ private static String removeComments(String code) { * */ static HashMap> loadAssignment(String inputCSV, boolean GS, - String assignment, String dataDir, String separator) throws IOException { + String assignment, String dataDir, String separator, String sourcePath) throws IOException { HashMap> filePathToNodes = new HashMap<>(); List csvRecords = readCSV(inputCSV); @@ -268,8 +272,12 @@ static HashMap> loadAssignment(String in String isCorrect = record[10]; String isAnnotated = record[13]; String clusterID = record[14]; + + /* + * if (isCorrect.toLowerCase().equals("false")) { continue; } + */ - File originalCode = new File(dataDir + studentID + separator + timestamp + "/" + assignment + ".java"); + File originalCode = new File(dataDir + studentID + separator + timestamp + sourcePath + assignment + ".java"); String originalSourceCode = new String(Files.readAllBytes(originalCode.toPath())); String jsonString; if (isAnnotated.toLowerCase().equals("true")) { From 86675bcaa71eb370d6319c42f1f23e4a8cafc6c6 Mon Sep 17 00:00:00 2001 From: Aditi Tripathi Date: Mon, 22 Mar 2021 20:11:30 -0400 Subject: [PATCH 10/13] Add files via upload To get the closest cluster's id for new code submissions. --- .../src/edu/isnap/eval/java/FindCluster.java | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 HintEvaluation/src/edu/isnap/eval/java/FindCluster.java diff --git a/HintEvaluation/src/edu/isnap/eval/java/FindCluster.java b/HintEvaluation/src/edu/isnap/eval/java/FindCluster.java new file mode 100644 index 00000000..c2b24197 --- /dev/null +++ b/HintEvaluation/src/edu/isnap/eval/java/FindCluster.java @@ -0,0 +1,168 @@ +package edu.isnap.eval.java; + +import org.json.JSONObject; + +import edu.isnap.node.JavaNode; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Optional; + +import com.github.javaparser.javaparser_symbol_solver_core.ASTParserJSON; + +import edu.isnap.node.Node; +import edu.isnap.hint.HintData; +import edu.isnap.hint.util.NullStream; + +import com.opencsv.CSVReader; +import com.opencsv.CSVReaderBuilder; +import java.io.Reader; +import edu.isnap.node.TextualNode; +import edu.isnap.sourcecheck.HintHighlighter; +import edu.isnap.sourcecheck.NodeAlignment.Mapping; + + +public class FindCluster { + public static int task = 1; + private static HashMap clusterMap = new HashMap<>(); + public static HintData hintData; + public static String dataDir = "./3.3_OPE_Submissions-anonymized/"; + public static String separator = "@andrew.cmu.edu_data-consistency-ope_consistency-ope-task_"; + public static String[] assignments = {"BankUserConcurrentGet", "BankUserConcurrentPut", "BankUserMultiThreaded", "BankUserStrongConsistency"}; + public static String assignment = assignments[task - 1]; + public static String sourcePath = "/src/main/java/Project_OMP/BankUserSystem/"; // The path to the source file folder for each student + + /** + * Export solution nodes with pre-computed clusterIDs as List + * @param inputCSV + * @return + */ + public static void getSolutionNodes(String inputCSV) throws IOException { + List csvRecords = readCSV(inputCSV); + LinkedHashMap> correctTraces = new LinkedHashMap<>(); + + for (String[] record : csvRecords) { + String timestamp = record[0]; + String studentID = record[1]; + String isCorrect = record[10]; +// String isAnnotated = record[13]; + String clusterID = record[14]; + + if (isCorrect.toLowerCase().equals("true")) { + if (!clusterID.equals("")) { + File originalCode = new File(dataDir + studentID + separator + timestamp + sourcePath + assignment + ".java"); + String originalSourceCode = new String(Files.readAllBytes(originalCode.toPath())); + String jsonString = ASTParserJSON.toJSON(originalSourceCode); + JSONObject parsedTree = new JSONObject(jsonString); + + JavaNode node = (JavaNode)JavaNode.fromJSON(parsedTree, studentID, originalSourceCode, JavaNode::new); +// node.correct = Optional.of(true); +// node.cluster = Optional.of((int)Float.parseFloat(clusterID)); + + if (!correctTraces.containsKey(studentID)) { + clusterMap.put(studentID, (int)Float.parseFloat(clusterID)); + correctTraces.put(studentID , new ArrayList()); + } + correctTraces.get(studentID).add(node); + } + } + } + hintData = JavaImport.createHintData(assignment, correctTraces); + + assert correctTraces.size() != 0 : "No correct Solution Nodes exist"; + + } + + + + /** + * readCSV + * @param fileName + * @return + */ + private static List readCSV(String fileName) { + List csvRecords = new ArrayList<>(); + try (Reader reader = Files.newBufferedReader(Paths.get(fileName)); + CSVReader csvReader = new CSVReaderBuilder(reader).withSkipLines(1).build()) { + String[] nextLine; + while ((nextLine = csvReader.readNext()) != null) { + String projectID = nextLine[1]; + String sourceFileID = nextLine[2]; + String compileTime = nextLine[3]; + String filePath = nextLine[4]; + String sourceCode = nextLine[5]; + String diff = nextLine[6]; + String isLastDiff = nextLine[7]; + String sessionID = nextLine[8]; + String compileID = nextLine[9]; + String originalFileID = nextLine[10]; + String isCorrect = nextLine[11]; + String doesCompile = nextLine[12]; + String sourceCodeJSON = nextLine[13]; + String isAnnotated = nextLine[14]; + String clusterID = nextLine[15]; + String[] record = { projectID, sourceFileID, compileTime, filePath, sourceCode, diff, isLastDiff, + sessionID, compileID, originalFileID, isCorrect, doesCompile, sourceCodeJSON, isAnnotated, clusterID }; + csvRecords.add(record); + } + } catch (IOException e) { + e.printStackTrace(); + } + return csvRecords; + } + + + + /** + * Get the JavaNode for the student code + * @param studentCodePath + * @return + */ + public static JavaNode preprocess(String studentCodePath) throws IOException { + File originalCode = new File(studentCodePath); + String originalSourceCode = new String(Files.readAllBytes(originalCode.toPath())); + String jsonString = ASTParserJSON.toJSON(originalSourceCode); + JSONObject parsedTree = new JSONObject(jsonString); + + JavaNode node = (JavaNode) JavaNode.fromJSON(parsedTree, originalSourceCode, JavaNode::new); + + return node; + + } + + + /** + * Return closest cluster id the student solution belongs to + * @param studentCodePath + * @return + * @throws IOException + */ + public static int getClusterID(String studentCodePath, int studentID) throws IOException { + TextualNode studentCode = (TextualNode) preprocess(studentCodePath); + + HintHighlighter highlighter = hintData.hintHighlighter(); + highlighter.trace = NullStream.instance; + Mapping mapping = highlighter.findSolutionMapping(studentCode); + Node target = mapping.to; + System.out.println("Student " + studentID + " :cluster similar to studentID: " + target.id); + + return clusterMap.get(target.id); + } + +// /** +// * Uncomment main to get all classpaths +// * @param args +// */ +// public static void main (String[] args) { +// String classpathStr = System.getProperty("java.class.path"); +// System.out.println("path: " + classpathStr); +// +// } + + +} \ No newline at end of file From 7cb2295284e7d57d8c5f586e0a73328499687022 Mon Sep 17 00:00:00 2001 From: Aditi Tripathi Date: Mon, 22 Mar 2021 20:16:45 -0400 Subject: [PATCH 11/13] Updated TetxualNode Added Overloaded method `fromJson` to receive String node.id as a parameter. --- CTD/src/edu/isnap/node/TextualNode.java | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/CTD/src/edu/isnap/node/TextualNode.java b/CTD/src/edu/isnap/node/TextualNode.java index aa20c468..495a7e3d 100644 --- a/CTD/src/edu/isnap/node/TextualNode.java +++ b/CTD/src/edu/isnap/node/TextualNode.java @@ -1,9 +1,9 @@ package edu.isnap.node; -import java.util.Optional; -import org.json.JSONObject; +import java.util.Optional; +import org.json.JSONObject; import edu.isnap.node.ASTNode.SourceLocation; import edu.isnap.sourcecheck.NodeAlignment.Mapping; @@ -105,12 +105,27 @@ public SourceLocation getLocationOfChildIndex(int index) { return null; } - public static TextualNode fromJSON(JSONObject jsonAST, String source, - NodeConstructor constructor) { + public static TextualNode fromJSON(JSONObject jsonAST, String source, NodeConstructor constructor) { ASTSnapshot astNode = ASTSnapshot.parse(jsonAST, source); TextualNode node = (TextualNode) fromASTNode(astNode, constructor); node.source = source; node.correct = Optional.of(astNode.isCorrect); return node; } + + /** + * Overloaded method to get clusterID + * @param jsonAST + * @param id + * @param source + * @param constructor + * @return + */ + public static TextualNode fromJSON(JSONObject jsonAST,String id, String source, NodeConstructor constructor) { + ASTSnapshot astNode = ASTSnapshot.parse(jsonAST, id, source); + TextualNode node = (TextualNode) fromASTNode(astNode, constructor); + node.source = source; + node.correct = Optional.of(astNode.isCorrect); + return node; + } } \ No newline at end of file From bb74ba50dd62fe8038405ae495cf193fbc08d2de Mon Sep 17 00:00:00 2001 From: Aditi Tripathi Date: Fri, 21 May 2021 10:54:31 -0400 Subject: [PATCH 12/13] Added high level hint functionality Added code for both high-level hints and low-level hints. --- .../src/edu/isnap/eval/java/FindCluster.java | 231 ++++++++++++++++-- 1 file changed, 207 insertions(+), 24 deletions(-) diff --git a/HintEvaluation/src/edu/isnap/eval/java/FindCluster.java b/HintEvaluation/src/edu/isnap/eval/java/FindCluster.java index c2b24197..a1d86528 100644 --- a/HintEvaluation/src/edu/isnap/eval/java/FindCluster.java +++ b/HintEvaluation/src/edu/isnap/eval/java/FindCluster.java @@ -1,5 +1,6 @@ package edu.isnap.eval.java; +import org.apache.commons.lang.StringEscapeUtils; import org.json.JSONObject; import edu.isnap.node.JavaNode; @@ -8,14 +9,19 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; -import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; import com.github.javaparser.javaparser_symbol_solver_core.ASTParserJSON; import edu.isnap.node.Node; +import edu.isnap.hint.HintConfig; import edu.isnap.hint.HintData; import edu.isnap.hint.util.NullStream; @@ -23,20 +29,29 @@ import com.opencsv.CSVReaderBuilder; import java.io.Reader; import edu.isnap.node.TextualNode; +import edu.isnap.node.ASTNode.SourceLocation; import edu.isnap.sourcecheck.HintHighlighter; import edu.isnap.sourcecheck.NodeAlignment.Mapping; +import edu.isnap.sourcecheck.edit.EditHint; +import edu.isnap.sourcecheck.edit.Insertion; +import edu.isnap.sourcecheck.edit.Suggestion; - +//import edu.isnap.hint.util.NullStream; public class FindCluster { - public static int task = 1; + public static int task; + public static int clusterid; private static HashMap clusterMap = new HashMap<>(); public static HintData hintData; - public static String dataDir = "./3.3_OPE_Submissions-anonymized/"; + public static TreeMap hintTree; +// public static String dataDir = "../data/S20_3.3_OPE_Grading_Anon/3.3_OPE_Submissions-anonymized/"; //for local run + public static String dataDir = "./data/S20_3.3_OPE_Grading_Anon/3.3_OPE_Submissions-anonymized/"; public static String separator = "@andrew.cmu.edu_data-consistency-ope_consistency-ope-task_"; public static String[] assignments = {"BankUserConcurrentGet", "BankUserConcurrentPut", "BankUserMultiThreaded", "BankUserStrongConsistency"}; - public static String assignment = assignments[task - 1]; - public static String sourcePath = "/src/main/java/Project_OMP/BankUserSystem/"; // The path to the source file folder for each student + public static String sourcePath = "/src/main/java/Project_OMP/BankUserSystem/"; // The path to the source file folder for each correct student submission + public static String DELETE_START = "This code may be incorrect "; + public static String REPLACE_START = "This code may need to be replaced with something else :"; + public static String CANDIDATE_START = "This code is good, but it may be in the wrong place : "; /** * Export solution nodes with pre-computed clusterIDs as List * @param inputCSV @@ -45,7 +60,7 @@ public class FindCluster { public static void getSolutionNodes(String inputCSV) throws IOException { List csvRecords = readCSV(inputCSV); LinkedHashMap> correctTraces = new LinkedHashMap<>(); - + String assignment = assignments[task - 1]; for (String[] record : csvRecords) { String timestamp = record[0]; String studentID = record[1]; @@ -61,8 +76,6 @@ public static void getSolutionNodes(String inputCSV) throws IOException { JSONObject parsedTree = new JSONObject(jsonString); JavaNode node = (JavaNode)JavaNode.fromJSON(parsedTree, studentID, originalSourceCode, JavaNode::new); -// node.correct = Optional.of(true); -// node.cluster = Optional.of((int)Float.parseFloat(clusterID)); if (!correctTraces.containsKey(studentID)) { clusterMap.put(studentID, (int)Float.parseFloat(clusterID)); @@ -119,7 +132,7 @@ private static List readCSV(String fileName) { /** - * Get the JavaNode for the student code + * Get the JavaNode for the new student code * @param studentCodePath * @return */ @@ -137,32 +150,202 @@ public static JavaNode preprocess(String studentCodePath) throws IOException { /** - * Return closest cluster id the student solution belongs to + * Returns Hints * @param studentCodePath + * @param taskID + * @param level + * @param numHints * @return * @throws IOException + * + * Reference class files: + * path: /CTD/src/edu/isnap/sourcecheck/edit/EditHint.java + * */ - public static int getClusterID(String studentCodePath, int studentID) throws IOException { + public static int getClusterID(String studentCodePath, int taskID, int level, int numHints) throws IOException { + + String pth = dataDir + "input" + taskID + ".csv" ; + getSolutionNodes(pth); TextualNode studentCode = (TextualNode) preprocess(studentCodePath); HintHighlighter highlighter = hintData.hintHighlighter(); highlighter.trace = NullStream.instance; Mapping mapping = highlighter.findSolutionMapping(studentCode); - Node target = mapping.to; - System.out.println("Student " + studentID + " :cluster similar to studentID: " + target.id); + List edits = highlighter.highlightWithPriorities(studentCode); +// String marked = studentCode.getSource(); +// List missing = new ArrayList<>(); + List suggestions = new ArrayList<>(); + for (EditHint hint : edits) { + hint.addSuggestions(suggestions); + } + Collections.sort(suggestions, (s1, s2) -> s1.location.line - s2.location.line); + //suggestion has locations (line, col), hint and type + Set suggestionSet = new HashSet<>(); + for (Suggestion suggestion : suggestions) { + if (numHints < 0) break; + if (suggestion.start) { + SourceLocation location = suggestion.location; + EditHint ed = suggestion.hint; + if (!suggestionSet.contains(location.line)) { + suggestionSet.add(location.line); + + //low-level hints + if (level == 0) { + switch (suggestion.type) { + case DELETE: + System.out.println("line " + location.line + "," + location.col+ ":" + DELETE_START); +// marked = location.markSource(marked, DELETE_START); + break; + case MOVE: + System.out.println("line " + location.line + ":" + CANDIDATE_START); +// marked = location.markSource(marked, CANDIDATE_START); + break; + case REPLACE: + System.out.println("line " + location.line + ":" + REPLACE_START); +// marked = location.markSource(marked, REPLACE_START); + break; + case INSERT: + + String insertionLine = getInsertHint((Insertion)ed, mapping.config); + String insertionCode = getTextToInsert((Insertion)ed, mapping); + System.out.println("line " + location.line + ":" + insertionLine + "\n" +insertionCode); + System.out.println(); +// marked = location.markSource(marked, insertionCode); +// missing.add(getHumanReadableName((Insertion) ed, mapping.config)); + } +// if (!missing.isEmpty()) { +// marked += "\n\nYou may be missing the following:"; +// for (String m : missing) marked += m + "\n"; +// } +// System.out.println("low level:\n\n " + marked); + } + //high-level hints + if (level == 1) { + Integer key = hintTree.floorKey(location.line); + if (key == null) key = hintTree.ceilingKey(location.line); + String highLvl = hintTree.get(key); + System.out.println("line " + location.line + ":" + highLvl); + } + numHints--; + } + } + } + Node target = mapping.to; return clusterMap.get(target.id); } -// /** -// * Uncomment main to get all classpaths -// * @param args -// */ -// public static void main (String[] args) { -// String classpathStr = System.getProperty("java.class.path"); -// System.out.println("path: " + classpathStr); -// -// } - + + /** + * + * @param insertion + * @param config + * @return + */ + private static String getInsertHint(Insertion insertion, HintConfig config) { + String hrName = getHumanReadableName(insertion, config); + String hint = "You may need to add " + hrName + " here"; + if (insertion.replaced != null) { + hint += ", instead of what you have."; + } else { + hint += "."; + } + return StringEscapeUtils.escapeHtml(hint); + } + + /** + * + * @param insertion + * @param config + * @return + */ + private static String getHumanReadableName(Insertion insertion, HintConfig config) { + String hrName = config.getHumanReadableName(insertion.pair); + if (insertion.replaced != null && insertion.replaced.hasType(insertion.type)) { + hrName = hrName.replaceAll("^(an?)", "$1 different"); +// System.out.println(hrName); + } + return hrName; + } + + /** + * + * @param insertion + * @param mapping + * @return + */ + private static String getTextToInsert(Insertion insertion, Mapping mapping) { + // TODO: Also need to handle newlines properly + Node mappedPair = insertion.pair.applyMapping(mapping); +// System.out.println("Pair:\n" + mappedPair); + String source = ((TextualNode) mappedPair).getSource(); + if (source != null) return source; + return insertion.pair.prettyPrint().replace("\n", ""); + } + + /** + * MAIN Function + * @param args + * @throws IOException + */ + public static void main (String[] args) throws IOException { +// String classpathStr = System.getProperty("java.class.path"); //Uncomment line to get the classpaths +// System.out.println("path: " + classpathStr); //Uncomment line to print the classpaths + String studentCodePath = args[0]; //path to student source code ".java" file + System.out.println("studentcodepath: " + studentCodePath); + String annotatedFilePath = args[1]; //"../data/S20_3.3_OPE_Grading_Anon/task4_annotated.txt" + System.out.println("annotatedFilePath: " + annotatedFilePath); + int taskID = Integer.parseInt(args[2]); // task ID + System.out.println("taskID: " + taskID); + int level = Integer.parseInt(args[3]); // high level hints = 1; low level hints = 0 + System.out.println("level: " + level); + int numHints = Integer.parseInt(args[4]); // max number of hints to show for any given snapshot + System.out.println("numHints: " + numHints); + + +// int taskID = 4; +// String studentCodePath = +//"../data/S20_3.3_OPE_Grading_Anon/3.3_OPE_Submissions-anonymized/13061@andrew.cmu.edu_data-consistency-ope_consistency-ope-task_20200303225053/src/main/java/Project_OMP/BankUserSystem/BankUserStrongConsistency.java"; +// int level = 0, numHints = 15; //level 1 high, level 0 low + + //option1 + //make jar file: https://cwiki.apache.org/confluence/display/MAVEN/Tutorial%3A+Build+a+JAR+file+with+Maven+in+5+minutes + + + //option2 + //can just right click project > run as > Run Configurations > java jre + + + //resolve maven error: + //step1: export M2_HOME="~/Downloads/apache-maven-3.6.3" + //step2: PATH="${M2_HOME}/bin:${PATH}" + //step3: export PATH + //step 4: mvn -version + + //To set default jvm path: +// On command line run - "/usr/libexec/java_home -V " +// Set the default version by "export JAVA_HOME=`/usr/libexec/java_home -v $version` " + + task = taskID; + hintTree = new TreeMap<>(); + + if (level == 1) { + File annotated = new File(annotatedFilePath); + String jsonString = new String(Files.readAllBytes(annotated.toPath())); + JSONObject hlvlHints = new JSONObject(jsonString); + @SuppressWarnings("unchecked") + Iterator k = hlvlHints.keys(); + while (k.hasNext()) { + String key = k.next(); + int line = Integer.parseInt(key); + String hint = hlvlHints.getString(key); + hintTree.put(line, hint); + } + } + + clusterid = getClusterID(studentCodePath, taskID, level, numHints); + System.out.println("cluster: " + clusterid); + + } } \ No newline at end of file From 96ffdf698ddfa11676cf92243ef060938f199e6c Mon Sep 17 00:00:00 2001 From: Aditi Tripathi Date: Fri, 21 May 2021 10:56:21 -0400 Subject: [PATCH 13/13] Fixed getSource() method --- CTD/src/edu/isnap/node/TextualNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CTD/src/edu/isnap/node/TextualNode.java b/CTD/src/edu/isnap/node/TextualNode.java index 495a7e3d..a166306c 100644 --- a/CTD/src/edu/isnap/node/TextualNode.java +++ b/CTD/src/edu/isnap/node/TextualNode.java @@ -50,7 +50,7 @@ public String getSource() { String rootSource = ((TextualNode) root()).source; if (startSourceLocation == null && endSourceLocation == null) return null; return rootSource.substring( - toIndex(rootSource, startSourceLocation), + toIndex(rootSource, startSourceLocation) - 1, toIndex(rootSource, endSourceLocation)); }