Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(formula): add the Maxifs function #1711

Merged
merged 4 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { VarP } from './var-p';
import { VarS } from './var-s';
import { Vara } from './vara';
import { Varpa } from './varpa';
import { Maxifs } from './maxifs';

export const functionStatistical = [
[Average, FUNCTION_NAMES_STATISTICAL.AVERAGE],
Expand All @@ -44,4 +45,5 @@ export const functionStatistical = [
[VarS, FUNCTION_NAMES_STATISTICAL.VAR_S],
[Vara, FUNCTION_NAMES_STATISTICAL.VARA],
[Varpa, FUNCTION_NAMES_STATISTICAL.VARPA],
[Maxifs, FUNCTION_NAMES_STATISTICAL.MAXIFS],
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed 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.
*/

import { describe, expect, it } from 'vitest';
import { FUNCTION_NAMES_STATISTICAL } from '../../function-names';
import { Maxifs } from '../index';
import { ArrayValueObject, transformToValue } from '../../../../engine/value-object/array-value-object';
import { NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object';

describe('Test maxifs function', () => {
const textFunction = new Maxifs(FUNCTION_NAMES_STATISTICAL.MAXIFS);

describe('Maxifs', () => {
it('Range and criteria', async () => {
const maxRange = ArrayValueObject.create(`{
1;
2;
3
}`);
const range = ArrayValueObject.create(`{
2;
3;
4
}`);

const criteria = StringValueObject.create('>2');
const resultObject = textFunction.calculate(maxRange, range, criteria);
expect(transformToValue(resultObject.getArrayValue())).toStrictEqual([[3]]);
});

it('Range and array criteria', async () => {
const maxRange = ArrayValueObject.create(`{
1;
2;
3
}`);

const range = ArrayValueObject.create(`{
2;
3;
4
}`);

const criteria = ArrayValueObject.create(`{
>2;
>3;
>4
}`);

const resultObject = textFunction.calculate(maxRange, range, criteria);
expect(transformToValue(resultObject.getArrayValue())).toStrictEqual([[3], [3], [0]]);
});

it('2 ranges and criteria', async () => {
const maxRange = ArrayValueObject.create(`{
1;
2;
3
}`);

const range1 = ArrayValueObject.create(`{
2;
3;
4
}`);

const criteria1 = StringValueObject.create('>2');

const range2 = ArrayValueObject.create(`{
3;
4;
5
}`);

const criteria2 = StringValueObject.create('<5');

const resultObject = textFunction.calculate(maxRange, range1, criteria1, range2, criteria2);
expect(transformToValue(resultObject.getArrayValue())).toStrictEqual([[2]]);
});

it('2 ranges and criteria, 1 array criteria', async () => {
const maxRange = ArrayValueObject.create(`{
1;
2;
3
}`);

const range1 = ArrayValueObject.create(`{
2;
3;
4
}`);

const criteria1 = ArrayValueObject.create(`{
>2;
>3;
>4
}`);

const range2 = ArrayValueObject.create(`{
3;
4;
5
}`);

const criteria2 = NumberValueObject.create(5);

const resultObject = textFunction.calculate(maxRange, range1, criteria1, range2, criteria2);
expect(transformToValue(resultObject.getArrayValue())).toStrictEqual([[3], [3], [0]]);
});

it('2 ranges and criteria, 2 array criteria', async () => {
const maxRange = ArrayValueObject.create(`{
1;
2;
3
}`);

const range1 = ArrayValueObject.create(`{
2;
3;
4
}`);

const criteria1 = ArrayValueObject.create(`{
>2;
>3;
>4
}`);

const range2 = ArrayValueObject.create(`{
3;
4;
5
}`);

const criteria2 = ArrayValueObject.create(`{
4;
4;
4;
4
}`);

const resultObject = textFunction.calculate(maxRange, range1, criteria1, range2, criteria2);
expect(transformToValue(resultObject.getArrayValue())).toStrictEqual([[2], [0], [0], [0]]);
});
});
});
130 changes: 130 additions & 0 deletions packages/engine-formula/src/functions/statistical/maxifs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed 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.
*/

import { ErrorType } from '../../../basics/error-type';
import { expandArrayValueObject } from '../../../engine/utils/array-object';
import { booleanObjectIntersection, valueObjectCompare } from '../../../engine/utils/object-compare';
import { ArrayValueObject } from '../../../engine/value-object/array-value-object';
import type { BaseValueObject, IArrayValueObject } from '../../../engine/value-object/base-value-object';
import { ErrorValueObject } from '../../../engine/value-object/base-value-object';
import { BaseFunction } from '../../base-function';

export class Maxifs extends BaseFunction {
override calculate(maxRange: BaseValueObject, ...variants: BaseValueObject[]) {
if (maxRange === null) {
return ErrorValueObject.create(ErrorType.NA);
}

if (variants.length < 2) {
return ErrorValueObject.create(ErrorType.NA);
}

if (maxRange.isError()) {
return ErrorValueObject.create(ErrorType.NA);
}

if (!maxRange.isArray()) {
return ErrorValueObject.create(ErrorType.VALUE);
}

// Range and criteria must be paired
if (variants.length % 2 !== 0) {
return ErrorValueObject.create(ErrorType.VALUE);
}

// Every range must be array
if (variants.some((variant, i) => i % 2 === 0 && !variant.isArray())) {
return ErrorValueObject.create(ErrorType.VALUE);
}

const sumRowLength = (maxRange as ArrayValueObject).getRowCount();
const sumColumnLength = (maxRange as ArrayValueObject).getColumnCount();
// The size of the extended range is determined by the maximum width and height of the criteria range.
let maxRowLength = 0;
let maxColumnLength = 0;

variants.forEach((variant, i) => {
if (i % 2 === 1) {
if (variant.isArray()) {
const arrayValue = variant as ArrayValueObject;
maxRowLength = Math.max(maxRowLength, arrayValue.getRowCount());
maxColumnLength = Math.max(maxColumnLength, arrayValue.getColumnCount());
} else {
maxRowLength = Math.max(maxRowLength, 1);
maxColumnLength = Math.max(maxColumnLength, 1);
}
}
});

const booleanResults: BaseValueObject[][] = [];

for (let i = 0; i < variants.length; i++) {
if (i % 2 === 1) continue;

const range = variants[i] as ArrayValueObject;

const rangeRowLength = range.getRowCount();
const rangeColumnLength = range.getColumnCount();
if (rangeRowLength !== sumRowLength || rangeColumnLength !== sumColumnLength) {
return expandArrayValueObject(maxRowLength, maxColumnLength, ErrorValueObject.create(ErrorType.NA));
}

const criteria = variants[i + 1];
const criteriaArray = expandArrayValueObject(maxRowLength, maxColumnLength, criteria, ErrorValueObject.create(ErrorType.NA));

criteriaArray.iterator((criteriaValueObject, rowIndex, columnIndex) => {
if (!criteriaValueObject) {
return;
}

const resultArrayObject = valueObjectCompare(range, criteriaValueObject);

if (booleanResults[rowIndex] === undefined) {
booleanResults[rowIndex] = [];
}

if (booleanResults[rowIndex][columnIndex] === undefined) {
booleanResults[rowIndex][columnIndex] = resultArrayObject;
}

booleanResults[rowIndex][columnIndex] = booleanObjectIntersection(booleanResults[rowIndex][columnIndex], resultArrayObject);
});
}

const maxResults = booleanResults.map((row) => {
return row.map((booleanResult) => {
const picked = (maxRange as ArrayValueObject).pick(booleanResult as ArrayValueObject);
if (picked.getColumnCount() === 0) {
return ArrayValueObject.create('0');
}

return picked.max();
});
});

const arrayValueObjectData: IArrayValueObject = {
calculateValueList: maxResults,
rowCount: maxResults.length,
columnCount: maxResults[0].length,
unitId: this.unitId || '',
sheetId: this.subUnitId || '',
row: this.row,
column: this.column,
};

return ArrayValueObject.create(arrayValueObjectData);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -890,8 +890,11 @@ export default {
},
],
functionParameter: {
number1: { name: 'number1', detail: 'first' },
number2: { name: 'number2', detail: 'second' },
maxRange: { name: 'sum_range', detail: 'The range of cells to max.' },
criteriaRange1: { name: 'criteria_range1 ', detail: 'Is the set of cells to evaluate with the criteria.' },
criteria1: { name: 'criteria1', detail: 'Is the criteria in the form of a number, expression, or text that defines which cells will be evaluated as maximum. ' },
criteriaRange2: { name: 'criteriaRange2', detail: 'Additional ranges. You can enter up to 127 range pairs.' },
criteria2: { name: 'criteria2', detail: 'Additional associated criteria. You can enter up to 127 criteria pairs.' },
},
},
MEDIAN: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -865,8 +865,11 @@ export default {
},
],
functionParameter: {
number1: { name: 'number1', detail: 'first' },
number2: { name: 'number2', detail: 'second' },
maxRange: { name: '最大値範囲', detail: '最大値を求めるセルの実際の範囲です。' },
criteriaRange1: { name: '条件範囲 1', detail: '条件で評価するセルのセットです。' },
criteria1: { name: '条件 1', detail: '最大として評価されるセルを定義する、数値、式、またはテキストの形式での条件です。 同じ条件セットを、MINIFS、SUMIFS、および AVERAGEIFS 関数に対して使用できます。' },
criteriaRange2: { name: '条件範囲 2', detail: '追加の範囲。 最大 127 の範囲のペアを入力できます。' },
criteria2: { name: '条件 2', detail: '追加対応する条件です。 最大 127 条件のペアを入力できます。' },
},
},
MEDIAN: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -890,8 +890,11 @@ export default {
},
],
functionParameter: {
number1: { name: 'number1', detail: 'first' },
number2: { name: 'number2', detail: 'second' },
maxRange: { name: '最大值范围', detail: '确定最大值的实际单元格区域。' },
criteriaRange1: { name: '条件范围 1', detail: '条件1 一组用于条件计算的单元格。' },
criteria1: { name: '条件 1', detail: '条件 1 用于确定哪些单元格是最大值的条件,格式为数字、表达式或文本。' },
criteriaRange2: { name: '条件范围 2', detail: '附加区域及其关联条件。 最多可以输入 127 个区域/条件对。' },
criteria2: { name: '条件 2', detail: '附加区域及其关联条件。 最多可以输入 127 个区域/条件对。' },
},
},
MEDIAN: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1349,19 +1349,40 @@ export const FUNCTION_LIST_STATISTICAL: IFunctionInfo[] = [
abstract: 'formula.functionList.MAXIFS.abstract',
functionParameter: [
{
name: 'formula.functionList.MAXIFS.functionParameter.number1.name',
detail: 'formula.functionList.MAXIFS.functionParameter.number1.detail',
name: 'formula.functionList.MAXIFS.functionParameter.maxRange.name',
detail: 'formula.functionList.MAXIFS.functionParameter.maxRange.detail',
example: 'A1:A20',
require: 1,
repeat: 0,
},
{
name: 'formula.functionList.MAXIFS.functionParameter.number2.name',
detail: 'formula.functionList.MAXIFS.functionParameter.number2.detail',
name: 'formula.functionList.MAXIFS.functionParameter.criteriaRange1.name',
detail: 'formula.functionList.MAXIFS.functionParameter.criteriaRange1.detail',
example: 'A1:A20',
require: 1,
repeat: 0,
},
{
name: 'formula.functionList.MAXIFS.functionParameter.criteria1.name',
detail: 'formula.functionList.MAXIFS.functionParameter.criteria1.detail',
example: '">10"',
require: 1,
repeat: 0,
},
{
name: 'formula.functionList.MAXIFS.functionParameter.criteriaRange2.name',
detail: 'formula.functionList.MAXIFS.functionParameter.criteriaRange2.detail',
example: 'A1:A20',
require: 0,
repeat: 1,
},
{
name: 'formula.functionList.MAXIFS.functionParameter.criteria2.name',
detail: 'formula.functionList.MAXIFS.functionParameter.criteria2.detail',
example: '"<100"',
require: 0,
repeat: 1,
},
],
},
{
Expand Down
Loading