【问题标题】:How to implement a natural sort algorithm in c++?如何在 C++ 中实现自然排序算法?
【发布时间】:2010-10-13 03:14:54
【问题描述】:

我正在对由文本和数字组成的字符串进行排序。 我希望排序将数字部分排序为数字,而不是字母数字。

例如我想要:abc1def, ..., abc9def, abc10def

代替:abc10def, abc1def, ..., abc9def

有谁知道这方面的算法(特别是在 c++ 中)

谢谢

【问题讨论】:

  • 查看“相关”侧边栏......
  • @dmckee - 公平地说,他没有使用“自然排序”这个词(当我问同样的问题时我没有使用)“自然排序”——后来被编辑了。

标签: c++ sorting natural-sort


【解决方案1】:

我问了this exact question (although in Java),并被指向http://www.davekoelle.com/alphanum.html,它有一个算法和多种语言的实现。

【讨论】:

  • +1 谢谢 Paul - 我寻找自然排序和 C++ 标记,但没有找到任何东西。
【解决方案2】:

有几种 C++ 的自然排序实现可用。简要回顾:

  • natural_sort<> - 基于 Boost.Regex。
    • 在我的测试中,它比其他选项慢了大约 20 倍。
  • Dirk Jagdmann 的 alnum.hpp,基于 Dave Koelle 的 alphanum algorithm
    • 超过 MAXINT 的值可能会出现整数过低问题
  • Martin Pool 的natsort - 用 C 编写,但在 C++ 中可以轻松使用。
    • 我见过的唯一 C/C++ 实现提供不区分大小写的版本,这似乎是“自然”排序的高优先级。
    • 与其他实现一样,它实际上并不解析小数点,但它会处理特殊情况下的前导零(任何带有前导 0 的东西都被假定为分数),这有点奇怪但可能很有用。
    • PHP 使用此算法。

【讨论】:

    【解决方案3】:

    这称为自然排序。有一种算法here 看起来很有前途。

    注意非 ASCII 字符的问题(请参阅 Jeff's blog entry 的主题)。

    【讨论】:

    • 这很好,但我没有权限提升:-|
    • 那么看起来 Paul Tomblin 的回答可能对您更有帮助 - C++ 变体似乎没有使用任何时髦的东西。
    【解决方案4】:

    部分转贴my another answer

    bool compareNat(const std::string& a, const std::string& b){
        if (a.empty())
            return true;
        if (b.empty())
            return false;
        if (std::isdigit(a[0]) && !std::isdigit(b[0]))
            return true;
        if (!std::isdigit(a[0]) && std::isdigit(b[0]))
            return false;
        if (!std::isdigit(a[0]) && !std::isdigit(b[0]))
        {
            if (a[0] == b[0])
                return compareNat(a.substr(1), b.substr(1));
            return (toUpper(a) < toUpper(b));
            //toUpper() is a function to convert a std::string to uppercase.
        }
    
        // Both strings begin with digit --> parse both numbers
        std::istringstream issa(a);
        std::istringstream issb(b);
        int ia, ib;
        issa >> ia;
        issb >> ib;
        if (ia != ib)
            return ia < ib;
    
        // Numbers are the same --> remove numbers and recurse
        std::string anew, bnew;
        std::getline(issa, anew);
        std::getline(issb, bnew);
        return (compareNat(anew, bnew));
    }
    

    toUpper()函数:

    std::string toUpper(std::string s){
        for(int i=0;i<(int)s.length();i++){s[i]=toupper(s[i]);}
        return s;
        }
    

    用法:

    std::vector<std::string> str;
    str.push_back("abc1def");
    str.push_back("abc10def");
    ...
    std::sort(str.begin(), str.end(), compareNat);
    

    【讨论】:

    • 这个效率不是很高,更高效更全面的解决方案是this one
    【解决方案5】:

    要解决本质上是解析问题,状态机(又名finite state automaton)是必经之路。对上述解决方案不满意,我编写了一个简单的一次性早期救助算法,该算法在性能方面优于上面建议的 C/C++ 变体,不会出现数值数据类型溢出错误,并且在需要时易于修改以添加不区分大小写.

    来源可以找到here

    【讨论】:

    • 请在此处发布您的代码,而不是让他们访问您的个人网站。
    • 我的个人网站是维护的地方,也将是。很高兴我能对其他人有所帮助。
    【解决方案6】:

    对于那些到达这里并且已经在他们的项目中使用 Qt 的人,您可以使用 QCollator 类。详情请见this question

    【讨论】:

      【解决方案7】:

      Avalanchesort 是自然排序的递归变体,在探索排序数据堆栈的同时运行合并。即使您在算法运行/排序时将数据添加到排序堆,算法也会稳定排序。

      搜索原理很简单。 仅合并具有相同排名的运行。

      在找到前两个自然运行(秩为 0)后,雪崩排序将它们合并为秩为 1 的运行。然后它调用雪崩排序,以生成第二次运行的秩为 1,并将两个运行合并为秩为 2 的运行。然后它调用 avalancheSort 在未排序的数据上生成排名为 2 的运行....

      我的实现porthd/avalanchesort 将排序与使用接口注入的数据处理分开。您可以将算法用于数组、关联数组或列表等数据结构。

          /**
       * @param DataListAvalancheSortInterface $dataList
       * @param DataRangeInterface $beginRange
       * @param int $avalancheIndex
       * @return bool
       */
      public function startAvalancheSort(DataListAvalancheSortInterface $dataList)
      {
          $avalancheIndex = 0;
          $rangeResult = $this->avalancheSort($dataList, $dataList->getFirstIdent(), $avalancheIndex);
          if (!$dataList->isLastIdent($rangeResult->getStop())) {
              do {
                  $avalancheIndex++;
                  $lastIdent = $rangeResult->getStop();
                  if ($dataList->isLastIdent($lastIdent)) {
                      $rangeResult = new $this->rangeClass();
                      $rangeResult->setStart($dataList->getFirstIdent());
                      $rangeResult->setStop($dataList->getLastIdent());
                      break;
                  }
                  $nextIdent = $dataList->getNextIdent($lastIdent);
                  $rangeFollow = $this->avalancheSort($dataList, $nextIdent, $avalancheIndex);
                  $rangeResult = $this->mergeAvalanche($dataList, $rangeResult, $rangeFollow);
              } while (true);
          }
          return $rangeResult;
      }
      
      /**
       * @param DataListAvalancheSortInterface $dataList
       * @param DataRangeInterface $range
       * @return DataRangeInterface
       */
      protected function findRun(DataListAvalancheSortInterface $dataList,
                                 $startIdent)
      {
          $result = new $this->rangeClass();
          $result->setStart($startIdent);
          $result->setStop($startIdent);
          do {
              if ($dataList->isLastIdent($result->getStop())) {
                  break;
              }
              $nextIdent = $dataList->getNextIdent($result->getStop());
              if ($dataList->oddLowerEqualThanEven(
                  $dataList->getDataItem($result->getStop()),
                  $dataList->getDataItem($nextIdent)
              )) {
                  $result->setStop($nextIdent);
              } else {
                  break;
              }
          } while (true);
          return $result;
      }
      
      /**
       * @param DataListAvalancheSortInterface $dataList
       * @param $beginIdent
       * @param int $avalancheIndex
       * @return DataRangeInterface|mixed
       */
      protected function avalancheSort(DataListAvalancheSortInterface $dataList,
                                       $beginIdent,
                                       int $avalancheIndex = 0)
      {
          if ($avalancheIndex === 0) {
              $rangeFirst = $this->findRun($dataList, $beginIdent);
              if ($dataList->isLastIdent($rangeFirst->getStop())) {
                  // it is the last run
                  $rangeResult = $rangeFirst;
              } else {
                  $nextIdent = $dataList->getNextIdent($rangeFirst->getStop());
                  $rangeSecond = $this->findRun($dataList, $nextIdent);
                  $rangeResult = $this->mergeAvalanche($dataList, $rangeFirst, $rangeSecond);
              }
          } else {
              $rangeFirst = $this->avalancheSort($dataList,
                  $beginIdent,
                  ($avalancheIndex - 1)
              );
              if ($dataList->isLastIdent($rangeFirst->getStop())) {
                  $rangeResult = $rangeFirst;
              } else {
                  $nextIdent = $dataList->getNextIdent($rangeFirst->getStop());
                  $rangeSecond = $this->avalancheSort($dataList,
                      $nextIdent,
                      ($avalancheIndex - 1)
                  );
                  $rangeResult = $this->mergeAvalanche($dataList, $rangeFirst, $rangeSecond);
              }
          }
          return $rangeResult;
      }
      
      protected function mergeAvalanche(DataListAvalancheSortInterface $dataList, $oddListRange, $evenListRange)
      {
          $resultRange = new $this->rangeClass();
          $oddNextIdent = $oddListRange->getStart();
          $oddStopIdent = $oddListRange->getStop();
          $evenNextIdent = $evenListRange->getStart();
          $evenStopIdent = $evenListRange->getStop();
          $dataList->initNewListPart($oddListRange, $evenListRange);
          do {
              if ($dataList->oddLowerEqualThanEven(
                  $dataList->getDataItem($oddNextIdent),
                  $dataList->getDataItem($evenNextIdent)
              )) {
                  $dataList->addListPart($oddNextIdent);
                  if ($oddNextIdent === $oddStopIdent) {
                      $restTail = $evenNextIdent;
                      $stopTail = $evenStopIdent;
                      break;
                  }
                  $oddNextIdent = $dataList->getNextIdent($oddNextIdent);
              } else {
                  $dataList->addListPart($evenNextIdent);
                  if ($evenNextIdent === $evenStopIdent) {
                      $restTail = $oddNextIdent;
                      $stopTail = $oddStopIdent;
                      break;
                  }
                  $evenNextIdent = $dataList->getNextIdent($evenNextIdent);
      
              }
          } while (true);
          while ($stopTail !== $restTail) {
              $dataList->addListPart($restTail);
              $restTail = $dataList->getNextIdent($restTail);
          }
          $dataList->addListPart($restTail);
          $dataList->cascadeDataListChange($resultRange);
          return $resultRange;
      
      }
      

      }

      【讨论】:

        【解决方案8】:

        我的算法带有 java 版本的测试代码。如果你想在你的项目中使用它,你可以自己定义一个比较器。

        import java.io.File;
        import java.io.IOException;
        import java.util.ArrayList;
        import java.util.Arrays;
        import java.util.Comparator;
        import java.util.List;
        import java.util.function.Consumer;
        
        public class FileNameSortTest {
        
            private static List<String> names = Arrays.asList(
                    "A__01__02",
                    "A__2__02",
                    "A__1__23",
                    "A__11__23",
                    "A__3++++",
                    "B__1__02",
                    "B__22_13",
                    "1_22_2222",
                    "12_222_222",
                    "2222222222",
                    "1.sadasdsadsa",
                    "11.asdasdasdasdasd",
                    "2.sadsadasdsad",
                    "22.sadasdasdsadsa",
                    "3.asdasdsadsadsa",
                    "adsadsadsasd1",
                    "adsadsadsasd10",
                    "adsadsadsasd3",
                    "adsadsadsasd02"
            );
        
            public static void main(String...args) {
                List<File> files = new ArrayList<>();
                names.forEach(s -> {
                    File f = new File(s);
                    try {
                        if (!f.exists()) {
                            f.createNewFile();
                        }
                        files.add(f);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
                files.sort(Comparator.comparing(File::getName));
                files.forEach(f -> System.out.print(f.getName() + " "));
                System.out.println();
        
                files.sort(new Comparator<File>() {
        
                    boolean caseSensitive = false;
                    int SPAN_OF_CASES = 'a' - 'A';
        
                    @Override
                    public int compare(File left, File right) {
                        char[] csLeft = left.getName().toCharArray(), csRight = right.getName().toCharArray();
                        boolean isNumberRegion = false;
                        int diff=0, i=0, j=0, lenLeft=csLeft.length, lenRight=csRight.length;
                        char cLeft = 0, cRight = 0;
                        for (; i<lenLeft && j<lenRight; i++, j++) {
                            cLeft = getCharByCaseSensitive(csLeft[i]);
                            cRight = getCharByCaseSensitive(csRight[j]);
                            boolean isNumericLeft = isNumeric(cLeft), isNumericRight = isNumeric(cRight);
                            if (isNumericLeft && isNumericRight) {
                                // Number start!
                                if (!isNumberRegion) {
                                    isNumberRegion = true;
                                    // Remove prefix '0'
                                    while (i < lenLeft && cLeft == '0') i++;
                                    while (j < lenRight && cRight == '0') j++;
                                    if (i == lenLeft || j == lenRight) break;
                                }
                                // Diff start: calculate the diff value.
                                if (cLeft != cRight && diff == 0)
                                    diff = cLeft - cRight;
                            } else {
                                if (isNumericLeft != isNumericRight) {
                                    // One numeric and one char.
                                    if (isNumberRegion)
                                        return isNumericLeft ? 1 : -1;
                                    return cLeft - cRight;
                                } else {
                                    // Two chars: if (number) diff don't equal 0 return it.
                                    if (diff != 0)
                                        return diff;
                                    // Calculate chars diff.
                                    diff = cLeft - cRight;
                                    if (diff != 0)
                                        return diff;
                                    // Reset!
                                    isNumberRegion = false;
                                    diff = 0;
                                }
                            }
                        }
                        // The longer one will be put backwards.
                        return (i == lenLeft && j == lenRight) ? cLeft - cRight : (i == lenLeft ? -1 : 1) ;
                    }
        
                    private boolean isNumeric(char c) {
                        return c >= '0' && c <= '9';
                    }
        
                    private char getCharByCaseSensitive(char c) {
                        return caseSensitive ? c : (c >= 'A' && c <= 'Z' ? (char) (c + SPAN_OF_CASES) : c);
                    }
                });
                files.forEach(f -> System.out.print(f.getName() + " "));
            }
        }
        

        输出是,

        1.sadasdsadsa 11.asdasdasdasdasd 12_222_222 1_22_2222 2.sadsadasdsad 22.sadasdasdsadsa 2222222222 3.asdasdsadsadsa A__01__02 A__11__23 A__1__23 A__2__02 A__3++++ B__1__02 B__22_13 adsadsadsasd02 adsadsadsasd1 adsadsadsasd10 adsadsadsasd3 
        1.sadasdsadsa 1_22_2222 2.sadsadasdsad 3.asdasdsadsadsa 11.asdasdasdasdasd 12_222_222 22.sadasdasdsadsa 2222222222 A__01__02 A__1__23 A__2__02 A__3++++ A__11__23 adsadsadsasd02 adsadsadsasd1 adsadsadsasd3 adsadsadsasd10 B__1__02 B__22_13 
        Process finished with exit code 0
        

        【讨论】:

          【解决方案9】:
          // -1: s0 < s1; 0: s0 == s1; 1: s0 > s1
          static int numericCompare(const string &s0, const string &s1) {
              size_t i = 0, j = 0;
              for (; i < s0.size() && j < s1.size();) {
                  string t0(1, s0[i++]);
                  while (i < s0.size() && !(isdigit(t0[0]) ^ isdigit(s0[i]))) {
                      t0.push_back(s0[i++]);
                  }
                  string t1(1, s1[j++]);
                  while (j < s1.size() && !(isdigit(t1[0]) ^ isdigit(s1[j]))) {
                      t1.push_back(s1[j++]);
                  }
                  if (isdigit(t0[0]) && isdigit(t1[0])) {
                      size_t p0 = t0.find_first_not_of('0');
                      size_t p1 = t1.find_first_not_of('0');
                      t0 = p0 == string::npos ? "" : t0.substr(p0);
                      t1 = p1 == string::npos ? "" : t1.substr(p1);
                      if (t0.size() != t1.size()) {
                          return t0.size() < t1.size() ? -1 : 1;
                      }
                  }
                  if (t0 != t1) {
                      return t0 < t1 ? -1 : 1;
                  }
              }
              return i == s0.size() && j == s1.size() ? 0 : i != s0.size() ? 1 : -1;
          }
          

          我不太确定是不是你想要的,反正你可以试一试:-)

          【讨论】:

          • 这会为numericCompare("z01", "z1") 返回 0,这似乎不太理想。
          • 此算法使用额外的内存:临时字符串。至少,您可以改用范围(迭代器对)。
          猜你喜欢
          • 1970-01-01
          • 2014-04-25
          • 2014-08-30
          • 1970-01-01
          • 1970-01-01
          • 2010-09-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多