Skip to content

Commit

Permalink
feat(formula): add arabic/roman function
Browse files Browse the repository at this point in the history
  • Loading branch information
wpxp123456 authored and wpxp123456 committed Oct 10, 2024
1 parent 29d3381 commit bb99a98
Show file tree
Hide file tree
Showing 11 changed files with 564 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* 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_MATH } from '../../function-names';
import { Arabic } from '../index';

describe('Test arabic function', () => {
const testFunction = new Arabic(FUNCTION_NAMES_MATH.ARABIC);

describe('Arabic', () => {
it('Value is normal', () => {
const text = StringValueObject.create('mcmxii');
const result = testFunction.calculate(text);
expect(getObjectValue(result)).toStrictEqual(1912);
});

it('Result is negtive', () => {
const text = StringValueObject.create('-mcmxii');
const result = testFunction.calculate(text);
expect(getObjectValue(result)).toStrictEqual(-1912);
});

it('Value is number', () => {
const text = NumberValueObject.create(1);
const result = testFunction.calculate(text);
expect(getObjectValue(result)).toStrictEqual(ErrorType.VALUE);
});

it('Value is boolean', () => {
const text = BooleanValueObject.create(true);
const result = testFunction.calculate(text);
expect(getObjectValue(result)).toStrictEqual(ErrorType.VALUE);
});

it('Value is null', () => {
const text = NullValueObject.create();
const result = testFunction.calculate(text);
expect(getObjectValue(result)).toStrictEqual(0);
});

it('Value is error', () => {
const text = ErrorValueObject.create(ErrorType.NAME);
const result = testFunction.calculate(text);
expect(getObjectValue(result)).toStrictEqual(ErrorType.NAME);
});

it('Value is array', () => {
const text = ArrayValueObject.create({
calculateValueList: transformToValueObject([
[1, ' ', 'mxmvii', true, false, null],
[0, 'llll', 'LVII', 'test', -3, ErrorType.NAME],
]),
rowCount: 2,
columnCount: 6,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const result = testFunction.calculate(text);
expect(getObjectValue(result)).toStrictEqual([
[ErrorType.VALUE, ErrorType.VALUE, 1997, ErrorType.VALUE, ErrorType.VALUE, 0],
[ErrorType.VALUE, ErrorType.VALUE, 57, ErrorType.VALUE, ErrorType.VALUE, ErrorType.NAME],
]);

const text2 = ArrayValueObject.create({
calculateValueList: transformToValueObject([
['LVII'],
]),
rowCount: 1,
columnCount: 1,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const result2 = testFunction.calculate(text2);
expect(getObjectValue(result2)).toStrictEqual(57);
});
});
});
104 changes: 104 additions & 0 deletions packages/engine-formula/src/functions/math/arabic/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* 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 { type BaseValueObject, ErrorValueObject } from '../../../engine/value-object/base-value-object';
import { NumberValueObject } from '../../../engine/value-object/primitive-object';
import { BaseFunction } from '../../base-function';
import type { ArrayValueObject } from '../../../engine/value-object/array-value-object';

export class Arabic extends BaseFunction {
override minParams = 1;

override maxParams = 1;

override calculate(text: BaseValueObject): BaseValueObject {
if (text.isArray()) {
const resultArray = (text as ArrayValueObject).mapValue((textObject) => this._handleSingleObject(textObject));

if ((resultArray as ArrayValueObject).getRowCount() === 1 && (resultArray as ArrayValueObject).getColumnCount() === 1) {
return (resultArray as ArrayValueObject).get(0, 0) as BaseValueObject;
}

return resultArray;
}

return this._handleSingleObject(text);
}

// eslint-disable-next-line
private _handleSingleObject(text: BaseValueObject): BaseValueObject {
if (text.isError()) {
return text;
}

if (text.isNull()) {
return NumberValueObject.create(0);
}

if (text.isBoolean() || text.isNumber()) {
return ErrorValueObject.create(ErrorType.VALUE);
}

let textValue = text.getValue().toLocaleString().toLocaleUpperCase();

if (textValue.length > 255) {
return ErrorValueObject.create(ErrorType.VALUE);
}

const isNegtive = textValue.startsWith('-');

if (isNegtive) {
textValue = textValue.slice(1);
}

let result = 0;

for (let i = 0; i < textValue.length; i++) {
const currentCharValue = this._romanToNumberMap.get(textValue[i]) || 0;
const nextCharValue = this._romanToNumberMap.get(textValue[i + 1]) || 0;
const nextnextCharValue = this._romanToNumberMap.get(textValue[i + 2]) || 0;
const nextnextnextCharValue = this._romanToNumberMap.get(textValue[i + 3]) || 0;

if (
!currentCharValue ||
(nextnextCharValue >= nextCharValue && nextnextCharValue > currentCharValue) ||
(currentCharValue === nextCharValue && currentCharValue === nextnextCharValue && currentCharValue === nextnextnextCharValue) ||
currentCharValue === nextCharValue / 2
) {
return ErrorValueObject.create(ErrorType.VALUE);
}

if (currentCharValue < nextCharValue) {
result -= currentCharValue;
} else {
result += currentCharValue;
}
}

return NumberValueObject.create(isNegtive ? -result : result);
}

private _romanToNumberMap = new Map<string, number>([
['I', 1],
['V', 5],
['X', 10],
['L', 50],
['C', 100],
['D', 500],
['M', 1000],
]);
}
4 changes: 4 additions & 0 deletions packages/engine-formula/src/functions/math/function-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Acos } from './acos';
import { Acosh } from './acosh';
import { Acot } from './acot';
import { Acoth } from './acoth';
import { Arabic } from './arabic';
import { Asin } from './asin';
import { Asinh } from './asinh';
import { Atan } from './atan';
Expand Down Expand Up @@ -68,6 +69,7 @@ import { Radians } from './radians';
import { Rand } from './rand';
import { Randarray } from './randarray';
import { Randbetween } from './randbetween';
import { Roman } from './roman';
import { Round } from './round';
import { Rounddown } from './rounddown';
import { Roundup } from './roundup';
Expand Down Expand Up @@ -99,6 +101,7 @@ export const functionMath = [
[Acosh, FUNCTION_NAMES_MATH.ACOSH],
[Acot, FUNCTION_NAMES_MATH.ACOT],
[Acoth, FUNCTION_NAMES_MATH.ACOTH],
[Arabic, FUNCTION_NAMES_MATH.ARABIC],
[Asin, FUNCTION_NAMES_MATH.ASIN],
[Asinh, FUNCTION_NAMES_MATH.ASINH],
[Atan, FUNCTION_NAMES_MATH.ATAN],
Expand Down Expand Up @@ -147,6 +150,7 @@ export const functionMath = [
[Rand, FUNCTION_NAMES_MATH.RAND],
[Randarray, FUNCTION_NAMES_MATH.RANDARRAY],
[Randbetween, FUNCTION_NAMES_MATH.RANDBETWEEN],
[Roman, FUNCTION_NAMES_MATH.ROMAN],
[Round, FUNCTION_NAMES_MATH.ROUND],
[Rounddown, FUNCTION_NAMES_MATH.ROUNDDOWN],
[Roundup, FUNCTION_NAMES_MATH.ROUNDUP],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* 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_MATH } from '../../function-names';
import { Roman } from '../index';

describe('Test roman function', () => {
const testFunction = new Roman(FUNCTION_NAMES_MATH.ROMAN);

describe('Roman', () => {
it('Value is normal', () => {
const number = NumberValueObject.create(499);
const form = NumberValueObject.create(0);
const result = testFunction.calculate(number, form);
expect(getObjectValue(result)).toStrictEqual('CDXCIX');
});

it('Number value test', () => {
const number = NumberValueObject.create(-1);
const form = NumberValueObject.create(0);
const result = testFunction.calculate(number, form);
expect(getObjectValue(result)).toStrictEqual(ErrorType.VALUE);

const number2 = NumberValueObject.create(4000);
const result2 = testFunction.calculate(number2, form);
expect(getObjectValue(result2)).toStrictEqual(ErrorType.VALUE);

const number3 = NullValueObject.create();
const result3 = testFunction.calculate(number3, form);
expect(getObjectValue(result3)).toStrictEqual('');

const number4 = ErrorValueObject.create(ErrorType.NAME);
const result4 = testFunction.calculate(number4, form);
expect(getObjectValue(result4)).toStrictEqual(ErrorType.NAME);

const number5 = BooleanValueObject.create(true);
const result5 = testFunction.calculate(number5, form);
expect(getObjectValue(result5)).toStrictEqual('I');

const number6 = StringValueObject.create('test');
const result6 = testFunction.calculate(number6, form);
expect(getObjectValue(result6)).toStrictEqual(ErrorType.VALUE);
});

it('Form value test', () => {
const number = NumberValueObject.create(499);
const form = NumberValueObject.create(-1);
const result = testFunction.calculate(number, form);
expect(getObjectValue(result)).toStrictEqual(ErrorType.VALUE);

const form2 = NumberValueObject.create(1);
const result2 = testFunction.calculate(number, form2);
expect(getObjectValue(result2)).toStrictEqual('LDVLIV');

const form3 = NumberValueObject.create(2);
const result3 = testFunction.calculate(number, form3);
expect(getObjectValue(result3)).toStrictEqual('XDIX');

const form4 = NumberValueObject.create(3);
const result4 = testFunction.calculate(number, form4);
expect(getObjectValue(result4)).toStrictEqual('VDIV');

const form5 = NumberValueObject.create(4);
const result5 = testFunction.calculate(number, form5);
expect(getObjectValue(result5)).toStrictEqual('ID');

const form6 = NumberValueObject.create(5);
const result6 = testFunction.calculate(number, form6);
expect(getObjectValue(result6)).toStrictEqual(ErrorType.VALUE);

const form7 = BooleanValueObject.create(true);
const result7 = testFunction.calculate(number, form7);
expect(getObjectValue(result7)).toStrictEqual('CDXCIX');

const form8 = BooleanValueObject.create(false);
const result8 = testFunction.calculate(number, form8);
expect(getObjectValue(result8)).toStrictEqual('ID');

const form9 = NullValueObject.create();
const result9 = testFunction.calculate(number, form9);
expect(getObjectValue(result9)).toStrictEqual('CDXCIX');

const form10 = ErrorValueObject.create(ErrorType.NAME);
const result10 = testFunction.calculate(number, form10);
expect(getObjectValue(result10)).toStrictEqual(ErrorType.NAME);

const form11 = StringValueObject.create('test');
const result11 = testFunction.calculate(number, form11);
expect(getObjectValue(result11)).toStrictEqual(ErrorType.VALUE);
});

it('Value is array', () => {
const number = NumberValueObject.create(1999);
const form = ArrayValueObject.create({
calculateValueList: transformToValueObject([
[-1, null, 1.23, 2, '3.2', 4],
[5, 0, true, false, 'test', ErrorType.NAME],
]),
rowCount: 2,
columnCount: 6,
unitId: '',
sheetId: '',
row: 0,
column: 0,
});
const result = testFunction.calculate(number, form);
expect(getObjectValue(result)).toStrictEqual([
[ErrorType.VALUE, 'MCMXCIX', 'MLMVLIV', 'MXMIX', 'MVMIV', 'MIM'],
[ErrorType.VALUE, 'MCMXCIX', 'MCMXCIX', 'MIM', ErrorType.VALUE, ErrorType.NAME],
]);
});
});
});
Loading

0 comments on commit bb99a98

Please sign in to comment.