• Skip to content
  • Skip to link menu
KDE 4.4 API Reference
  • KDE API Reference
  • kdeedu
  • Sitemap
  • Contact Us
 

parley

answervalidatorold.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002     Copyright 2007 Frederik Gladhorn <frederik.gladhorn@kdemail.net>
00003  ***************************************************************************/
00004 
00005 /***************************************************************************
00006  *                                                                         *
00007  *   This program is free software; you can redistribute it and/or modify  *
00008  *   it under the terms of the GNU General Public License as published by  *
00009  *   the Free Software Foundation; either version 2 of the License, or     *
00010  *   (at your option) any later version.                                   *
00011  *                                                                         *
00012  ***************************************************************************/
00013 #include "answervalidatorold.h"
00014 
00015 #include "prefs.h"
00016 
00017 #include <keduvocexpression.h>
00018 #include <keduvocdocument.h>
00019 #include <keduvocidentifier.h>
00020 
00021 #include <KLocalizedString>
00022 #include <sonnet/speller.h>
00023 #include <kdebug.h>
00024 
00027 namespace ParleyStringHandlerOld {
00028     QString stripAccents(const QString& original){
00029         QString noAccents;
00030         QString decomposed = original.normalized(QString::NormalizationForm_D);
00031         for (int i = 0; i < decomposed.length(); ++i) {
00032             if ( decomposed[i].category() != 1 ) {
00033                 noAccents.append(decomposed[i]);
00034             }
00035         }
00036 kDebug() << original << " without accents: " << noAccents;
00037         return noAccents;
00038     }
00039 }
00040 
00041 
00042 const double AnswerValidatorOld::LEVENSHTEIN_THRESHOLD = 0.2;
00043 const double AnswerValidatorOld::UNRELATED_WORD_GRADE = 0.0;
00044 const double AnswerValidatorOld::FALSE_FRIEND_GRADE = 0.0;
00045 const double AnswerValidatorOld::SPELLING_MISTAKE_PER_LETTER_PUNISHMENT = 0.2;
00046 const double AnswerValidatorOld::CAPITALIZATION_MISTAKE_PUNISHMENT = 0.1;
00047 const double AnswerValidatorOld::ACCENT_MISTAKE_PUNISHMENT = 0.1;
00048 const double AnswerValidatorOld::WRONG_ARTICLE_PUNISHMENT = 0.1;
00049 
00050 AnswerValidatorOld::AnswerValidatorOld(KEduVocDocument* doc)
00051 {
00052     m_doc = doc;
00053     m_entry = 0;
00054     m_speller = 0;
00055     m_spellerAvailable = false;
00056 }
00057 
00058 AnswerValidatorOld::~AnswerValidatorOld()
00059 {
00060     delete m_speller;
00061 }
00062 
00063 
00064 void AnswerValidatorOld::setLanguage(int translation)
00065 {
00066     m_translation = translation;
00067 
00068     // default: try locale
00069     if ( !m_speller ) {
00070         m_speller = new Sonnet::Speller(m_doc->identifier(translation).locale());
00071     } else {
00072         m_speller->setLanguage(m_doc->identifier(translation).locale());
00073     }
00074 
00075     // we might succeed with language name instead.
00076     if ( !m_speller->isValid() ) {
00077         m_speller->setLanguage(m_doc->identifier(translation).name());
00078     }
00079 
00080     if ( !m_speller->isValid() ) {
00081         kDebug() << "No spellchecker for current language found: " << m_doc->identifier(m_translation).locale();
00082         kDebug() << "Available dictionaries: " << m_speller->availableLanguages()
00083                 << "\n names: " << m_speller->availableLanguageNames()
00084                 << "\n backends: " << m_speller->availableBackends();
00085         m_spellerAvailable = false;
00086     } else {
00087         m_spellerAvailable = true;
00088     }
00089 }
00090 
00091 
00092 void AnswerValidatorOld::setTestEntry(TestEntry * entry)
00093 {
00094     m_entry = entry;
00095     if (m_entry) {
00096         m_solution = m_entry->entry()->translation(m_translation)->text();
00097     }
00098 }
00099 
00100 int AnswerValidatorOld::levenshteinDistance(const QString& s, const QString& t)
00101 {
00102     int m = s.length();
00103     int n = t.length();
00104 
00105     int dWidth = m+1 ;
00106 
00107     // make sure the matrix is big enough
00108     if( m_d.size() < (m+1) * (n+1)) {
00109         m_d.resize( (m+1) * (n+1) );
00110     }
00111 
00112     int i;
00113     int j;
00114 
00115     // init 0..m, 0..n as starting values - distance to ""
00116     for ( i = 0; i <= m; i++ )
00117     {
00118         m_d[i + 0*dWidth] = i;
00119     }
00120     for ( j = 0; j <= n; j++ ) {
00121         m_d[0 + j*dWidth] = j;
00122     }
00123 
00124     int cost;
00125     for (i = 1; i <= m; i++) {
00126         for (j = 1; j <= n; j++) {
00127             if ( s[i-1] == t[j-1] ) {
00128                 // if current char is equal, no cost for substitution
00129                 cost = 0;
00130             } else {
00131                 cost = 1;
00132             }
00133             m_d[i + j*dWidth] = qMin( qMin ( // min of three possibilities
00134                       m_d[i-1 + (j  )*dWidth] + 1,     // deletion
00135                       m_d[i   + (j-1)*dWidth] + 1),     // insertion
00136                       m_d[i-1 + (j-1)*dWidth] + cost);   // substitution
00137         }
00138     }
00139     return m_d[m + n*dWidth];
00140 }
00141 
00142 bool AnswerValidatorOld::spellcheckerMisspelled(const QString& userAnswer)
00143 {
00144     if (!m_spellerAvailable) {
00145         return true;
00146     }
00147     return m_speller->isMisspelled(userAnswer);
00148 }
00149 
00150 bool AnswerValidatorOld::spellcheckerInSuggestionList(const QString& solution, const QString& userAnswer)
00151 {
00152     if ( !m_spellerAvailable ) {
00153         return false;
00154     }
00155 
00156     kDebug() << "entered: " << userAnswer << " misspelled: " << m_speller->isMisspelled(userAnswer) << " suggestions: " << m_speller->suggest(userAnswer);
00157 
00158     if ( m_speller->suggest(userAnswer).contains(solution) ) {
00159         kDebug() << "I think this is a spelling error.";
00160         return true;
00161     } else {
00162         kDebug() << "No, this is a different word I think.";
00163         return false;
00164     }
00165 }
00166 
00167 
00168 void AnswerValidatorOld::simpleCorrector()
00169 {
00170 kDebug() << "simpleCorrector";
00171     if ( m_entry == 0 ) {
00172         kError() << "No entry set, cannot verify answer.";
00173         return;
00174     }
00175 
00177     if ( m_solution == m_userAnswer ) {
00178         m_entry->setLastErrors(TestEntry::Correct);
00179         m_entry->setLastPercentage(1.0);
00180 //         m_htmlCorrection = i18n("Your answer is right!");
00181 kDebug() << "right";
00182         return;
00183     }
00184 
00185     TestEntry::ErrorTypes errorTypes = TestEntry::UnknownMistake;
00186 
00187     foreach(KEduVocTranslation *synonym, m_entry->entry()->translation(m_translation)->synonyms()) {
00188         if (synonym->text() == m_userAnswer) {
00189             m_entry->setLastErrors(TestEntry::Synonym);
00190             if ( Prefs::countSynonymsAsCorrect() ) {
00191                 m_entry->setLastPercentage(1.0);
00192             } else {
00193                 m_entry->setLastPercentage(0.0); // bit harsh maybe
00194             }
00195             return;
00196         }
00197     }
00198 
00199     int levensthein = levenshteinDistance( m_solution, m_userAnswer );
00200 
00201     m_entry->setLastPercentage(1.0 - ((double)levensthein/ qMax(m_solution.length(), m_userAnswer.length())));
00202 
00203     kDebug() << "simpleCorrector" << m_userAnswer << "-" << m_solution << "has levensthein distance: " << levensthein << " grade: " << m_entry->lastPercentage();
00204 }
00205 
00206 
00207 void AnswerValidatorOld::defaultCorrector()
00208 {
00211     if ( m_solution == m_userAnswer ) {
00212         m_entry->setLastErrors(TestEntry::Correct);
00213         m_entry->setLastPercentage(1.0);
00214         return;
00215     }
00216 
00217     if ( m_userAnswer.isEmpty() ) {
00218         m_entry->setLastErrors(TestEntry::Empty);
00219         m_entry->setLastPercentage(0.0);
00220         return;
00221     }
00222 
00223     if ( m_solution.toLower() == m_userAnswer.toLower() ) {
00224         if (Prefs::ignoreCapitalizationMistakes()) {
00225             m_entry->setLastPercentage(1.0);
00226         } else {
00227             m_entry->setLastPercentage(1.0 - CAPITALIZATION_MISTAKE_PUNISHMENT);
00228         }
00229         m_entry->setLastErrors(TestEntry::CapitalizationMistake);
00230         return ;
00231     }
00232 
00233     if ( ParleyStringHandlerOld::stripAccents(m_solution) == ParleyStringHandlerOld::stripAccents(m_userAnswer) ) {
00234         if (Prefs::ignoreAccentMistakes()) {
00235             m_entry->setLastPercentage(1.0);
00236         } else {
00237             m_entry->setLastPercentage(1.0 - ACCENT_MISTAKE_PUNISHMENT);
00238         }
00239         m_entry->setLastErrors(TestEntry::AccentMistake);
00240         return ;
00241     }
00242 
00243     foreach(KEduVocTranslation *synonym, m_entry->entry()->translation(m_translation)->synonyms()) {
00244         if (synonym->text() == m_userAnswer) {
00245             m_entry->setLastErrors(TestEntry::Synonym);
00246             if ( Prefs::countSynonymsAsCorrect() ) {
00247                 m_entry->setLastPercentage(1.0);
00248             } else {
00249                 m_entry->setLastPercentage(0.0); // bit harsh maybe
00250             }
00251             return;
00252         }
00253     }
00254 
00255     int numberSolutionWords = m_solution.simplified().split(' ').count();
00256     int numberAnswerWords = m_userAnswer.simplified().split(' ').count();
00257 
00258     if ( numberSolutionWords == 1 ) {
00259         double grade;
00260         TestEntry::ErrorTypes errors;
00261         wordCompare(m_solution, m_userAnswer, grade, errors);
00262         m_entry->setLastPercentage(grade);
00263         m_entry->setLastErrors(errors);
00264         return;
00265     }
00266 
00267     if ( numberSolutionWords == 2 ) {
00268         // could be noun + article
00269         QStringList solutionWords = m_solution.simplified().split(' ');
00270 
00271         if ( m_translation >= 0 ) {
00272             if (m_doc->identifier(m_translation).article().isArticle(solutionWords.value(0)) ) {
00273                 // yes, the answer is an article + noun
00274                 if ( numberAnswerWords == 1 ) {
00275                     double percent;
00276                     TestEntry::ErrorTypes errors;
00277                     wordCompare(solutionWords.value(1), m_userAnswer.simplified(), percent, errors);
00278                     m_entry->setLastPercentage(qMax(percent-WRONG_ARTICLE_PUNISHMENT, 0.0));
00279                     m_entry->setLastErrors(errors|TestEntry::ArticleMissing);
00280                     return;
00281                 }
00282                 if ( numberAnswerWords == 2 ) {
00283                     double percent;
00284                     TestEntry::ErrorTypes errors;
00285                     wordCompare(solutionWords.value(1), m_userAnswer.simplified().split(' ').value(1), percent, 
00286 errors);
00287 
00288                     if ( m_userAnswer.simplified().split(' ').value(0) == solutionWords.value(0) ) {
00289                         m_entry->setLastErrors(errors);
00290                     } else {
00291                         m_entry->setLastPercentage(qMax(percent-WRONG_ARTICLE_PUNISHMENT, 0.0));
00292                         m_entry->setLastErrors(errors|TestEntry::ArticleWrong);
00293                     }
00294                     return;
00295                 }
00296             }
00297         }
00298     }
00299 
00300     // ok, more than one word (or one+article)
00301     sentenceAnalysis();
00302 }
00303 
00304 
00305 
00306 
00307 void AnswerValidatorOld::checkUserAnswer(const QString & userAnswer)
00308 {
00309     if ( m_entry == 0 ) {
00310         kError() << "Error: no entry set for validator.";
00311         return;
00312     }
00313 
00314     m_userAnswer = userAnswer;
00315 
00316 //     simpleCorrector();
00317     defaultCorrector();
00318 }
00319 
00320 void AnswerValidatorOld::checkUserAnswer(const QString & solution, const QString & userAnswer, const QString& language)
00321 {
00322 kDebug() << "CheckUserAnswer with two strings. The one string version is preferred.";
00323     if ( !language.isEmpty() ) {
00324 
00325     } else {
00326         m_spellerAvailable = false;
00327     }
00328 
00329     m_solution = solution;
00330 
00331     checkUserAnswer(userAnswer);
00332 }
00333 
00334 
00335 void AnswerValidatorOld::wordCompare(const QString & solution, const QString & userWord, double& grade, TestEntry::ErrorTypes& errorTypes)
00336 {
00338 
00339     // nothing to be done here if it's right
00340     if ( solution == userWord ) {
00341         grade = 1.0;
00342         errorTypes = TestEntry::Correct;
00343         return;
00344     }
00345 
00346     if ( solution.toLower() == userWord.toLower() ) {
00347         if (Prefs::ignoreCapitalizationMistakes()) {
00348             grade = 1.0;
00349         } else {
00350             grade = 1.0 - CAPITALIZATION_MISTAKE_PUNISHMENT;
00351         }
00352         errorTypes = TestEntry::CapitalizationMistake;
00353         return ;
00354     }
00355 
00356     if ( ParleyStringHandlerOld::stripAccents(solution) == ParleyStringHandlerOld::stripAccents(userWord) ) {
00357         if (Prefs::ignoreAccentMistakes()) {
00358             grade = 1.0;
00359         } else {
00360             grade = 1.0 - ACCENT_MISTAKE_PUNISHMENT;
00361         }
00362         errorTypes = TestEntry::AccentMistake;
00363         return ;
00364     }
00365 
00366     int levenshtein = levenshteinDistance(solution, userWord);
00367 
00368     if ( m_spellerAvailable ) {
00369         bool inSuggestions = false;
00370         bool isMisspelled = m_speller->isMisspelled( userWord );
00371         if ( m_speller->suggest(userWord).contains(solution) ) {
00372             inSuggestions = true;
00373         }
00374         // probably misspelled
00375         if ( isMisspelled && inSuggestions ) {
00376             grade = 1.0 - qMax (levenshtein * SPELLING_MISTAKE_PER_LETTER_PUNISHMENT, 1.0);
00377             errorTypes = TestEntry::SpellingMistake;
00378             return;
00379         }
00380         // this is a different word but sounds similar!
00381         if ( !isMisspelled && inSuggestions ) {
00382             grade = FALSE_FRIEND_GRADE;
00383 //             htmlCorrection = QString::fromLatin1("<font color=\"#8C1818\">NOOOO! That was a false friend!</font> ");
00384             errorTypes = errorTypes = TestEntry::FalseFriend;
00385             return ;
00386         }
00387         // unrelated word
00388         if ( !isMisspelled && !inSuggestions ) {
00389             grade = UNRELATED_WORD_GRADE;
00390 //             htmlCorrection = QString::fromLatin1("<font color=\"#8C1818\">Do you have any idea what you are talking about? (Wrong word, you spelled it correct I guess.)</font> ");
00391             errorTypes = TestEntry::UnrelatedWord;
00392             return;
00393         }
00394         // complete nonsense, unless levenshtein comes to the rescue
00395         if ( isMisspelled && !inSuggestions ) {
00396             if ( ((double)levenshtein/ qMax(solution.length(), userWord.length())) < LEVENSHTEIN_THRESHOLD ) {
00397 //                 htmlCorrection = QString::fromLatin1("<font color=\"#8C1818\">Seems like you got the spellig wrong.</font> ");
00398                 errorTypes = TestEntry::SpellingMistake;
00399                 return;
00400             } else {
00401 //                 htmlCorrection = QString::fromLatin1("<font color=\"#8C1818\">I don't know that word and it is not similar to the solution.</font> ");
00402                 errorTypes = TestEntry::UnknownMistake;
00403                 return;
00404             }
00405         }
00406     } else {
00407         if ( ((double)levenshtein/ qMax(solution.length(), userWord.length())) < LEVENSHTEIN_THRESHOLD ) {
00408             grade = 1.0 - ((double)levenshtein/ qMax(solution.length(), userWord.length()));
00409 //             htmlCorrection = QString::fromLatin1("<font color=\"#8C1818\">No spellchecker, but seems like a spelling error.</font> ");
00410             errorTypes = TestEntry::SpellingMistake;
00411             return;
00412         } else {
00413             grade = 1.0 - ((double)levenshtein/ qMax(solution.length(), userWord.length()));
00414 //             htmlCorrection = QString::fromLatin1("<font color=\"#8C1818\">No dictionary and no clue.</font> ");
00415             errorTypes = TestEntry::UnknownMistake;
00416             return;
00417         }
00418     }
00419 
00420     errorTypes = TestEntry::UnknownMistake;
00421     return;
00422 }
00423 
00424 
00425 void AnswerValidatorOld::sentenceAnalysis()
00426 {
00427     QStringList solutionWords;
00428     QStringList userAnswerWords;
00429 
00430     // filter !?.,;:¿ etc and throw them away
00431     solutionWords = m_solution.simplified().split(QRegExp("\\W"), QString::SkipEmptyParts);
00432     userAnswerWords = m_userAnswer.simplified().split(QRegExp("\\W"), QString::SkipEmptyParts);
00433     kDebug() << "Solution words: " << solutionWords;
00434     kDebug() << "Answer: " << userAnswerWords;
00435 
00436     QStringList correctWords;
00437     QStringList wrongWords;
00438 
00439     for ( int i = 0; i < userAnswerWords.count(); i++ ) {
00440         int pos = solutionWords.indexOf( userAnswerWords.value(i) );
00441         if ( pos >= 0 ) {
00442             correctWords.append(userAnswerWords.value(i));
00443             solutionWords.removeAt(pos);
00444             userAnswerWords.removeAt(i);
00445         } else {
00446             wrongWords.append(userAnswerWords.value(i));
00447         }
00448     }
00449 
00450     kDebug() << " remaining: solution: " << solutionWords.count()
00451             << "user: " << userAnswerWords.count();
00452 
00453     QList< QPair<QString, QString> > pairs = bestPairs(solutionWords, userAnswerWords);
00454 
00455     for ( int i = 0; i < pairs.count(); i++ ) {
00456         kDebug() << "Possible pair: " << pairs.value(i).first << " and " << pairs.value(i).second;
00457     }
00458 
00459 
00460     QString correction;
00461     correction.append("Correct: ");
00462     foreach (const QString &correctWord, correctWords) {
00463         correction.append(QString::fromLatin1("<font color=\"#188C18\">") + correctWord + QString::fromLatin1("</font> "));
00464     }
00465 
00466     correction.append(" Wrong: ");
00467     foreach (const QString &wrongWord, wrongWords) {
00468         correction.append(QString::fromLatin1("<font color=\"#8C1818\">") + wrongWord + QString::fromLatin1("</font> "));
00469     }
00470 
00471     int levenshtein = levenshteinDistance(m_solution, m_userAnswer);
00472 
00473     kDebug() << correction;
00474     kDebug() << "IMPLEMENT ME TO ACTUALLY EVALUATE THE ABOVE AND GENERATE A GRADE!";
00475     m_entry->setLastPercentage(1.0 - ((double)levenshtein/ qMax(m_solution.length(), m_userAnswer.length())));
00476     m_entry->setLastErrors(TestEntry::UnknownMistake);
00477 }
00478 
00479 
00480 QList< QPair < QString , QString > > AnswerValidatorOld::bestPairs(const QStringList& solutionWords , const QStringList& userAnswerWords )
00481 {
00482     int nSol = solutionWords.count();
00483     int nUser = userAnswerWords.count();
00484 
00485     QByteArray d;
00486     d.resize( nSol * nUser );
00487 
00488     QList< QPair < QString , QString > > pairList;
00489 
00490     // matrix of levenshteinDistances
00491     for ( int i = 0; i < nSol; i++) {
00492         for ( int j = 0; j < nUser; j++) {
00493             d[i + nSol*j] = levenshteinDistance(solutionWords.value(i), userAnswerWords.value(j));
00494         }
00495     }
00496 
00497     int MAX_LEVENSHTEIN = 5;
00498 
00499     // check if another pair is possible
00500     int min;
00501     int posSol = -1;
00502     int posUser = -1;
00503     do {
00504         min = MAX_LEVENSHTEIN;
00505         for ( int i = 0; i < nSol; i++ ) {
00506             for ( int j = 0; j < nUser; j++) {
00507                 if ( d.at(i + j*nSol ) < min ) {
00508                     min = d.at(i + j*nSol );
00509                     posSol = i;
00510                     posUser = j;
00511                 }
00512             }
00513         }
00514         if ( min < MAX_LEVENSHTEIN && posUser != -1 && posSol != -1) {
00515             pairList.append( qMakePair(solutionWords.value(posSol), userAnswerWords.value(posUser)) );
00516 
00517             // taken
00518             d[posSol + posUser*nSol] = MAX_LEVENSHTEIN;
00519         }
00520     } while ( min < MAX_LEVENSHTEIN );
00521 
00522     return pairList;
00523 }
00524 
00525 
00526 bool AnswerValidatorOld::spellcheckerAvailable()
00527 {
00528         return m_spellerAvailable;
00529 }

parley

Skip menu "parley"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdeedu

Skip menu "kdeedu"
  •     lib
  • kalzium
  • kanagram
  • kig
  •   lib
  • klettres
  • kstars
  • libkdeedu
  •   keduvocdocument
  • marble
  • parley
  •   stepcore
Generated for kdeedu by doxygen 1.5.9-20090814
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal