new deterministic searcher

This commit is contained in:
alemuntoni 2022-01-21 15:56:09 +01:00
parent 6e3df79048
commit 17640937f4
9 changed files with 256 additions and 244 deletions

View File

@ -65,6 +65,7 @@ set(HEADERS
plugins/interfaces/filter_plugin.h
plugins/interfaces/io_plugin.h
plugins/interfaces/render_plugin.h
plugins/action_searcher.h
plugins/meshlab_plugin_type.h
plugins/plugin_manager.h
python/function.h
@ -81,8 +82,7 @@ set(HEADERS
ml_selection_buffers.h
ml_thread_safe_memory_info.h
mlapplication.h
mlexception.h
searcher.h)
mlexception.h)
set(SOURCES
ml_document/helpers/mesh_document_state_data.cpp
@ -107,6 +107,7 @@ set(SOURCES
plugins/interfaces/decorate_plugin.cpp
plugins/interfaces/filter_plugin.cpp
plugins/interfaces/io_plugin.cpp
plugins/action_searcher.cpp
plugins/meshlab_plugin_type.cpp
plugins/plugin_manager.cpp
python/function.cpp
@ -121,8 +122,7 @@ set(SOURCES
filterscript.cpp
ml_selection_buffers.cpp
ml_thread_safe_memory_info.cpp
mlapplication.cpp
searcher.cpp)
mlapplication.cpp)
set(RESOURCES meshlab-common.qrc)

View File

@ -0,0 +1,176 @@
/*****************************************************************************
* MeshLab o o *
* A versatile mesh processing toolbox o o *
* _ O _ *
* Copyright(C) 2005-2022 \/)\/ *
* Visual Computing Lab /\/| *
* ISTI - Italian National Research Council | *
* \ *
* All rights reserved. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt) *
* for more details. *
* *
****************************************************************************/
#include "action_searcher.h"
#include <cmath>
#include <QRegExp>
#include "interfaces/filter_plugin.h"
ActionSearcher::ActionSearcher()
{
}
void ActionSearcher::clear()
{
titleActionsMap.clear();
}
/**
* @brief Adds the given action to the ActionSearcher, allowing queries to it.
* @param action
*/
void ActionSearcher::addAction(QAction *action, bool usePythonFilterNames)
{
if (action != nullptr) {
if (!usePythonFilterNames) {
// add title to the action map
QString title = action->text();
title = title.toLower();
title.remove(ignexp);
QStringList res = title.split(sepexp, Qt::SkipEmptyParts);
res.removeDuplicates();
addSubStrings(res);
for (const QString& str : qAsConst(res)) {
titleActionsMap[str].push_back(action);
}
}
else {
// if the action is a filter, we should add also the python name to the search
QObject* parent = action->parent();
FilterPlugin* fp = qobject_cast<FilterPlugin*>(parent);
if (fp) {
QString title = fp->pythonFilterName(action);
title.replace("_", " ");
title.remove(ignexp);
QStringList res = title.split(sepexp, Qt::SkipEmptyParts);
res.removeDuplicates();
addSubStrings(res);
for (const QString& str : qAsConst(res)) {
titleActionsMap[str].push_back(action);
}
}
}
// add info to the action map
QString info = action->toolTip();
info = info.toLower();
info.remove(ignexp);
QStringList res = info.split(sepexp, Qt::SkipEmptyParts);
res.removeDuplicates();
addSubStrings(res);
for (const QString& str : qAsConst(res)) {
infoActionsMap[str].push_back(action);
}
}
}
/**
* @brief Performs a query using the inputString and returns an array containing the best matching
* actions matching to the input string.
*
* The array will have maximum size equal to maxNumberactions.
* @param inputString: query string
* @param maxNumberActions: maximum size of the output array
* @return array containing the best matching actions
*/
std::vector<QAction *> ActionSearcher::bestMatchingActions(QString inputString, int maxNumberActions) const
{
std::vector<QAction *> res;
// clean the input string
inputString = inputString.toLower();
inputString.replace("_", " "); // to allow python search
inputString.remove(ignexp);
// split the input string
QStringList inputList = inputString.split(sepexp, Qt::SkipEmptyParts);
inputList.removeDuplicates();
const float bonuspertitleword = 1.0f / std::pow(10,inputList.size());
// store the ranking for each action
std::map<QAction*, float> actionRanking;
// for each input string
for (const QString& str : qAsConst(inputList)) {
auto it = titleActionsMap.find(str);
// if matches in a title of an action
if (it != titleActionsMap.end()) {
for (QAction* act : it->second) { // for each matching action, increment its ranking
actionRanking[act] += bonuspertitleword;
}
}
it = infoActionsMap.find(str);
// if matches in a info of an action
if (it != infoActionsMap.end()) {
for (QAction* act : it->second) { // for each matching action, increment its ranking
actionRanking[act]++;
}
}
}
std::map<float, std::vector<QAction*>> rankingMap; // flipped map of actionRanking
// populate the flipped map
for (const auto& p : actionRanking) {
// for each ranking, push another action havint that ranking
rankingMap[p.second].push_back(p.first);
}
// at the end, the map will store the actions stored by ranking
int count = 0; // used to stop the number of inserted actions in the resulting vector
// reverse iteration: start from the biggest ranking
for (auto it = rankingMap.rbegin(); it != rankingMap.rend() && count < maxNumberActions; ++it) {
auto v1 = it->second;
// need to sort properly actions having the same ranking, since they would be sorted using
// memory addresses, that are not deterministic: we use action titles
std::sort(v1.begin(), v1.end(), ActionComparator());
// insert the sorted actions to the result vector
res.insert(res.end(), v1.begin(), v1.end());
count += it->second.size();
}
// if at the end the number exceeded the maximum number, we truncate the array
if (count >= maxNumberActions) {
res.resize(maxNumberActions);
}
return res;
}
void ActionSearcher::addSubStrings(QStringList &res)
{
QStringList resWithPrefix;
foreach(QString str, res)
{
QString strPref = str;
resWithPrefix.push_back(strPref);
for(int i=0;i<str.length()-3;++i)
{
strPref.chop(1);
resWithPrefix.push_back(strPref);
}
}
resWithPrefix.removeDuplicates();
res = resWithPrefix;
}

View File

@ -0,0 +1,62 @@
/*****************************************************************************
* MeshLab o o *
* A versatile mesh processing toolbox o o *
* _ O _ *
* Copyright(C) 2005-2022 \/)\/ *
* Visual Computing Lab /\/| *
* ISTI - Italian National Research Council | *
* \ *
* All rights reserved. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt) *
* for more details. *
* *
****************************************************************************/
#ifndef ACTION_SEARCHER_H
#define ACTION_SEARCHER_H
#include <map>
#include <vector>
#include <QString>
#include <QAction>
class ActionSearcher
{
public:
ActionSearcher();
void clear();
void addAction(QAction* action, bool usePythonFilterNames = false);
std::vector<QAction*> bestMatchingActions(QString inputString, int maxNumberActions) const;
private:
const QRegExp sepexp = QRegExp("\\W+");
const QRegExp ignexp = QRegExp(
"\\b(an|the|of|it|as|in|by|and|or|for)\\b|\\b[a-z]\\b|'s\\b|\\.|<[^>]*>");
// map that stores, for each string, all the actions that store that string in their titles
std::map<QString, std::vector<QAction*>> titleActionsMap;
// map that stores, for each stirng, all the actions that store that stirng in their info
std::map<QString, std::vector<QAction*>> infoActionsMap;
struct ActionComparator {
bool operator()(QAction* a1, QAction* a2) {
return a1->text() < a2->text();
}
};
static void addSubStrings(QStringList& res);
};
#endif // ACTION_SEARCHER_H

View File

@ -1,151 +0,0 @@
#include "searcher.h"
#include "mlexception.h"
#include <cmath>
WordActionsMap::WordActionsMap()
:wordacts()
{
}
void WordActionsMap::addWordsPerAction(QAction& act,const QStringList& words)
{
foreach(QString word,words)
wordacts[word].push_back(&act);
}
void WordActionsMap::removeActionReferences(QAction& act )
{
for(QMap<QString,QList<QAction*> >::iterator it = wordacts.begin();it != wordacts.end();++it)
it.value().removeAll(&act);
}
bool WordActionsMap::getActionsPerWord( const QString& word,QList<QAction*>& res ) const
{
QMap< QString,QList<QAction*> >::const_iterator it = wordacts.find(word);
if (it != wordacts.end())
{
res = it.value();
return true;
}
return false;
}
void WordActionsMap::clear()
{
wordacts.clear();
}
WordActionsMapAccessor::WordActionsMapAccessor()
:map(),sepexp(),ignexp()
{
sepexp.setPattern("\\W+");
ignexp.setPattern("\\b(an|the|of|it|as|in|by|and|or|for)\\b|\\b[a-z]\\b|'s\\b|\\.|<[^>]*>");
}
void WordActionsMapAccessor::addWordsPerAction(QAction& act,const QString& st )
{
QStringList wlist;
purifiedSplit(st,wlist);
addSubStrings(wlist);
map.addWordsPerAction(act,wlist);
}
int WordActionsMapAccessor::rankedMatchesPerInputString( const QString& input,RankedMatches& rm ) const
{
QStringList inputlist;
purifiedSplit(input,inputlist);
return rm.computeRankedMatches(inputlist,map);
}
void WordActionsMapAccessor::purifiedSplit( const QString& input,QStringList& res ) const
{
res.clear();
QString tmp = input;
tmp = tmp.toLower();
tmp.remove(ignexp);
res = tmp.split(sepexp,QString::SkipEmptyParts);
res.removeDuplicates();
}
void WordActionsMapAccessor::addSubStrings( QStringList& res ) const
{
QStringList resWithPrefix;
foreach(QString str, res)
{
QString strPref = str;
resWithPrefix.push_back(strPref);
for(int i=0;i<str.length()-3;++i)
{
strPref.chop(1);
resWithPrefix.push_back(strPref);
}
}
resWithPrefix.removeDuplicates();
res = resWithPrefix;
}
RankedMatches::RankedMatches()
:ranking()
{
}
int RankedMatches::computeRankedMatches( const QStringList& inputst,const WordActionsMap& map, bool matchesontitlearemoreimportant)
{
QMap<QAction*, float> wordmatchesperaction;
ranking.clear();
int inputstsize = inputst.size();
ranking.resize(inputstsize);
float bonuspertitleword = 0.0f;
if (matchesontitlearemoreimportant)
bonuspertitleword = 1.0f / std::pow(10,inputstsize);
foreach(const QString& st,inputst)
{
QList<QAction*> res;
bool found = map.getActionsPerWord(st,res);
if (found)
{
foreach(QAction* act, res)
{
++wordmatchesperaction[act];
QString title = act->text().toLower().trimmed();
if (title.contains(st))
wordmatchesperaction[act] = wordmatchesperaction[act] + bonuspertitleword;
}
}
}
QMap<float, QList<QAction*> > rankedmatches;
for (QMap<QAction*, float>::iterator it = wordmatchesperaction.begin(); it != wordmatchesperaction.end(); ++it)
rankedmatches[it.value()].push_back(it.key());
int maxindex = -1;
for(QMap<float,QList<QAction*> >::iterator it = rankedmatches.end() - 1;it != rankedmatches.begin() - 1;--it)
{
int index = std::floor(it.key()) - 1;
if (index >= ranking.size())
{
throw InvalidInvariantException("WARNING! Index contained in wordmatchesperaction it's out-of-bound.");
return 0;
}
if (index > maxindex)
maxindex = index;
ranking[index].append(it.value());
}
return maxindex + 1;
}
void RankedMatches::getActionsWithNMatches( const int n,QList<QAction*>& res )
{
res.clear();
int index = n -1;
if ((index >= ranking.size()) || (n < 1))
{
throw InvalidInvariantException(QString("WARNING! Parameter n MUST be in the range [1..") + QString::number(ranking.size()) + "].");
return;
}
res = ranking[index];
}

View File

@ -1,58 +0,0 @@
#ifndef SEARCHER_H
#define SEARCHER_H
#include<QString>
#include<QMap>
#include<QList>
#include<QAction>
#include<QRegExp>
#include<QVector>
#include<QSet>
class WordActionsMap
{
public:
WordActionsMap();
void addWordsPerAction(QAction& act,const QStringList& words);
void removeActionReferences(QAction& act);
bool getActionsPerWord( const QString& word,QList<QAction*>& res ) const;
void clear();
private:
QMap<QString,QList<QAction*> > wordacts;
};
class RankedMatches;
class WordActionsMapAccessor
{
public:
WordActionsMapAccessor();
void addWordsPerAction(QAction& act,const QString& st);
inline void removeActionReferences(QAction& act) {map.removeActionReferences(act);}
inline void setSeparator(const QRegExp& sep) {sepexp = sep;}
inline void setIgnoredWords(const QRegExp& ign) {ignexp = ign;}
int rankedMatchesPerInputString(const QString& input,RankedMatches& rm) const;
inline QRegExp separtor() const {return sepexp;}
inline QRegExp ignored() const {return ignexp;}
void clear() {map.clear(); sepexp = QRegExp(); ignexp = QRegExp();}
private:
void purifiedSplit(const QString& input,QStringList& res) const;
void addSubStrings(QStringList& res) const;
WordActionsMap map;
QRegExp sepexp;
QRegExp ignexp;
};
class RankedMatches
{
public:
RankedMatches();
void getActionsWithNMatches(const int n,QList<QAction*>& res);
private:
friend int WordActionsMapAccessor::rankedMatchesPerInputString(const QString& input,RankedMatches& rm) const;
int computeRankedMatches(const QStringList& inputst,const WordActionsMap& map,bool matchesontitlearemoreimportant = true);
QVector<QList<QAction*> > ranking;
};
#endif

View File

@ -8,7 +8,7 @@
#include <QStyle>
#include <QDebug>
SearchMenu::SearchMenu(const WordActionsMapAccessor& wm,const int max,QWidget* parent,const int fixedwidth)
SearchMenu::SearchMenu(const ActionSearcher& wm,const int max,QWidget* parent,const int fixedwidth)
:MenuWithToolTip(QString(),parent),searchline(NULL),wama(wm),maxres(max),fixedwidthsize(fixedwidth)
{
searchline = new MenuLineEdit(this);
@ -24,29 +24,14 @@ SearchMenu::SearchMenu(const WordActionsMapAccessor& wm,const int max,QWidget* p
void SearchMenu::getResults(const QString& text,QList<QAction*>& result)
{
try
{
RankedMatches rm;
int ii = wama.rankedMatchesPerInputString(text,rm);
int inserted = 0;
while(ii > 0)
{
QList<QAction*> myacts;
rm.getActionsWithNMatches(ii,myacts);
if (inserted + myacts.size() > maxres)
myacts = myacts.mid(0,myacts.size() - (inserted + myacts.size() - maxres));
result.append(myacts);
QAction* sep = new QAction(this);
sep->setSeparator(true);
result.append(sep);
inserted += myacts.size();
--ii;
}
}
catch(InvalidInvariantException& e)
{
qDebug() << "WARNING!!!!!!!!!!!!!!!!!!!" << e.what() << "\n";
}
std::vector<QAction*> rm = wama.bestMatchingActions(text, 15);
for (QAction* act : rm) {
result.append(act);
}
QAction* sep = new QAction(this);
sep->setSeparator(true);
result.append(sep);
}
void SearchMenu::updateGUI( const QList<QAction*>& results )

View File

@ -20,7 +20,7 @@
#include <QPushButton>
#include <QLabel>
#include <QSlider>
#include "../common/searcher.h"
#include "../common/plugins/action_searcher.h"
#include <QToolTip>
#include <QSyntaxHighlighter>
#include <QProxyStyle>
@ -75,7 +75,7 @@ class SearchMenu : public MenuWithToolTip
{
Q_OBJECT
public:
SearchMenu(const WordActionsMapAccessor& wm,const int max,QWidget* parent,const int fixedwidth = -1);
SearchMenu(const ActionSearcher& wm,const int max,QWidget* parent,const int fixedwidth = -1);
int& searchLineWidth();
void clearResults();
QSize sizeHint () const;
@ -84,7 +84,7 @@ protected:
void resizeEvent ( QResizeEvent * event);
private:
MenuLineEdit* searchline;
const WordActionsMapAccessor& wama;
const ActionSearcher& wama;
int maxres;
int fixedwidthsize;

View File

@ -361,7 +361,7 @@ public:
QMenu* rasterLayerMenu() { return filterMenuRasterLayer; }
private:
WordActionsMapAccessor wama;
ActionSearcher wama;
//////// ToolBars ///////////////
QToolBar* mainToolBar;
QToolBar* decoratorToolBar;

View File

@ -36,7 +36,6 @@
#include <QWidgetAction>
#include <QMessageBox>
#include "mainwindow.h"
#include <common/searcher.h>
#include <common/mlapplication.h>
#include <common/mlexception.h>
#include <common/globals.h>
@ -692,8 +691,7 @@ void MainWindow::initMenuForSearching(QMenu* menu)
void MainWindow::initItemForSearching(QAction* act)
{
QString tx = act->text() + " " + act->toolTip();
wama.addWordsPerAction(*act, tx);
wama.addAction(act);
}
QString MainWindow::getDecoratedFileName(const QString& name)