对字符串数组进行不区分大小写的排序

时间:2015-10-27 22:46:44

标签: c++ string sorting case-insensitive

基本上,我必须使用选择排序来对string[]进行排序。我已经完成了这部分,但这是我遇到的困难。

然而,这种排序应该不区分大小写,以便"天线"将会出现在" Jupiter"之前。 ASCII从大写到小写排序,那么是不是只能交换排序字符串的顺序?还是有一个更简单的解决方案?

void stringSort(string array[], int size) {
    int startScan, minIndex;
    string minValue;

    for(startScan = 0 ; startScan < (size - 1); startScan++) {
        minIndex = startScan;
        minValue = array[startScan];

        for (int index = startScan + 1; index < size; index++) {
            if (array[index] < minValue) {
                minValue = array[index];
                minIndex = index;
            }
        }
        array[minIndex] = array[startScan];
        array[startScan] = minValue;
    }
}

6 个答案:

答案 0 :(得分:4)

C ++为您提供了sort,它具有比较功能。在您使用vector<string>的情况下,您将比较两个字符串。如果第一个参数较小,则比较函数应返回true

对于我们的比较功能,我们想要在应用tolower后的string之间找到第一个不匹配的字符。要做到这一点,我们可以使用mismatch,只要两个字符相等,它们就会在两个字符之间返回true

const auto result = mismatch(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(), [](const unsigned char lhs, const unsigned char rhs){return tolower(lhs) == tolower(rhs);});

要确定lhs是否小于rhs馈送到mismatch,我们需要测试3件事情:

  1. string s长度不等
  2. string lhs缩短了
  3. 或者char中的第一个不匹配lhs小于char
  4. 中第一个不匹配的rhs

    此评估可以通过以下方式执行:

    result.second != rhs.cend() && (result.first == lhs.cend() || tolower(*result.first) < tolower(*result.second));
    

    最终,我们想要将它包装成lambda并将其作为我们的比较器插回sort

    sort(foo.begin(), foo.end(), [](const unsigned char lhs, const unsigned char rhs){
        const auto result = mismatch(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(), [](const unsigned char lhs, const unsigned char rhs){return tolower(lhs) == tolower(rhs);});
    
        return result.second != rhs.cend() && (result.first == lhs.cend() || tolower(*result.first) < tolower(*result.second));
    });
    

    这将正确排序vector<string> foo。您可以在此处查看实时示例:http://ideone.com/BVgyD2

    修改

    刚看到你的问题更新了。您也可以将sortstring array[]一起使用。您只需要这样称呼它:sort(array, std::next(array, size), ...

答案 1 :(得分:1)

使用不区分大小写的字符串比较函数,而不是<运算符。

C89 / C99提供strcoll(字符串整理),它进行区域设置感知字符串比较。它在C ++中以std::strcoll形式提供。在某些(大多数?)语言环境中,例如en_CA.UTF-8,Aa(以及任何一个的所有重音形式)都在同一个等价类中。我认为如果整个字符串相等,strcoll只在等价类中作为抢七进行比较,这给出了与不区分大小写的比较非常相似的排序顺序。排序(至少在GNU / Linux上的英语语言环境中)忽略了一些字符(如[)。所以ls /usr/share | sort给出了像

这样的输出
acpi-support
adduser
ADM_scripts
aglfn
aisleriot

我管道sort因为ls执行自己的排序,这与sort基于区域设置的排序不完全相同。

如果要将一些用户输入的任意字符串排序为用户将直接看到的顺序,通常可以使用区域设置感知字符串比较。仅在大小写或重音符号上不同的字符串将不会相等,因此如果您使用稳定排序并且根据大小写不同的字符串进行比较相等,则这将不起作用,但否则您将获得不错的结果。根据用例,比普通的不区分大小写更好。

对于POSIX(ASCII)以外的语言环境,

FreeBSD's strcoll可能仍然区分大小写。该论坛帖子表明,在大多数其他系统中, 案例并不敏感。

MSVC为不区分大小写的排序规则提供了_stricoll,暗示其正常strcoll区分大小写。但是,这可能只意味着在等价类中进行比较的后退不会发生。也许有人可以使用MSVC测试以下示例。

// strcoll.c: show that these strings sort in a different order, depending on locale
#include <stdio.h>
#include <locale.h>

int main()
{
    // TODO: try some strings containing characters like '[' that strcoll ignores completely.
    const char * s[] = { "FooBar - abc", "Foobar - bcd", "FooBar - cde" };
#ifdef USE_LOCALE
    setlocale(LC_ALL, ""); // empty string means look at env vars
#endif
    strcoll(s[0], s[1]);
    strcoll(s[0], s[2]);
    strcoll(s[1], s[2]);
    return 0;
}

gcc -DUSE_LOCALE -Og strcoll.c && ltrace ./a.out的输出(或运行LANG = C ltrace a.out):

__libc_start_main(0x400586, 1, ...
setlocale(LC_ALL, "")                        = "en_CA.UTF-8"   # my env contains LANG=en_CA.UTF-8
strcoll("FooBar - abc", "Foobar - bcd")      = -1
strcoll("FooBar - abc", "FooBar - cde")      = -2
strcoll("Foobar - bcd", "FooBar - cde")      = -1
  # the three strings are in order
+++ exited (status 0) +++

gcc -Og -UUSE_LOCALE strcoll.c && ltrace ./a.out

__libc_start_main(0x400536, ...
# no setlocale, so current locale is C
strcoll("FooBar - abc", "Foobar - bcd")      = -32
strcoll("FooBar - abc", "FooBar - cde")      = -2
strcoll("Foobar - bcd", "FooBar - cde")      = 32   # s[1] should sort after s[2], so it's out of order
+++ exited (status 0) +++

POSIX.1-2001提供strcasecmp。 POSIX规范说明除了普通ASCII之外的语言环境的结果是“未指定的”,所以我不确定常见的实现是否正确处理utf-8。

this post for portability issues with strcasecmp, e.g. to Windows。有关进行不区分大小写的字符串比较的其他C ++方法,请参阅该问题的其他答案。

一旦你有一个不区分大小写的比较函数,你可以将它与其他排序算法一起使用,比如C标准库qsort或c ++ std::sort,而不是写你自己的O(n ^ 2)选择 - 排序。

正如b.buchhold的回答指出的那样,在运行中进行不区分大小写的比较可能比将所有内容转换为小写一次并对索引数组进行排序要慢。每个字符串的小写版本需要多次。 std::strxfrm会对字符串进行转换,以便结果上的strcmp与原始字符串上的strcoll结果相同。

答案 2 :(得分:1)

我使用这个lambda函数对字符串向量进行排序:

Date

答案 3 :(得分:0)

您可以在比较的每个字符上调用tolower。这可能是最简单的,但不是一个很好的解决方案,因为:

  • 您多次查看每个字符,因此您需要更频繁地调用该方法
  • 你需要特别小心处理w -r.t到其编码的宽字符(UTF8等)

您也可以用自己的功能替换比较器。即会有一些地方比较stringone[i] < stringtwo[j]charA < charB之类的内容。将其更改为my_less_than(stringone[i], stringtwo[j])并实现您想要的确切顺序。

另一种方法是将每个字符串转换为小写一次并创建一对数组。然后你只将你的比较基于小写值,但你交换整个对,以便你的最终字符串也是正确的顺序。

最后,您可以创建一个包含小写版本的数组并对此进行排序。每当你在这个中交换两个元素时,你也可以交换原始数组。

请注意,所有这些提案仍需要正确处理宽字符(如果您需要的话)

答案 4 :(得分:0)

这个解决方案比Jonathan Mee更容易理解并且效率很低,但出于教育目的可能会很好:

std::string lowercase( std::string s )
{
   std::transform( s.begin(), s.end(), s.begin(), ::tolower );
   return s;
}

std::sort( array, array + length, 
           []( const std::string &s1, const std::string &s2 ) { 
               return lowercase( s1 ) < lowercase( s2 ); 
           } );

如果必须使用排序功能,可以使用相同的方法:

    ....
    minValue = lowercase( array[startScan] );

    for (int index = startScan + 1; index < size; index++) {
        const std::string &tstr = lowercase( array[index] );
        if (tstr < minValue) {
            minValue = tstr;
            minIndex = index;
        }
    }
    ...

答案 5 :(得分:0)

count_remote_files() {
  local host=$1 dirname=$2 dirname_q
  printf -v dirname_q '%q' "$dirname"
  remote_files=$(ssh "$host" "bash -s ${dirname_q}" <<EOF
    cd "$1" || exit
    set -- *
    if [ "$#" -eq 1 ] && [ ! -e "$1" ] && [ ! -L "$1" ]; then
      echo "0"
    else
      echo "$#"
    fi
EOF
  )
}

count_local_files() {
  local dirname=$1
  cd "$dirname" || return
  set -- *
  if [ "$#" -eq 1 ] && [ ! -e "$1" ] && [ ! -L "$1" ]; then
    echo "0"
  else
    echo "$#"
  fi
}

if (( $(count_remote_files "$host" "$remote_dir") ==
      $(count_local_files "$local_dir") )); then
  echo "File count is identical"
else
  echo "File count differs"
  ssh "$host" 'echo "doing something else now"'
fi