diff options
| -rw-r--r-- | Swift/Controllers/Contact.cpp | 45 | ||||
| -rw-r--r-- | Swift/Controllers/Contact.h | 4 | ||||
| -rw-r--r-- | Swift/Controllers/ContactSuggester.cpp | 26 | ||||
| -rw-r--r-- | Swift/Controllers/ContactSuggester.h | 7 | ||||
| -rw-r--r-- | Swift/Controllers/SConscript | 1 | ||||
| -rw-r--r-- | Swift/Controllers/UnitTest/ContactSuggesterTest.cpp | 128 |
6 files changed, 187 insertions, 24 deletions
diff --git a/Swift/Controllers/Contact.cpp b/Swift/Controllers/Contact.cpp index 7eb446c..198443d 100644 --- a/Swift/Controllers/Contact.cpp +++ b/Swift/Controllers/Contact.cpp @@ -6,2 +6,4 @@ +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/find.hpp> #include <Swift/Controllers/Contact.h> @@ -16,2 +18,45 @@ Contact::Contact(const std::string& name, const JID& jid, StatusShow::Type statu +bool Contact::lexicographicalSortPredicate(const Contact::ref& a, const Contact::ref& b) { + return a->jid < b->jid; +} + +bool Contact::equalityPredicate(const Contact::ref& a, const Contact::ref& b) { + return a->jid == b->jid; +} + +bool Contact::sortPredicate(const Contact::ref& a, const Contact::ref& b, const std::string& search) { + /* perform case insensitive comparisons */ + std::string aLower = a->name; + boost::to_lower(aLower); + std::string bLower = b->name; + boost::to_lower(bLower); + std::string searchLower = search; + boost::to_lower(searchLower); + + /* name starts with the search term */ + if (aLower.find(searchLower) == 0 && bLower.find(searchLower) != 0) { + return true; + } else if (bLower.find(searchLower) == 0 && aLower.find(searchLower) != 0) { + return false; + } + + /* name contains search term */ + if (aLower.find(searchLower) != std::string::npos && bLower.find(searchLower) == std::string::npos) { + return true; + } else if (bLower.find(searchLower) != std::string::npos && aLower.find(searchLower) == std::string::npos) { + return false; + } + + /* Levenshtein should be done here */ + /* if edit distances are equal, fall through to the tests below */ + + /* lexicographical sort */ + if (a->statusType == b->statusType) { + return aLower.compare(bLower) < 0; + } + + /* online status */ + return a->statusType < b->statusType; +} + } diff --git a/Swift/Controllers/Contact.h b/Swift/Controllers/Contact.h index ceba152..f83230f 100644 --- a/Swift/Controllers/Contact.h +++ b/Swift/Controllers/Contact.h @@ -29,2 +29,6 @@ class Contact : public boost::enable_shared_from_this<Contact> { + static bool lexicographicalSortPredicate(const Contact::ref& a, const Contact::ref& b); + static bool equalityPredicate(const Contact::ref& a, const Contact::ref& b); + static bool sortPredicate(const Contact::ref& a, const Contact::ref& b, const std::string& search); + public: diff --git a/Swift/Controllers/ContactSuggester.cpp b/Swift/Controllers/ContactSuggester.cpp index 41e0261..09a1567 100644 --- a/Swift/Controllers/ContactSuggester.cpp +++ b/Swift/Controllers/ContactSuggester.cpp @@ -14,3 +14,5 @@ +#include <boost/algorithm/string.hpp> #include <boost/algorithm/string/find.hpp> +#include <boost/bind.hpp> #include <boost/lambda/lambda.hpp> @@ -53,7 +55,7 @@ std::vector<Contact::ref> ContactSuggester::getSuggestions(const std::string& se - std::sort(results.begin(), results.end(), ContactSuggester::contactLexicographicalSortPredicate); - results.erase(std::unique(results.begin(), results.end(), ContactSuggester::contactEqualityPredicate), results.end()); - results.erase(std::remove_if(results.begin(), results.end(), !lambda::bind(&ContactSuggester::matchContact, search, lambda::_1)), + std::sort(results.begin(), results.end(), Contact::lexicographicalSortPredicate); + results.erase(std::unique(results.begin(), results.end(), Contact::equalityPredicate), results.end()); + results.erase(std::remove_if(results.begin(), results.end(), !lambda::bind(&matchContact, search, lambda::_1)), results.end()); - std::sort(results.begin(), results.end(), ContactSuggester::contactSmartSortPredicate); + std::sort(results.begin(), results.end(), boost::bind(&Contact::sortPredicate, _1, _2, search)); @@ -74,18 +76,2 @@ bool ContactSuggester::fuzzyMatch(std::string text, std::string match) { -bool ContactSuggester::contactLexicographicalSortPredicate(const Contact::ref& a, const Contact::ref& b) { - return a->jid < b->jid; -} - -bool ContactSuggester::contactEqualityPredicate(const Contact::ref& a, const Contact::ref& b) { - return a->jid == b->jid; -} - -bool ContactSuggester::contactSmartSortPredicate(const Contact::ref& a, const Contact::ref& b) { - if (a->statusType == b->statusType) { - return a->name.compare(b->name) < 0; - } else { - return a->statusType < b->statusType; - } -} - } diff --git a/Swift/Controllers/ContactSuggester.h b/Swift/Controllers/ContactSuggester.h index 67b912c..1c796c9 100644 --- a/Swift/Controllers/ContactSuggester.h +++ b/Swift/Controllers/ContactSuggester.h @@ -19,2 +19,4 @@ +class ContactSuggesterTest; + namespace Swift { @@ -30,3 +32,3 @@ namespace Swift { std::vector<Contact::ref> getSuggestions(const std::string& search) const; - private: + public: static bool matchContact(const std::string& search, const Contact::ref& c); @@ -36,5 +38,2 @@ namespace Swift { static bool fuzzyMatch(std::string text, std::string match); - static bool contactLexicographicalSortPredicate(const Contact::ref& a, const Contact::ref& b); - static bool contactEqualityPredicate(const Contact::ref& a, const Contact::ref& b); - static bool contactSmartSortPredicate(const Contact::ref& a, const Contact::ref& b); diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index e0fa508..e911a3a 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -107,2 +107,3 @@ if env["SCONS_STAGE"] == "build" : File("UnitTest/HighlightRuleTest.cpp"), + File("UnitTest/ContactSuggesterTest.cpp") ]) diff --git a/Swift/Controllers/UnitTest/ContactSuggesterTest.cpp b/Swift/Controllers/UnitTest/ContactSuggesterTest.cpp new file mode 100644 index 0000000..222491b --- /dev/null +++ b/Swift/Controllers/UnitTest/ContactSuggesterTest.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2014 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <boost/bind.hpp> +#include <boost/function.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include "Swift/Controllers/ContactSuggester.h" + +using namespace Swift; + +class ContactSuggesterTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(ContactSuggesterTest); + CPPUNIT_TEST(equalityTest); + CPPUNIT_TEST(lexicographicalSortTest); + CPPUNIT_TEST(sortTest); + CPPUNIT_TEST_SUITE_END(); + +public: + + std::vector<std::string> wordList() { + const std::string words[] = { + "abc", + "ab", + "bc", + "d" + }; + + return std::vector<std::string>(words, words+sizeof(words)/sizeof(*words)); + } + + std::vector<StatusShow::Type> statusList() { + StatusShow::Type types[] = { + StatusShow::Online, + StatusShow::Away, + StatusShow::FFC, + StatusShow::XA, + StatusShow::DND, + StatusShow::None + }; + + return std::vector<StatusShow::Type>(types, types+sizeof(types)/sizeof(*types)); + } + + std::vector<Contact::ref> contactList() { + std::vector<Contact::ref> contacts; + std::vector<std::string> words = wordList(); + std::vector<StatusShow::Type> statuses = statusList(); + foreach (const std::string& name, words) { + foreach (const std::string& jid, words) { + foreach (const StatusShow::Type& status, statuses) { + contacts.push_back(boost::make_shared<Contact>(name, jid, status, "")); + } + } + } + return contacts; + } + + /* a = a */ + bool isReflexive(const boost::function<bool (const Contact::ref&, const Contact::ref&)>& comparitor) { + std::vector<Contact::ref> contacts = contactList(); + foreach (const Contact::ref& a, contacts) { + if (!comparitor(a, a)) { + return false; + } + } + return true; + } + + /* a = b -> b = a */ + bool isSymmetric(const boost::function<bool (const Contact::ref&, const Contact::ref&)>& comparitor) { + std::vector<Contact::ref> contacts = contactList(); + foreach (const Contact::ref& a, contacts) { + foreach (const Contact::ref& b, contacts) { + if (comparitor(a, b)) { + if (!comparitor(b, a)) { + return false; + } + } + } + } + return true; + } + + /* a = b && b = c -> a = c */ + bool isTransitive(const boost::function<bool (const Contact::ref&, const Contact::ref&)>& comparitor) { + std::vector<Contact::ref> contacts = contactList(); + foreach (const Contact::ref& a, contacts) { + foreach (const Contact::ref& b, contacts) { + foreach (const Contact::ref& c, contacts) { + if (comparitor(a, b) && comparitor(b, c)) { + if (!comparitor(a, c)) { + return false; + } + } + } + } + } + return true; + } + + void equalityTest() { + CPPUNIT_ASSERT(isReflexive(Contact::equalityPredicate)); + CPPUNIT_ASSERT(isSymmetric(Contact::equalityPredicate)); + CPPUNIT_ASSERT(isTransitive(Contact::equalityPredicate)); + } + + void lexicographicalSortTest() { + CPPUNIT_ASSERT(isTransitive(Contact::lexicographicalSortPredicate)); + } + + void sortTest() { + std::vector<std::string> words = wordList(); + foreach (const std::string& word, words) { + CPPUNIT_ASSERT(isTransitive(boost::bind(Contact::sortPredicate, _1, _2, word))); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ContactSuggesterTest); |
Swift