diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/CharPrefixTree.java b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/CharPrefixTree.java index 618e2a0a..92a0b895 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/CharPrefixTree.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/CharPrefixTree.java @@ -18,7 +18,7 @@ public class CharPrefixTree> ex } } - public CharPrefixTree(NodeFactory nodeFactory, LookupEntryFactory resultFactory) { + public CharPrefixTree(final NodeFactory nodeFactory, final LookupEntryFactory resultFactory) { super(nodeFactory, resultFactory); } @@ -41,7 +41,7 @@ public class CharPrefixTree> ex * @param create * @return */ - public L lookup(final char[] chars, boolean create){ + public L lookup(final char[] chars, final boolean create){ return lookup(toCharacterList(chars), create); } @@ -51,7 +51,7 @@ public class CharPrefixTree> ex * @param create * @return */ - public L lookup(final String input, boolean create){ + public L lookup(final String input, final boolean create){ return lookup(input.toCharArray(), create); } @@ -73,7 +73,7 @@ public class CharPrefixTree> ex return feed(toCharacterList(chars)); } - public void feedAll(final Collection inputs, boolean trim, boolean lowerCase){ + public void feedAll(final Collection inputs, final boolean trim, final boolean lowerCase){ for (String input : inputs){ if (trim) input = input.toLowerCase(); if (lowerCase) input = input.toLowerCase(); @@ -118,7 +118,7 @@ public class CharPrefixTree> ex } }, new LookupEntryFactory>() { @Override - public CharLookupEntry newLookupEntry(CharNode node, CharNode insertion, int depth, boolean hasPrefix) { + public final CharLookupEntry newLookupEntry(final CharNode node, final CharNode insertion, final int depth, final boolean hasPrefix) { return new CharLookupEntry(node, insertion, depth, hasPrefix); } }); diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/PrefixTree.java b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/PrefixTree.java index 12c3ab83..111ab42c 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/PrefixTree.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/PrefixTree.java @@ -83,7 +83,7 @@ public class PrefixTree, L extends LookupEntry>{ /** If nodes visit method shall be called. */ protected boolean visit; - public PrefixTree(NodeFactory nodeFactory, LookupEntryFactory resultFactory){ + public PrefixTree(final NodeFactory nodeFactory, final LookupEntryFactory resultFactory){ this.nodeFactory = nodeFactory; this.root = nodeFactory.newNode(null); this.resultFactory = resultFactory; @@ -113,7 +113,7 @@ public class PrefixTree, L extends LookupEntry>{ * @param create * @return */ - public L lookup(K[] keys, final boolean create){ + public L lookup(final K[] keys, final boolean create){ return lookup(Arrays.asList(keys), create); } @@ -156,7 +156,9 @@ public class PrefixTree, L extends LookupEntry>{ else if (depth == keys.size()){ node = current; } - return resultFactory.newLookupEntry(node, insertion, depth, hasPrefix); + final L result = resultFactory.newLookupEntry(node, insertion, depth, hasPrefix); + decorate(result); + return result; } /** @@ -166,6 +168,13 @@ public class PrefixTree, L extends LookupEntry>{ protected void visit(final N node){ } + /** + * Decorate before returning. + * @param result + */ + protected void decorate(final L result){ + } + /** * * @param keys @@ -258,7 +267,7 @@ public class PrefixTree, L extends LookupEntry>{ } }, new LookupEntryFactory, LookupEntry>>() { @Override - public LookupEntry> newLookupEntry(Node node, Node insertion, int depth, boolean hasPrefix) { + public final LookupEntry> newLookupEntry(final Node node, final Node insertion, final int depth, final boolean hasPrefix) { return new LookupEntry>(node, insertion, depth, hasPrefix); } }); diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/SimpleCharPrefixTree.java b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/SimpleCharPrefixTree.java index 4e87a8dd..25ed5d56 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/SimpleCharPrefixTree.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/SimpleCharPrefixTree.java @@ -25,7 +25,7 @@ public class SimpleCharPrefixTree extends CharPrefixTree() { @Override - public SimpleCharLookupEntry newLookupEntry(CharNode node, CharNode insertion, int depth, boolean hasPrefix) { + public final SimpleCharLookupEntry newLookupEntry(final CharNode node, final CharNode insertion, final int depth, final boolean hasPrefix) { return new SimpleCharLookupEntry(node, insertion, depth, hasPrefix); } }); diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/SimpleTimedCharPrefixTree.java b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/SimpleTimedCharPrefixTree.java new file mode 100644 index 00000000..2f674b0a --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/SimpleTimedCharPrefixTree.java @@ -0,0 +1,37 @@ +package fr.neatmonster.nocheatplus.checks.chat.analysis.ds; + +import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.SimpleTimedCharPrefixTree.SimpleTimedCharLookupEntry; +import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.TimedCharPrefixTree.TimedCharLookupEntry; +import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.TimedCharPrefixTree.TimedCharNode; + +public class SimpleTimedCharPrefixTree extends TimedCharPrefixTree { + + public static class SimpleTimedCharLookupEntry extends TimedCharLookupEntry{ + public SimpleTimedCharLookupEntry(TimedCharNode node, TimedCharNode insertion, int depth, boolean hasPrefix) { + super(node, insertion, depth, hasPrefix); + } + } + + public SimpleTimedCharPrefixTree(final boolean access){ + super( + new NodeFactory(){ + @Override + public final TimedCharNode newNode(final TimedCharNode parent) { + final long ts; + if (parent == null) ts = System.currentTimeMillis(); + else ts = parent.ts; + return new TimedCharNode(ts); + } + } + , + new LookupEntryFactory() { + @Override + public final SimpleTimedCharLookupEntry newLookupEntry(final TimedCharNode node, + final TimedCharNode insertion, final int depth, final boolean hasPrefix) { + return new SimpleTimedCharLookupEntry(node, insertion, depth, hasPrefix); + } + }, + access); + } + +} diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/TimedCharPrefixTree.java b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/TimedCharPrefixTree.java new file mode 100644 index 00000000..45446a49 --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/ds/TimedCharPrefixTree.java @@ -0,0 +1,101 @@ +package fr.neatmonster.nocheatplus.checks.chat.analysis.ds; + +import java.util.Arrays; +import java.util.List; + +import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.TimedCharPrefixTree.TimedCharLookupEntry; +import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.TimedCharPrefixTree.TimedCharNode; + +public class TimedCharPrefixTree> extends CharPrefixTree { + + public static class TimedCharLookupEntry extends CharLookupEntry{ + public long[] timeInsertion = null; + public TimedCharLookupEntry(N node, N insertion, int depth, boolean hasPrefix){ + super(node, insertion, depth, hasPrefix); + } + } + + public static class TimedCharNode extends CharNode{ + public long ts = 0; + public TimedCharNode(long ts){ + this.ts = ts; + } + } + + protected long ts; + + protected long[] timeInsertion = new long[200]; + + protected final boolean access; + + protected boolean updateTime = false; + + protected int depth; + + protected float arrayGrowth = 1.3f; + + /** + * + * @param nodeFactory + * @param resultFactory + * @param access If to set timestamps, even if create is false. + */ + public TimedCharPrefixTree(final NodeFactory nodeFactory, final LookupEntryFactory resultFactory, final boolean access) { + super(nodeFactory, resultFactory); + visit = true; + this.access = access; + } + + @Override + public L lookup(final List keys, final boolean create) { + ts = System.currentTimeMillis(); + updateTime = access || create; + depth = 0; + return super.lookup(keys, create); + } + + @Override + protected void visit(final N node) { + if (depth == timeInsertion.length){ + // This might be excluded by contract. + timeInsertion = Arrays.copyOf(timeInsertion, (int) (timeInsertion.length * arrayGrowth)); + } + timeInsertion[depth] = node.ts; + if (updateTime) node.ts = ts; + depth ++; + } + + @Override + protected void decorate(final L result) { + result.timeInsertion = Arrays.copyOf(timeInsertion, depth); + } + + /** + * + * @param access If to set timestamps, even if create is false. + * @return + */ + public static TimedCharPrefixTree> newTimedCharPrefixTree(final boolean access) { + return new TimedCharPrefixTree>( + new NodeFactory(){ + @Override + public final TimedCharNode newNode(final TimedCharNode parent) { + final long ts; + if (parent == null) ts = System.currentTimeMillis(); + else ts = parent.ts; + return new TimedCharNode(ts); + } + } + , + new LookupEntryFactory>() { + @Override + public final TimedCharLookupEntry newLookupEntry(final TimedCharNode node, + final TimedCharNode insertion, final int depth, final boolean hasPrefix) { + return new TimedCharLookupEntry(node, insertion, depth, hasPrefix); + } + }, + access + ); + } + +} diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/engine/CompressedChars.java b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/engine/CompressedChars.java deleted file mode 100644 index 3a690186..00000000 --- a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/engine/CompressedChars.java +++ /dev/null @@ -1,78 +0,0 @@ -package fr.neatmonster.nocheatplus.checks.chat.analysis.engine; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import fr.neatmonster.nocheatplus.checks.chat.analysis.MessageLetterCount; -import fr.neatmonster.nocheatplus.checks.chat.analysis.WordLetterCount; -import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.SimplePrefixTree; -import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.SimplePrefixTree.SimpleLookupEntry; - -public class CompressedChars extends AbstractWordProcessor{ - - protected final SimplePrefixTree tree = new SimplePrefixTree(); - - protected final int maxAdd; - - protected int added = 0; - - private final boolean sort; - - public CompressedChars(int maxAdd, boolean sort) { - super("CompressedChars"); - this.maxAdd = maxAdd; - this.sort = sort; - } - - @Override - public void start(MessageLetterCount message) { - // This allows adding up to maximum messge length more characters, - // but also allows to set size of nodes exactly. - // TODO: Some better method than blunt clear (extra LinkedHashSet/LRU?). - if (added > maxAdd) tree.clear(); - added = 0; - } - - @Override - public float loop(long ts, int index, String key, WordLetterCount word) { - final int len = word.counts.size(); - final List letters = new ArrayList(len); - final List numbers = new ArrayList(Math.min(len, 10)); - final List other = new ArrayList(Math.min(len, 10)); - for (Character c : word.counts.keySet()){ - if (Character.isLetter(c)) letters.add(c); - else if (Character.isDigit(c)) numbers.add(c); - else other.add(c); - } - if (sort){ - Collections.sort(letters); - Collections.sort(numbers); - Collections.sort(other); - } - float score = 0; - if (!letters.isEmpty()){ - score += getScore(letters); - } - if (!numbers.isEmpty()){ - score += getScore(numbers); - } - if (!other.isEmpty()){ - score += getScore(other); - } - return word.counts.isEmpty()?0f:(score / (float) len); - } - - private float getScore(List chars) { - final int len = chars.size(); - SimpleLookupEntry entry = tree.lookup(chars, true); - float score = (float) (entry.depth*entry.depth) / (float) (len*len) ; - if (entry.depth == chars.size()){ - score += 0.2; - if (entry.insertion.isEnd) score += 0.2; - } - if (len != entry.depth) added += len - entry.depth; - return score; - } - -} diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/engine/CompressedWords.java b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/engine/CompressedWords.java new file mode 100644 index 00000000..c936a83b --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/engine/CompressedWords.java @@ -0,0 +1,91 @@ +package fr.neatmonster.nocheatplus.checks.chat.analysis.engine; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import fr.neatmonster.nocheatplus.checks.chat.analysis.MessageLetterCount; +import fr.neatmonster.nocheatplus.checks.chat.analysis.WordLetterCount; +import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.SimpleTimedCharPrefixTree; +import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.SimpleTimedCharPrefixTree.SimpleTimedCharLookupEntry; + +public class CompressedWords extends AbstractWordProcessor{ + + protected final SimpleTimedCharPrefixTree tree = new SimpleTimedCharPrefixTree(true); + + protected final int maxAdd; + + protected int added = 0; + + protected final boolean sort; + + protected final long durExpire; + + protected final List letters = new ArrayList(10); + protected final List digits = new ArrayList(10); + protected final List other = new ArrayList(10); + + public CompressedWords(long durExpire, int maxAdd, boolean sort) { + super("CompressedWords"); + this.durExpire = durExpire; + this.maxAdd = maxAdd; + this.sort = sort; + } + + @Override + public void start(MessageLetterCount message) { + // This allows adding up to maximum messge length more characters, + // but also allows to set size of nodes exactly. + // TODO: Some better method than blunt clear (extra LinkedHashSet/LRU?). + if (added > maxAdd) tree.clear(); + added = 0; + } + + @Override + public float loop(final long ts, final int index, final String key, final WordLetterCount word) { + final int len = word.counts.size(); + letters.clear(); + digits.clear(); + other.clear(); + for (Character c : word.counts.keySet()){ + if (Character.isLetter(c)) letters.add(c); + else if (Character.isDigit(c)) digits.add(c); + else other.add(c); + } + if (sort){ + Collections.sort(letters); + Collections.sort(digits); + Collections.sort(other); + } + float score = 0; + if (!letters.isEmpty()){ + score += getScore(letters, ts); + } + if (!digits.isEmpty()){ + score += getScore(digits, ts); + } + if (!other.isEmpty()){ + score += getScore(other, ts); + } + return word.counts.isEmpty()?0f:(score / (float) len); + } + + protected float getScore(final List chars, final long ts) { + final int len = chars.size(); + final SimpleTimedCharLookupEntry entry = tree.lookup(chars, true); + final int depth = entry.depth; + float score = 0f; + for (int i = 0; i < depth ; i++){ + final long age = ts - entry.timeInsertion[i]; + if (age < durExpire) + score += 1f / (float) (depth - i) * (float) (durExpire - age) / (float) durExpire; + } + if (depth == len){ + score += 0.2; + if (entry.insertion.isEnd) score += 0.2; + } + if (len != depth) added += len - depth; + return score; + } + +} diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/engine/LetterEngine.java b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/engine/LetterEngine.java index 965f0586..2830d8bc 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/analysis/engine/LetterEngine.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/analysis/engine/LetterEngine.java @@ -25,7 +25,7 @@ public class LetterEngine { } if (config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_ENGINE_GLCOMPRWORDS_CHECK, false)){ // TODO: Make aspects configurable. - processors.add(new CompressedChars(2000, false)); + processors.add(new CompressedWords(30000, 2000, false)); } }