0

I think I am too dumb to solve this problem...

I have some formulas which need to be "translated" from one syntax to another. Let's say I have a formula that goes like that (it's a simple one, others have many "Ceilings" in it):

string formulaString = "If([Param1] = 0, 1, Ceiling([Param2] / 0.55) * [Param3])";

I need to replace "Ceiling()" with "Ceiling(; 1)" (basically, insert "; 1" before the ")"). My attempt is to split the fomulaString at "Ceiling(" so I am able to iterate through the string array and insert my string at the correct index (counting every "(" and ")" to get the right index)

What I have so far:

//splits correct, but loses "CEILING("
string[] parts = formulaString.Split(new[] { "CEILING(" }, StringSplitOptions.None);
//splits almost correct, "CEILING(" is in another group
string[] parts = Regex.Split(formulaString, @"(CEILING\()");
//splits almost every letter
string[] parts = Regex.Split(formulaString, @"(?=[(CEILING\()])");

When everything is done, I concat the string so I have my complete formula again.

What do I have to set as Regex pattern to achieve this sample? (Or any other method that will help me)

part1 = "If([Param1] = 0, 1, ";
part2 = "Ceiling([Param2] / 0.55) * [Param3])";
//part3 = next "CEILING(" in a longer formula and so on...
  • You could just join the string using [`String.Join(char separator, string[] value)`](https://learn.microsoft.com/en-us/dotnet/api/system.string.join?view=netcore-3.1#System_String_Join_System_String_System_String___) like so: `String.Join ("CEILING(", parts)` instead of concatenating it. – Prime Oct 16 '20 at 12:57
  • **I have some formulas which need to be "translated" from one syntax to another**: Depending on the overall complexity of the conversion, it might be worth having a look at [ANTLR](https://www.antlr.org/). – Oliver Oct 16 '20 at 12:58
  • For languages with simpler grammar, it might be worth constructing the parsers yourself using something like [Sprache](https://github.com/sprache/Sprache) or [Pidgin](https://github.com/benjamin-hodgson/Pidgin) – MindSwipe Oct 16 '20 at 13:01
  • 1
    You almost got it: `(?=Ceiling)` Using a Positive Lookahead Zero-Assertion in regex. https://www.regular-expressions.info/lookaround.html – micha Oct 16 '20 at 13:04
  • `formulaString.Replace("Ceiling()", "Ceiling(; 1)");` – vernou Oct 16 '20 at 13:11
  • @micha Thank you very much, may I ask you to post it as answer? It has helped me solve my problem – LiliumCandidum Oct 16 '20 at 13:13
  • 1
    Thank you to all commentators, I will have a further look into all the links you have provided me. As the syntax translation is relatively easy (just insert some string before the closing parenthesis) I will stick with Regex.Split() – LiliumCandidum Oct 16 '20 at 13:15

3 Answers3

1

As I mention in a comment, you almost got it: (?=Ceiling). This is incomplete for your use case unfortunately.

I need to replace "Ceiling()" with "Ceiling(; 1)" (basically, insert "; 1" before the ")").

Depending on your regex engine (for example JS) this works:

string[] parts = Regex.Split(formulaString, @"(?<=Ceiling\([^)]*(?=\)))");
string modifiedFormula = String.join("; 1", parts);

The regex

(?<=Ceiling\([^)]*(?=\)))
(?<=                    )   Positive lookbehind
    Ceiling\(               Search for literal "Ceiling("
             [^)]           Match any char which is not ")" ..
                 *          .. 0 or more times
                  (?=\))    Positive lookahead for ")", effectively making us stop before the ")"

This regex is a zero-assertion, therefore nothing is lost and it will cut your strings before the last ")" in every "Ceiling()".

This solution would break whenever you have nested "Ceiling()". Then your only solution would be writing your own parser for the same reasons why you can't parse markup with regex.

micha
  • 1,036
  • 8
  • 12
  • Thank you very much for your explanation. I have just checked my ~300 formulas which I have to translate and yes, there are some nested "Ceiling()". So I have to write a parser for that. – LiliumCandidum Oct 16 '20 at 13:50
0
Regex.Replace(formulaString, @"(?<=Ceiling\()(.*?)(?=\))","$1; 1");

Note: This will not work for nested "Ceilings", but it does for Ceiling(), It will also not work fir Ceiling(AnotherFunc(x)). For that you need something like:

Regex.Replace(formulaString, @"(?<=Ceiling\()((.*\((?>[^()]+|(?1))*\))*|[^\)]*)(\))","$1; 1$3");

but I could not get that to work with .NET, only in JavaScript.

Clay Ver Valen
  • 1,033
  • 6
  • 10
0

This is my solution:

private string ConvertCeiling(string formula)
{
    int ceilingsCount = formula.CountOccurences("Ceiling(");

    int startIndex = 0;
    int bracketCounter;
    for (int i = 0; i < ceilingsCount; i++)
    {
        startIndex = formula.IndexOf("Ceiling(", startIndex);

        bracketCounter = 0;
        for (int j = 0; j < formula.Length; j++)
        {
            if (j < startIndex) continue;

            var c = formula[j];
            if (c == '(')
            {
                bracketCounter++;
            }
            if (c == ')')
            {
                bracketCounter--;
                if (bracketCounter == 0)
                {
                    // found end
                    formula = formula.Insert(j, "; 1");
                    startIndex++;
                    break;
                }
            }
        }
    }
    return formula;
}

And CountOccurence:

public static int CountOccurences(this string value, string parameter)
{
    int counter = 0;
    int startIndex = 0;
    int indexOfCeiling;
    do
    {
        indexOfCeiling = value.IndexOf(parameter, startIndex);
        if (indexOfCeiling < 0)
        {
            break;
        }
        else
        {
            startIndex = indexOfCeiling + 1;
            counter++;
        }

    } while (true);
    return counter;
}
Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92