https://issues.apache.org/bugzilla/show_bug.cgi?id=56822

            Bug ID: 56822
           Summary: Countifs function implements wrong logic
           Product: POI
           Version: 3.10-FINAL
          Hardware: PC
            Status: NEW
          Severity: normal
          Priority: P2
         Component: XSSF
          Assignee: [email protected]
          Reporter: [email protected]

Hello!

Unfortunately has to rewrite Countifs function from scratch due to utterly
incorrect algorithm logic in existing implementation.
In contrast, sumifs function looks good.
So I adapted sumifs implementation to provide correct countifs implementation.

The source code goes below.

----------------

/* ====================================================================
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
==================================================================== */

package org.apache.poi.ss.formula.functions;

import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.AreaEval;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.RefEval;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.functions.CountUtils.I_MatchPredicate;

/**
 * Implementation for the function COUNTIFS
 * <p>
 * Syntax: COUNTIFS(criteria_range1, criteria1, [criteria_range2, criteria2])
 * </p>
 */

public class Countifs implements FreeRefFunction {
    public static final FreeRefFunction instance = new Countifs();

    @Override
    public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec)
{
        if (args.length == 0 || args.length % 2 != 0) {
            return ErrorEval.VALUE_INVALID;
        }
        try {
            // collect pairs of ranges and criteria
            int len = args.length / 2;
            AreaEval[] ae = new AreaEval[len];
            I_MatchPredicate[] mp = new I_MatchPredicate[len];
            for (int i = 0; i < len; i++) {
                ae[i] = convertRangeArg(args[2 * i]);
                mp[i] = Countif.createCriteriaPredicate(args[2 * i + 1],
                        ec.getRowIndex(), ec.getColumnIndex());
            }

            validateCriteriaRanges(ae);

            int result = countMatchingCells(ae, mp);
            return new NumberEval(result);
        } catch (EvaluationException e) {
            return e.getErrorEval();
        }
    }

    /**
     * Verify that each <code>criteriaRanges</code> argument contains the same
     * number of rows and columns as the <code>sumRange</code> argument
     *
     * @throws EvaluationException
     *             if
     */
    private static void validateCriteriaRanges(AreaEval[] criteriaRanges)
throws EvaluationException {
        for (AreaEval r : criteriaRanges) {
            if (r.getHeight() != criteriaRanges[0].getHeight()
                    || r.getWidth() != criteriaRanges[0].getWidth()) {
                throw EvaluationException.invalidValue();
            }
        }
    }

    private static AreaEval convertRangeArg(ValueEval eval)
            throws EvaluationException {
        if (eval instanceof AreaEval) {
            return (AreaEval) eval;
        }
        if (eval instanceof RefEval) {
            return ((RefEval) eval).offset(0, 0, 0, 0);
        }
        throw new EvaluationException(ErrorEval.VALUE_INVALID);
    }

    /**
     *
     * @param ranges
     *            criteria ranges, each range must be of the same dimensions as
     *            <code>aeSum</code>
     * @param predicates
     *            array of predicates, a predicate for each value in
     *            <code>ranges</code>
     * @param aeSum
     *            the range to sum
     *
     * @return the computed value
     */
    private static int countMatchingCells(AreaEval[] ranges,
            I_MatchPredicate[] predicates) {
        int height = ranges[0].getHeight();
        int width = ranges[0].getWidth();

        int result = 0;
        for (int r = 0; r < height; r++) {
            for (int c = 0; c < width; c++) {

                boolean matches = true;
                for (int i = 0; i < ranges.length; i++) {
                    AreaEval aeRange = ranges[i];
                    I_MatchPredicate mp = predicates[i];

                    ValueEval relativeValue = aeRange.getRelativeValue(r, c);
                    if (!mp.matches(relativeValue)) {
                        matches = false;
                        break;
                    }

                }

                if (matches) { // count only if all of the corresponding
                                // criteria specified are true for that cell.
                    result += 1;
                }
            }
        }
        return result;
    }
}

-- 
You are receiving this mail because:
You are the assignee for the bug.

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to