0

I need to write a program which by inputted formula can define mass fraction of each element. Something like that: input: Na[Al(OH)4]
output: Mr = 118
w(Na) = 19,49%
w(Al) = 22,88%
w(O) = 54,24%
w(H) = 3,39%
Everything works except for parentheses and brackets analysis, though this is fine (i'll get "good"), but i am interested, why it doesnt work

#include <stdio.h>
#include <ctype.h>
#include <string.h>

//Creating a struct which describes the element
static struct elementData {
    char sym[3];
    double mass;
    int counter; // for formula like NH4NO3, N needs to be counted twice
};


//Creating a periodic table (an array of elements' structs)
static struct elementData periodicTable[] = {
        {"H",  1.0079,  0}, // Not sure if we have to set counter to 0
        {"He", 4.0026,  0}, // as far as i know global variables are set to 0 by default,
        {"Li", 6.939,   0}, // but we'll do that just in case
        {"Be", 9.0122,  0},
        {"Na", 22.9898, 0},
        {"O",  15.9994, 0},
        {"Al", 26.9815, 0}
        //TODO: finish the table
};
static int tableSize = sizeof(periodicTable) / sizeof(periodicTable[0]); //Size of the table

static double MrOfCompound = 0; // Molecular mass of the compound also should be stored

/* "Declaring functions" block*/
void getFormula(int charArray[]);

double getArBySymbol(int symbolLetter1, int symbolLetter2);

void increaseIndexCounter(int symbolLetter1, int symbolLetter2);

void parseOneSymbol(int formula[], int i);

/*End of "declaring functions" block*/

int main() {
    int dontQuit = 1; //while it is not zero the program will be looping
    int formula[100]; // a char array for the formula
    //double temp; //Could have been useful in brackets and parentheses analysis
    while (dontQuit) {
        printf("Enter the formula of a compound or type q to quit\n");
        for (int j = 0; j < 100; ++j) { //The program will be looped, so need to clean formula every loop
            formula[j] = -1;
        }
        for (int k = 0; k < tableSize; ++k) { //The program will be looped, so need to clean counters every loop
            periodicTable[k].counter = 0;
        }
        MrOfCompound = 0;

        getFormula(formula); //Acquiring formula-input from user

        for (int i = 0; i < 100; i++) {
            switch (formula[i]) {
                case 'q':
                    dontQuit = 0;
                    break;
                case -1: // out of record range
                    break;
                    /* case '(': // Parentheses doesnt work
                         i++;
                         temp = MrOfCompound;
                         while(formula[i] != ')') {
                             parseOneSymbol(formula, i);
                             i++;
                         }
                         i++;
                         if(isdigit(formula[i])) {
                             i++;
                             MrOfCompound += (MrOfCompound - temp) * ((formula[i] - '0') - 1);
                         }
                         break; */
                    /*case '[': // Brackets doesnt work
                        i++;
                        temp = MrOfCompound;
                        while(formula[i] != ']')
                            parseOneSymbol(formula, i++);
                        if(isdigit(formula[++i]) != 0 )
                            MrOfCompound += (MrOfCompound - temp)*((formula[i] - '0') - 1);
                        break; */
                case '\0': //End of formula-line
                    break;
                default:
                    parseOneSymbol(formula, i);
            }
        }

        //Data output
        if(dontQuit) {
            printf("Mr: %f\n", MrOfCompound);
            for (int l = 0; l < tableSize; ++l) {
                if (periodicTable[l].counter != 0) {
                    printf("%s: %f%c\n", periodicTable[l].sym, (periodicTable[l].mass / MrOfCompound) * 100.0, '%');
                }
            }
        }
    }
}
/*Defining functions block*/

//A function that handles the input of formula
void getFormula(int s[]) {
    int c, i;
    for (i = 0; (c = getchar()) != EOF && c != '\n'; ++i) {
        s[i] = c;
    }
    s[i] = '\0'; // Line-terminator
}

/* A function which performs linear search through the periodic table
 * to find the symbol (either it consists of 2 chars or of 1)
 * and return its atomic weight Ar */
double getArBySymbol(int l1, int l2) {
    if (l2 == -1) {
        for (int i = 0; i < tableSize; ++i) {
            if (l1 == periodicTable[i].sym[0] && strlen(periodicTable[i].sym) == 1)
                return periodicTable[i].mass;
        }
    } else {
        for (int i = 0; i < tableSize; ++i) {
            if (l1 == periodicTable[i].sym[0] && l2 == periodicTable[i].sym[1])
                return periodicTable[i].mass;
        }
    }

}

/* A function which performs linear search through the periodic table
 * to find the symbol (either it consists of 2 chars or of 1)
 * and increases it's counter */

void increaseIndexCounter(int s1, int s2) {
    if (s2 == -1) {
        for (int i = 0; i < tableSize; ++i) {
            if (s1 == periodicTable[i].sym[0] && strlen(periodicTable[i].sym) == 1)
                periodicTable[i].counter++;
        }
    } else {
        for (int i = 0; i < tableSize; ++i) {
            if (s1 == periodicTable[i].sym[0] && s2 == periodicTable[i].sym[1])
                periodicTable[i].counter++;
        }
    }
}

//Function which handles parsing element symbols and indexes
void parseOneSymbol(int formula[], int i) {
    if (isupper(formula[i])) {
        if (formula[i + 1] == '\0' || isupper(formula[i + 1])) {
            increaseIndexCounter(formula[i], -1);
            MrOfCompound += getArBySymbol(formula[i], -1);
        } else if (isdigit(formula[i + 1])) {
            for (int j = 0; j < (formula[i + 1] - '0'); ++j)
                increaseIndexCounter(formula[i], -1);
            MrOfCompound += (formula[i + 1] - '0') * (getArBySymbol(formula[i], -1));
        } else if (islower(formula[i + 1]) && formula[i + 2] == '\0') {
            increaseIndexCounter(formula[i], formula[i + 1]);
            MrOfCompound += getArBySymbol(formula[i], formula[i + 1]);
        } else if (islower(formula[i + 1]) && isupper(formula[i + 2])) {
            increaseIndexCounter(formula[i], formula[i + 1]);
            MrOfCompound += getArBySymbol(formula[i], formula[i + 1]);
        } else if (islower(formula[i + 1]) && isdigit(formula[i + 2])) {
            for (int j = 0; j < (formula[i + 2] - '0'); ++j)
                increaseIndexCounter(formula[i], formula[i + 1]);
            MrOfCompound += (formula[i + 2] - '0') * (getArBySymbol(formula[i], formula[i + 1]));
        }

    }
}
  • 2
    Can you reduce this to a question about parsing 1 or 2 letter symbols, pairs of `()` or `[]` and multplier numerals? – Yunnosch Dec 10 '17 at 15:27
  • 2
    Aside: you are using magic numbers in the code like `65` and `90`. Please do yourself a favour and use `'A'` and `'Z'` etc, or even better, functions like `isalpha()`, `isupper()`, `isdigit()` etc. – Weather Vane Dec 10 '17 at 15:35
  • 1
    `char c; for (int i = 0; (c = getchar()) != EOF && c != '\n' ; ++i) { s[i] = c; }` 1) c should be an int. 2) nul-terminate the string after the loop. – wildplasser Dec 10 '17 at 15:35
  • It's an expression which parsing might be adapted from [this solution](https://stackoverflow.com/a/47717/338904) – Déjà vu Dec 10 '17 at 15:37
  • `Na[Al(OH)4]` BTW: I'd expect `.6H2O` , here. – wildplasser Dec 10 '17 at 15:39
  • 1
    ... I also see you are testing the range `30` to `39` however these are not the decimal ASCII values of `'0'` to `'9'` but the *hexadecimal values*. So even more reason to use library functions and not trip up. Having corrected that you should use `formula[i+1] - '0'` instead of `formula[i+1] - 30`. – Weather Vane Dec 10 '17 at 16:08
  • @wildplasser Na[Al(OH)4(H2O)2]*nH2O, but this water is often omitted, and parsing formulae like CuSO4*5H20 is not the part of the task – DrNightingales Dec 10 '17 at 17:13
  • 1
    But there are two forms for copper sulphate. Hydrated, and the other. – Weather Vane Dec 10 '17 at 17:37
  • 1
    Note: `if(65 <= formula[i] <= 90){` does **not** do what you expect. – wildplasser Dec 10 '17 at 17:53
  • ... should be `if(isupper(formula[i]))` ? – Weather Vane Dec 10 '17 at 18:20
  • Thanks a lot for your useful tips, applied most of the to the new version of the program – DrNightingales Dec 12 '17 at 23:04

2 Answers2

0

Parsing strings and equations is not a trivial thing to do in C. Or any other basic programing language.

You need to set up the rules and symbols your code can evaluate from an expression. Define all elements, define posfix/prefix/infix rules for parenthesis, etc...

Try to look for Compiler parses and natural text interpreters.

This question should be useful.

How to parse a formula from a string?

Thadeu Melo
  • 947
  • 14
  • 40
0

Everything works except for parentheses and brackets analysis…

No, not quite. In

printf("%s: %f%c\n", periodicTable[l].sym, (periodicTable[l].mass / MrOfCompound) * 100.0, '%');

you forgot to multiply periodicTable[l].mass by periodicTable[l].counter.

Armali
  • 18,255
  • 14
  • 57
  • 171