Mercurial Hosting > luan
changeset 274:8afe9f2fdfec
AB testing, not fully tested
git-svn-id: https://luan-java.googlecode.com/svn/trunk@275 21e917c8-12df-6dd8-5cb6-c86387c605b9
author | fschmidt@gmail.com <fschmidt@gmail.com@21e917c8-12df-6dd8-5cb6-c86387c605b9> |
---|---|
date | Mon, 10 Nov 2014 03:28:32 +0000 |
parents | 073044e3ac03 |
children | 340656b18b74 |
files | core/src/luan/AbstractLuanTable.java core/src/luan/LuanTable.java core/src/luan/impl/LuanParser.java core/src/luan/modules/TableLuan.java dist/jars/luan-core-trunk.jar dist/jars/luan-logging-trunk.jar dist/jars/luan-lucene-trunk.jar dist/jars/luan-mail-trunk.jar dist/jars/luan-web-trunk.jar lucene/src/luan/modules/lucene/Ab_testing.luan lucene/src/luan/modules/lucene/LuceneSearcher.java |
diffstat | 11 files changed, 211 insertions(+), 3 deletions(-) [+] |
line wrap: on
line diff
--- a/core/src/luan/AbstractLuanTable.java Fri Oct 31 18:44:11 2014 +0000 +++ b/core/src/luan/AbstractLuanTable.java Mon Nov 10 03:28:32 2014 +0000 @@ -76,4 +76,8 @@ @Override public void setMetatable(LuanTable metatable) { throw new UnsupportedOperationException("can't set a metatable on a "+type()); } + + @Override public LuanTable cloneTable() { + return isList() ? new LuanTableImpl(new ArrayList<Object>(asList())) : new LuanTableImpl(new HashMap<Object,Object>(asMap())); + } }
--- a/core/src/luan/LuanTable.java Fri Oct 31 18:44:11 2014 +0000 +++ b/core/src/luan/LuanTable.java Mon Nov 10 03:28:32 2014 +0000 @@ -20,4 +20,5 @@ public LuanTable subList(int from,int to); public LuanTable getMetatable(); public void setMetatable(LuanTable metatable); + public LuanTable cloneTable(); }
--- a/core/src/luan/impl/LuanParser.java Fri Oct 31 18:44:11 2014 +0000 +++ b/core/src/luan/impl/LuanParser.java Mon Nov 10 03:28:32 2014 +0000 @@ -562,7 +562,8 @@ Spaces(In.NOTHING); Expressions values = ExpList(In.NOTHING); if( values==null ) - throw parser.exception("Expressions expected"); +// throw parser.exception("Expressions expected"); + return parser.failure(null); return parser.success( new SetStmt( vars.toArray(new Settable[0]), values ) ); }
--- a/core/src/luan/modules/TableLuan.java Fri Oct 31 18:44:11 2014 +0000 +++ b/core/src/luan/modules/TableLuan.java Mon Nov 10 03:28:32 2014 +0000 @@ -20,6 +20,7 @@ @Override public Object call(LuanState luan,Object[] args) { LuanTable module = Luan.newTable(); try { + add( module, "clone", LuanTable.class ); add( module, "concat", LuanState.class, LuanTable.class, String.class, Integer.class, Integer.class ); add( module, "insert", LuanTable.class, Integer.TYPE, Object.class ); add( module, "pack", new Object[0].getClass() ); @@ -133,4 +134,8 @@ return list.subList(from,to); } + public static LuanTable clone(LuanTable tbl) { + return tbl.cloneTable(); + } + }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lucene/src/luan/modules/lucene/Ab_testing.luan Mon Nov 10 03:28:32 2014 +0000 @@ -0,0 +1,160 @@ +import "luan:Math" +import "luan:Table" + + +function of(index) + + local ab_testing = {} + + ab_testing.test_map = {} + ab_testing.test_list = {} + + function ab_testing.test(test) + test.name or error "name not defined" + test.values or error "values not defined" + -- test.date_field is optional + + local field = "ab_test_" .. test.name + index.fields[field] == nil or error("test "+test.name+" already defined") + index.fields[field] = field .. " index" + test.field = field + + -- pass in map of name to aggregator factory + -- returns map of name to (map of value to result) and "start_date" + function test.results(aggregator_factories) + return index.Searcher( function(searcher) + local results = {} + for name in pairs(aggregator_factories) do + results[name] = {} + end + local date_field = test.date_field + local start_date = nil + for _, value in ipairs(test.values) do + local aggregators = {} + for name, factory in pairs(aggregator_factories) do + aggregators[name] = factory() + end + local query = { [field] = value } + searcher.search(query, function(doc) + for _, aggregator in pairs(aggregators) do + aggregator.aggregate(doc) + end + if date_field ~= nil then + local date = doc[date_field] + if date ~= nil and (start_date==nil or start_date > date) then + start_date = date + end + end + end) + for name, aggregator in pairs(aggregators) do + results[name][value] = aggregator.result + end + end + results.start_date = start_date + return results + end ) + end + + ab_testing.test_map[test.name] = test + ab_testing.test_list[#ab_testing.test_list + 1] = test + + return test + end + + function ab_testing.value(test_name,values) + return values[test_name] or ab_testing.test_map[test_name].values[1] + end + + -- returns map from test name to value + function ab_testing.from_doc(doc) + local tests = ab_testing.test_list + local values = {} + for _, test in ipairs(tests) do + values[test.name] = doc[test.field] + end + return values + end + + function ab_testing.to_doc(doc,values,tests) + tests = tests or ab_testing.test_list + if values == nil then + for _, test in ipairs(tests) do + doc[test.field] = test.values[Math.random(#test.values)] + end + else + for _, test in ipairs(tests) do + doc[test.field] = values[test.name] + end + end + end + + return ab_testing +end + + +-- aggregator factories + +-- fn(doc) should return boolean whether doc should be counted +function count(fn) + return function() + local aggregator = {} + aggregator.result = 0 + function aggregator.aggregate(doc) + if fn(doc) then + aggregator.result = aggregator.result + 1 + end + end + return aggregator + end +end + +count_all = count( function() return true end ) + +-- fn(doc) should return number to add to result, return 0 for nothing +function sum(fn) + return function() + local aggregator = {} + aggregator.result = 0 + function aggregator.aggregate(doc) + aggregator.result = aggregator.result + fn(doc) + end + return aggregator + end +end + + + +local function percent(x,total) + if total==0 then + return 0 + else + return 100 * x / total + end +end + +function fancy(results,names) + local fancy = {} + fancy.start_date = results.start_date + local name = names[1] + fancy[name] = {} + for value, count in pairs(result[name]) do + fancy[name][value] = {} + fancy[name][value].count = count + fancy[name][value].pct_of_total = 100 + fancy[name][value].pct_of_prev = 100 + end + local all = result[name] + local prev = all + for i in range(2,#names) do + name = names[i] + fancy[name] = {} + for value, count in pairs(result[name]) do + fancy[name][value] = {} + fancy[name][value].count = count + fancy[name][value].pct_of_total = percent(count,all[value]) + fancy[name][value].pct_of_prev = percent(count,prev[value]) + end + prev = result[name] + end + return fancy +end
--- a/lucene/src/luan/modules/lucene/LuceneSearcher.java Fri Oct 31 18:44:11 2014 +0000 +++ b/lucene/src/luan/modules/lucene/LuceneSearcher.java Mon Nov 10 03:28:32 2014 +0000 @@ -16,13 +16,17 @@ import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.Collector; import org.apache.lucene.search.TotalHitCountCollector; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.index.AtomicReaderContext; import luan.Luan; import luan.LuanState; import luan.LuanTable; import luan.LuanFunction; import luan.LuanJavaFunction; import luan.LuanException; +import luan.LuanRuntimeException; public final class LuceneSearcher { @@ -166,10 +170,43 @@ } }; - public Object[] search( LuanState luan, LuanTable queryTbl, int n, LuanTable sortTbl ) throws LuanException, IOException { + private static abstract class MyCollector extends Collector { + @Override public void setScorer(Scorer scorer) {} + @Override public void setNextReader(AtomicReaderContext context) {} + @Override public boolean acceptsDocsOutOfOrder() { + return true; + } + } + + public Object[] search( final LuanState luan, LuanTable queryTbl, Object nObj, LuanTable sortTbl ) throws LuanException, IOException { Query query = query(queryTbl); if( query == null ) throw luan.exception("invalid query"); + if( nObj instanceof LuanFunction ) { + final LuanFunction fn = (LuanFunction)nObj; + Collector col = new MyCollector() { + @Override public void collect(int doc) { + try { + LuanTable docTbl = doc(luan,doc); + luan.call(fn,new Object[]{docTbl}); + } catch(LuanException e) { + throw new LuanRuntimeException(e); + } catch(IOException e) { + throw new LuanRuntimeException(luan.exception(e)); + } + } + }; + try { + searcher.search(query,col); + } catch(LuanRuntimeException e) { + throw (LuanException)e.getCause(); + } + return LuanFunction.NOTHING; + } + Integer nI = Luan.asInteger(nObj); + if( nI == null ) + throw luan.exception("bad argument #2 (integer or function expected, got "+Luan.type(nObj)+")"); + int n = nI; if( n==0 ) { TotalHitCountCollector thcc = new TotalHitCountCollector(); searcher.search(query,thcc); @@ -201,7 +238,7 @@ LuanTable table() { LuanTable tbl = Luan.newTable(); try { - add( tbl, "search", LuanState.class, LuanTable.class, Integer.TYPE, LuanTable.class ); + add( tbl, "search", LuanState.class, LuanTable.class, Object.class, LuanTable.class ); } catch(NoSuchMethodException e) { throw new RuntimeException(e); }