Actually it is CompressedWords, rather.

This commit is contained in:
asofold 2012-09-05 00:30:48 +02:00
parent 17ded8adee
commit f2b29da6a6
8 changed files with 249 additions and 89 deletions

View File

@ -18,7 +18,7 @@ public class CharPrefixTree<N extends CharNode, L extends CharLookupEntry<N>> ex
} }
} }
public CharPrefixTree(NodeFactory<Character, N> nodeFactory, LookupEntryFactory<Character, N, L> resultFactory) { public CharPrefixTree(final NodeFactory<Character, N> nodeFactory, final LookupEntryFactory<Character, N, L> resultFactory) {
super(nodeFactory, resultFactory); super(nodeFactory, resultFactory);
} }
@ -41,7 +41,7 @@ public class CharPrefixTree<N extends CharNode, L extends CharLookupEntry<N>> ex
* @param create * @param create
* @return * @return
*/ */
public L lookup(final char[] chars, boolean create){ public L lookup(final char[] chars, final boolean create){
return lookup(toCharacterList(chars), create); return lookup(toCharacterList(chars), create);
} }
@ -51,7 +51,7 @@ public class CharPrefixTree<N extends CharNode, L extends CharLookupEntry<N>> ex
* @param create * @param create
* @return * @return
*/ */
public L lookup(final String input, boolean create){ public L lookup(final String input, final boolean create){
return lookup(input.toCharArray(), create); return lookup(input.toCharArray(), create);
} }
@ -73,7 +73,7 @@ public class CharPrefixTree<N extends CharNode, L extends CharLookupEntry<N>> ex
return feed(toCharacterList(chars)); return feed(toCharacterList(chars));
} }
public void feedAll(final Collection<String> inputs, boolean trim, boolean lowerCase){ public void feedAll(final Collection<String> inputs, final boolean trim, final boolean lowerCase){
for (String input : inputs){ for (String input : inputs){
if (trim) input = input.toLowerCase(); if (trim) input = input.toLowerCase();
if (lowerCase) input = input.toLowerCase(); if (lowerCase) input = input.toLowerCase();
@ -118,7 +118,7 @@ public class CharPrefixTree<N extends CharNode, L extends CharLookupEntry<N>> ex
} }
}, new LookupEntryFactory<Character, CharNode, CharLookupEntry<CharNode>>() { }, new LookupEntryFactory<Character, CharNode, CharLookupEntry<CharNode>>() {
@Override @Override
public CharLookupEntry<CharNode> newLookupEntry(CharNode node, CharNode insertion, int depth, boolean hasPrefix) { public final CharLookupEntry<CharNode> newLookupEntry(final CharNode node, final CharNode insertion, final int depth, final boolean hasPrefix) {
return new CharLookupEntry<CharNode>(node, insertion, depth, hasPrefix); return new CharLookupEntry<CharNode>(node, insertion, depth, hasPrefix);
} }
}); });

View File

@ -83,7 +83,7 @@ public class PrefixTree<K, N extends Node<K>, L extends LookupEntry<K, N>>{
/** If nodes visit method shall be called. */ /** If nodes visit method shall be called. */
protected boolean visit; protected boolean visit;
public PrefixTree(NodeFactory<K, N> nodeFactory, LookupEntryFactory<K, N, L> resultFactory){ public PrefixTree(final NodeFactory<K, N> nodeFactory, final LookupEntryFactory<K, N, L> resultFactory){
this.nodeFactory = nodeFactory; this.nodeFactory = nodeFactory;
this.root = nodeFactory.newNode(null); this.root = nodeFactory.newNode(null);
this.resultFactory = resultFactory; this.resultFactory = resultFactory;
@ -113,7 +113,7 @@ public class PrefixTree<K, N extends Node<K>, L extends LookupEntry<K, N>>{
* @param create * @param create
* @return * @return
*/ */
public L lookup(K[] keys, final boolean create){ public L lookup(final K[] keys, final boolean create){
return lookup(Arrays.asList(keys), create); return lookup(Arrays.asList(keys), create);
} }
@ -156,7 +156,9 @@ public class PrefixTree<K, N extends Node<K>, L extends LookupEntry<K, N>>{
else if (depth == keys.size()){ else if (depth == keys.size()){
node = current; 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<K, N extends Node<K>, L extends LookupEntry<K, N>>{
protected void visit(final N node){ protected void visit(final N node){
} }
/**
* Decorate before returning.
* @param result
*/
protected void decorate(final L result){
}
/** /**
* *
* @param keys * @param keys
@ -258,7 +267,7 @@ public class PrefixTree<K, N extends Node<K>, L extends LookupEntry<K, N>>{
} }
}, new LookupEntryFactory<K, Node<K>, LookupEntry<K,Node<K>>>() { }, new LookupEntryFactory<K, Node<K>, LookupEntry<K,Node<K>>>() {
@Override @Override
public LookupEntry<K, Node<K>> newLookupEntry(Node<K> node, Node<K> insertion, int depth, boolean hasPrefix) { public final LookupEntry<K, Node<K>> newLookupEntry(final Node<K> node, final Node<K> insertion, final int depth, final boolean hasPrefix) {
return new LookupEntry<K, PrefixTree.Node<K>>(node, insertion, depth, hasPrefix); return new LookupEntry<K, PrefixTree.Node<K>>(node, insertion, depth, hasPrefix);
} }
}); });

View File

@ -25,7 +25,7 @@ public class SimpleCharPrefixTree extends CharPrefixTree<CharNode, SimpleCharLoo
} }
}, new LookupEntryFactory<Character, CharNode, SimpleCharLookupEntry>() { }, new LookupEntryFactory<Character, CharNode, SimpleCharLookupEntry>() {
@Override @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); return new SimpleCharLookupEntry(node, insertion, depth, hasPrefix);
} }
}); });

View File

@ -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<TimedCharNode, SimpleTimedCharLookupEntry> {
public static class SimpleTimedCharLookupEntry extends TimedCharLookupEntry<TimedCharNode>{
public SimpleTimedCharLookupEntry(TimedCharNode node, TimedCharNode insertion, int depth, boolean hasPrefix) {
super(node, insertion, depth, hasPrefix);
}
}
public SimpleTimedCharPrefixTree(final boolean access){
super(
new NodeFactory<Character, TimedCharNode>(){
@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<Character, TimedCharNode, SimpleTimedCharLookupEntry>() {
@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);
}
}

View File

@ -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<N extends TimedCharNode, L extends TimedCharLookupEntry<N>> extends CharPrefixTree<N, L> {
public static class TimedCharLookupEntry<N extends TimedCharNode> extends CharLookupEntry<N>{
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<Character, N> nodeFactory, final LookupEntryFactory<Character, N, L> resultFactory, final boolean access) {
super(nodeFactory, resultFactory);
visit = true;
this.access = access;
}
@Override
public L lookup(final List<Character> 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<TimedCharNode, TimedCharLookupEntry<TimedCharNode>> newTimedCharPrefixTree(final boolean access) {
return new TimedCharPrefixTree<TimedCharNode, TimedCharLookupEntry<TimedCharNode>>(
new NodeFactory<Character, TimedCharNode>(){
@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<Character, TimedCharNode, TimedCharLookupEntry<TimedCharNode>>() {
@Override
public final TimedCharLookupEntry<TimedCharNode> newLookupEntry(final TimedCharNode node,
final TimedCharNode insertion, final int depth, final boolean hasPrefix) {
return new TimedCharLookupEntry<TimedCharNode>(node, insertion, depth, hasPrefix);
}
},
access
);
}
}

View File

@ -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<Character> tree = new SimplePrefixTree<Character>();
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<Character> letters = new ArrayList<Character>(len);
final List<Character> numbers = new ArrayList<Character>(Math.min(len, 10));
final List<Character> other = new ArrayList<Character>(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<Character> chars) {
final int len = chars.size();
SimpleLookupEntry<Character> 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;
}
}

View File

@ -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<Character> letters = new ArrayList<Character>(10);
protected final List<Character> digits = new ArrayList<Character>(10);
protected final List<Character> other = new ArrayList<Character>(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<Character> 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;
}
}

View File

@ -25,7 +25,7 @@ public class LetterEngine {
} }
if (config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_ENGINE_GLCOMPRWORDS_CHECK, false)){ if (config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_ENGINE_GLCOMPRWORDS_CHECK, false)){
// TODO: Make aspects configurable. // TODO: Make aspects configurable.
processors.add(new CompressedChars(2000, false)); processors.add(new CompressedWords(30000, 2000, false));
} }
} }