Skip to content

Commit

Permalink
feat(formula): minifs
Browse files Browse the repository at this point in the history
  • Loading branch information
Dushusir committed Jun 19, 2024
1 parent 9992773 commit 64ea86c
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 21 deletions.
4 changes: 4 additions & 0 deletions packages/engine-formula/src/functions/math/sumifs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ export class Sumifs extends BaseFunction {
});
}

return this._aggregateResults(sumRange, booleanResults);
}

private _aggregateResults(sumRange: BaseValueObject, booleanResults: BaseValueObject[][]): ArrayValueObject {
const sumResults = booleanResults.map((row) => {
return row.map((booleanResult) => {
return (sumRange as ArrayValueObject).pick(booleanResult as ArrayValueObject).sum();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { VarS } from './var-s';
import { Vara } from './vara';
import { Varpa } from './varpa';
import { Maxifs } from './maxifs';
import { Minifs } from './minifs';

export const functionStatistical = [
[Average, FUNCTION_NAMES_STATISTICAL.AVERAGE],
Expand All @@ -46,4 +47,5 @@ export const functionStatistical = [
[Vara, FUNCTION_NAMES_STATISTICAL.VARA],
[Varpa, FUNCTION_NAMES_STATISTICAL.VARPA],
[Maxifs, FUNCTION_NAMES_STATISTICAL.MAXIFS],
[Minifs, FUNCTION_NAMES_STATISTICAL.MINIFS],
];
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ export class Maxifs extends BaseFunction {
});
}

return this._aggregateResults(maxRange, booleanResults);
}

private _aggregateResults(maxRange: BaseValueObject, booleanResults: BaseValueObject[][]): ArrayValueObject {
const maxResults = booleanResults.map((row) => {
return row.map((booleanResult) => {
const picked = (maxRange as ArrayValueObject).pick(booleanResult as ArrayValueObject);
Expand Down
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:https://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 { Minifs } from '../index';
import { ArrayValueObject, transformToValue } from '../../../../engine/value-object/array-value-object';
import { NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object';

describe('Test minifs function', () => {
const testFunction = new Minifs(FUNCTION_NAMES_STATISTICAL.MINIFS);

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

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

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

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

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

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

it('2 ranges and criteria', async () => {
const minRange = 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 = testFunction.calculate(minRange, range1, criteria1, range2, criteria2);
expect(transformToValue(resultObject.getArrayValue())).toStrictEqual([[2]]);
});

it('2 ranges and criteria, 1 array criteria', async () => {
const minRange = 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 = testFunction.calculate(minRange, range1, criteria1, range2, criteria2);
expect(transformToValue(resultObject.getArrayValue())).toStrictEqual([[3], [3], [0]]);
});

it('2 ranges and criteria, 2 array criteria', async () => {
const minRange = 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 = testFunction.calculate(minRange, 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/minifs/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:https://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 Minifs extends BaseFunction {
override minParams = 3;

override maxParams = 255;

override calculate(minRange: BaseValueObject, ...variants: BaseValueObject[]) {
if (minRange.isError()) {
return ErrorValueObject.create(ErrorType.NA);
}

if (!minRange.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 = (minRange as ArrayValueObject).getRowCount();
const sumColumnLength = (minRange 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);
});
}

return this._aggregateResults(minRange, booleanResults);
}

private _aggregateResults(minRange: BaseValueObject, booleanResults: BaseValueObject[][]): ArrayValueObject {
const maxResults = booleanResults.map((row) => {
return row.map((booleanResult) => {
const picked = (minRange as ArrayValueObject).pick(booleanResult as ArrayValueObject);
if (picked.getColumnCount() === 0) {
return ArrayValueObject.create('0');
}

return picked.min();
});
});

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 @@ -881,7 +881,7 @@ export default {
},
},
MAXIFS: {
description: 'Returns the maximum value among cells specified by a given set of conditions or criteria',
description: 'Returns the maximum value among cells specified by a given set of conditions or criteria.',
abstract: 'Returns the maximum value among cells specified by a given set of conditions or criteria',
links: [
{
Expand All @@ -893,8 +893,8 @@ export default {
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.' },
criteriaRange2: { name: 'criteriaRange2', detail: 'Additional ranges. You can enter up to 127 ranges.' },
criteria2: { name: 'criteria2', detail: 'Additional associated criteria. You can enter up to 127 criteria.' },
},
},
MEDIAN: {
Expand Down Expand Up @@ -947,16 +947,19 @@ export default {
},
MINIFS: {
description: 'Returns the minimum value among cells specified by a given set of conditions or criteria.',
abstract: 'Returns the minimum value among cells specified by a given set of conditions or criteria.',
abstract: 'Returns the minimum value among cells specified by a given set of conditions or criteria',
links: [
{
title: 'Instruction',
url: 'https://support.microsoft.com/en-us/office/minifs-function-6ca1ddaa-079b-4e74-80cc-72eef32e6599',
},
],
functionParameter: {
number1: { name: 'number1', detail: 'first' },
number2: { name: 'number2', detail: 'second' },
minRange: { name: 'min_range', detail: 'The actual range of cells in which the minimum value will be determined.' },
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 minimum. The same set of criteria works for the MAXIFS, SUMIFS and AVERAGEIFS functions.' },
criteriaRange2: { name: 'criteria_range2', detail: 'Additional ranges. You can enter up to 126 range.' },
criteria2: { name: 'criteria2', detail: 'Additional associated criteria. You can enter up to 126 criteria.' },
},
},
MODE_MULT: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -916,16 +916,19 @@ export default {
},
MINIFS: {
description: '条件セットで指定されたセルの中の最小値を返します。',
abstract: '条件セットで指定されたセルの中の最小値を返します',
abstract: '条件セットで指定されたセルの中の最小値を返します',
links: [
{
title: '指導',
url: 'https://support.microsoft.com/ja-jp/office/minifs-%E9%96%A2%E6%95%B0-6ca1ddaa-079b-4e74-80cc-72eef32e6599',
},
],
functionParameter: {
number1: { name: 'number1', detail: 'first' },
number2: { name: 'number2', detail: 'second' },
minRange: { name: '最少範囲', detail: '最小値を求めるセルの実際の範囲です。' },
criteriaRange1: { name: '条件範囲 1', detail: '条件で評価するセルのセットです。' },
criteria1: { name: '条件 1', detail: '最小として評価されるセルを定義する、数値、式、またはテキストの形式での条件です。 同じ条件セットを、MAXIFS、SUMIFS、および AVERAGEIFS 関数に対して使用できます。' },
criteriaRange2: { name: '条件範囲 2', detail: '追加の範囲と対応する条件です。 最大 126 個の範囲/条件ペアを入力できます。' },
criteria2: { name: '条件 2', detail: '追加の範囲と対応する条件です。 最大 126 個の範囲/条件ペアを入力できます。' },
},
},
MODE_MULT: {
Expand Down
Loading

0 comments on commit 64ea86c

Please sign in to comment.