From 8f2e48941c7937c03957ca7ef6f1bb669f59a0be Mon Sep 17 00:00:00 2001 From: Yuri Gorshenin Date: Tue, 21 Feb 2017 12:45:10 +0300 Subject: [PATCH] [search] Added filtering by hotel type. --- indexer/ftypes_matcher.cpp | 68 +++++++++++++++++-- indexer/ftypes_matcher.hpp | 31 ++++++++- search/hotels_filter.cpp | 3 + search/hotels_filter.hpp | 50 ++++++++++++-- search/search_integration_tests/helpers.cpp | 7 ++ search/search_integration_tests/helpers.hpp | 2 + .../processor_test.cpp | 54 ++++++++++----- 7 files changed, 182 insertions(+), 33 deletions(-) diff --git a/indexer/ftypes_matcher.cpp b/indexer/ftypes_matcher.cpp index d3eefbbc7b..9f60ceba7e 100644 --- a/indexer/ftypes_matcher.cpp +++ b/indexer/ftypes_matcher.cpp @@ -1,8 +1,11 @@ #include "indexer/ftypes_matcher.hpp" +#include "indexer/classificator.hpp" #include "indexer/feature.hpp" #include "indexer/feature_data.hpp" -#include "indexer/classificator.hpp" + +#include "base/assert.hpp" +#include "base/buffer_vector.hpp" #include #include @@ -373,8 +376,19 @@ IsBookingChecker const & IsBookingChecker::Instance() IsHotelChecker::IsHotelChecker() { Classificator const & c = classif(); - for (auto const & tag : GetHotelTags()) - m_types.push_back(c.GetTypeByPath({"tourism", tag})); + for (size_t i = 0; i < static_cast(Type::Count); ++i) + { + auto const hotelType = static_cast(i); + auto const * const tag = GetHotelTypeTag(hotelType); + auto const type = c.GetTypeByPath({"tourism", tag}); + + m_types.push_back(type); + + m_sortedTypes[i].first = type; + m_sortedTypes[i].second = hotelType; + } + + sort(m_sortedTypes.begin(), m_sortedTypes.end()); } IsHotelChecker const & IsHotelChecker::Instance() @@ -383,11 +397,51 @@ IsHotelChecker const & IsHotelChecker::Instance() return inst; } -vector const & IsHotelChecker::GetHotelTags() +unsigned IsHotelChecker::GetHotelTypesMask(FeatureType const & ft) const { - static vector hotelTags = {"hotel", "apartment", "camp_site", "chalet", - "guest_house", "hostel", "motel", "resort"}; - return hotelTags; + feature::TypesHolder types(ft); + buffer_vector sortedTypes(types.begin(), types.end()); + sort(sortedTypes.begin(), sortedTypes.end()); + + unsigned mask = 0; + size_t i = 0; + size_t j = 0; + while (i < sortedTypes.size() && j < m_sortedTypes.size()) + { + if (sortedTypes[i] < m_sortedTypes[j].first) + { + ++i; + } + else if (sortedTypes[i] > m_sortedTypes[j].first) + { + ++j; + } + else + { + mask |= 1U << static_cast(m_sortedTypes[j].second); + ++i; + ++j; + } + } + + return mask; +} + +// static +char const * const IsHotelChecker::GetHotelTypeTag(Type type) +{ + switch (type) + { + case Type::Hotel: return "hotel"; + case Type::Apartment: return "apartment"; + case Type::CampSite: return "camp_site"; + case Type::Chalet: return "chalet"; + case Type::GuestHouse: return "guest_house"; + case Type::Hostel: return "hostel"; + case Type::Motel: return "motel"; + case Type::Resort: return "resort"; + case Type::Count: CHECK(false, ("Can't get hotel type tag")); return ""; + } } IsWifiChecker::IsWifiChecker() diff --git a/indexer/ftypes_matcher.hpp b/indexer/ftypes_matcher.hpp index 760d391120..dc0da449a7 100644 --- a/indexer/ftypes_matcher.hpp +++ b/indexer/ftypes_matcher.hpp @@ -3,7 +3,9 @@ #include "base/base.hpp" #include "std/algorithm.hpp" +#include "std/array.hpp" #include "std/initializer_list.hpp" +#include "std/limits.hpp" #include "std/string.hpp" #include "std/utility.hpp" #include "std/vector.hpp" @@ -160,11 +162,34 @@ public: class IsHotelChecker : public BaseChecker { +public: + enum class Type + { + Hotel, + Apartment, + CampSite, + Chalet, + GuestHouse, + Hostel, + Motel, + Resort, + + Count + }; + + static_assert(static_cast(Type::Count) <= CHAR_BIT * sizeof(unsigned), + "Too many types of hotels"); + + static IsHotelChecker const & Instance(); + + static char const * const GetHotelTypeTag(Type type); + + unsigned GetHotelTypesMask(FeatureType const & ft) const; + +private: IsHotelChecker(); -public: - static IsHotelChecker const & Instance(); - static vector const & GetHotelTags(); + array, static_cast(Type::Count)> m_sortedTypes; }; // WiFi is a type in classificator.txt, diff --git a/search/hotels_filter.cpp b/search/hotels_filter.cpp index 9a64085449..b3fd8b9785 100644 --- a/search/hotels_filter.cpp +++ b/search/hotels_filter.cpp @@ -2,6 +2,7 @@ #include "indexer/feature.hpp" #include "indexer/feature_meta.hpp" +#include "indexer/ftypes_matcher.hpp" #include "base/assert.hpp" @@ -40,6 +41,8 @@ void Description::FromFeature(FeatureType & ft) if (strings::to_int(priceRate, pr)) m_priceRate = pr; } + + m_types = ftypes::IsHotelChecker::Instance().GetHotelTypesMask(ft); } // Rule -------------------------------------------------------------------------------------------- diff --git a/search/hotels_filter.hpp b/search/hotels_filter.hpp index 86f63e01ad..4adbc23877 100644 --- a/search/hotels_filter.hpp +++ b/search/hotels_filter.hpp @@ -4,10 +4,12 @@ #include "search/cbv.hpp" #include "search/mwm_context.hpp" +#include "indexer/ftypes_matcher.hpp" #include "indexer/mwm_set.hpp" #include "std/map.hpp" #include "std/shared_ptr.hpp" +#include "std/sstream.hpp" #include "std/string.hpp" #include "std/unique_ptr.hpp" #include "std/utility.hpp" @@ -63,6 +65,7 @@ struct Description Rating::Value m_rating = Rating::kDefault; PriceRate::Value m_priceRate = PriceRate::kDefault; + unsigned m_types = 0; }; struct Rule @@ -83,7 +86,7 @@ struct EqRule final : public Rule { using Value = typename Field::Value; - EqRule(Value value) : m_value(value) {} + explicit EqRule(Value value) : m_value(value) {} // Rule overrides: bool Matches(Description const & d) const override @@ -112,7 +115,7 @@ struct LtRule final : public Rule { using Value = typename Field::Value; - LtRule(Value value) : m_value(value) {} + explicit LtRule(Value value) : m_value(value) {} // Rule overrides: bool Matches(Description const & d) const override @@ -141,7 +144,7 @@ struct LeRule final : public Rule { using Value = typename Field::Value; - LeRule(Value value) : m_value(value) {} + explicit LeRule(Value value) : m_value(value) {} // Rule overrides: bool Matches(Description const & d) const override @@ -171,7 +174,7 @@ struct GtRule final : public Rule { using Value = typename Field::Value; - GtRule(Value value) : m_value(value) {} + explicit GtRule(Value value) : m_value(value) {} // Rule overrides: bool Matches(Description const & d) const override @@ -200,7 +203,7 @@ struct GeRule final : public Rule { using Value = typename Field::Value; - GeRule(Value value) : m_value(value) {} + explicit GeRule(Value value) : m_value(value) {} // Rule overrides: bool Matches(Description const & d) const override @@ -297,6 +300,35 @@ struct OrRule final : public Rule shared_ptr m_rhs; }; +struct OneOfRule final : public Rule +{ + explicit OneOfRule(unsigned types) : m_types(types) {} + + // Rule overrides: + bool Matches(Description const & d) const override { return (d.m_types & m_types) != 0; } + + bool IdenticalTo(Rule const & rhs) const override + { + auto const * r = dynamic_cast(&rhs); + return r && m_types == r->m_types; + } + + string ToString() const override + { + ostringstream os; + os << "[ one of:"; + for (size_t i = 0; i < static_cast(ftypes::IsHotelChecker::Type::Count); ++i) + { + auto const type = static_cast(i); + os << " " << ftypes::IsHotelChecker::GetHotelTypeTag(type); + } + os << " ]"; + return os.str(); + } + + unsigned m_types; +}; + template shared_ptr Eq(typename Field::Value value) { @@ -337,6 +369,14 @@ inline shared_ptr Or(shared_ptr lhs, shared_ptr rhs) return make_shared(lhs, rhs); } +inline shared_ptr Is(ftypes::IsHotelChecker::Type type) +{ + CHECK(type != ftypes::IsHotelChecker::Type::Count, ()); + return make_shared(1U << static_cast(type)); +} + +inline shared_ptr OneOf(unsigned types) { return make_shared(types); } + class HotelsFilter { public: diff --git a/search/search_integration_tests/helpers.cpp b/search/search_integration_tests/helpers.cpp index b1849dad69..c537163d49 100644 --- a/search/search_integration_tests/helpers.cpp +++ b/search/search_integration_tests/helpers.cpp @@ -72,6 +72,13 @@ bool SearchTest::ResultsMatch(vector const & results, TRules con return MatchResults(m_engine, rules, results); } +bool SearchTest::ResultsMatch(SearchParams const & params, TRules const & rules) +{ + tests_support::TestSearchRequest request(m_engine, params, m_viewport); + request.Run(); + return ResultsMatch(request.Results(), rules); +} + unique_ptr SearchTest::MakeRequest(string const & query) { SearchParams params; diff --git a/search/search_integration_tests/helpers.hpp b/search/search_integration_tests/helpers.hpp index 0c80cad599..199e8b11f3 100644 --- a/search/search_integration_tests/helpers.hpp +++ b/search/search_integration_tests/helpers.hpp @@ -111,6 +111,8 @@ public: bool ResultsMatch(vector const & results, TRules const & rules); + bool ResultsMatch(SearchParams const & params, TRules const & rules); + unique_ptr MakeRequest(string const & query); size_t CountFeatures(m2::RectD const & rect); diff --git a/search/search_integration_tests/processor_test.cpp b/search/search_integration_tests/processor_test.cpp index ec892e44a6..c61d46aa3a 100644 --- a/search/search_integration_tests/processor_test.cpp +++ b/search/search_integration_tests/processor_test.cpp @@ -12,6 +12,7 @@ #include "generator/generator_tests_support/test_mwm_builder.hpp" #include "indexer/feature.hpp" +#include "indexer/ftypes_matcher.hpp" #include "indexer/index.hpp" #include "geometry/mercator.hpp" @@ -36,8 +37,10 @@ namespace class TestHotel : public TestPOI { public: + using Type = ftypes::IsHotelChecker::Type; + TestHotel(m2::PointD const & center, string const & name, string const & lang, float rating, - int priceRate) + int priceRate, Type type) : TestPOI(center, name, lang), m_rating(rating), m_priceRate(priceRate) { CHECK_GREATER_OR_EQUAL(m_rating, 0.0, ()); @@ -46,7 +49,7 @@ public: CHECK_GREATER_OR_EQUAL(m_priceRate, 0, ()); CHECK_LESS_OR_EQUAL(m_priceRate, 5, ()); - SetTypes({{"tourism", "hotel"}}); + SetTypes({{"tourism", ftypes::IsHotelChecker::GetHotelTypeTag(type)}}); } // TestPOI overrides: @@ -671,10 +674,14 @@ UNIT_CLASS_TEST(ProcessorTest, HotelsFiltering) { char const countryName[] = "Wonderland"; - TestHotel h1(m2::PointD(0, 0), "h1", "en", 8.0 /* rating */, 2 /* priceRate */); - TestHotel h2(m2::PointD(0, 1), "h2", "en", 7.0 /* rating */, 5 /* priceRate */); - TestHotel h3(m2::PointD(1, 0), "h3", "en", 9.0 /* rating */, 0 /* priceRate */); - TestHotel h4(m2::PointD(1, 1), "h4", "en", 2.0 /* rating */, 4 /* priceRate */); + TestHotel h1(m2::PointD(0, 0), "h1", "en", 8.0 /* rating */, 2 /* priceRate */, + TestHotel::Type::Hotel); + TestHotel h2(m2::PointD(0, 1), "h2", "en", 7.0 /* rating */, 5 /* priceRate */, + TestHotel::Type::Hostel); + TestHotel h3(m2::PointD(1, 0), "h3", "en", 9.0 /* rating */, 0 /* priceRate */, + TestHotel::Type::GuestHouse); + TestHotel h4(m2::PointD(1, 1), "h4", "en", 2.0 /* rating */, 4 /* priceRate */, + TestHotel::Type::GuestHouse); auto id = BuildCountry(countryName, [&](TestMwmBuilder & builder) { builder.Add(h1); @@ -691,36 +698,47 @@ UNIT_CLASS_TEST(ProcessorTest, HotelsFiltering) SetViewport(m2::RectD(m2::PointD(-1, -1), m2::PointD(2, 2))); { - TestSearchRequest request(m_engine, params, m_viewport); - request.Run(); TRules rules = {ExactMatch(id, h1), ExactMatch(id, h2), ExactMatch(id, h3), ExactMatch(id, h4)}; - TEST(ResultsMatch(request.Results(), rules), ()); + TEST(ResultsMatch(params, rules), ()); } using namespace hotels_filter; params.m_hotelsFilter = And(Gt(7.0), Le(2)); { - TestSearchRequest request(m_engine, params, m_viewport); - request.Run(); TRules rules = {ExactMatch(id, h1), ExactMatch(id, h3)}; - TEST(ResultsMatch(request.Results(), rules), ()); + TEST(ResultsMatch(params, rules), ()); } params.m_hotelsFilter = Or(Eq(9.0), Le(4)); { - TestSearchRequest request(m_engine, params, m_viewport); - request.Run(); TRules rules = {ExactMatch(id, h1), ExactMatch(id, h3), ExactMatch(id, h4)}; - TEST(ResultsMatch(request.Results(), rules), ()); + TEST(ResultsMatch(params, rules), ()); } params.m_hotelsFilter = Or(And(Eq(7.0), Eq(5)), Eq(4)); { - TestSearchRequest request(m_engine, params, m_viewport); - request.Run(); TRules rules = {ExactMatch(id, h2), ExactMatch(id, h4)}; - TEST(ResultsMatch(request.Results(), rules), ()); + TEST(ResultsMatch(params, rules), ()); + } + + params.m_hotelsFilter = Or(Is(TestHotel::Type::GuestHouse), Is(TestHotel::Type::Hostel)); + { + TRules rules = {ExactMatch(id, h2), ExactMatch(id, h3), ExactMatch(id, h4)}; + TEST(ResultsMatch(params, rules), ()); + } + + params.m_hotelsFilter = And(Gt(3), Is(TestHotel::Type::GuestHouse)); + { + TRules rules = {ExactMatch(id, h4)}; + TEST(ResultsMatch(params, rules), ()); + } + + params.m_hotelsFilter = OneOf((1U << static_cast(TestHotel::Type::Hotel)) | + (1U << static_cast(TestHotel::Type::Hostel))); + { + TRules rules = {ExactMatch(id, h1), ExactMatch(id, h2)}; + TEST(ResultsMatch(params, rules), ()); } }