суббота, 3 декабря 2011 г.

Ленивое сравнение для boost::any()

Понадобилось мне быстро распарсить много данных из XML-файла. Возможность хранить всё, предлагаемая boost::any, как бы предполагает возможность это "всё" сравнивать... Ан нет! Выяснилось, что для boost::any (в который собирались xml-данные) готового сравнения нет. В общем-то, если задуматься, это логично. Но я так привык работать с нестрого типизированными языками, что просто не мог смириться. C++ - злой язык :) Время на написание довольно скучного кода было потрачено. И если Вам нужен equals() для boost::any() - код ниже.


Метод equals( boost::any, boost::any ) возвращает пару булевых значений:
  • (1) проверка на равенство типов
  • (2) проверка собственно самих значений

Сравнение я назвал "ленивым", т.к. если простые типы оказались не равными и хотя бы одно из значений является строкой, делается попытка распознать в строках числа и сравнить их.

Типами метод чрезмерно не нагружал, т.к. каждая проверка - это процессорное время. Вы можете запросто добавить в код свои типы и/или исключить имеющиеся. Ведь код должен эффективно решать поставленную задачу и (в идеале) быть лёгким для понимания. Остальное - от лукавого!

/**
* Сравнивает два значения boost::any.
*
* (!) Может возвращать ( false, true ), т.к. числа разных типов
* сравниваются как числа.
*
* @param precision Точность для сравнения значений с плавающей точкой.
*/
inline std::pair<
    // типы совпадают?
    bool,
    // содержимое совпадает?
    bool
> lazyEquals(
    const boost::any& a,
    const boost::any& b,
    double precision = 1e-8
) {
    using namespace boost;

    // Быстрая оценка на равенство
    const type_info& at = a.type();
    const type_info& bt = b.type();
    std::pair< bool, bool >  r = std::make_pair(
        (at == bt),
        false
    );
    
    // double
    if (at == typeid( double )) {
        const auto ca = any_cast< double >( a );
        double cb = numeric_limits< double >::infinity();
        if (bt == typeid( double )) {
            cb = any_cast< double >( b );
        } else if (bt == typeid( float )) {
            cb = (double)any_cast< float >( b );
        } else if (bt == typeid( int )) {
            cb = (double)any_cast< int >( b );
        } else if (bt == typeid( size_t )) {
            cb = (double)any_cast< size_t >( b );
        } else if (bt == typeid( string )) {
            cb = numberCast( any_cast< string >( b ) );
            // Получим infinity(), если не число
        }
        if (cb != numeric_limits< double >::infinity()) {
            r.second = (precision > std::abs( ca - cb ));
        }

    // float
    } else if (at == typeid( float )) {
        const auto ca = any_cast< float >( a );
        float cb = numeric_limits< float >::infinity();
        if (bt == typeid( double )) {
            cb = (float)any_cast< double >( b );
        } else if (bt == typeid( float )) {
            cb = any_cast< float >( b );
        } else if (bt == typeid( int )) {
            cb = (float)any_cast< int >( b );
        } else if (bt == typeid( size_t )) {
            cb = (float)any_cast< size_t >( b );
        } else if (bt == typeid( string )) {
            cb = (float)numberCast( any_cast< string >( b ) );
            // Получим infinity(), если не число
        }
        if (cb != numeric_limits< float >::infinity()) {
            r.second = (precision > std::abs( ca - cb ));
        }

    // int
    } else if (at == typeid( int )) {
        const auto ca = any_cast< int >( a );
        if (bt == typeid( double )) {
            r.second = (ca == (int)any_cast< double >( b ));
        } else if (bt == typeid( float )) {
            r.second = (ca == any_cast< float >( b ));
        } else if (bt == typeid( int )) {
            r.second = (ca == any_cast< int >( b ));
        } else if (bt == typeid( size_t )) {
            r.second = (ca == (int)any_cast< size_t >( b ));
        } else if (bt == typeid( string )) {
            const double temp = numberCast( any_cast< string >( b ) );
            // Получим infinity(), если не число
            if (temp != numeric_limits< double >::infinity()) {
                r.second = (ca == (int)temp);
            }
        }

    // size_t
    } else if (at == typeid( size_t )) {
        const auto ca = any_cast< size_t >( a );
        if (bt == typeid( double )) {
            r.second = (ca == (size_t)any_cast< double >( b ));
        } else if (bt == typeid( float )) {
            r.second = (ca == (size_t)any_cast< float >( b ));
        } else if (bt == typeid( int )) {
            r.second = (ca == (size_t)any_cast< int >( b ));
        } else if (bt == typeid( size_t )) {
            r.second = (ca == any_cast< size_t >( b ));
        } else if (bt == typeid( string )) {
            const double temp = numberCast( any_cast< string >( b ) );
            // Получим infinity(), если не число
            if (temp != numeric_limits< double >::infinity()) {
                r.second = (ca == (size_t)temp);
            }
        }

    // string
    } else if (at == typeid( string )) {
        const string ca = any_cast< string >( a );
        if (bt == typeid( string )) {
            r.second = (ca == any_cast< string >( b ));
        }
        // Может, число представлено строкой? Строки переведём
        // в числа и сравним числа согласно их типу.
        const double nca = numberCast( ca );
        if (nca != numeric_limits< double >::infinity()) {
            if (bt == typeid( double )) {
                const auto ncb = any_cast< double >( b );
                r.second = (precision > std::abs( nca - ncb ));
            } else if (bt == typeid( float )) {
                const auto ncb = any_cast< float >( b );
                r.second = (precision > std::abs( (float)nca - ncb ));
            } else if (bt == typeid( int )) {
                const auto ncb = any_cast< int >( b );
                r.second = ((int)nca == ncb);
            } else if (bt == typeid( size_t )) {
                const auto ncb = any_cast< size_t >( b );
                r.second = ((size_t)nca == ncb);
            }
            // string посмотрели выше
        }

    } // if-else ...

    return r;
}

В методе используется вот такая функция, которая кастует преобразует строку в число:

/**
* @return Распознанное число или numeric_limits< double >::infinity().
*/
inline double numberCast( const string& s ) {
    try {
        const double number = boost::lexical_cast< double >( s );
        return number;
    }
    catch ( boost::bad_lexical_cast& ) {
        return numeric_limits< double >::infinity();
    }
}


И, конечно же, тесты! Без них любой код смотрится подозрительно. Используем googletest. Надо говорить, что все t. - зелёные?

/**
* Фикстура.
*/
class DTest : public ::testing::Test {
  protected:
    inline DTest() {
    }

    virtual inline ~DTest() {
    }

    virtual inline void SetUp() {
    }

    virtual void TearDown() {
    }
};

Одинаковые типы данных
TEST_F( DTest, IntEqualsInt_LazyEquals ) {
    const auto a = boost::any( (int)100 );
    const auto b = boost::any( (int)100 );
    const auto r = d::lazyEquals( a, b );
    EXPECT_TRUE( r.first );
    EXPECT_TRUE( r.second );
}


TEST_F( DTest, IntNotEqualsInt_LazyEquals ) {
    const auto a = boost::any( (int)100 );
    const auto b = boost::any( (int)200 );
    const auto r = d::lazyEquals( a, b );
    EXPECT_TRUE( r.first );
    EXPECT_FALSE( r.second );
}


TEST_F( DTest, SizeTEqualsSizeT_LazyEquals ) {
    const auto a = boost::any( (size_t)100 );
    const auto b = boost::any( (size_t)100 );
    const auto r = d::lazyEquals( a, b );
    EXPECT_TRUE( r.first );
    EXPECT_TRUE( r.second );
}


TEST_F( DTest, SizeTNotEqualsSizeT_LazyEquals ) {
    const auto a = boost::any( (size_t)100 );
    const auto b = boost::any( (size_t)200 );
    const auto r = d::lazyEquals( a, b );
    EXPECT_TRUE( r.first );
    EXPECT_FALSE( r.second );
}


TEST_F( DTest, FloatEqualsFloat_LazyEquals ) {
    const auto a = boost::any( (float)100 );
    const auto b = boost::any( (float)100 );
    const auto r = d::lazyEquals( a, b );
    EXPECT_TRUE( r.first );
    EXPECT_TRUE( r.second );
}


TEST_F( DTest, FloatNotEqualsFloat_LazyEquals ) {
    const auto a = boost::any( (float)100 );
    const auto b = boost::any( (float)200 );
    const auto r = d::lazyEquals( a, b );
    EXPECT_TRUE( r.first );
    EXPECT_FALSE( r.second );
}


TEST_F( DTest, DoubleEqualsDouble_LazyEquals ) {
    const auto a = boost::any( (double)100 );
    const auto b = boost::any( (double)100 );
    const auto r = d::lazyEquals( a, b );
    EXPECT_TRUE( r.first );
    EXPECT_TRUE( r.second );
}


TEST_F( DTest, DoubleNotEqualsDouble_LazyEquals ) {
    const auto a = boost::any( (double)100 );
    const auto b = boost::any( (double)200 );
    const auto r = d::lazyEquals( a, b );
    EXPECT_TRUE( r.first );
    EXPECT_FALSE( r.second );
}


TEST_F( DTest, StringLatinEqualsStringLatin_LazyEquals ) {
    const auto a = boost::any( (string)"abcd" );
    const auto b = boost::any( (string)"abcd" );
    const auto r = d::lazyEquals( a, b );
    EXPECT_TRUE( r.first );
    EXPECT_TRUE( r.second );
}


TEST_F( DTest, StringLatinNotEqualsStringLatin_LazyEquals ) {
    const auto a = boost::any( (string)"abcd" );
    const auto b = boost::any( (string)"abcdefgh" );
    const auto r = d::lazyEquals( a, b );
    EXPECT_TRUE( r.first );
    EXPECT_FALSE( r.second );
}


TEST_F( DTest, StringUnicodeEqualsStringUnicode_LazyEquals ) {
    const auto a = boost::any( (string)"Юникод" );
    const auto b = boost::any( (string)"Юникод" );
    const auto r = d::lazyEquals( a, b );
    EXPECT_TRUE( r.first );
    EXPECT_TRUE( r.second );
}


TEST_F( DTest, StringUnicodeNotEqualsStringUnicode_LazyEquals ) {
    const auto a = boost::any( (string)"Юникод" );
    const auto b = boost::any( (string)"Юникод тоже" );
    const auto r = d::lazyEquals( a, b );
    EXPECT_TRUE( r.first );
    EXPECT_FALSE( r.second );
}

Разные типы данных
TEST_F( DTest, IntEqualsSizeT_LazyEquals ) {
    auto a = boost::any( (int)100 );
    auto b = boost::any( (size_t)100 );
    auto r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (size_t)100 );
    b = boost::any( (int)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );
}


TEST_F( DTest, IntNotEqualsSizeT_LazyEquals ) {
    auto a = boost::any( (int)100 );
    auto b = boost::any( (size_t)200 );
    auto r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (size_t)100 );
    b = boost::any( (int)200 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );
}


TEST_F( DTest, IntEqualsFloat_LazyEquals ) {
    auto a = boost::any( (int)100 );
    auto b = boost::any( (float)100 );
    auto r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (float)100 );
    b = boost::any( (int)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );
}


TEST_F( DTest, IntNotEqualsFloat_LazyEquals ) {
    auto a = boost::any( (int)100 );
    auto b = boost::any( (float)200 );
    auto r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (float)100 );
    b = boost::any( (int)200 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );
}


TEST_F( DTest, IntEqualsDouble_LazyEquals ) {
    auto a = boost::any( (int)100 );
    auto b = boost::any( (double)100 );
    auto r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (double)100 );
    b = boost::any( (int)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );
}


TEST_F( DTest, IntNotEqualsDouble_LazyEquals ) {
    auto a = boost::any( (int)100 );
    auto b = boost::any( (double)200 );
    auto r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (double)100 );
    b = boost::any( (int)200 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );
}

Строки - Равенство
TEST_F( DTest, NumberEqualsString_LazyEquals ) {
    // int
    auto a = boost::any( (int)100 );
    auto b = boost::any( (string)"100" );
    auto r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (string)"100" );
    b = boost::any( (int)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (int)100 );
    b = boost::any( (string)"100.0" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (string)"100.0" );
    b = boost::any( (int)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    // size_t
    a = boost::any( (size_t)100 );
    b = boost::any( (string)"100" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (string)"100" );
    b = boost::any( (size_t)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (size_t)100 );
    b = boost::any( (string)"100.0" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (string)"100.0" );
    b = boost::any( (size_t)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (size_t)100 );
    b = boost::any( (string)"100.1" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (string)"100.1" );
    b = boost::any( (size_t)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    // float
    a = boost::any( (float)100 );
    b = boost::any( (string)"100" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (string)"100" );
    b = boost::any( (float)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (float)100 );
    b = boost::any( (string)"100.0" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (string)"100.0" );
    b = boost::any( (float)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (float)100.1 );
    b = boost::any( (string)"100.1" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (string)"100.1" );
    b = boost::any( (float)100.1 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    // double
    a = boost::any( (double)100 );
    b = boost::any( (string)"100" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (string)"100" );
    b = boost::any( (double)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (double)100 );
    b = boost::any( (string)"100.0" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (string)"100.0" );
    b = boost::any( (double)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (double)100.1 );
    b = boost::any( (string)"100.1" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

    a = boost::any( (string)"100.1" );
    b = boost::any( (double)100.1 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_TRUE( r.second );

}

Строки - Не равенство
TEST_F( DTest, NumberNotEqualsString2_LazyEquals ) {
    // int
    auto a = boost::any( (int)100 );
    auto b = boost::any( (string)"200" );
    auto r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"200" );
    b = boost::any( (int)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (int)100 );
    b = boost::any( (string)"200.0" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"200.0" );
    b = boost::any( (int)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    // size_t
    a = boost::any( (size_t)100 );
    b = boost::any( (string)"200" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"200" );
    b = boost::any( (size_t)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (size_t)100 );
    b = boost::any( (string)"200.0" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"200.0" );
    b = boost::any( (size_t)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    // float
    a = boost::any( (float)100 );
    b = boost::any( (string)"200" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"200" );
    b = boost::any( (float)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (float)100 );
    b = boost::any( (string)"200.0" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"200.0" );
    b = boost::any( (float)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    // double
    a = boost::any( (double)100 );
    b = boost::any( (string)"200" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"200" );
    b = boost::any( (double)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (double)100 );
    b = boost::any( (string)"200.0" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"200.0" );
    b = boost::any( (double)100 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );
}


TEST_F( DTest, Number2NotEqualsString_LazyEquals ) {
    // int
    auto a = boost::any( (int)200 );
    auto b = boost::any( (string)"100" );
    auto r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"100" );
    b = boost::any( (int)200 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (int)200 );
    b = boost::any( (string)"100.0" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"100.0" );
    b = boost::any( (int)200 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    // size_t
    a = boost::any( (size_t)200 );
    b = boost::any( (string)"100" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"100" );
    b = boost::any( (size_t)200 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (size_t)200 );
    b = boost::any( (string)"100.0" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"100.0" );
    b = boost::any( (size_t)200 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    // float
    a = boost::any( (float)200 );
    b = boost::any( (string)"100" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"100" );
    b = boost::any( (float)200 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (float)200 );
    b = boost::any( (string)"100.0" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"100.0" );
    b = boost::any( (float)200 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    // double
    a = boost::any( (double)200 );
    b = boost::any( (string)"100" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"100" );
    b = boost::any( (double)200 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (double)200 );
    b = boost::any( (string)"100.0" );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );

    a = boost::any( (string)"100.0" );
    b = boost::any( (double)200 );
    r = d::lazyEquals( a, b );
    EXPECT_FALSE( r.first );
    EXPECT_FALSE( r.second );
}

Комментариев нет: