Regułowy System ekspercki / Rule-driven expert system
Jeden z dwóch artykułów poświęconych sztucznej inteligencji. Artykuł prezentuje realizację systemu eksperckiego w C#. Drugi artykuł dotyczy znajdowania reguł w zbiorach danych. Link do drugiego artykułu znajduje się w części "Dodatkowe artykuły na tym portalu". One of two articles concerning artificial intelligence. One presents an expert system in C# and is a pair with one about finding rules in data sets. You can find the link to the second one in part " Additional articles at this site".
***
###Licencja i pobieranie kodu / Licence and code download
Po polsku
Uwaga: Jeśli nie chcesz tego czytać, a zobaczyć, jak to działa, pobierz pełny spakowany (.zip) projekt C# z kodem źródłowym, klikając poniżej:
ExpertSystem.zip
Treść udostępniona na zasadach licencji Creative Commons Attribution.
Autor uznaje załączony projekt C# za integralną część poniższego artykułu i rezerwuje sobie prawo do modyfikacji tego projektu na rzecz poprawienia jego jakości
bez dokonywania zmian w poniższym artykule, w szczególności bez informowania czytelników o tym fakcie.
In English
Note: If you do not want to read it, but you want to see how it works, please, download full zipped C# project with enclosed source code by clicking below:
ExpertSystem.zip
Content shared under license Creative Commons Attribution.
Author also considers enclosed C# project as integral part of the article, and reserves himself the right to modify this project for purposes of increasing its quality
without making changes to the rest of article, especially without informing readers about this fact.
###Wstęp / Introduction
Po polsku
W tym temacie pokazuję jak rozpocząć budowę systemu eksperckiego. System ekspercki zawiera wiedzę ekspertów z jednej wąskiej dziedziny. Wiedza ta jest umieszczona w bazie wiedzy w postaci sformatowanej. Wiedzę ekspertów do systemu eksperckiego wprowadza inżynier wiedzy, który z jednej strony częściowo zna dziedzinę reprezentowaną przez ekspertów, a z drugiej strony potrafi ją sformatować do zapisów z dziedziny logiki. System ekspercki jest programem, któremu zadajemy pytania i od którego otrzymujemy odpowiedzi. Jeżeli odpowiedzi udzielone przez program są identyczne z odpowiedziami, jakich udzieliliby eksperci (w 70-80 lub więcej procentach), system uznaje się za działający prawidłowo.
Ostateczną decyzję podejmuje człowiek, a system ekspercki mu tylko pomaga.
Ważną cechą systemu eksperckiego jest udzielanie odpowiedzi na pytanie dlaczego podjął taką a nie inną decyzję. Pozwala to na weryfikację poprawności odpowiedzi systemu. Mechanizm uzasadniania odpowiedzi warto zaimplementować już na początku tworzenia systemu. To ułatwi testowanie poprawności wnioskowania. Składnikami systemu eksperckiego są: baza wiedzy złożona z bazy faktów i bazy reguł, mechanizmy wnioskowania, mechanizmy uzasadniania odpowiedzi oraz baza danych. Systemy eksperckie znalazły największe zastosowanie w medycynie i ekonomii. Pokazuję działanie systemu eksperckiego na prostym i zrozumiałym przykładzie szukania kandydata na męża dla Alicji. Przykład z medycyny lub ekonomii nie byłby dla wszystkich zrozumiały, dlatego wybrałem coś, co każdy zna z życia. Mój system jest sterowany regułami to znaczy, że reguły decydują o wnioskowaniu. Można spotkać systemy eksperckie zawierające tylko mechanizmy wnioskowania i pustą bazę wiedzy. Są to systemy szkieletowe, gotowe do przyjęcia wiedzy z dowolnej dziedziny. W przykładzie przyjąłem następujące założenia: Alicja jest heteroseksualna, mówimy o papieżu rzymsko-katolickim, jest rok 2013 (przed 28 lutego, kiedy to Benedykt XVI oficjalnie abdykował).
In English
In this topic I show how to start building an expert system. Expert system provides expert knowledge in one small area. This knowledge is contained in a knowledge base in formatted form. Knowledge engineer introduces expert knowledge into an expert system. He is on the one hand partially familiar with the field represented by the experts, on the other hand is able to format it in the field of logic. Expert system is a program which we ask questions and get answers from it. If the answers given by the program are the same answers, which experts ones are (of 70-80 percent or more), the system is considered running properly.
The final decision is made by man, and an expert system only helps him.
An important feature of an expert system is to answer the question why it made this decision not a different one. This allows the verification of the correctness of system responses. We implement mechanism to justify the response at the beginning of the creation of the system. This facilitates testing the correctness of reasoning. Components of an expert system are: a knowledge base consisting of a database of facts and database of rules, reasoning mechanism, mechanism of responses to question why. Expert systems have found greatest use in medicine and economics. I show the action of an expert system on a simple and understandable example of finding a candidate for a husband for Alice. An example from medicine or economics would not be to understand for everyone why I chose something that everyone knows from life. My system is controlled by rules (rule-driven) that mean that the rules affect the reasoning. There can be found expert systems containing only reasoning mechanism and an empty knowledge base. In this example, I assume that: Alice is heterosexual, we are talking about the Roman Catholic Pope, the year is 2013 (before February 28, when Benedict XVI officially abdicated).
###Dodatkowe artykuły na tym portalu / Additional articles at this site
Metody i Style Zarządzania, LOSMARCELOS, 4programmers.net (Tylko po polsku / Polish only)
Ekstrakcja reguł z danych / Extraction of rules from data, Artur Protasewicz, 4programmers.net
###Baza danych i postać reguły / Database and form of rule
Po polsku
Fakty i reguły tworzące bazę wiedzy będziemy przechowywać w bazie danych. Moja baza danych nie zawiera kluczy. Nie jest to przedmiotem tego artykułu. Zakładam, że je stworzysz. Jako dodatek znajdziesz w kodzie na końcu funkcje odczytujące i zapisujące dane do bazy danych. Najpierw pokazuję poniżej jaką sformatowaną postać reguły przyjąłem, a potem ogólne postaci reguł stosowane zwykle w systemach eksperckich.
Moja postać reguły:** wniosek := (faktA and|or faktB) and (wniosekX and|or wniosekY)**
Stosowane postaci reguł:
wniosekA or wniosekB or ... or wniosekM := [not] fakt1|wniosek1 and [not] fakt2|wniosek2 and ... and [not] faktN|wniosekN
wniosekA and wniosekB and ... and wniosekM := [not] fakt1|wniosek1 or [not] fakt2|wniosek2 or ... or [not] faktN|wniosekN
In English
Facts and rules for creating a knowledge base will be stored in a database. My database does not contain primary keys. This is not the subject of this article. I assume that you will create them. As a supplement you can find in the code at the end reading and inserting functions for the database. First, I show below a formatted form of rule I use, then the general form of rules typically used in expert systems.
My form of rule: conclusion := (factA and|or factB) and (conclusionX and|or conclusionY)
Used forms of rules:
conclusionA or conclusionB or ... or conclusionM := [not] fact1|conclusion1 and [not] fact2|conclusion2 and ... and [not] factN|conclusionN
conclusionA and conclusionB and ... and conclusionM := [not] fact1|conclusion1 or [not] fact2|conclusion2 or ... or [not] factN|conclusionN
###Tabele bazy danych / Database tables
###Funkcje logiczne AND, OR / Logic functions AND, OR
Po polsku:
Aby poprawnie wprowadzić reguły, musisz wiedzieć, co to jest relacja AND i relacja OR. Obrazują to poniższe tabele dla dwóch parametrów relacji. Zakładam, że wiesz, co to jest zaprzeczenie NOT (0 daje 1, 1 daje 0). Liczba 0 w tabeli oznacza wartość false, liczba 1 wartość true. Mój kod operuje na dwóch parametrach relacji, ale jeżeli zrobisz jak poniżej, nic w nim nie będziesz musiał zmieniać oprócz treści uzasadnień, bo nadal maszyna wnioskująca będzie działać prawidłowo. Możesz rozbudować moją postać reguły tak, aby zawierała więcej parametrów np.
wynik = (x1 and (x2 and (x3 and x4))) lub wynik = (x1 or (x2 or (x3 or x4)))
In English:
To correctly implement the rules, you need to know what are the AND and OR relationships. The following tables illustrate them for two parameters. I assume you know what it is the negation NOT (0 gives 1, 1 gives 0). | Number 0 in the table means false, number 1 true. My code operates on two parameters of the relationship, but if you do as below, you will have nothing to change except justifying texts, because the reasoning machine will continue to work properly.You can extend my form of a rule to include more parameters such as:
result = (x1 and (x2 and (x3 and x4))) or result = (x1 or (x2 or (x3 or x4)))
###Cel poszukiwań / Search goal
Po polsku
Szukając osoby, która może poślubić Alicję musimy wziąć pod uwagę regułę "może poślubić" i jej zaprzeczenie "nie może poślubić", a następnie stworzyć iloczyn warunków:
cel = "może poślubić" and not "nie może poślubić"
In English
Searching for a person who can marry Alice, we must consider the rule "can marry" and its denial "cannot marry" and then create a product of conditions:
goal = "can marry" and not "cannot marry"
###Kod C# i zrzuty ekranu / C# code and screenshots
Po polsku
Ponieważ dostępny jest do pobrania pełen kod projektu, wyróżniono tylko jego część wnioskującą. Dodany jest region kodu związany z operacjami na bazie danych SQL, którego w projekcie do pobrania nie ma.
In English
Because there is whole project avalable to download, here is only the part concerning conclusioning. There is a region of code added concerning SQL database operations, which does not exist in downlodable project.
Moduły / Modules
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.Data;
Okno programu i jego zrzut ekranu / Program window and its screenshot
class FormMain
{
public const int conclusionCol = 1;
public const int factACol = 3;
public const int abRelationCol = 4;
public const int factBCol = 5;
public const int conclusionXCol = 7;
public const int xyRelationCol = 8;
public const int conclusionYCol = 9;
public const int objectCol = 0;
public const int attributeCol = 1;
public string orCol;
public string bracketLeftCol;
public string andCol;
public string bracketRightCol;
public RichTextBox rchWhy;
public RichTextBox rchWhyYes;
public RichTextBox rchWhyNot;
public DataGridView grdConclusions;
public DataGridView grdFacts;
public DataGridViewColumn cFactsObject;
public DataGridViewColumn cFactsAttribute;
public DataGridViewColumn cRulesConclusion;
public DataGridViewColumn cRulesFactA;
public DataGridViewColumn cRulesOperatorAB;
public DataGridViewColumn cRulesFactB;
public DataGridViewColumn cRulesConclusionX;
public DataGridViewColumn cRulesOperatorXY;
public DataGridViewColumn cRulesConclusionY;
public object Column { get; set; }
}
Kod wnioskujący / Conclusioning code
partial class ExpertSystem
{
FormMain f;
int Nested;
public ExpertSystem(FormMain F)
{
f = F;
}
bool Is(string Conclusion, string Candidate)
{
if (Candidate.Trim() == "")
{
return false;
}
if (Conclusion == "może poślubić"
|| Conclusion == "can marry")
{
f.rchWhy = f.rchWhyYes;
}
if (Conclusion == "nie może poślubić"
|| Conclusion == "cannot marry")
{
f.rchWhy = f.rchWhyNot;
}
Nested++;
for (int ConclusionIndex = 0; ConclusionIndex
< f.grdConclusions.RowCount; ConclusionIndex++)
{
if (Conclusions(ColumnSelector.Conclusions,
FormMain.conclusionCol, ConclusionIndex) == Conclusion)
{
string sFact = "";
string sConclusion = "";
string s = "";
string FactA = Conclusions(ColumnSelector.Conclusions,
FormMain.factACol, ConclusionIndex);
string FactB = Conclusions(ColumnSelector.Conclusions,
FormMain.factBCol, ConclusionIndex);
string ConclusionX = Conclusions(
ColumnSelector.Conclusions, FormMain.conclusionXCol,
ConclusionIndex);
string ConclusionY
= Conclusions(ColumnSelector.Conclusions,
FormMain.conclusionYCol, ConclusionIndex);
bool resultORFacts = false;
bool resultORConclusions = false;
bool resultANDFacts = false;
bool resultANDConclusions = false;
if (Conclusions(ColumnSelector.Conclusions,
FormMain.abRelationCol, ConclusionIndex) == "and")
{
sFact += "(" + FactA + " and " + FactB + ")";
}
if (Conclusions(ColumnSelector.Conclusions,
FormMain.abRelationCol, ConclusionIndex) == "or")
{
sFact += "(" + FactA + " or " + FactB + ")";
}
if (Conclusions(ColumnSelector.Conclusions,
FormMain.xyRelationCol, ConclusionIndex) == "and")
{
sConclusion += "(" + ConclusionX + " and "
+ ConclusionY + ")";
}
if (Conclusions(ColumnSelector.Conclusions,
FormMain.xyRelationCol, ConclusionIndex) == "or")
{
sConclusion += "(" + ConclusionX + " or "
+ ConclusionY + ")";
}
s = sFact + " and " + sConclusion;
WhyPath(Conclusion + " if " + s);
if (Conclusions(ColumnSelector.Conclusions,
FormMain.abRelationCol, ConclusionIndex) == "and")
{
resultANDFacts = (IsFactA(ConclusionIndex,
Candidate) && IsFactB(ConclusionIndex, Candidate))
;
}
if (Conclusions(ColumnSelector.Conclusions,
FormMain.abRelationCol, ConclusionIndex) == "or")
{
resultORFacts = (IsFactA(ConclusionIndex, Candidate)
|| IsFactB(ConclusionIndex, Candidate));
}
if (Conclusions(ColumnSelector.Conclusions,
FormMain.xyRelationCol, ConclusionIndex) == "and")
{
resultANDConclusions
= (IsConclusionX(ConclusionIndex, Candidate)
&& IsConclusionY(ConclusionIndex, Candidate));
}
if (Conclusions(ColumnSelector.Conclusions,
FormMain.xyRelationCol, ConclusionIndex) == "or")
{
resultORConclusions
= (IsConclusionX(ConclusionIndex, Candidate)
|| IsConclusionY(ConclusionIndex, Candidate));
}
bool result;
result = (resultANDFacts && resultANDConclusions);
result = (result || (resultANDFacts
&& resultORConclusions));
result = (result || (resultORFacts
&& resultANDConclusions));
result = (result || (resultORFacts
&& resultORConclusions));
if (result)
{
WhyPath("conclusion " + Conclusion + " is true");
Nested--;
return true;
}
}
}
WhyPath("conclusion " + Conclusion + " is false");
Nested--;
return false;
}
bool IsFactA(int ConclusionIndex, string Candidate)
{
string FactA = Conclusions(ColumnSelector.Conclusions,
FormMain.factACol, ConclusionIndex);
if (FactA == "1")
{
return true;
}
if (FactA == "0")
{
return false;
}
Nested++;
for (int iFact = 0; iFact < f.grdFacts.RowCount; iFact++)
{
if (Facts(ColumnSelector.Facts, FormMain.objectCol, iFact)
== Candidate && Facts(ColumnSelector.Facts,
FormMain.attributeCol, iFact) == FactA)
{
WhyPath("fact " + FactA + " is true for " + Candidate);
Nested--;
return true;
}
}
WhyPath("fact " + FactA + " is false for " + Candidate);
Nested--;
return false;
}
bool IsFactB(int ConclusionIndex, string Candidate)
{
string FactB = Conclusions(ColumnSelector.Conclusions, FormMain.factBCol,
ConclusionIndex);
if (FactB == "1")
{
return true;
}
if (FactB == "0")
{
return false;
}
Nested++;
for (int iFact = 0; iFact < f.grdFacts.RowCount; iFact++)
{
if (Facts(ColumnSelector.Facts, FormMain.objectCol, iFact)
== Candidate && Facts(ColumnSelector.Facts, FormMain.attributeCol,
iFact) == FactB)
{
WhyPath("fact " + FactB + " is true for " + Candidate);
Nested--;
return true;
}
}
WhyPath("fact " + FactB + " is false for " + Candidate);
Nested--;
return false;
}
bool IsConclusionX(int ConclusionIndex, string Candidate)
{
string ConclusionX = Conclusions(ColumnSelector.Conclusions,
FormMain.conclusionXCol, ConclusionIndex);
if (ConclusionX == "1")
{
return true;
}
if (ConclusionX == "0")
{
return false;
}
return Is(ConclusionX, Candidate);
}
bool IsConclusionY(int ConclusionIndex, string Candidate)
{
string ConclusionY = Conclusions(ColumnSelector.Conclusions,
FormMain.conclusionYCol, ConclusionIndex);
if (ConclusionY == "1")
{
return true;
}
if (ConclusionY == "0")
{
return false;
}
return Is(ConclusionY, Candidate);
}
public void SolveWhoCanMarryAlice(List<string> solve)
{
List<string> result = new List<string>();
List<string> Persons = new List<string>();
f.rchWhyYes.Clear();
f.rchWhyNot.Clear();
for (int iFact = 0; iFact < f.grdFacts.RowCount; iFact++)
{
string Person = Facts(ColumnSelector.Facts, FormMain.objectCol, iFact);
if ((Person != "Alicja" && Person != "Alice")
&& Persons.IndexOf(Person) < 0)
{
Persons.Add(Person);
}
}
foreach (string Person in Persons)
{
Nested = -1;
if ((Is("może poślubić", Person)
&& !Is("nie może poślubić", Person))
||
(Is("can marry", Person)
&& !Is("cannot marry", Person)))
{
result.Add(Person);
}
}
solve = result;
}
void WhyPath(string Text)
{
f.rchWhy.Text += strNested() + Text + Environment.NewLine;
}
string Facts(ColumnSelector colSelect, int Column, int Row)
{
int colIndex = SelectColumn(colSelect, Column);
return f.grdFacts[colIndex, Row].FormattedValue.ToString();
}
string Conclusions(ColumnSelector colSelect, int Column, int Row)
{
int colIndex = SelectColumn(colSelect, Column);
return f.grdConclusions[colIndex, Row].FormattedValue.ToString();
}
string strNested()
{
string s = "";
for (int j = 0; j < Nested; j++)
{
for (int i = 0; i < 10; i++)
{
s += " ";
}
}
return s;
}
#region ColumnSelector
public enum ColumnSelector
{
Facts,
Conclusions
}
int SelectColumn(ColumnSelector colSelect, int Column)
{
DataGridViewColumn col = null;
switch (Column)
{
case FormMain.objectCol:
switch (colSelect)
{
case ColumnSelector.Facts:
col = f.cFactsObject;
break;
}
break;
case FormMain.conclusionCol:
switch (colSelect)
{
case ColumnSelector.Facts:
col = f.cFactsAttribute;
break;
case ColumnSelector.Conclusions:
col = f.cRulesConclusion;
break;
}
break;
case FormMain.factACol:
col = f.cRulesFactA;
break;
case FormMain.abRelationCol:
col = f.cRulesOperatorAB;
break;
case FormMain.factBCol:
col = f.cRulesFactB;
break;
case FormMain.conclusionXCol:
col = f.cRulesConclusionX;
break;
case FormMain.xyRelationCol:
col = f.cRulesOperatorXY;
break;
case FormMain.conclusionYCol:
col = f.cRulesConclusionY;
break;
default:
break;
}
return col.Index;
}
#endregion ColumnSelector
}
Kod operujący na bazie danych SQL / Code operating on SQL database
partial class ExpertSystem
{
#region SqlOperation
void LoadConclusions()
{
SqlConnection Conn = new SqlConnection(ConnStr);
Conn.Open();
SqlCommand CmdSelectConclusions = new SqlCommand("select * from Rules");
CmdSelectConclusions.Connection = Conn;
SqlDataReader readerConclusions
= CmdSelectConclusions.ExecuteReader();
int row = 0;
ConclusionCount = 0;
f.grdConclusions.Rows.Clear();
while (readerConclusions.Read())
{
f.grdConclusions.Rows.Add();
ConclusionCount++;
f.grdConclusions[FormMain.conclusionCol, row].Value = readerConclusions[0];
f.grdConclusions[FormMain.factACol, row].Value = readerConclusions[1];
f.grdConclusions[FormMain.abRelationCol, row].Value = readerConclusions[2];
f.grdConclusions[FormMain.factBCol, row].Value = readerConclusions[3];
f.grdConclusions[FormMain.conclusionXCol, row].Value = readerConclusions[4]
;
f.grdConclusions[FormMain.xyRelationCol, row].Value = readerConclusions[5]
;
f.grdConclusions[FormMain.conclusionYCol, row].Value = readerConclusions[6]
;
if (row == 0)
{
f.grdConclusions[f.orCol, row].Value = "";
}
else
{
f.grdConclusions[f.orCol, row].Value = "or";
}
f.grdConclusions[f.bracketLeftCol, row].Value = "(";
f.grdConclusions[f.andCol, row].Value = ") and (";
f.grdConclusions[f.bracketRightCol, row].Value = ")";
row++;
}
readerConclusions.Close();
Conn.Close();
}
void LoadFacts()
{
SqlConnection Conn = new SqlConnection(ConnStr);
Conn.Open();
SqlCommand CmdSelectFacts = new SqlCommand("select * from Facts");
CmdSelectFacts.Connection = Conn;
SqlDataReader readerFacts = CmdSelectFacts.ExecuteReader();
int row = 0;
FactCount = 0;
f.grdFacts.Rows.Clear();
while (readerFacts.Read())
{
f.grdFacts.Rows.Add();
FactCount++;
for (int col = 0; col < f.grdFacts.ColumnCount; col++)
{
f.grdFacts[col, row].Value = readerFacts[col];
}
row++;
}
readerFacts.Close();
Conn.Close();
}
void SaveConclusions()
{
SqlConnection Conn = new SqlConnection(ConnStr);
Conn.Open();
SqlCommand CmdDeleteAllConclusions = new SqlCommand("delete from Rules");
CmdDeleteAllConclusions.Connection = Conn;
CmdDeleteAllConclusions.ExecuteNonQuery();
SqlCommand CmdInsertConclusion =
new SqlCommand("insert Rules (Conclusion, FactA, abRelation," +
"FactB, ConclusionX, xyRelation, ConclusionY) " +
"values (@par0, @par1, @par2, @par3, @par4, @par5, @par6)");
CmdInsertConclusion.Connection = Conn;
for (int par = 0; par < 7; par++)
{
CmdInsertConclusion.Parameters.Add("@par" + par.ToString(),
SqlDbType.Text);
}
for (int row = 0; row < f.grdConclusions.RowCount; row++)
{
CmdInsertConclusion.Parameters[0].Value
= f.grdConclusions[FormMain.conclusionCol, row].Value;
CmdInsertConclusion.Parameters[1].Value
= f.grdConclusions[FormMain.factACol, row].Value;
CmdInsertConclusion.Parameters[2].Value
= f.grdConclusions[FormMain.abRelationCol, row].Value;
CmdInsertConclusion.Parameters[3].Value
= f.grdConclusions[FormMain.factBCol, row].Value;
CmdInsertConclusion.Parameters[4].Value
= f.grdConclusions[FormMain.conclusionXCol, row].Value;
CmdInsertConclusion.Parameters[5].Value
= f.grdConclusions[FormMain.xyRelationCol, row].Value;
CmdInsertConclusion.Parameters[6].Value
= f.grdConclusions[FormMain.conclusionYCol, row].Value;
try
{
CmdInsertConclusion.ExecuteNonQuery();
}
catch { }
}
Conn.Close();
}
void SaveFacts()
{
SqlConnection Conn = new SqlConnection(ConnStr);
Conn.Open();
SqlCommand CmdDeleteAllFacts = new SqlCommand("delete from Facts");
CmdDeleteAllFacts.Connection = Conn;
CmdDeleteAllFacts.ExecuteNonQuery();
SqlCommand CmdInsertFact = new SqlCommand("insert Facts (Object, " +
"Attribute) values (@par0, @par1)");
CmdInsertFact.Connection = Conn;
for (int par = 0; par < 2; par++)
{
CmdInsertFact.Parameters.Add("@par" + par.ToString(),
SqlDbType.Text);
}
for (int row = 0; row < f.grdFacts.RowCount; row++)
{
for (int par = 0; par < f.grdFacts.ColumnCount; par++)
{
CmdInsertFact.Parameters[par].Value
= f.grdFacts[par, row].Value;
}
try
{
CmdInsertFact.ExecuteNonQuery();
}
catch { }
}
Conn.Close();
}
private void btnLoadData_Click(object sender, EventArgs e)
{
LoadFacts();
LoadConclusions();
}
private void btnSaveData_Click(object sender, EventArgs e)
{
SaveFacts();
SaveConclusions();
}
public int ConclusionCount { get; set; }
public string ConnStr { get; set; }
public int FactCount { get; set; }
#endregion SqlOperation
}
###Słownik / Vocabulary
Polski / English
jest kobietą -- is woman
lubi grać na gitarze -- likes play guitar
lubi grać w brydża -- likes play bridge
jest papieżem -- is pope
jest mężczyzną -- is man
lubi grać na pianinie -- likes play piano
lubi hazard -- likes gambling
mogą być przyjaciółmi -- can be friends
lubi muzykę -- likes music
może poślubić -- can marry
nie może poślubić -- cannot marry
lubi grać w karty -- likes play cards
lubi pasjansa -- likes solitaire
wniosek -- conclusion
jest prawdą -- is true
nie jest prawdą -- is false
kto -- who
dlaczego -- why
fakty -- facts
reguły -- rules
obiekt -- object
atrybut, cecha -- attribute
odpowiedź -- answer
fakt -- fact
może poślubić Alicję -- can marry Alice
nie może poślubić Alicję -- cannot marry Alice
Artykuł w odbudowie. Nie będzie form tabelarycznych, tylko naprzemienne akapity Po polsku / In English
Bardzo ciekawy artykuł, pozdrawiam.
Szacun za artykuł. Temat ciekawy i wykonanie również :)
Bardzo ciekawy ;) Chociaż ze względu na osoby, które chcą się nauczyć języka czy bardziej podszkolić się bardziej podzieliłbym zwarty tekst na pojedyncze paragrafy zawierające maksymalnie trzy, cztery zdania. Wiem, że to może by trochę rozbiło jedność tego artykułu, ale byłoby bardziej czytelne/przydatne dla osób, które chcą w ten sposób poduczyć się takiego języka...
Bardzo ciekawy pomysł z dwoma językami.