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();
+	}
+
 }
Binary file dist/jars/luan-core-trunk.jar has changed
Binary file dist/jars/luan-logging-trunk.jar has changed
Binary file dist/jars/luan-lucene-trunk.jar has changed
Binary file dist/jars/luan-mail-trunk.jar has changed
Binary file dist/jars/luan-web-trunk.jar has changed
--- /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);
 		}