Browse Source

refactor similarity to return a normalized [0,1] value

Jan Wittler 4 years ago
parent
commit
b977d24275

+ 1 - 1
README.md

@@ -77,7 +77,7 @@ named arguments:
   -x X             All files named in this file will be ignored in the comparison (line-separated list)
   -t T             Tunes the comparison sensitivity by adjusting the minimum token  required  to be counted as a matching section. A smaller
                         <n> increases the sensitivity but might lead to more false-positives
-  -m M             Comparison similarity threshold [0-100]: All comparisons above this threshold will be saved (default: 0.0)
+  -m M             Comparison similarity threshold [0-1]: All comparisons above this threshold will be saved (default: 0.0)
   -n N             The maximum number of comparisons that will be shown in the  generated report, if set to -1 all comparisons will be shown
                         (default: 30)
   -r R             Name of the directory in which the comparison results will be stored (default: result)

+ 4 - 4
cli/src/test/java/de/jplag/cli/SimiliarityThresholdTest.java

@@ -25,21 +25,21 @@ class SimiliarityThresholdTest extends CommandLineInterfaceTest {
 
     @Test
     void testLowerBound() {
-        String argument = buildArgument(CommandLineArgument.SIMILARITY_THRESHOLD, Double.toString(-1.0));
+        String argument = buildArgument(CommandLineArgument.SIMILARITY_THRESHOLD, Double.toString(-0.01));
         buildOptionsFromCLI(argument, CURRENT_DIRECTORY);
         assertEquals(0.0, options.similarityThreshold(), DELTA);
     }
 
     @Test
     void testUpperBound() {
-        String argument = buildArgument(CommandLineArgument.SIMILARITY_THRESHOLD, Double.toString(101.0));
+        String argument = buildArgument(CommandLineArgument.SIMILARITY_THRESHOLD, Double.toString(1.01));
         buildOptionsFromCLI(argument, CURRENT_DIRECTORY);
-        assertEquals(100.0, options.similarityThreshold(), DELTA);
+        assertEquals(1.0, options.similarityThreshold(), DELTA);
     }
 
     @Test
     void testValidThreshold() {
-        double expectedValue = 50.0;
+        double expectedValue = 0.5;
         String argument = buildArgument(CommandLineArgument.SIMILARITY_THRESHOLD, Double.toString(expectedValue));
         buildOptionsFromCLI(argument, CURRENT_DIRECTORY);
         assertEquals(expectedValue, options.similarityThreshold(), DELTA);

+ 6 - 8
core/src/main/java/de/jplag/JPlagComparison.java

@@ -30,21 +30,21 @@ public record JPlagComparison(Submission firstSubmission, Submission secondSubmi
     }
 
     /**
-     * @return Maximum similarity in percent of both submissions.
+     * @return Maximum similarity in interval [0, 1]. O means no similarity, 1 means maximum similarity.
      */
     public final double maximalSimilarity() {
         return Math.max(similarityOfFirst(), similarityOfSecond());
     }
 
     /**
-     * @return Minimum similarity in percent of both submissions.
+     * @return Minimum similarity in interval [0, 1]. O means no similarity, 1 means maximum similarity.
      */
     public final double minimalSimilarity() {
         return Math.min(similarityOfFirst(), similarityOfSecond());
     }
 
     /**
-     * @return Similarity in percent (what percentage of tokens across both submissions are matched).
+     * @return Average similarity in interval [0, 1]. O means no similarity, 1 means maximum similarity.
      */
     public final double similarity() {
         boolean subtractBaseCode = firstSubmission.hasBaseCodeMatches() && secondSubmission.hasBaseCodeMatches();
@@ -54,8 +54,7 @@ public record JPlagComparison(Submission firstSubmission, Submission secondSubmi
     }
 
     /**
-     * @return Similarity in percent for the first submission (what percent of the first submission is similar to the
-     * second).
+     * @return Similarity of the first submission in interval [0, 1]. O means no similarity, 1 means maximum similarity.
      */
     public final double similarityOfFirst() {
         int divisor = firstSubmission.getSimilarityDivisor(true);
@@ -63,8 +62,7 @@ public record JPlagComparison(Submission firstSubmission, Submission secondSubmi
     }
 
     /**
-     * @return Similarity in percent for the second submission (what percent of the second submission is similar to the
-     * first).
+     * @return Similarity of the second submission in interval [0, 1]. O means no similarity, 1 means maximum similarity.
      */
     public final double similarityOfSecond() {
         int divisor = secondSubmission.getSimilarityDivisor(true);
@@ -77,6 +75,6 @@ public record JPlagComparison(Submission firstSubmission, Submission secondSubmi
     }
 
     private double similarity(int divisor) {
-        return (divisor == 0 ? 0.0 : (getNumberOfMatchedTokens() * 100 / (double) divisor));
+        return (divisor == 0 ? 0.0 : (getNumberOfMatchedTokens() / (double) divisor));
     }
 }

+ 3 - 4
core/src/main/java/de/jplag/JPlagResult.java

@@ -136,11 +136,10 @@ public class JPlagResult {
 
     private int[] calculateDistributionFor(List<JPlagComparison> comparisons, ToDoubleFunction<JPlagComparison> similarityExtractor) {
         int[] similarityDistribution = new int[SIMILARITY_DISTRIBUTION_SIZE];
-        int similarityDistributionBucketSize = 100 / SIMILARITY_DISTRIBUTION_SIZE;
         for (JPlagComparison comparison : comparisons) {
-            double similarity = similarityExtractor.applyAsDouble(comparison); // extract similarity in percent: 0f <= similarity <= 100f
-            int index = (int) (similarity / similarityDistributionBucketSize); // divide similarity by bucket size to find index of correct bucket.
-            index = Math.min(index, SIMILARITY_DISTRIBUTION_SIZE - 1);// index is out of bounds when similarity is 100%. decrease by one to count
+            double similarity = similarityExtractor.applyAsDouble(comparison); // extract similarity: 0.0 <= similarity <= 1.0
+            int index = (int) (similarity * SIMILARITY_DISTRIBUTION_SIZE); // divide similarity by bucket size to find index of correct bucket.
+            index = Math.min(index, SIMILARITY_DISTRIBUTION_SIZE - 1);// index is out of bounds when similarity is 1.0. decrease by one to count
                                                                       // towards the highest value bucket
             similarityDistribution[SIMILARITY_DISTRIBUTION_SIZE - 1 - index]++; // count comparison towards its determined bucket. bucket order is
             // reversed, so that the highest value bucket has the lowest index

+ 3 - 3
core/src/main/java/de/jplag/options/JPlagOptions.java

@@ -222,9 +222,9 @@ public record JPlagOptions(Language language, Integer minimumTokenMatch, List<St
     }
 
     private static double normalizeSimilarityThreshold(double similarityThreshold) {
-        if (similarityThreshold > 100) {
-            logger.warn("Maximum threshold of 100 used instead of {}", similarityThreshold);
-            return 100;
+        if (similarityThreshold > 1) {
+            logger.warn("Maximum threshold of 1 used instead of {}", similarityThreshold);
+            return 1;
         } else if (similarityThreshold < 0) {
             logger.warn("Minimum threshold of 0 used instead of {}", similarityThreshold);
             return 0;

+ 2 - 2
core/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java

@@ -8,10 +8,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
  * ReportViewer DTO for the comparison of two submissions.
  * @param firstSubmissionId id of the first submission
  * @param secondSubmissionId id of the second submission
- * @param matchPercentage similarity in percent. between 0f and 100f.
+ * @param similarity average similarity. between 0.0 and 1.0.
  * @param matches the list of matches found in the comparison of the two submissions
  */
 public record ComparisonReport(@JsonProperty("id1") String firstSubmissionId, @JsonProperty("id2") String secondSubmissionId,
-        @JsonProperty("similarity") double matchPercentage, @JsonProperty("matches") List<Match> matches) {
+        @JsonProperty("similarity") double similarity, @JsonProperty("matches") List<Match> matches) {
 
 }

+ 1 - 1
core/src/main/resources/de/jplag/messages.properties

@@ -9,7 +9,7 @@ CommandLineArgument.RootDirectory=Root-directory with submissions to check for p
 CommandLineArgument.PlagiarismDirectory=Root-directory with submissions to check for plagiarism
 CommandLineArgument.PriorDirectory=Root-directory with prior submissions to compare against
 CommandLineArgument.ShownComparisons=The maximum number of comparisons that will be shown in the generated report, if set to -1 all comparisons will be shown
-CommandLineArgument.SimilarityThreshold=Comparison similarity threshold [0-100]: All comparisons above this threshold will be saved
+CommandLineArgument.SimilarityThreshold=Comparison similarity threshold [0-1]: All comparisons above this threshold will be saved
 CommandLineArgument.Subdirectory=Look in directories <root-dir>/*/<dir> for programs
 CommandLineArgument.Verbosity=Verbosity of the logging
 CommandLineArgument.ClusterDisable=Skips the clustering

+ 1 - 1
core/src/test/java/de/jplag/BaseCodeTest.java

@@ -42,7 +42,7 @@ public class BaseCodeTest extends TestBase {
         assertEquals(1, result.getAllComparisons().size());
         assertEquals(1, result.getAllComparisons().get(0).matches().size());
         assertEquals(1, result.getSimilarityDistribution()[1]);
-        assertEquals(85, result.getAllComparisons().get(0).similarity(), DELTA);
+        assertEquals(0.85, result.getAllComparisons().get(0).similarity(), DELTA);
     }
 
     @Test

+ 1 - 1
core/src/test/java/de/jplag/NewJavaFeaturesTest.java

@@ -8,7 +8,7 @@ import de.jplag.exceptions.ExitException;
 
 public class NewJavaFeaturesTest extends TestBase {
     private static final int EXPECTED_MATCHES = 6; // might change if you add files to the submissions
-    private static final double EXPECTED_SIMILARITY = 96.0; // might change if you add files to the submissions
+    private static final double EXPECTED_SIMILARITY = 0.96; // might change if you add files to the submissions
 
     private static final String EXCLUSION_FILE_NAME = "blacklist.txt";
     private static final String ROOT_DIRECTORY = "NewJavaFeatures";

+ 10 - 10
core/src/test/java/de/jplag/ParallelComparisonTest.java

@@ -25,7 +25,7 @@ public class ParallelComparisonTest extends TestBase {
         assertEquals(1, result.getAllComparisons().size());
         assertEquals(1, result.getAllComparisons().get(0).matches().size());
         assertEquals(1, result.getSimilarityDistribution()[3]);
-        assertEquals(62.07, result.getAllComparisons().get(0).similarity(), DELTA);
+        assertEquals(0.6207, result.getAllComparisons().get(0).similarity(), DELTA);
     }
 
     /**
@@ -40,7 +40,7 @@ public class ParallelComparisonTest extends TestBase {
         assertEquals(1, result.getAllComparisons().size());
         assertEquals(2, result.getAllComparisons().get(0).matches().size());
         assertArrayEquals(expectedDistribution, result.getSimilarityDistribution());
-        assertEquals(96.55, result.getAllComparisons().get(0).similarity(), DELTA);
+        assertEquals(0.9655, result.getAllComparisons().get(0).similarity(), DELTA);
     }
 
     /**
@@ -74,17 +74,17 @@ public class ParallelComparisonTest extends TestBase {
                 .forEach(comparison -> assertEquals(0, comparison.similarity(), DELTA));
 
         // Hard coded assertions on selected comparisons
-        assertEquals(24.6, getSelectedPercent(result, "A", "B"), DELTA);
-        assertEquals(99.7, getSelectedPercent(result, "A", "C"), DELTA);
-        assertEquals(77.9, getSelectedPercent(result, "A", "D"), DELTA);
-        assertEquals(24.6, getSelectedPercent(result, "B", "C"), DELTA);
-        assertEquals(28.3, getSelectedPercent(result, "B", "D"), DELTA);
-        assertEquals(77.9, getSelectedPercent(result, "C", "D"), DELTA);
+        assertEquals(0.246, getSelectedPercent(result, "A", "B"), DELTA);
+        assertEquals(0.997, getSelectedPercent(result, "A", "C"), DELTA);
+        assertEquals(0.779, getSelectedPercent(result, "A", "D"), DELTA);
+        assertEquals(0.246, getSelectedPercent(result, "B", "C"), DELTA);
+        assertEquals(0.283, getSelectedPercent(result, "B", "D"), DELTA);
+        assertEquals(0.779, getSelectedPercent(result, "C", "D"), DELTA);
 
         // More detailed assertions for the plagiarism in A-D
         var biggestMatch = getSelectedComparison(result, "A", "D");
-        assertEquals(96.4, biggestMatch.get().maximalSimilarity(), DELTA);
-        assertEquals(65.3, biggestMatch.get().minimalSimilarity(), DELTA);
+        assertEquals(0.964, biggestMatch.get().maximalSimilarity(), DELTA);
+        assertEquals(0.653, biggestMatch.get().minimalSimilarity(), DELTA);
         assertEquals(12, biggestMatch.get().matches().size());
     }
 

+ 1 - 1
core/src/test/java/de/jplag/TestBase.java

@@ -14,7 +14,7 @@ import de.jplag.options.Verbosity;
 public abstract class TestBase {
 
     protected static final String BASE_PATH = Path.of("src", "test", "resources", "de", "jplag", "samples").toString();
-    protected static final double DELTA = 0.1;
+    protected static final double DELTA = 0.001;
 
     protected String getBasePath() {
         return BASE_PATH;

File diff suppressed because it is too large
+ 317 - 317
endtoend-testing/src/test/resources/results/java/sortAlgo.json


Some files were not shown because too many files changed in this diff