From 3afaad973124dda2365e9e98b4cbca39fe9af12f Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Tue, 21 Aug 2018 10:23:13 +0300 Subject: [PATCH] Changed gathered ping average to be median instead #691 (For each datapoint a median is calculated, which is then used in calculation for mean values, averages) --- .../player/PingInsertProcessor.java | 25 ++++-- .../plan/utilities/analysis/Median.java | 58 ++++++++++++ .../player/PingInsertProcessorTest.java | 66 ++++++++++++++ .../plan/utilities/analysis/MedianTest.java | 90 +++++++++++++++++++ 4 files changed, 230 insertions(+), 9 deletions(-) create mode 100644 Plan/src/main/java/com/djrapitops/plan/utilities/analysis/Median.java create mode 100644 Plan/src/test/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessorTest.java create mode 100644 Plan/src/test/java/com/djrapitops/plan/utilities/analysis/MedianTest.java diff --git a/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessor.java b/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessor.java index 0f068bfcf..abc1d6702 100644 --- a/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessor.java +++ b/Plan/src/main/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessor.java @@ -9,10 +9,12 @@ import com.djrapitops.plan.data.store.objects.DateObj; import com.djrapitops.plan.system.database.databases.Database; import com.djrapitops.plan.system.info.server.ServerInfo; import com.djrapitops.plan.system.processing.CriticalRunnable; +import com.djrapitops.plan.utilities.analysis.Median; import java.util.List; import java.util.OptionalInt; import java.util.UUID; +import java.util.stream.Collectors; /** * Processes 60s values of a Ping list. @@ -44,23 +46,28 @@ public class PingInsertProcessor implements CriticalRunnable { return; } - int minValue = history.stream() - .mapToInt(DateObj::getValue) - .filter(i -> i > 0 && i < 4000) - .min().orElse(-1); + int minValue = getMinValue(history); - double avgValue = history.stream() - .mapToInt(DateObj::getValue) - .filter(i -> i > 0 && i < 4000) - .average().orElse(-1); + int meanValue = getMeanValue(history); int maxValue = max.getAsInt(); Ping ping = new Ping(lastDate, ServerInfo.getServerUUID(), minValue, maxValue, - avgValue); + meanValue); Database.getActive().save().ping(uuid, ping); } + + int getMinValue(List> history) { + return history.stream() + .mapToInt(DateObj::getValue) + .filter(i -> i > 0 && i < 4000) + .min().orElse(-1); + } + + int getMeanValue(List> history) { + return (int) Median.forInt(history.stream().map(DateObj::getValue).collect(Collectors.toList())).calculate(); + } } diff --git a/Plan/src/main/java/com/djrapitops/plan/utilities/analysis/Median.java b/Plan/src/main/java/com/djrapitops/plan/utilities/analysis/Median.java new file mode 100644 index 000000000..92abc49cc --- /dev/null +++ b/Plan/src/main/java/com/djrapitops/plan/utilities/analysis/Median.java @@ -0,0 +1,58 @@ +package com.djrapitops.plan.utilities.analysis; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Math utility for calculating the median from Integer values. + * + * @author Rsl1122 + */ +public class Median { + + private final List values; + private int size; + + private Median(Collection values, int b) { + this(values.stream().map(i -> (long) i).collect(Collectors.toList())); + } + + private Median(List values) { + this.values = values; + Collections.sort(values); + size = values.size(); + } + + public static Median forInt(Collection integers) { + return new Median(integers, 0); + } + + public static Median forLong(List longs) { + return new Median(longs); + } + + public double calculate() { + if (values.isEmpty()) { + return -1; + } + if (size % 2 == 0) { + return calculateEven(); + } else { + return calculateOdd(); + } + } + + private double calculateEven() { + int half = size / 2; + double x1 = values.get(half); + double x2 = values.get(half - 1); + return (x1 + x2) / 2; + } + + private double calculateOdd() { + int half = size / 2; + return (double) values.get(half); + } +} \ No newline at end of file diff --git a/Plan/src/test/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessorTest.java b/Plan/src/test/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessorTest.java new file mode 100644 index 000000000..ba7c40523 --- /dev/null +++ b/Plan/src/test/java/com/djrapitops/plan/system/processing/processors/player/PingInsertProcessorTest.java @@ -0,0 +1,66 @@ +package com.djrapitops.plan.system.processing.processors.player; + +import com.djrapitops.plan.data.store.objects.DateObj; +import com.djrapitops.plan.utilities.analysis.Median; +import com.djrapitops.plugin.api.TimeAmount; +import org.junit.Before; +import org.junit.Test; +import utilities.RandomData; +import utilities.TestConstants; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; + +/** + * Test for {@link PingInsertProcessor}. + * + * @author Rsl1122 + */ +public class PingInsertProcessorTest { + + private List> testPing; + + @Before + public void setUp() { + testPing = new ArrayList<>(); + + for (int i = 0; i < TimeAmount.MINUTE.ms(); i += TimeAmount.SECOND.ms() * 2L) { + testPing.add(new DateObj<>(i, RandomData.randomInt(1, 4000))); + } + } + + @Test + public void testMedian() { + List collect = testPing.stream().map(DateObj::getValue).sorted().collect(Collectors.toList()); + System.out.println(collect); + int expected = (int) Median.forInt(collect).calculate(); + int result = new PingInsertProcessor(TestConstants.PLAYER_ONE_UUID, new ArrayList<>()).getMeanValue(testPing); + System.out.println(result); + + assertEquals(expected, result); + } + + @Test + public void testMedianSingleEntry() { + int expected = 50; + int result = new PingInsertProcessor(TestConstants.PLAYER_ONE_UUID, new ArrayList<>()).getMeanValue( + Collections.singletonList(new DateObj<>(0, expected)) + ); + + assertEquals(expected, result); + } + + @Test + public void testMedianEmpty() { + int expected = -1; + int result = new PingInsertProcessor(TestConstants.PLAYER_ONE_UUID, new ArrayList<>()).getMeanValue( + Collections.emptyList() + ); + + assertEquals(expected, result); + } +} \ No newline at end of file diff --git a/Plan/src/test/java/com/djrapitops/plan/utilities/analysis/MedianTest.java b/Plan/src/test/java/com/djrapitops/plan/utilities/analysis/MedianTest.java new file mode 100644 index 000000000..50a0a3656 --- /dev/null +++ b/Plan/src/test/java/com/djrapitops/plan/utilities/analysis/MedianTest.java @@ -0,0 +1,90 @@ +package com.djrapitops.plan.utilities.analysis; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for {@link Median}. + * + * @author Rsl1122 + */ +public class MedianTest { + + @Test + public void simpleOdd() { + List testValues = Arrays.asList(1, 3, 3, 6, 7, 8, 9); + Collections.shuffle(testValues); + double expected = 6; + double result = Median.forInt(testValues).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void simpleEven() { + List testValues = Arrays.asList(1, 2, 3, 4, 5, 6, 8, 9); + Collections.shuffle(testValues); + double expected = 4.5; + double result = Median.forInt(testValues).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void empty() { + double expected = -1; + double result = Median.forInt(Collections.emptyList()).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void singleValue() { + double expected = 50; + double result = Median.forInt(Collections.singletonList((int) expected)).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void twoValues() { + List testValues = Arrays.asList(1, 2); + double expected = 1.5; + double result = Median.forInt(testValues).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void overflowOdd() { + List testValues = Arrays.asList(Integer.MIN_VALUE, 2, Integer.MAX_VALUE); + double expected = 2; + double result = Median.forInt(testValues).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void overflowEven() { + List testValues = Arrays.asList(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + double expected = -0.5; + double result = Median.forInt(testValues).calculate(); + + assertEquals(expected, result, 0.01); + } + + @Test + public void overflowEven2() { + List testValues = Arrays.asList(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + double expected = Integer.MAX_VALUE; + double result = Median.forInt(testValues).calculate(); + + assertEquals(expected, result, 0.01); + } + +} \ No newline at end of file