【问题标题】:Compare versions as strings将版本比较为字符串
【发布时间】:2011-02-25 20:33:32
【问题描述】:

将版本号作为字符串进行比较并不容易...
“1.0.0.9” > “1.0.0.10”,但不正确。
正确执行此操作的明显方法是解析这些字符串,转换为数字并作为数字进行比较。 还有另一种方法可以更“优雅”地做到这一点吗?比如 boost::string_algo...

【问题讨论】:

标签: c++ string-comparison version-numbering


【解决方案1】:

我看不出还有什么比解析更优雅的——但是利用已经存在的标准库设施。假设您不需要错误检查:

void Parse(int result[4], const std::string& input)
{
    std::istringstream parser(input);
    parser >> result[0];
    for(int idx = 1; idx < 4; idx++)
    {
        parser.get(); //Skip period
        parser >> result[idx];
    }
}

bool LessThanVersion(const std::string& a,const std::string& b)
{
    int parsedA[4], parsedB[4];
    Parse(parsedA, a);
    Parse(parsedB, b);
    return std::lexicographical_compare(parsedA, parsedA + 4, parsedB, parsedB + 4);
}

任何更复杂的东西都将更难维护,不值得你花时间。

【讨论】:

  • 算法不错。我建议将其包装为class Version { Version(std::string const&amp;); bool operator&lt;(Version const&amp; rhs) const; };。例如,这使您可以拥有std::set&lt;Version&gt;
  • @Johnsyweb:感谢您纠正错字。 @MSalters:我同意。我并不是说将其用于生产——我只是在演示我认为 OP 应该使用的算法。
  • @Julien:不,它适用于多位数字。 (假设您的标准库的 operator>> 做了“正确的事情”)它确实假设句点是一个字符。
  • 通过一个简单的更改,可以使代码对无效输入更加健壮,并且可以比较少于 4 个字段的版本号(如果传递的字段较少,则可以假定未指定的字段为为零)。所需要做的就是始终将变量 parsedAparsedB 初始化为零,就像这样 (C++11ish): int parsedA[4]{}, parsedB[4]{}; 或这样 (pre C++11): int parsedA[4] = {0}, parsedB[4] = {0}
【解决方案2】:

我会创建一个版本类。
那么为版本类定义比较运算符就很简单了。

#include <iostream>
#include <sstream>
#include <vector>
#include <iterator>

class Version
{
    // An internal utility structure just used to make the std::copy in the constructor easy to write.
    struct VersionDigit
    {
        int value;
        operator int() const {return value;}
    };
    friend std::istream& operator>>(std::istream& str, Version::VersionDigit& digit);
    public:
        Version(std::string const& versionStr)
        {
            // To Make processing easier in VersionDigit prepend a '.'
            std::stringstream   versionStream(std::string(".") + versionStr);

            // Copy all parts of the version number into the version Info vector.
            std::copy(  std::istream_iterator<VersionDigit>(versionStream),
                        std::istream_iterator<VersionDigit>(),
                        std::back_inserter(versionInfo)
                     );
        }

        // Test if two version numbers are the same. 
        bool operator<(Version const& rhs) const
        {
            return std::lexicographical_compare(versionInfo.begin(), versionInfo.end(), rhs.versionInfo.begin(), rhs.versionInfo.end());
        }

    private:
        std::vector<int>    versionInfo;
};

// Read a single digit from the version. 
std::istream& operator>>(std::istream& str, Version::VersionDigit& digit)
{
    str.get();
    str >> digit.value;
    return str;
}


int main()
{
    Version     v1("10.0.0.9");
    Version     v2("10.0.0.10");

    if (v1 < v2)
    {
        std::cout << "Version 1 Smaller\n";
    }
    else
    {
        std::cout << "Fail\n";
    }
}

【讨论】:

  • 你应该使用 std::vector&lt;T&gt;::assign 而不是 std::copy ;) 否则 +1。
  • 只是一个小建议,让类更完整,操作员明智。如果 boost 可用,可以derive from boost::less_than_comparable 自动添加operator&gt;operator&lt;=operator&gt;=,这些都是根据operator&lt; 实现的。同样有用的是operator== 并从boost::equality_comparable 派生以提供operator!=
  • @zett42。没必要。要添加比较运算符,您只需添加using namespace std::rel_opssee
  • std::rel_ops 有一些问题。首先,如链接页面所述:“请注意,使用此命名空间会为所有未定义自己的类型引入这些重载。” - 谁知道这会如何改变复杂模板库的行为?第二,可用性。您班级的用户必须记住添加using namespace std::rel_ops,在将这些运算符添加为班级成员(手动或使用 boost)时,情况并非如此。
  • @zett42:当然。但是我们正在考虑添加几乎从未使用过的功能(对于版本的情况)。因此,为这些异常情况隔离 using 类既不困难也不麻烦(因为您基本上从不需要它,而且当您只做一行时)。其次,它比在不需要的情况下强制继承要好。第三,如果这是一个类需要这些运算符的情况(使用它们是有意义的),那么使用std::tuple(每个都是一个衬垫)。使用std::tuple 比强制继承到一个类要轻得多。
【解决方案3】:

首先是测试代码:

int main()
{
    std::cout << ! ( Version("1.2")   >  Version("1.3") );
    std::cout <<   ( Version("1.2")   <  Version("1.2.3") );
    std::cout <<   ( Version("1.2")   >= Version("1") );
    std::cout << ! ( Version("1")     <= Version("0.9") );
    std::cout << ! ( Version("1.2.3") == Version("1.2.4") );
    std::cout <<   ( Version("1.2.3") == Version("1.2.3") );
}
// output is 111111

实施:

#include <string>
#include <iostream>

// Method to compare two version strings
//   v1 <  v2  -> -1
//   v1 == v2  ->  0
//   v1 >  v2  -> +1
int version_compare(std::string v1, std::string v2)
{
    size_t i=0, j=0;
    while( i < v1.length() || j < v2.length() )
    {
        int acc1=0, acc2=0;

        while (i < v1.length() && v1[i] != '.') {  acc1 = acc1 * 10 + (v1[i] - '0');  i++;  }
        while (j < v2.length() && v2[j] != '.') {  acc2 = acc2 * 10 + (v2[j] - '0');  j++;  }

        if (acc1 < acc2)  return -1;
        if (acc1 > acc2)  return +1;

        ++i;
        ++j;
    }
    return 0;
}

struct Version
{
    std::string version_string;
    Version( std::string v ) : version_string(v)
    { }
};

bool operator <  (Version u, Version v) {  return version_compare(u.version_string, v.version_string) == -1;  }
bool operator >  (Version u, Version v) {  return version_compare(u.version_string, v.version_string) == +1;  }
bool operator <= (Version u, Version v) {  return version_compare(u.version_string, v.version_string) != +1;  }
bool operator >= (Version u, Version v) {  return version_compare(u.version_string, v.version_string) != -1;  }
bool operator == (Version u, Version v) {  return version_compare(u.version_string, v.version_string) ==  0;  }

https://coliru.stacked-crooked.com/a/7c74ad2cc4dca888

【讨论】:

    【解决方案4】:

    这是一个干净、紧凑的 C++20 解决方案,使用新的 spaceship operator &lt;=&gt; 和 Boost 的字符串拆分算法。

    这会将版本字符串构造并保存为数字向量 - 可用于进一步处理,或者可以作为临时处理。这也处理不同长度的版本字符串,并接受多个分隔符。

    spaceship 运算符让我们可以在单个函数定义中为 &lt;&gt;== 运算符提供结果(尽管等于 has to be separately defined)。

    #include <compare>
    #include <boost/algorithm/string.hpp>
    
    struct version {
      std::vector<size_t> data;
    
      version() {};
      version(std::string_view from_string) {
        /// Construct from a string
        std::vector<std::string> data_str;
        boost::split(data_str, from_string, boost::is_any_of("._-"), boost::token_compress_on);
        for(auto const &it : data_str) {
          data.emplace_back(std::stol(it));
        }
      };
    
      std::strong_ordering operator<=>(version const& rhs) const noexcept {
        /// Three-way comparison operator
        size_t const fields = std::min(data.size(), rhs.data.size());
    
        // first compare all common fields
        for(size_t i = 0; i != fields; ++i) {
          if(data[i] == rhs.data[i]) continue;
          else if(data[i] < rhs.data[i]) return std::strong_ordering::less;
          else return std::strong_ordering::greater;
        }
    
        // if we're here, all common fields are equal - check for extra fields
        if(data.size() == rhs.data.size()) return std::strong_ordering::equal; // no extra fields, so both versions equal
        else if(data.size() > rhs.data.size()) return std::strong_ordering::greater; // lhs has more fields - we assume it to be greater
        else return std::strong_ordering::less; // rhs has more fields - we assume it to be greater
      }
    
      bool operator==(version const& rhs) const noexcept {
        return std::is_eq(*this <=> rhs);
      }
    };
    

    示例用法:

      std::cout << (version{"1.2.3.4"} <  version{"1.2.3.5"}) << std::endl; // true
      std::cout << (version{"1.2.3.4"} >  version{"1.2.3.5"}) << std::endl; // false
      std::cout << (version{"1.2.3.4"} == version{"1.2.3.5"}) << std::endl; // false
      std::cout << (version{"1.2.3.4"} >  version{"1.2.3"})   << std::endl; // true
      std::cout << (version{"1.2.3.4"} <  version{"1.2.3.4.5"}) << std::endl; // true
    

    【讨论】:

      【解决方案5】:
      int VersionParser(char* version1, char* version2) {
      
          int a1,b1, ret; 
          int a = strlen(version1); 
          int b = strlen(version2);
          if (b>a) a=b;
          for (int i=0;i<a;i++) {
                  a1 += version1[i];
                  b1 += version2[i];
          }
          if (b1>a1) ret = 1 ; // second version is fresher
          else if (b1==a1) ret=-1; // versions is equal
          else ret = 0; // first version is fresher
          return ret;
      }
      

      【讨论】:

      • 在这种情况下,1.0.2 比 1.1.0 更新 + 索引将超出范围
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-26
      • 2012-04-29
      • 2021-11-21
      • 2015-11-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多