FUZZY – The job news filter: Rank – Salary – Citizen problem

Omarine.org welcomes the new year 2019 with an information filter that can be applied to job networks, using fuzzy inference. When there is a job news from the employer, that information is provided to the candidate who has logged in to the network. But is the job news suitable for candidates? How to choose only the jobs that job hunters can apply feasibly?

This article will give you the key to unlock the secret door and solve this challenge easily using FUZZY programming language.

Where is FUZZY programming language?

It is right in front of you, in FUZZY tab. I offer you all the source code of the interpreter program. You can freely use, modify and distribute. But first, let’s use the program in this article.

Personnel problem onboard

We use shipboard personnel to illustrate because seafarers are one of the few areas spread globally with multi-national crew and many standardized ranks.

The $ 10,000 is an attractive salary for a Korean captain, but it is impossible for a Vietnamese captain because the ship owner usually does not hire Vietnamese crew at that price. So a job news with too high a salary will have little value for a Vietnamese candidate, it will be scored low as we will see below. On the contrary, that news reached a high score for Japanese candidates.

Similarly, for low ratings such as sailors or oilers, the job bulletin with the highest salary should be the lowest score so that it is excluded from the interest of these candidates.

With the language FUZZY is an artificial intelligence language we can set up natural rules like when we normally state about the way we calculate scores.

Implementing

We create a source file called job.fuz (or job.c), first defining 4 fuzzy terms:

term SALARY(10)
{
    fuzzy discrete bottom(1/1000, 0.33/2000, 0/3000, 0/4000, 0/5000, 0/6000, 0/7000, 0/8000, 0/9000, 0/10000);
    fuzzy discrete low(0/1000, 0.33/2000, 1/3000, 0.33/4000, 0/5000, 0/6000, 0/7000, 0/8000, 0/9000, 0/10000);
    fuzzy discrete medium(0/1000, 0/2000, 0/3000, 0.33/4000, 1/5000, 1/6000, 0.33/7000, 0/8000, 0/9000, 0/10000);
    fuzzy discrete high(0/1000, 0/2000, 0/3000, 0/4000, 0/5000, 0/6000, 0.33/7000, 1/8000, 0.33/9000, 0/10000);
    fuzzy discrete top(0/1000, 0/2000, 0/3000, 0/4000, 0/5000, 0/6000, 0/7000, 0/8000, 0.33/9000, 1/10000);
};

term RANK(5)
{
    fuzzy discrete low(1/1, 0.33/2, 0/3, 0/4, 0/5);
    fuzzy discrete medium(0/1, 0.33/2, 1/3, 0.33/4, 0/5);
    fuzzy discrete high(0/1, 0/2, 0/3, 0.33/4, 1/5);
};

term CITIZEN(5)
{
    fuzzy discrete bottom(1/1, 0.33/2, 0/3, 0/4, 0/5);
    fuzzy discrete low(0.33/1, 1/2, 0.33/3, 0/4, 0/5);
    fuzzy discrete medium(0/1, 0.33/2, 1/3, 0.33/4, 0/5);
    fuzzy discrete high(0/1, 0/2, 0.33/3, 1/4, 0.33/5);
    fuzzy discrete top(0/1, 0/2, 0/3, 0.33/4, 1/5);
};

term SCORE(10)
{
    fuzzy discrete bottom(1/1, 0.33/2, 0/3, 0/4, 0/5, 0/6, 0/7, 0/8, 0/9, 0/10);
    fuzzy discrete low(0/1, 0.33/2, 1/3, 0.33/4, 0/5, 0/6, 0/7, 0/8, 0/9, 0/10);
    fuzzy discrete medium(0/1, 0/2, 0/3, 0.33/4, 1/5, 1/6, 0.33/7, 0/8, 0/9, 0/10);
    fuzzy discrete high(0/1, 0/2, 0/3, 0/4, 0/5, 0/6, 0.33/7, 1/8, 0.33/9, 0/10);
    fuzzy discrete top(0/1, 0/2, 0/3, 0/4, 0/5, 0/6, 0/7, 0/8, 0.33/9, 1/10);
};

Add two functions to convert from string to number as follows:

int Rank2Num(String rank) {

    switch (rank) {
    
    case "O/S":
    case "A/B":
    case "Oiler":
            return 1;
    case "3/O":
    case "4/E":
    case "Bosun":
    case "No.1 Oiler":
    case "Chief Cook":
    case "Electrician":
            return 2;
    case "2/O":
    case "3/E":
            return 3;
    case "Chief Officer":
    case "2/E":
            return 4;
    case "Chief Engineer":
    case "Master":
            return 5;
    default:
            return -1;
    }
}

int Citizen2Num(String citizen) {

    switch (citizen) {
    
    case "American":
    case "British":
    case "Japanese":
            return 5;
    case "Korean":
            return 4;
    case "Chinese":
    case "Philippines":
            return 3;
    case "Thailand":
    case "Vietnamese":
    case "Indonesian":
            return 2;
    case "Cambodian":
            return 1;
    default:
            return -1;
    }
}

Note that citizen classification is just an illustration. I do not mean the hierarchy in fact.

The main part of the program is the JOB structure, with a set of rules:

struct JOB
{
    SALARY salary;
    RANK rank;
    CITIZEN citizen;
    SCORE score;

    bool Read(int salaryVal, String rankVal, String citizenVal)
    {
        int i;
        
        switch ( salaryVal )
        {
        case 1000:
                put salary := (0.5/salaryVal, 0/2000, 0/3000, 0/4000, 0/5000, 0/6000, 0/7000, 0/8000, 0/9000, 0/10000);
                break;
        case 2000:
                put salary := (0/1000, 0.5/salaryVal, 0/3000, 0/4000, 0/5000, 0/6000, 0/7000, 0/8000, 0/9000, 0/10000);
                break;
        case 3000:
                put salary := (0/1000, 0/2000, 0.5/salaryVal, 0/4000, 0/5000, 0/6000, 0/7000, 0/8000, 0/9000, 0/10000);
                break;
        case 4000:
                put salary := (0/1000, 0/2000, 0/3000, 0.5/salaryVal, 0/5000, 0/6000, 0/7000, 0/8000, 0/9000, 0/10000);
                break;
        case 5000:
                put salary := (0/1000, 0/2000, 0/3000, 0/4000, 0.5/salaryVal, 0/6000, 0/7000, 0/8000, 0/9000, 0/10000);
                break;
        case 6000:
                put salary := (0/1000, 0/2000, 0/3000, 0/4000, 0/5000, 0.5/salaryVal, 0/7000, 0/8000, 0/9000, 0/10000);
                break;
        case 7000:
                put salary := (0/1000, 0/2000, 0/3000, 0/4000, 0/5000, 0/6000, 0.5/salaryVal, 0/8000, 0/9000, 0/10000);
                break;
        case 8000:
                put salary := (0/1000, 0/2000, 0/3000, 0/4000, 0/5000, 0/6000, 0/7000, 0.5/salaryVal, 0/9000, 0/10000);
                break;
        case 9000:
                put salary := (0/1000, 0/2000, 0/3000, 0/4000, 0/5000, 0/6000, 0/7000, 0/8000, 0.5/salaryVal, 0/10000);
                break;
        case 10000:
                put salary := (0/1000, 0/2000, 0/3000, 0/4000, 0/5000, 0/6000, 0/7000, 0/8000, 0/9000, 0.5/salaryVal);
                break;
        default:
                return false;
        }
        
        switch ( i = Rank2Num(rankVal) )
        {
        case 1:
                put rank := (0.5/i, 0/2, 0/3, 0/4, 0/5);
                break;
        case 2:
                put rank := (0/1, 0.5/i, 0/3, 0/4, 0/5);
                break;
        case 3:
                put rank := (0/1, 0/2, 0.5/i, 0/4, 0/5);
                break;
        case 4:
                put rank := (0/1, 0/2, 0/3, 0.5/i, 0/5);
                break;
        case 5:
                put rank := (0/1, 0/2, 0/3, 0/4, 0.5/i);
                break;
        default:
                return false;
        }
        
        switch ( i = Citizen2Num(citizenVal) )
        {
        case 1:
                put citizen := (0.5/i, 0/2, 0/3, 0/4, 0/5);
                break;
        case 2:
                put citizen := (0/1, 0.5/i, 0/3, 0/4, 0/5);
                break;
        case 3:
                put citizen := (0/1, 0/2, 0.5/i, 0/4, 0/5);
                break;
        case 4:
                put citizen := (0/1, 0/2, 0/3, 0.5/i, 0/5);
                break;
        case 5:
                put citizen := (0/1, 0/2, 0/3, 0/4, 0.5/i);
                break;
        default:
                return false;
        }

        return true;
    }

    void SetRules()
    {
        ZeroFuzz(score);
        
        rule rank.high AND ( citizen.top AND salary.top OR citizen.high AND VERY salary.high OR citizen.medium AND LITTLE salary.high OR ( citizen.low OR citizen.bottom ) AND LITTLE salary.low ) => score.top;
        
        rule rank.high AND ( citizen.top AND salary.high OR citizen.high AND ( salary.top OR salary.medium ) OR citizen.medium AND salary.high OR ( citizen.low OR citizen.bottom ) AND salary.medium ) => score.high;
        
        rule rank.high AND ( citizen.top AND salary.medium OR citizen.high AND REALLY salary.medium OR citizen.medium AND ( salary.top OR salary.medium) OR ( citizen.low OR citizen.bottom ) AND salary.high ) => score.medium;
        
        rule rank.high AND ( ( citizen.top OR citizen.high OR citizen.medium ) AND salary.low ) => score.low;
        

        rule rank.medium AND ( citizen.top AND salary.high OR citizen.high AND LITTLE salary.high OR citizen.medium AND salary.medium OR citizen.low AND VERY salary.low OR citizen.bottom AND LITTLE salary.bottom ) => score.top;
        
        rule rank.medium AND ( ( citizen.top OR citizen.high ) AND ( salary.top OR salary.medium ) OR citizen.medium AND salary.high OR ( citizen.low OR citizen.bottom ) AND ( salary.medium OR salary.bottom ) ) => score.high;
        
        rule rank.medium AND ( ( citizen.top OR citizen.high ) AND LITTLE salary.low OR citizen.medium AND salary.top OR ( citizen.low OR citizen.bottom ) AND LITTLE salary.high ) => score.medium;
        
        rule rank.medium AND ( ( citizen.top OR citizen.high OR citizen.medium ) AND VERY salary.low OR ( citizen.low OR citizen.bottom ) AND salary.high ) => score.low;
        
        
        rule rank.low AND ( ( citizen.top OR citizen.high ) AND salary.low OR (citizen.medium OR citizen.low OR citizen.bottom ) AND salary.bottom ) => score.top;
        
        rule rank.low AND ( ( citizen.top OR citizen.high ) AND salary.medium OR ( citizen.medium OR citizen.low OR citizen.bottom ) AND LITTLE salary.bottom ) => score.high;
        
        rule rank.low AND ( citizen.top OR citizen.high ) AND LITTLE salary.high => score.medium;
        
        rule rank.low AND ( citizen.top OR citizen.high ) AND LITTLE salary.top => score.low;
        
        rule rank.low AND ( citizen.medium OR citizen.low OR citizen.bottom ) AND NOT salary.bottom => score.low;
        
        rule rank.high AND salary.bottom  => score.bottom;
        rule ( citizen.top OR citizen.high ) AND LITTLE salary.bottom  => score.bottom;
        rule ( citizen.low OR citizen.bottom ) AND salary.top => score.bottom;
        
    }

    void Process(int salaryVal, String rankVal, String citizenVal)
    {
        if ( Read(salaryVal, rankVal, citizenVal) )
        {
                SetRules();
                Display(salaryVal, rankVal, citizenVal);
        }
        else
                print "Invalid input!\n";
    } 

    void Display(int salaryVal, String rankVal, String citizenVal)
    {
        Number val = Defuzz(score);
        String valStr = round ( val );
        String salaryStr = salaryVal, msg = "SALARY: " + salaryStr;
        msg += ", RANK: " + rankVal;
        msg += ", CITIZEN: " + citizenVal;
        msg += ", SCORE: " + valStr;
        print msg;
    }
};

There are 16 rules. They are easy to understand so I only explain the last 3 rules:

rule rank.high AND salary.bottom  => score.bottom;

In above rule, for candidates with high rank while the job bulletin salary is the lowest, regardless of citizenship rankings, the score for the news is the lowest.

rule ( citizen.top OR citizen.high ) AND LITTLE salary.bottom  => score.bottom;

If the candidate is a high or highest citizen, and the salary of news is close to the bottom, the bulletin will get the lowest score for all positions.

On the contrary, in the final rule

rule ( citizen.low OR citizen.bottom ) AND salary.top => score.bottom;

if the candidate is a low or lowest citizen and the news salary is the highest, the job news will also receive the lowest score for all positions.

The program is run with the following code lines:

JOB job;
job.Process(3000, "Master", "Japanese");
job.Process(3000, "Master", "Korean");
job.Process(3000, "Master", "Philippines");
job.Process(3000, "Master", "Vietnamese");
job.Process(3000, "Master", "Cambodian");

print "";

job.Process(3000, "A/B", "Japanese");
job.Process(3000, "A/B", "Korean");
job.Process(3000, "A/B", "Philippines");
job.Process(3000, "A/B", "Vietnamese");
job.Process(3000, "A/B", "Cambodian");

print "";

job.Process(10000, "Master", "Japanese");
job.Process(10000, "Master", "Korean");
job.Process(10000, "Master", "Philippines");
job.Process(10000, "Master", "Vietnamese");
job.Process(10000, "Master", "Cambodian");

print "";

job.Process(1000, "2/O", "Japanese");
job.Process(1000, "2/O", "Korean");
job.Process(1000, "2/O", "Philippines");
job.Process(1000, "2/O", "Vietnamese");
job.Process(1000, "2/O", "Cambodian");

Analysing the output

The output of the program is as follows:

There are 4 output groups, each consisting of 5 information lines.

The first group is for the candidates who are the captain. The employer offers a salary of $ 3,000. $ 3,000 is too low a salary for Japanese or Korean captains. Therefore this newsletter is of low value to them, only score of 3. But the value of the newsletter gradually increases for those with lower rankings of citizen: 4 for Philippines captains, 6 for Vietnamese captains, and highest for Cambodian captains, 9.

The second group is the opposite. The employer need an A/B position, also with a salary of $ 3,000. That salary is suitable for Japanese sailors with a score of 9, while it is too high for Vietnamese sailors, the newsletter does not fit them and the score is only 3.

Now you can analyze the rest yourself, so there is no need to explain further. This is when you start your own FUZZY program.


O/S: Ordinary seaman

A/B: Able seaman

3/O: Third Officer

4/E: Fourth Engineer

2/O: Second Officer

3/E: Third Engineer

2/E: Second Engineer

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.