You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Match.java 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.ss.formula.functions;
  16. import org.apache.poi.ss.formula.eval.ErrorEval;
  17. import org.apache.poi.ss.formula.eval.EvaluationException;
  18. import org.apache.poi.ss.formula.eval.NumberEval;
  19. import org.apache.poi.ss.formula.eval.NumericValueEval;
  20. import org.apache.poi.ss.formula.eval.OperandResolver;
  21. import org.apache.poi.ss.formula.eval.RefEval;
  22. import org.apache.poi.ss.formula.eval.StringEval;
  23. import org.apache.poi.ss.formula.eval.ValueEval;
  24. import org.apache.poi.ss.formula.functions.LookupUtils.CompareResult;
  25. import org.apache.poi.ss.formula.functions.LookupUtils.LookupValueComparer;
  26. import org.apache.poi.ss.formula.functions.LookupUtils.ValueVector;
  27. import org.apache.poi.ss.formula.TwoDEval;
  28. /**
  29. * Implementation for the MATCH() Excel function.<p/>
  30. *
  31. * <b>Syntax:</b><br/>
  32. * <b>MATCH</b>(<b>lookup_value</b>, <b>lookup_array</b>, match_type)<p/>
  33. *
  34. * Returns a 1-based index specifying at what position in the <b>lookup_array</b> the specified
  35. * <b>lookup_value</b> is found.<p/>
  36. *
  37. * Specific matching behaviour can be modified with the optional <b>match_type</b> parameter.
  38. *
  39. * <table border="0" cellpadding="1" cellspacing="0" summary="match_type parameter description">
  40. * <tr><th>Value</th><th>Matching Behaviour</th></tr>
  41. * <tr><td>1</td><td>(default) find the largest value that is less than or equal to lookup_value.
  42. * The lookup_array must be in ascending <i>order</i>*.</td></tr>
  43. * <tr><td>0</td><td>find the first value that is exactly equal to lookup_value.
  44. * The lookup_array can be in any order.</td></tr>
  45. * <tr><td>-1</td><td>find the smallest value that is greater than or equal to lookup_value.
  46. * The lookup_array must be in descending <i>order</i>*.</td></tr>
  47. * </table>
  48. *
  49. * * Note regarding <i>order</i> - For the <b>match_type</b> cases that require the lookup_array to
  50. * be ordered, MATCH() can produce incorrect results if this requirement is not met. Observed
  51. * behaviour in Excel is to return the lowest index value for which every item after that index
  52. * breaks the match rule.<br>
  53. * The (ascending) sort order expected by MATCH() is:<br/>
  54. * numbers (low to high), strings (A to Z), boolean (FALSE to TRUE)<br/>
  55. * MATCH() ignores all elements in the lookup_array with a different type to the lookup_value.
  56. * Type conversion of the lookup_array elements is never performed.
  57. *
  58. *
  59. * @author Josh Micich
  60. * @author Cedric Walter at innoveo.com
  61. */
  62. public final class Match extends Var2or3ArgFunction {
  63. public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
  64. // default match_type is 1.0
  65. return eval(srcRowIndex, srcColumnIndex, arg0, arg1, 1.0);
  66. }
  67. public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
  68. ValueEval arg2) {
  69. double match_type;
  70. try {
  71. match_type = evaluateMatchTypeArg(arg2, srcRowIndex, srcColumnIndex);
  72. } catch (EvaluationException e) {
  73. // Excel/MATCH() seems to have slightly abnormal handling of errors with
  74. // the last parameter. Errors do not propagate up. Every error gets
  75. // translated into #REF!
  76. return ErrorEval.REF_INVALID;
  77. }
  78. return eval(srcRowIndex, srcColumnIndex, arg0, arg1, match_type);
  79. }
  80. private static ValueEval eval(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
  81. double match_type) {
  82. boolean matchExact = match_type == 0;
  83. // Note - Excel does not strictly require -1 and +1
  84. boolean findLargestLessThanOrEqual = match_type > 0;
  85. try {
  86. ValueEval lookupValue = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
  87. ValueVector lookupRange = evaluateLookupRange(arg1);
  88. int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual);
  89. return new NumberEval(index + 1); // +1 to convert to 1-based
  90. } catch (EvaluationException e) {
  91. return e.getErrorEval();
  92. }
  93. }
  94. private static final class SingleValueVector implements ValueVector {
  95. private final ValueEval _value;
  96. public SingleValueVector(ValueEval value) {
  97. _value = value;
  98. }
  99. public ValueEval getItem(int index) {
  100. if (index != 0) {
  101. throw new RuntimeException("Invalid index ("
  102. + index + ") only zero is allowed");
  103. }
  104. return _value;
  105. }
  106. public int getSize() {
  107. return 1;
  108. }
  109. }
  110. private static ValueVector evaluateLookupRange(ValueEval eval) throws EvaluationException {
  111. if (eval instanceof RefEval) {
  112. RefEval re = (RefEval) eval;
  113. return new SingleValueVector(re.getInnerValueEval());
  114. }
  115. if (eval instanceof TwoDEval) {
  116. ValueVector result = LookupUtils.createVector((TwoDEval)eval);
  117. if (result == null) {
  118. throw new EvaluationException(ErrorEval.NA);
  119. }
  120. return result;
  121. }
  122. // Error handling for lookup_range arg is also unusual
  123. if(eval instanceof NumericValueEval) {
  124. throw new EvaluationException(ErrorEval.NA);
  125. }
  126. if (eval instanceof StringEval) {
  127. StringEval se = (StringEval) eval;
  128. Double d = OperandResolver.parseDouble(se.getStringValue());
  129. if(d == null) {
  130. // plain string
  131. throw new EvaluationException(ErrorEval.VALUE_INVALID);
  132. }
  133. // else looks like a number
  134. throw new EvaluationException(ErrorEval.NA);
  135. }
  136. throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
  137. }
  138. private static double evaluateMatchTypeArg(ValueEval arg, int srcCellRow, int srcCellCol)
  139. throws EvaluationException {
  140. ValueEval match_type = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
  141. if(match_type instanceof ErrorEval) {
  142. throw new EvaluationException((ErrorEval)match_type);
  143. }
  144. if(match_type instanceof NumericValueEval) {
  145. NumericValueEval ne = (NumericValueEval) match_type;
  146. return ne.getNumberValue();
  147. }
  148. if (match_type instanceof StringEval) {
  149. StringEval se = (StringEval) match_type;
  150. Double d = OperandResolver.parseDouble(se.getStringValue());
  151. if(d == null) {
  152. // plain string
  153. throw new EvaluationException(ErrorEval.VALUE_INVALID);
  154. }
  155. // if the string parses as a number, it is OK
  156. return d.doubleValue();
  157. }
  158. throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")");
  159. }
  160. /**
  161. * @return zero based index
  162. */
  163. private static int findIndexOfValue(ValueEval lookupValue, ValueVector lookupRange,
  164. boolean matchExact, boolean findLargestLessThanOrEqual) throws EvaluationException {
  165. LookupValueComparer lookupComparer = createLookupComparer(lookupValue, matchExact);
  166. int size = lookupRange.getSize();
  167. if(matchExact) {
  168. for (int i = 0; i < size; i++) {
  169. if(lookupComparer.compareTo(lookupRange.getItem(i)).isEqual()) {
  170. return i;
  171. }
  172. }
  173. throw new EvaluationException(ErrorEval.NA);
  174. }
  175. if(findLargestLessThanOrEqual) {
  176. // Note - backward iteration
  177. for (int i = size - 1; i>=0; i--) {
  178. CompareResult cmp = lookupComparer.compareTo(lookupRange.getItem(i));
  179. if(cmp.isTypeMismatch()) {
  180. continue;
  181. }
  182. if(!cmp.isLessThan()) {
  183. return i;
  184. }
  185. }
  186. throw new EvaluationException(ErrorEval.NA);
  187. }
  188. // else - find smallest greater than or equal to
  189. // TODO - is binary search used for (match_type==+1) ?
  190. for (int i = 0; i<size; i++) {
  191. CompareResult cmp = lookupComparer.compareTo(lookupRange.getItem(i));
  192. if(cmp.isEqual()) {
  193. return i;
  194. }
  195. if(cmp.isGreaterThan()) {
  196. if(i<1) {
  197. throw new EvaluationException(ErrorEval.NA);
  198. }
  199. return i-1;
  200. }
  201. }
  202. throw new EvaluationException(ErrorEval.NA);
  203. }
  204. private static LookupValueComparer createLookupComparer(ValueEval lookupValue, boolean matchExact) {
  205. return LookupUtils.createLookupComparer(lookupValue, matchExact, true);
  206. }
  207. private static boolean isLookupValueWild(String stringValue) {
  208. if(stringValue.indexOf('?') >=0 || stringValue.indexOf('*') >=0) {
  209. return true;
  210. }
  211. return false;
  212. }
  213. }