From 385eabf6b49b7bc362fe8398c4c45c4ab2296653 Mon Sep 17 00:00:00 2001 From: wpxp123456 Date: Mon, 14 Oct 2024 20:19:55 +0800 Subject: [PATCH] feat(formula): add dstdevp/dsum/dvar/dvarp function --- .../src/functions/database/dstdev/index.ts | 2 +- .../database/dstdevp/__tests__/index.spec.ts | 290 ++++++++++++++++++ .../src/functions/database/dstdevp/index.ts | 83 +++++ .../database/dsum/__tests__/index.spec.ts | 290 ++++++++++++++++++ .../src/functions/database/dsum/index.ts | 62 ++++ .../database/dvar/__tests__/index.spec.ts | 290 ++++++++++++++++++ .../src/functions/database/dvar/index.ts | 83 +++++ .../database/dvarp/__tests__/index.spec.ts | 290 ++++++++++++++++++ .../src/functions/database/dvarp/index.ts | 83 +++++ .../src/functions/database/function-map.ts | 8 + .../locale/function-list/database/en-US.ts | 20 +- .../locale/function-list/database/ja-JP.ts | 20 +- .../locale/function-list/database/ru-RU.ts | 20 +- .../locale/function-list/database/vi-VN.ts | 20 +- .../locale/function-list/database/zh-CN.ts | 20 +- .../locale/function-list/database/zh-TW.ts | 20 +- .../src/services/function-list/database.ts | 76 +++-- 17 files changed, 1604 insertions(+), 73 deletions(-) create mode 100644 packages/engine-formula/src/functions/database/dstdevp/__tests__/index.spec.ts create mode 100644 packages/engine-formula/src/functions/database/dstdevp/index.ts create mode 100644 packages/engine-formula/src/functions/database/dsum/__tests__/index.spec.ts create mode 100644 packages/engine-formula/src/functions/database/dsum/index.ts create mode 100644 packages/engine-formula/src/functions/database/dvar/__tests__/index.spec.ts create mode 100644 packages/engine-formula/src/functions/database/dvar/index.ts create mode 100644 packages/engine-formula/src/functions/database/dvarp/__tests__/index.spec.ts create mode 100644 packages/engine-formula/src/functions/database/dvarp/index.ts diff --git a/packages/engine-formula/src/functions/database/dstdev/index.ts b/packages/engine-formula/src/functions/database/dstdev/index.ts index bb238d12390a..8c58cbcfb1d3 100644 --- a/packages/engine-formula/src/functions/database/dstdev/index.ts +++ b/packages/engine-formula/src/functions/database/dstdev/index.ts @@ -64,7 +64,7 @@ export class Dstdev extends BaseFunction { } } - if (count === 0 || count === 1) { + if (count <= 1) { return ErrorValueObject.create(ErrorType.DIV_BY_ZERO); } diff --git a/packages/engine-formula/src/functions/database/dstdevp/__tests__/index.spec.ts b/packages/engine-formula/src/functions/database/dstdevp/__tests__/index.spec.ts new file mode 100644 index 000000000000..646613d56783 --- /dev/null +++ b/packages/engine-formula/src/functions/database/dstdevp/__tests__/index.spec.ts @@ -0,0 +1,290 @@ +/** + * 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 { ErrorType } from '../../../../basics/error-type'; +import { ArrayValueObject, transformToValueObject } from '../../../../engine/value-object/array-value-object'; +import { ErrorValueObject } from '../../../../engine/value-object/base-value-object'; +import { BooleanValueObject, NullValueObject, NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object'; +import { getObjectValue } from '../../../__tests__/create-function-test-bed'; +import { FUNCTION_NAMES_DATABASE } from '../../function-names'; +import { Dstdevp } from '../index'; + +describe('Test dstdevp function', () => { + const testFunction = new Dstdevp(FUNCTION_NAMES_DATABASE.DSTDEVP); + + describe('Dstdevp', () => { + it('Value is normal', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 14, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = StringValueObject.create('产量'); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ['苹果树', '>10'], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(2); + }); + + it('Database value test', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ]), + rowCount: 1, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = StringValueObject.create('产量'); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ['苹果树', '>10'], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(ErrorType.VALUE); + + const database2 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', null, true, false, ErrorType.NAME], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 6, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result2 = testFunction.calculate(database2, field, criteria); + expect(getObjectValue(result2)).toStrictEqual(0); + + const database3 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 'test', 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result3 = testFunction.calculate(database3, field, criteria); + expect(getObjectValue(result3)).toStrictEqual(0); + + const database4 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, null, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result4 = testFunction.calculate(database4, field, criteria); + expect(getObjectValue(result4)).toStrictEqual(0); + }); + + it('Field value test', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 14, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = NumberValueObject.create(4); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ['苹果树', '>10'], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(2); + + const field2 = NullValueObject.create(); + const result2 = testFunction.calculate(database, field2, criteria); + expect(getObjectValue(result2)).toStrictEqual(ErrorType.VALUE); + + const field3 = BooleanValueObject.create(true); + const result3 = testFunction.calculate(database, field3, criteria); + expect(getObjectValue(result3)).toStrictEqual(ErrorType.DIV_BY_ZERO); + + const field4 = StringValueObject.create('test'); + const result4 = testFunction.calculate(database, field4, criteria); + expect(getObjectValue(result4)).toStrictEqual(ErrorType.VALUE); + + const field5 = ErrorValueObject.create(ErrorType.NAME); + const result5 = testFunction.calculate(database, field5, criteria); + expect(getObjectValue(result5)).toStrictEqual(ErrorType.VALUE); + + const field6 = ArrayValueObject.create('{1,2,3}'); + const result6 = testFunction.calculate(database, field6, criteria); + expect(getObjectValue(result6)).toStrictEqual(ErrorType.VALUE); + }); + + it('Criteria value test', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 14, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = StringValueObject.create('产量'); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + [null, null], + [null, null], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(2.4324199198877374); + + const criteria2 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ]), + rowCount: 1, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result2 = testFunction.calculate(database, field, criteria2); + expect(getObjectValue(result2)).toStrictEqual(ErrorType.VALUE); + + const criteria3 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + [null, '高度'], + [true, false], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result3 = testFunction.calculate(database, field, criteria3); + expect(getObjectValue(result3)).toStrictEqual(ErrorType.DIV_BY_ZERO); + + const criteria4 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + [null, '高度'], + [true, null], + [null, false], + ]), + rowCount: 3, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result4 = testFunction.calculate(database, field, criteria4); + expect(getObjectValue(result4)).toStrictEqual(2.4324199198877374); + }); + }); +}); diff --git a/packages/engine-formula/src/functions/database/dstdevp/index.ts b/packages/engine-formula/src/functions/database/dstdevp/index.ts new file mode 100644 index 000000000000..7143253f6c24 --- /dev/null +++ b/packages/engine-formula/src/functions/database/dstdevp/index.ts @@ -0,0 +1,83 @@ +/** + * 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 type { BaseValueObject } from '../../../engine/value-object/base-value-object'; +import { checkCriteria, checkDatabase, checkField, isCriteriaMatch } from '../../../basics/database'; +import { ErrorType } from '../../../basics/error-type'; +import { ErrorValueObject } from '../../../engine/value-object/base-value-object'; +import { NumberValueObject } from '../../../engine/value-object/primitive-object'; +import { BaseFunction } from '../../base-function'; + +export class Dstdevp extends BaseFunction { + override minParams = 3; + + override maxParams = 3; + + override calculate(database: BaseValueObject, field: BaseValueObject, criteria: BaseValueObject) { + const { isError: databaseIsError, errorObject: databaseErrorObject, databaseValues } = checkDatabase(database); + + if (databaseIsError) { + return databaseErrorObject as ErrorValueObject; + } + + const { isError: fieldIsError, errorObject: filedErrorObject, fieldIndex } = checkField(field, databaseValues); + + if (fieldIsError) { + return filedErrorObject as ErrorValueObject; + } + + const { isError: criteriaIsError, errorObject: criteriaErrorObject, criteriaValues } = checkCriteria(criteria); + + if (criteriaIsError) { + return criteriaErrorObject as ErrorValueObject; + } + + const values = []; + + let sum = 0; + let count = 0; + + for (let r = 1; r < databaseValues.length; r++) { + const value = databaseValues[r][fieldIndex]; + + if (typeof value !== 'number') { + continue; + } + + if (isCriteriaMatch(criteriaValues, databaseValues, r)) { + values.push(value); + sum += value; + count++; + } + } + + if (count === 0) { + return ErrorValueObject.create(ErrorType.DIV_BY_ZERO); + } + + const mean = sum / count; + + let sumOfSquaresDifferences = 0; + + for (let i = 0; i < count; i++) { + sumOfSquaresDifferences += (values[i] - mean) ** 2; + } + + const result = Math.sqrt(sumOfSquaresDifferences / count); + + return NumberValueObject.create(result); + } +} diff --git a/packages/engine-formula/src/functions/database/dsum/__tests__/index.spec.ts b/packages/engine-formula/src/functions/database/dsum/__tests__/index.spec.ts new file mode 100644 index 000000000000..9d301f599e36 --- /dev/null +++ b/packages/engine-formula/src/functions/database/dsum/__tests__/index.spec.ts @@ -0,0 +1,290 @@ +/** + * 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 { ErrorType } from '../../../../basics/error-type'; +import { ArrayValueObject, transformToValueObject } from '../../../../engine/value-object/array-value-object'; +import { ErrorValueObject } from '../../../../engine/value-object/base-value-object'; +import { BooleanValueObject, NullValueObject, NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object'; +import { getObjectValue } from '../../../__tests__/create-function-test-bed'; +import { FUNCTION_NAMES_DATABASE } from '../../function-names'; +import { Dsum } from '../index'; + +describe('Test dsum function', () => { + const testFunction = new Dsum(FUNCTION_NAMES_DATABASE.DSUM); + + describe('Dsum', () => { + it('Value is normal', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 14, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = StringValueObject.create('产量'); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ['苹果树', '>10'], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(24); + }); + + it('Database value test', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ]), + rowCount: 1, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = StringValueObject.create('产量'); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ['苹果树', '>10'], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(ErrorType.VALUE); + + const database2 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', null, true, false, ErrorType.NAME], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 6, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result2 = testFunction.calculate(database2, field, criteria); + expect(getObjectValue(result2)).toStrictEqual(10); + + const database3 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 'test', 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result3 = testFunction.calculate(database3, field, criteria); + expect(getObjectValue(result3)).toStrictEqual(10); + + const database4 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, null, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result4 = testFunction.calculate(database4, field, criteria); + expect(getObjectValue(result4)).toStrictEqual(10); + }); + + it('Field value test', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 14, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = NumberValueObject.create(4); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ['苹果树', '>10'], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(24); + + const field2 = NullValueObject.create(); + const result2 = testFunction.calculate(database, field2, criteria); + expect(getObjectValue(result2)).toStrictEqual(ErrorType.VALUE); + + const field3 = BooleanValueObject.create(true); + const result3 = testFunction.calculate(database, field3, criteria); + expect(getObjectValue(result3)).toStrictEqual(0); + + const field4 = StringValueObject.create('test'); + const result4 = testFunction.calculate(database, field4, criteria); + expect(getObjectValue(result4)).toStrictEqual(ErrorType.VALUE); + + const field5 = ErrorValueObject.create(ErrorType.NAME); + const result5 = testFunction.calculate(database, field5, criteria); + expect(getObjectValue(result5)).toStrictEqual(ErrorType.VALUE); + + const field6 = ArrayValueObject.create('{1,2,3}'); + const result6 = testFunction.calculate(database, field6, criteria); + expect(getObjectValue(result6)).toStrictEqual(ErrorType.VALUE); + }); + + it('Criteria value test', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 14, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = StringValueObject.create('产量'); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + [null, null], + [null, null], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(57); + + const criteria2 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ]), + rowCount: 1, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result2 = testFunction.calculate(database, field, criteria2); + expect(getObjectValue(result2)).toStrictEqual(ErrorType.VALUE); + + const criteria3 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + [null, '高度'], + [true, false], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result3 = testFunction.calculate(database, field, criteria3); + expect(getObjectValue(result3)).toStrictEqual(0); + + const criteria4 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + [null, '高度'], + [true, null], + [null, false], + ]), + rowCount: 3, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result4 = testFunction.calculate(database, field, criteria4); + expect(getObjectValue(result4)).toStrictEqual(57); + }); + }); +}); diff --git a/packages/engine-formula/src/functions/database/dsum/index.ts b/packages/engine-formula/src/functions/database/dsum/index.ts new file mode 100644 index 000000000000..2dc996de16b5 --- /dev/null +++ b/packages/engine-formula/src/functions/database/dsum/index.ts @@ -0,0 +1,62 @@ +/** + * 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 type { BaseValueObject, ErrorValueObject } from '../../../engine/value-object/base-value-object'; +import { checkCriteria, checkDatabase, checkField, isCriteriaMatch } from '../../../basics/database'; +import { NumberValueObject } from '../../../engine/value-object/primitive-object'; +import { BaseFunction } from '../../base-function'; + +export class Dsum extends BaseFunction { + override minParams = 3; + + override maxParams = 3; + + override calculate(database: BaseValueObject, field: BaseValueObject, criteria: BaseValueObject) { + const { isError: databaseIsError, errorObject: databaseErrorObject, databaseValues } = checkDatabase(database); + + if (databaseIsError) { + return databaseErrorObject as ErrorValueObject; + } + + const { isError: fieldIsError, errorObject: filedErrorObject, fieldIndex } = checkField(field, databaseValues); + + if (fieldIsError) { + return filedErrorObject as ErrorValueObject; + } + + const { isError: criteriaIsError, errorObject: criteriaErrorObject, criteriaValues } = checkCriteria(criteria); + + if (criteriaIsError) { + return criteriaErrorObject as ErrorValueObject; + } + + let result = 0; + + for (let r = 1; r < databaseValues.length; r++) { + const value = databaseValues[r][fieldIndex]; + + if (typeof value !== 'number') { + continue; + } + + if (isCriteriaMatch(criteriaValues, databaseValues, r)) { + result += value; + } + } + + return NumberValueObject.create(result); + } +} diff --git a/packages/engine-formula/src/functions/database/dvar/__tests__/index.spec.ts b/packages/engine-formula/src/functions/database/dvar/__tests__/index.spec.ts new file mode 100644 index 000000000000..77f01833b7e4 --- /dev/null +++ b/packages/engine-formula/src/functions/database/dvar/__tests__/index.spec.ts @@ -0,0 +1,290 @@ +/** + * 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 { ErrorType } from '../../../../basics/error-type'; +import { ArrayValueObject, transformToValueObject } from '../../../../engine/value-object/array-value-object'; +import { ErrorValueObject } from '../../../../engine/value-object/base-value-object'; +import { BooleanValueObject, NullValueObject, NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object'; +import { getObjectValue } from '../../../__tests__/create-function-test-bed'; +import { FUNCTION_NAMES_DATABASE } from '../../function-names'; +import { Dvar } from '../index'; + +describe('Test dvar function', () => { + const testFunction = new Dvar(FUNCTION_NAMES_DATABASE.DVAR); + + describe('Dvar', () => { + it('Value is normal', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 14, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = StringValueObject.create('产量'); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ['苹果树', '>10'], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(8); + }); + + it('Database value test', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ]), + rowCount: 1, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = StringValueObject.create('产量'); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ['苹果树', '>10'], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(ErrorType.VALUE); + + const database2 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', null, true, false, ErrorType.NAME], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 6, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result2 = testFunction.calculate(database2, field, criteria); + expect(getObjectValue(result2)).toStrictEqual(ErrorType.DIV_BY_ZERO); + + const database3 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 'test', 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result3 = testFunction.calculate(database3, field, criteria); + expect(getObjectValue(result3)).toStrictEqual(ErrorType.DIV_BY_ZERO); + + const database4 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, null, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result4 = testFunction.calculate(database4, field, criteria); + expect(getObjectValue(result4)).toStrictEqual(ErrorType.DIV_BY_ZERO); + }); + + it('Field value test', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 14, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = NumberValueObject.create(4); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ['苹果树', '>10'], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(8); + + const field2 = NullValueObject.create(); + const result2 = testFunction.calculate(database, field2, criteria); + expect(getObjectValue(result2)).toStrictEqual(ErrorType.VALUE); + + const field3 = BooleanValueObject.create(true); + const result3 = testFunction.calculate(database, field3, criteria); + expect(getObjectValue(result3)).toStrictEqual(ErrorType.DIV_BY_ZERO); + + const field4 = StringValueObject.create('test'); + const result4 = testFunction.calculate(database, field4, criteria); + expect(getObjectValue(result4)).toStrictEqual(ErrorType.VALUE); + + const field5 = ErrorValueObject.create(ErrorType.NAME); + const result5 = testFunction.calculate(database, field5, criteria); + expect(getObjectValue(result5)).toStrictEqual(ErrorType.VALUE); + + const field6 = ArrayValueObject.create('{1,2,3}'); + const result6 = testFunction.calculate(database, field6, criteria); + expect(getObjectValue(result6)).toStrictEqual(ErrorType.VALUE); + }); + + it('Criteria value test', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 14, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = StringValueObject.create('产量'); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + [null, null], + [null, null], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(7.1); + + const criteria2 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ]), + rowCount: 1, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result2 = testFunction.calculate(database, field, criteria2); + expect(getObjectValue(result2)).toStrictEqual(ErrorType.VALUE); + + const criteria3 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + [null, '高度'], + [true, false], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result3 = testFunction.calculate(database, field, criteria3); + expect(getObjectValue(result3)).toStrictEqual(ErrorType.DIV_BY_ZERO); + + const criteria4 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + [null, '高度'], + [true, null], + [null, false], + ]), + rowCount: 3, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result4 = testFunction.calculate(database, field, criteria4); + expect(getObjectValue(result4)).toStrictEqual(7.1); + }); + }); +}); diff --git a/packages/engine-formula/src/functions/database/dvar/index.ts b/packages/engine-formula/src/functions/database/dvar/index.ts new file mode 100644 index 000000000000..c1753f73d548 --- /dev/null +++ b/packages/engine-formula/src/functions/database/dvar/index.ts @@ -0,0 +1,83 @@ +/** + * 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 type { BaseValueObject } from '../../../engine/value-object/base-value-object'; +import { checkCriteria, checkDatabase, checkField, isCriteriaMatch } from '../../../basics/database'; +import { ErrorType } from '../../../basics/error-type'; +import { ErrorValueObject } from '../../../engine/value-object/base-value-object'; +import { NumberValueObject } from '../../../engine/value-object/primitive-object'; +import { BaseFunction } from '../../base-function'; + +export class Dvar extends BaseFunction { + override minParams = 3; + + override maxParams = 3; + + override calculate(database: BaseValueObject, field: BaseValueObject, criteria: BaseValueObject) { + const { isError: databaseIsError, errorObject: databaseErrorObject, databaseValues } = checkDatabase(database); + + if (databaseIsError) { + return databaseErrorObject as ErrorValueObject; + } + + const { isError: fieldIsError, errorObject: filedErrorObject, fieldIndex } = checkField(field, databaseValues); + + if (fieldIsError) { + return filedErrorObject as ErrorValueObject; + } + + const { isError: criteriaIsError, errorObject: criteriaErrorObject, criteriaValues } = checkCriteria(criteria); + + if (criteriaIsError) { + return criteriaErrorObject as ErrorValueObject; + } + + const values = []; + + let sum = 0; + let count = 0; + + for (let r = 1; r < databaseValues.length; r++) { + const value = databaseValues[r][fieldIndex]; + + if (typeof value !== 'number') { + continue; + } + + if (isCriteriaMatch(criteriaValues, databaseValues, r)) { + values.push(value); + sum += value; + count++; + } + } + + if (count <= 1) { + return ErrorValueObject.create(ErrorType.DIV_BY_ZERO); + } + + const mean = sum / count; + + let sumOfSquaresDifferences = 0; + + for (let i = 0; i < count; i++) { + sumOfSquaresDifferences += (values[i] - mean) ** 2; + } + + const result = sumOfSquaresDifferences / (count - 1); + + return NumberValueObject.create(result); + } +} diff --git a/packages/engine-formula/src/functions/database/dvarp/__tests__/index.spec.ts b/packages/engine-formula/src/functions/database/dvarp/__tests__/index.spec.ts new file mode 100644 index 000000000000..9737072c8a3e --- /dev/null +++ b/packages/engine-formula/src/functions/database/dvarp/__tests__/index.spec.ts @@ -0,0 +1,290 @@ +/** + * 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 { ErrorType } from '../../../../basics/error-type'; +import { ArrayValueObject, transformToValueObject } from '../../../../engine/value-object/array-value-object'; +import { ErrorValueObject } from '../../../../engine/value-object/base-value-object'; +import { BooleanValueObject, NullValueObject, NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object'; +import { getObjectValue } from '../../../__tests__/create-function-test-bed'; +import { FUNCTION_NAMES_DATABASE } from '../../function-names'; +import { Dvarp } from '../index'; + +describe('Test dvarp function', () => { + const testFunction = new Dvarp(FUNCTION_NAMES_DATABASE.DVARP); + + describe('Dvarp', () => { + it('Value is normal', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 14, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = StringValueObject.create('产量'); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ['苹果树', '>10'], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(4); + }); + + it('Database value test', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ]), + rowCount: 1, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = StringValueObject.create('产量'); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ['苹果树', '>10'], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(ErrorType.VALUE); + + const database2 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', null, true, false, ErrorType.NAME], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 6, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result2 = testFunction.calculate(database2, field, criteria); + expect(getObjectValue(result2)).toStrictEqual(0); + + const database3 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 'test', 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result3 = testFunction.calculate(database3, field, criteria); + expect(getObjectValue(result3)).toStrictEqual(0); + + const database4 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, null, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result4 = testFunction.calculate(database4, field, criteria); + expect(getObjectValue(result4)).toStrictEqual(0); + }); + + it('Field value test', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 14, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = NumberValueObject.create(4); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ['苹果树', '>10'], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(4); + + const field2 = NullValueObject.create(); + const result2 = testFunction.calculate(database, field2, criteria); + expect(getObjectValue(result2)).toStrictEqual(ErrorType.VALUE); + + const field3 = BooleanValueObject.create(true); + const result3 = testFunction.calculate(database, field3, criteria); + expect(getObjectValue(result3)).toStrictEqual(ErrorType.DIV_BY_ZERO); + + const field4 = StringValueObject.create('test'); + const result4 = testFunction.calculate(database, field4, criteria); + expect(getObjectValue(result4)).toStrictEqual(ErrorType.VALUE); + + const field5 = ErrorValueObject.create(ErrorType.NAME); + const result5 = testFunction.calculate(database, field5, criteria); + expect(getObjectValue(result5)).toStrictEqual(ErrorType.VALUE); + + const field6 = ArrayValueObject.create('{1,2,3}'); + const result6 = testFunction.calculate(database, field6, criteria); + expect(getObjectValue(result6)).toStrictEqual(ErrorType.VALUE); + }); + + it('Criteria value test', () => { + const database = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度', '年数', '产量', '利润'], + ['苹果树', 18, 20, 14, 105], + ['梨树', 1.2, 1.2, 10, 96], + ['樱桃树', 1.3, 14, 9, 105], + ['苹果树', 14, 15, 10, 75], + ['梨树', 9, 8, 8, 76.8], + ['苹果树', 8, 9, 6, 45], + ]), + rowCount: 7, + columnCount: 5, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const field = StringValueObject.create('产量'); + const criteria = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + [null, null], + [null, null], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result = testFunction.calculate(database, field, criteria); + expect(getObjectValue(result)).toStrictEqual(5.916666666666667); + + const criteria2 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + ['树种', '高度'], + ]), + rowCount: 1, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result2 = testFunction.calculate(database, field, criteria2); + expect(getObjectValue(result2)).toStrictEqual(ErrorType.VALUE); + + const criteria3 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + [null, '高度'], + [true, false], + ]), + rowCount: 2, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result3 = testFunction.calculate(database, field, criteria3); + expect(getObjectValue(result3)).toStrictEqual(ErrorType.DIV_BY_ZERO); + + const criteria4 = ArrayValueObject.create({ + calculateValueList: transformToValueObject([ + [null, '高度'], + [true, null], + [null, false], + ]), + rowCount: 3, + columnCount: 2, + unitId: '', + sheetId: '', + row: 0, + column: 0, + }); + const result4 = testFunction.calculate(database, field, criteria4); + expect(getObjectValue(result4)).toStrictEqual(5.916666666666667); + }); + }); +}); diff --git a/packages/engine-formula/src/functions/database/dvarp/index.ts b/packages/engine-formula/src/functions/database/dvarp/index.ts new file mode 100644 index 000000000000..7f724b843832 --- /dev/null +++ b/packages/engine-formula/src/functions/database/dvarp/index.ts @@ -0,0 +1,83 @@ +/** + * 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 type { BaseValueObject } from '../../../engine/value-object/base-value-object'; +import { checkCriteria, checkDatabase, checkField, isCriteriaMatch } from '../../../basics/database'; +import { ErrorType } from '../../../basics/error-type'; +import { ErrorValueObject } from '../../../engine/value-object/base-value-object'; +import { NumberValueObject } from '../../../engine/value-object/primitive-object'; +import { BaseFunction } from '../../base-function'; + +export class Dvarp extends BaseFunction { + override minParams = 3; + + override maxParams = 3; + + override calculate(database: BaseValueObject, field: BaseValueObject, criteria: BaseValueObject) { + const { isError: databaseIsError, errorObject: databaseErrorObject, databaseValues } = checkDatabase(database); + + if (databaseIsError) { + return databaseErrorObject as ErrorValueObject; + } + + const { isError: fieldIsError, errorObject: filedErrorObject, fieldIndex } = checkField(field, databaseValues); + + if (fieldIsError) { + return filedErrorObject as ErrorValueObject; + } + + const { isError: criteriaIsError, errorObject: criteriaErrorObject, criteriaValues } = checkCriteria(criteria); + + if (criteriaIsError) { + return criteriaErrorObject as ErrorValueObject; + } + + const values = []; + + let sum = 0; + let count = 0; + + for (let r = 1; r < databaseValues.length; r++) { + const value = databaseValues[r][fieldIndex]; + + if (typeof value !== 'number') { + continue; + } + + if (isCriteriaMatch(criteriaValues, databaseValues, r)) { + values.push(value); + sum += value; + count++; + } + } + + if (count === 0) { + return ErrorValueObject.create(ErrorType.DIV_BY_ZERO); + } + + const mean = sum / count; + + let sumOfSquaresDifferences = 0; + + for (let i = 0; i < count; i++) { + sumOfSquaresDifferences += (values[i] - mean) ** 2; + } + + const result = sumOfSquaresDifferences / count; + + return NumberValueObject.create(result); + } +} diff --git a/packages/engine-formula/src/functions/database/function-map.ts b/packages/engine-formula/src/functions/database/function-map.ts index c042e8c02b14..2cf04260a91b 100644 --- a/packages/engine-formula/src/functions/database/function-map.ts +++ b/packages/engine-formula/src/functions/database/function-map.ts @@ -22,6 +22,10 @@ import { Dmax } from './dmax'; import { Dmin } from './dmin'; import { Dproduct } from './dproduct'; import { Dstdev } from './dstdev'; +import { Dstdevp } from './dstdevp'; +import { Dsum } from './dsum'; +import { Dvar } from './dvar'; +import { Dvarp } from './dvarp'; import { FUNCTION_NAMES_DATABASE } from './function-names'; export const functionDatabase = [ @@ -33,4 +37,8 @@ export const functionDatabase = [ [Dmin, FUNCTION_NAMES_DATABASE.DMIN], [Dproduct, FUNCTION_NAMES_DATABASE.DPRODUCT], [Dstdev, FUNCTION_NAMES_DATABASE.DSTDEV], + [Dstdevp, FUNCTION_NAMES_DATABASE.DSTDEVP], + [Dsum, FUNCTION_NAMES_DATABASE.DSUM], + [Dvar, FUNCTION_NAMES_DATABASE.DVAR], + [Dvarp, FUNCTION_NAMES_DATABASE.DVARP], ]; diff --git a/packages/sheets-formula-ui/src/locale/function-list/database/en-US.ts b/packages/sheets-formula-ui/src/locale/function-list/database/en-US.ts index 305f4b7d0237..d9244e62e434 100644 --- a/packages/sheets-formula-ui/src/locale/function-list/database/en-US.ts +++ b/packages/sheets-formula-ui/src/locale/function-list/database/en-US.ts @@ -145,8 +145,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: 'database', detail: 'The range of cells that makes up the list or database.' }, + field: { name: 'field', detail: 'Indicates which column is used in the function.' }, + criteria: { name: 'criteria', detail: 'The range of cells that contains the conditions you specify.' }, }, }, DSUM: { @@ -159,8 +160,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: 'database', detail: 'The range of cells that makes up the list or database.' }, + field: { name: 'field', detail: 'Indicates which column is used in the function.' }, + criteria: { name: 'criteria', detail: 'The range of cells that contains the conditions you specify.' }, }, }, DVAR: { @@ -173,8 +175,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: 'database', detail: 'The range of cells that makes up the list or database.' }, + field: { name: 'field', detail: 'Indicates which column is used in the function.' }, + criteria: { name: 'criteria', detail: 'The range of cells that contains the conditions you specify.' }, }, }, DVARP: { @@ -187,8 +190,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: 'database', detail: 'The range of cells that makes up the list or database.' }, + field: { name: 'field', detail: 'Indicates which column is used in the function.' }, + criteria: { name: 'criteria', detail: 'The range of cells that contains the conditions you specify.' }, }, }, }; diff --git a/packages/sheets-formula-ui/src/locale/function-list/database/ja-JP.ts b/packages/sheets-formula-ui/src/locale/function-list/database/ja-JP.ts index a10d67cd4155..f9422cdfc070 100644 --- a/packages/sheets-formula-ui/src/locale/function-list/database/ja-JP.ts +++ b/packages/sheets-formula-ui/src/locale/function-list/database/ja-JP.ts @@ -145,8 +145,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: 'データベース', detail: 'リストまたはデータベースを構成するセル範囲を指定します。' }, + field: { name: 'フィールド', detail: '関数の中で使用する列を指定します。' }, + criteria: { name: '検索条件', detail: '指定した条件が設定されているセル範囲を指定します。' }, }, }, DSUM: { @@ -159,8 +160,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: 'データベース', detail: 'リストまたはデータベースを構成するセル範囲を指定します。' }, + field: { name: 'フィールド', detail: '関数の中で使用する列を指定します。' }, + criteria: { name: '検索条件', detail: '指定した条件が設定されているセル範囲を指定します。' }, }, }, DVAR: { @@ -173,8 +175,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: 'データベース', detail: 'リストまたはデータベースを構成するセル範囲を指定します。' }, + field: { name: 'フィールド', detail: '関数の中で使用する列を指定します。' }, + criteria: { name: '検索条件', detail: '指定した条件が設定されているセル範囲を指定します。' }, }, }, DVARP: { @@ -187,8 +190,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: 'データベース', detail: 'リストまたはデータベースを構成するセル範囲を指定します。' }, + field: { name: 'フィールド', detail: '関数の中で使用する列を指定します。' }, + criteria: { name: '検索条件', detail: '指定した条件が設定されているセル範囲を指定します。' }, }, }, }; diff --git a/packages/sheets-formula-ui/src/locale/function-list/database/ru-RU.ts b/packages/sheets-formula-ui/src/locale/function-list/database/ru-RU.ts index 32fdbe3c8ec3..cbbb2678efd7 100644 --- a/packages/sheets-formula-ui/src/locale/function-list/database/ru-RU.ts +++ b/packages/sheets-formula-ui/src/locale/function-list/database/ru-RU.ts @@ -145,8 +145,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'первый' }, - number2: { name: 'number2', detail: 'второй' }, + database: { name: 'База данных', detail: 'интервал ячеек, образующих список или базу данных.' }, + field: { name: 'поле', detail: 'столбец, используемый функцией.' }, + criteria: { name: 'условия', detail: 'диапазон ячеек, который содержит задаваемые условия.' }, }, }, DSUM: { @@ -159,8 +160,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'первый' }, - number2: { name: 'number2', detail: 'второй' }, + database: { name: 'База данных', detail: 'интервал ячеек, образующих список или базу данных.' }, + field: { name: 'поле', detail: 'столбец, используемый функцией.' }, + criteria: { name: 'условия', detail: 'диапазон ячеек, который содержит задаваемые условия.' }, }, }, DVAR: { @@ -173,8 +175,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'первый' }, - number2: { name: 'number2', detail: 'второй' }, + database: { name: 'База данных', detail: 'интервал ячеек, образующих список или базу данных.' }, + field: { name: 'поле', detail: 'столбец, используемый функцией.' }, + criteria: { name: 'условия', detail: 'диапазон ячеек, который содержит задаваемые условия.' }, }, }, DVARP: { @@ -187,8 +190,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'первый' }, - number2: { name: 'number2', detail: 'второй' }, + database: { name: 'База данных', detail: 'интервал ячеек, образующих список или базу данных.' }, + field: { name: 'поле', detail: 'столбец, используемый функцией.' }, + criteria: { name: 'условия', detail: 'диапазон ячеек, который содержит задаваемые условия.' }, }, }, }; diff --git a/packages/sheets-formula-ui/src/locale/function-list/database/vi-VN.ts b/packages/sheets-formula-ui/src/locale/function-list/database/vi-VN.ts index 3a81a71b2026..1cdcfb80983d 100644 --- a/packages/sheets-formula-ui/src/locale/function-list/database/vi-VN.ts +++ b/packages/sheets-formula-ui/src/locale/function-list/database/vi-VN.ts @@ -145,8 +145,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'Tham số thứ nhất' }, - number2: { name: 'number2', detail: 'Tham số thứ hai' }, + database: { name: 'cơ sở dữ liệu', detail: 'là phạm vi ô tạo thành danh sách hoặc cơ sở dữ liệu.' }, + field: { name: 'cánh đồng', detail: 'chỉ rõ cột nào được dùng trong hàm.' }, + criteria: { name: 'tiêu chuẩn', detail: 'là phạm vi ô chứa các điều kiện mà bạn chỉ rõ.' }, }, }, DSUM: { @@ -159,8 +160,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'Tham số thứ nhất' }, - number2: { name: 'number2', detail: 'Tham số thứ hai' }, + database: { name: 'cơ sở dữ liệu', detail: 'là phạm vi ô tạo thành danh sách hoặc cơ sở dữ liệu.' }, + field: { name: 'cánh đồng', detail: 'chỉ rõ cột nào được dùng trong hàm.' }, + criteria: { name: 'tiêu chuẩn', detail: 'là phạm vi ô chứa các điều kiện mà bạn chỉ rõ.' }, }, }, DVAR: { @@ -173,8 +175,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'Tham số thứ nhất' }, - number2: { name: 'number2', detail: 'Tham số thứ hai' }, + database: { name: 'cơ sở dữ liệu', detail: 'là phạm vi ô tạo thành danh sách hoặc cơ sở dữ liệu.' }, + field: { name: 'cánh đồng', detail: 'chỉ rõ cột nào được dùng trong hàm.' }, + criteria: { name: 'tiêu chuẩn', detail: 'là phạm vi ô chứa các điều kiện mà bạn chỉ rõ.' }, }, }, DVARP: { @@ -187,8 +190,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'Tham số thứ nhất' }, - number2: { name: 'number2', detail: 'Tham số thứ hai' }, + database: { name: 'cơ sở dữ liệu', detail: 'là phạm vi ô tạo thành danh sách hoặc cơ sở dữ liệu.' }, + field: { name: 'cánh đồng', detail: 'chỉ rõ cột nào được dùng trong hàm.' }, + criteria: { name: 'tiêu chuẩn', detail: 'là phạm vi ô chứa các điều kiện mà bạn chỉ rõ.' }, }, }, }; diff --git a/packages/sheets-formula-ui/src/locale/function-list/database/zh-CN.ts b/packages/sheets-formula-ui/src/locale/function-list/database/zh-CN.ts index 2d3c95af14d0..6743c607d827 100644 --- a/packages/sheets-formula-ui/src/locale/function-list/database/zh-CN.ts +++ b/packages/sheets-formula-ui/src/locale/function-list/database/zh-CN.ts @@ -145,8 +145,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: '数据库', detail: '构成列表或数据库的单元格区域。' }, + field: { name: '字段', detail: '指定函数所使用的列。' }, + criteria: { name: '条件', detail: '包含指定条件的单元格区域。' }, }, }, DSUM: { @@ -159,8 +160,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: '数据库', detail: '构成列表或数据库的单元格区域。' }, + field: { name: '字段', detail: '指定函数所使用的列。' }, + criteria: { name: '条件', detail: '包含指定条件的单元格区域。' }, }, }, DVAR: { @@ -173,8 +175,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: '数据库', detail: '构成列表或数据库的单元格区域。' }, + field: { name: '字段', detail: '指定函数所使用的列。' }, + criteria: { name: '条件', detail: '包含指定条件的单元格区域。' }, }, }, DVARP: { @@ -187,8 +190,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: '数据库', detail: '构成列表或数据库的单元格区域。' }, + field: { name: '字段', detail: '指定函数所使用的列。' }, + criteria: { name: '条件', detail: '包含指定条件的单元格区域。' }, }, }, }; diff --git a/packages/sheets-formula-ui/src/locale/function-list/database/zh-TW.ts b/packages/sheets-formula-ui/src/locale/function-list/database/zh-TW.ts index a4e460a1d928..d37e2b6fc464 100644 --- a/packages/sheets-formula-ui/src/locale/function-list/database/zh-TW.ts +++ b/packages/sheets-formula-ui/src/locale/function-list/database/zh-TW.ts @@ -145,8 +145,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: '資料庫', detail: '組成清單或資料庫的儲存格範圍。' }, + field: { name: '欄位', detail: '指出函數中所使用的欄。' }, + criteria: { name: '條件', detail: '含有指定條件的儲存格範圍。' }, }, }, DSUM: { @@ -159,8 +160,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: '資料庫', detail: '組成清單或資料庫的儲存格範圍。' }, + field: { name: '欄位', detail: '指出函數中所使用的欄。' }, + criteria: { name: '條件', detail: '含有指定條件的儲存格範圍。' }, }, }, DVAR: { @@ -173,8 +175,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: '資料庫', detail: '組成清單或資料庫的儲存格範圍。' }, + field: { name: '欄位', detail: '指出函數中所使用的欄。' }, + criteria: { name: '條件', detail: '含有指定條件的儲存格範圍。' }, }, }, DVARP: { @@ -187,8 +190,9 @@ export default { }, ], functionParameter: { - number1: { name: 'number1', detail: 'first' }, - number2: { name: 'number2', detail: 'second' }, + database: { name: '資料庫', detail: '組成清單或資料庫的儲存格範圍。' }, + field: { name: '欄位', detail: '指出函數中所使用的欄。' }, + criteria: { name: '條件', detail: '含有指定條件的儲存格範圍。' }, }, }, }; diff --git a/packages/sheets-formula/src/services/function-list/database.ts b/packages/sheets-formula/src/services/function-list/database.ts index 7cac7f963a1c..aeab487208df 100644 --- a/packages/sheets-formula/src/services/function-list/database.ts +++ b/packages/sheets-formula/src/services/function-list/database.ts @@ -256,16 +256,23 @@ export const FUNCTION_LIST_DATABASE: IFunctionInfo[] = [ abstract: 'formula.functionList.DSTDEVP.abstract', functionParameter: [ { - name: 'formula.functionList.DSTDEVP.functionParameter.number1.name', - detail: 'formula.functionList.DSTDEVP.functionParameter.number1.detail', - example: 'A1:A20', + name: 'formula.functionList.DSTDEVP.functionParameter.database.name', + detail: 'formula.functionList.DSTDEVP.functionParameter.database.detail', + example: 'A4:E10', require: 1, repeat: 0, }, { - name: 'formula.functionList.DSTDEVP.functionParameter.number2.name', - detail: 'formula.functionList.DSTDEVP.functionParameter.number2.detail', - example: 'A1:A20', + name: 'formula.functionList.DSTDEVP.functionParameter.field.name', + detail: 'formula.functionList.DSTDEVP.functionParameter.field.detail', + example: 'D4', + require: 1, + repeat: 0, + }, + { + name: 'formula.functionList.DSTDEVP.functionParameter.criteria.name', + detail: 'formula.functionList.DSTDEVP.functionParameter.criteria.detail', + example: 'A1:B2', require: 1, repeat: 0, }, @@ -278,16 +285,23 @@ export const FUNCTION_LIST_DATABASE: IFunctionInfo[] = [ abstract: 'formula.functionList.DSUM.abstract', functionParameter: [ { - name: 'formula.functionList.DSUM.functionParameter.number1.name', - detail: 'formula.functionList.DSUM.functionParameter.number1.detail', - example: 'A1:A20', + name: 'formula.functionList.DSUM.functionParameter.database.name', + detail: 'formula.functionList.DSUM.functionParameter.database.detail', + example: 'A4:E10', require: 1, repeat: 0, }, { - name: 'formula.functionList.DSUM.functionParameter.number2.name', - detail: 'formula.functionList.DSUM.functionParameter.number2.detail', - example: 'A1:A20', + name: 'formula.functionList.DSUM.functionParameter.field.name', + detail: 'formula.functionList.DSUM.functionParameter.field.detail', + example: 'D4', + require: 1, + repeat: 0, + }, + { + name: 'formula.functionList.DSUM.functionParameter.criteria.name', + detail: 'formula.functionList.DSUM.functionParameter.criteria.detail', + example: 'A1:B2', require: 1, repeat: 0, }, @@ -300,16 +314,23 @@ export const FUNCTION_LIST_DATABASE: IFunctionInfo[] = [ abstract: 'formula.functionList.DVAR.abstract', functionParameter: [ { - name: 'formula.functionList.DVAR.functionParameter.number1.name', - detail: 'formula.functionList.DVAR.functionParameter.number1.detail', - example: 'A1:A20', + name: 'formula.functionList.DVAR.functionParameter.database.name', + detail: 'formula.functionList.DVAR.functionParameter.database.detail', + example: 'A4:E10', require: 1, repeat: 0, }, { - name: 'formula.functionList.DVAR.functionParameter.number2.name', - detail: 'formula.functionList.DVAR.functionParameter.number2.detail', - example: 'A1:A20', + name: 'formula.functionList.DVAR.functionParameter.field.name', + detail: 'formula.functionList.DVAR.functionParameter.field.detail', + example: 'D4', + require: 1, + repeat: 0, + }, + { + name: 'formula.functionList.DVAR.functionParameter.criteria.name', + detail: 'formula.functionList.DVAR.functionParameter.criteria.detail', + example: 'A1:B2', require: 1, repeat: 0, }, @@ -322,16 +343,23 @@ export const FUNCTION_LIST_DATABASE: IFunctionInfo[] = [ abstract: 'formula.functionList.DVARP.abstract', functionParameter: [ { - name: 'formula.functionList.DVARP.functionParameter.number1.name', - detail: 'formula.functionList.DVARP.functionParameter.number1.detail', - example: 'A1:A20', + name: 'formula.functionList.DVARP.functionParameter.database.name', + detail: 'formula.functionList.DVARP.functionParameter.database.detail', + example: 'A4:E10', require: 1, repeat: 0, }, { - name: 'formula.functionList.DVARP.functionParameter.number2.name', - detail: 'formula.functionList.DVARP.functionParameter.number2.detail', - example: 'A1:A20', + name: 'formula.functionList.DVARP.functionParameter.field.name', + detail: 'formula.functionList.DVARP.functionParameter.field.detail', + example: 'D4', + require: 1, + repeat: 0, + }, + { + name: 'formula.functionList.DVARP.functionParameter.criteria.name', + detail: 'formula.functionList.DVARP.functionParameter.criteria.detail', + example: 'A1:B2', require: 1, repeat: 0, },