From 09fa36198c39d0daf4e8ca590343319ecbbd9e2e Mon Sep 17 00:00:00 2001 From: Iain Sproat Date: Sat, 20 Nov 2010 18:04:11 +0000 Subject: [PATCH] Additions to GREL: * Factorial function allowing variable steps * GreatestCommonDenominator function * LeastCommonMultiple function * Multinomial function * Quotient function git-svn-id: http://google-refine.googlecode.com/svn/trunk@1910 7d457c2a-affb-35e4-300a-418c747d4874 --- .../refine/expr/functions/math/Combin.java | 6 +- .../refine/expr/functions/math/Fact.java | 13 +-- .../refine/expr/functions/math/FactN.java | 80 +++++++++++++++++++ .../math/GreatestCommonDenominator.java | 68 ++++++++++++++++ .../functions/math/LeastCommonMultiple.java | 79 ++++++++++++++++++ .../expr/functions/math/Multinomial.java | 71 ++++++++++++++++ .../refine/expr/functions/math/Quotient.java | 64 +++++++++++++++ .../refine/grel/ControlFunctionRegistry.java | 10 +++ 8 files changed, 376 insertions(+), 15 deletions(-) create mode 100644 main/src/com/google/refine/expr/functions/math/FactN.java create mode 100644 main/src/com/google/refine/expr/functions/math/GreatestCommonDenominator.java create mode 100644 main/src/com/google/refine/expr/functions/math/LeastCommonMultiple.java create mode 100644 main/src/com/google/refine/expr/functions/math/Multinomial.java create mode 100644 main/src/com/google/refine/expr/functions/math/Quotient.java diff --git a/main/src/com/google/refine/expr/functions/math/Combin.java b/main/src/com/google/refine/expr/functions/math/Combin.java index 7db5c3f29..aeb580c8e 100644 --- a/main/src/com/google/refine/expr/functions/math/Combin.java +++ b/main/src/com/google/refine/expr/functions/math/Combin.java @@ -60,9 +60,9 @@ public class Combin implements Function { throw new IllegalArgumentException ("the number of elements, n, should be larger than the number of combinations, k"); if (n < 1) throw new IllegalArgumentException ("the number of elements, n, cannot be less than 1"); - int nFact = Fact.factorial(n); - int rFact = Fact.factorial(k); - int nminusrFact = Fact.factorial(n - k); + int nFact = FactN.factorial(n, 1); + int rFact = FactN.factorial(k, 1); + int nminusrFact = FactN.factorial(n - k, 1); return nFact / (rFact * nminusrFact); } diff --git a/main/src/com/google/refine/expr/functions/math/Fact.java b/main/src/com/google/refine/expr/functions/math/Fact.java index 70bc45090..3e20e2d44 100644 --- a/main/src/com/google/refine/expr/functions/math/Fact.java +++ b/main/src/com/google/refine/expr/functions/math/Fact.java @@ -46,22 +46,11 @@ public class Fact implements Function { public Object call(Properties bindings, Object[] args) { if (args.length == 1 && args[0] != null && args[0] instanceof Number) { - return Fact.factorial(((Number) args[0]).intValue()); + return FactN.factorial(((Number) args[0]).intValue(), 1); } return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects a number"); } - /* - * Calculates the factorial of an integer, i. - * Returns 1 for zero and negative integers. - */ - public static int factorial(int i){ - if(i <= 1) - return 1; - else - return i * Fact.factorial(i - 1); - } - public void write(JSONWriter writer, Properties options) throws JSONException { diff --git a/main/src/com/google/refine/expr/functions/math/FactN.java b/main/src/com/google/refine/expr/functions/math/FactN.java new file mode 100644 index 000000000..177cbed27 --- /dev/null +++ b/main/src/com/google/refine/expr/functions/math/FactN.java @@ -0,0 +1,80 @@ +/* + +Copyright 2010, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +package com.google.refine.expr.functions.math; + +import java.util.Properties; + +import org.json.JSONException; +import org.json.JSONWriter; + +import com.google.refine.expr.EvalError; +import com.google.refine.grel.ControlFunctionRegistry; +import com.google.refine.grel.Function; + +public class FactN implements Function { + + public Object call(Properties bindings, Object[] args) { + if (args.length != 2) + return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects two numbers"); + if(args[0] == null || !(args[0] instanceof Number)) + return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects the first parameter to be a number"); + if(args[1] == null && !(args[1] instanceof Number)) + return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects the second parameter to be a number"); + + return FactN.factorial(((Number) args[0]).intValue(), ((Number) args[1]).intValue()); + + } + + /* + * Calculates the factorial of an integer, i, for a decreasing step of n. + * e.g. A double factorial would have a step of 2. + * Returns 1 for zero and negative integers. + */ + public static int factorial(int i, int step){ + if(i <= 1) + return 1; + else + return i * FactN.factorial(i - step, step); + } + + public void write(JSONWriter writer, Properties options) + throws JSONException { + + writer.object(); + writer.key("description"); writer.value("Returns the factorial of a number"); + writer.key("params"); writer.value("number i"); + writer.key("returns"); writer.value("number"); + writer.endObject(); + } +} \ No newline at end of file diff --git a/main/src/com/google/refine/expr/functions/math/GreatestCommonDenominator.java b/main/src/com/google/refine/expr/functions/math/GreatestCommonDenominator.java new file mode 100644 index 000000000..9da0262ed --- /dev/null +++ b/main/src/com/google/refine/expr/functions/math/GreatestCommonDenominator.java @@ -0,0 +1,68 @@ +/* + +Copyright 2010, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +package com.google.refine.expr.functions.math; + +import java.util.Properties; + +import org.json.JSONException; +import org.json.JSONWriter; + +import com.google.refine.expr.EvalError; +import com.google.refine.grel.ControlFunctionRegistry; +import com.google.refine.grel.Function; + +public class GreatestCommonDenominator implements Function { + + public Object call(Properties bindings, Object[] args) { + if (args.length == 2 && args[0] != null && args[0] instanceof Number + && args[1] != null && args[1] instanceof Number) { + return GreatestCommonDenominator.GCD(((Number) args[0]).doubleValue(), ((Number) args[1]).doubleValue()); + } + return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects two numbers"); + } + + public static double GCD(double a, double b){ + return b == 0 ? a : GCD(b, a % b); + } + + public void write(JSONWriter writer, Properties options) + throws JSONException { + + writer.object(); + writer.key("description"); writer.value("Returns the greatest common denominator of the two numbers"); + writer.key("params"); writer.value("number d, number e"); + writer.key("returns"); writer.value("number"); + writer.endObject(); + } +} \ No newline at end of file diff --git a/main/src/com/google/refine/expr/functions/math/LeastCommonMultiple.java b/main/src/com/google/refine/expr/functions/math/LeastCommonMultiple.java new file mode 100644 index 000000000..354ec1b88 --- /dev/null +++ b/main/src/com/google/refine/expr/functions/math/LeastCommonMultiple.java @@ -0,0 +1,79 @@ +/* + +Copyright 2010, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +package com.google.refine.expr.functions.math; + +import java.util.Properties; + +import org.json.JSONException; +import org.json.JSONWriter; + +import com.google.refine.expr.EvalError; +import com.google.refine.grel.ControlFunctionRegistry; +import com.google.refine.grel.Function; + +public class LeastCommonMultiple implements Function { + + public Object call(Properties bindings, Object[] args) { + if (args.length == 2 + && args[0] != null && args[0] instanceof Number + && args[1] != null && args[1] instanceof Number) { + return LeastCommonMultiple.LCM(((Number) args[0]).doubleValue(), ((Number) args[1]).doubleValue()); + } + return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects two numbers"); + } + + public static double LCM(double a, double b){ + double largerValue = a; + double smallerValue = b; + if(b > a){ + largerValue = b; + smallerValue = a; + } + for(int i = 1; i <= largerValue; i++){ + if((largerValue*i) % smallerValue == 0) + return largerValue * i; + } + return largerValue * smallerValue; + } + + public void write(JSONWriter writer, Properties options) + throws JSONException { + + writer.object(); + writer.key("description"); writer.value("Returns the greatest common denominator of the two numbers"); + writer.key("params"); writer.value("number d, number e"); + writer.key("returns"); writer.value("number"); + writer.endObject(); + } +} \ No newline at end of file diff --git a/main/src/com/google/refine/expr/functions/math/Multinomial.java b/main/src/com/google/refine/expr/functions/math/Multinomial.java new file mode 100644 index 000000000..93cb61885 --- /dev/null +++ b/main/src/com/google/refine/expr/functions/math/Multinomial.java @@ -0,0 +1,71 @@ +/* + +Copyright 2010, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +package com.google.refine.expr.functions.math; + +import java.util.Properties; + +import org.json.JSONException; +import org.json.JSONWriter; + +import com.google.refine.expr.EvalError; +import com.google.refine.grel.ControlFunctionRegistry; +import com.google.refine.grel.Function; + +public class Multinomial implements Function { + + public Object call(Properties bindings, Object[] args) { + if (args.length < 1) + return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects one or more numbers"); + int sum = 0; + int product = 1; + for(int i = 0; i < args.length; i++){ + if(args[i] == null && !(args[i] instanceof Number)) + return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects parameter " + (i + 1) + " to be a number"); + int num = ((Number) args[i]).intValue(); + sum += num; + product *= FactN.factorial(num, 1); + } + return FactN.factorial(sum, 1) / product; + } + + public void write(JSONWriter writer, Properties options) + throws JSONException { + + writer.object(); + writer.key("description"); writer.value("Calculates the multinomial of a series of numbers"); + writer.key("params"); writer.value("one or more numbers"); + writer.key("returns"); writer.value("number"); + writer.endObject(); + } +} diff --git a/main/src/com/google/refine/expr/functions/math/Quotient.java b/main/src/com/google/refine/expr/functions/math/Quotient.java new file mode 100644 index 000000000..4fbc69d03 --- /dev/null +++ b/main/src/com/google/refine/expr/functions/math/Quotient.java @@ -0,0 +1,64 @@ +/* + +Copyright 2010, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +package com.google.refine.expr.functions.math; + +import java.util.Properties; + +import org.json.JSONException; +import org.json.JSONWriter; + +import com.google.refine.expr.EvalError; +import com.google.refine.grel.ControlFunctionRegistry; +import com.google.refine.grel.Function; + +public class Quotient implements Function { + + public Object call(Properties bindings, Object[] args) { + if (args.length == 2 && args[0] != null && args[0] instanceof Number + && args[1] != null && args[1] instanceof Number) { + return Math.floor((((Number) args[0]).doubleValue() / ((Number) args[1]).doubleValue())); + } + return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects two numbers"); + } + + public void write(JSONWriter writer, Properties options) + throws JSONException { + + writer.object(); + writer.key("description"); writer.value("Returns the integer portion of a division"); + writer.key("params"); writer.value("number numerator, number denominator"); + writer.key("returns"); writer.value("number"); + writer.endObject(); + } +} diff --git a/main/src/com/google/refine/grel/ControlFunctionRegistry.java b/main/src/com/google/refine/grel/ControlFunctionRegistry.java index 0fdc365ab..8bd3fff7e 100644 --- a/main/src/com/google/refine/grel/ControlFunctionRegistry.java +++ b/main/src/com/google/refine/grel/ControlFunctionRegistry.java @@ -72,14 +72,19 @@ import com.google.refine.expr.functions.math.Degrees; import com.google.refine.expr.functions.math.Even; import com.google.refine.expr.functions.math.Exp; import com.google.refine.expr.functions.math.Fact; +import com.google.refine.expr.functions.math.FactN; import com.google.refine.expr.functions.math.Floor; +import com.google.refine.expr.functions.math.GreatestCommonDenominator; +import com.google.refine.expr.functions.math.LeastCommonMultiple; import com.google.refine.expr.functions.math.Ln; import com.google.refine.expr.functions.math.Log; import com.google.refine.expr.functions.math.Max; import com.google.refine.expr.functions.math.Min; import com.google.refine.expr.functions.math.Mod; +import com.google.refine.expr.functions.math.Multinomial; import com.google.refine.expr.functions.math.Odd; import com.google.refine.expr.functions.math.Pow; +import com.google.refine.expr.functions.math.Quotient; import com.google.refine.expr.functions.math.Radians; import com.google.refine.expr.functions.math.Round; import com.google.refine.expr.functions.math.Sin; @@ -253,9 +258,14 @@ public class ControlFunctionRegistry { registerFunction("exp", new Exp()); registerFunction("sum", new Sum()); registerFunction("fact", new Fact()); + registerFunction("factn", new FactN()); registerFunction("combin", new Combin()); registerFunction("degrees", new Degrees()); registerFunction("radians", new Radians()); + registerFunction("gcd", new GreatestCommonDenominator()); + registerFunction("lcm", new LeastCommonMultiple()); + registerFunction("multinomial", new Multinomial()); + registerFunction("quotient", new Quotient()); registerFunction("and", new And()); registerFunction("or", new Or());