【问题标题】:Removing duplicate characters from two argument strings in C从C中的两个参数字符串中删除重复字符
【发布时间】:2018-09-17 00:10:54
【问题描述】:

我正在尝试优化一个问题,我必须通过相同的速度优化使其更具可读性。我的问题在于:

允许的函数:write.c,仅此而已。

编写一个程序,它接受两个字符串,并在没有双精度的情况下显示 出现在任一字符串中的字符。

将按照字符在命令行中出现的顺序显示,并且 后面会跟一个\n。

如您所见,在使用 GCC 编译它之后,它会将您的两个参数字符串(argv[1]argv[2])带入我们的函数(void remove_dup(char *str, char *str2))。该临时数组将保存 ASCII 值检测到重复后的字符。例如,str1 = "hello"str2 = "laoblc"。使用 write 函数,预期的输出将是“heloabc”。

但是,GCC 抱怨,因为我有一个数组下标,其中我的临时字符数组用我的字符串索引中的零填充。为了停止让编译器抱怨,我不得不将字符串索引转换为 int 以将 ASCII 值保存在我的临时数组中。这将是我们的检查器,它将根据字符的值确定我们的字符串中是否存在重复项。再次重新编译它,但这次使用警告标志:gcc -Wextra -Werror -Wall remove_dup.c。这是我得到的错误:

remove_dup:11 错误:数组下标是 'char' 类型 [-Werror,-Wchar-subscripts]

           if (temp[str[i]] == 0)
                     ^~~~~~~

remove_dup:13 错误:数组下标是 'char' 类型 [-Werror,-Wchar-subscripts]

                   temp[str[i]] = 1;
                        ^~~~~~~

remove_dup:21 错误:数组下标是 'char' 类型 [-Werror,-Wchar-subscripts]

           if (temp[str2[i]]  == 0)
                   ^~~~~~~~

remove_dup.c:23 错误:数组下标是 'char' 类型 [-Werror,-Wchar-subscripts]

                  temp[str2[i]] = 1;
                      ^~~~~~~~

现在我真正的问题是,如何在不使用任何类型的转换的情况下获得相同的时间效率?该程序以O(m + n) 运行,其中m 是我们的第一个字符串,n 是我们的第二个字符串。

这是代码:

void    remove_dup(char *str, char *str2)
{
    int temp[10000] = {0};
    int i;

    i = 0;
    while (str[i])
    {
        if (temp[(int)str[i]] == 0)
        {
            temp[(int)str[i]] = 1;
            write(1, &str[i], 1);
        }
        i++;
    }
    i = 0;
    while (str2[i])
    {
        if (temp[(int)str2[i]]  == 0)
        {
            temp[(int)str2[i]] = 1;
            write(1, &str2[i], 1);
        }
        i++;
    }
}

int main(int argc, char *argv[])
{
    if (argc == 3)
        remove_dup(argv[1], argv[2]);
    write(1, "\n", 1);
    return (0);
}

我希望我解释的逻辑结构足够清楚。我可能有语法错误,所以请耐心等待:)。

【问题讨论】:

  • AFAIK 转换不会导致运行时损失。在相关说明中,temp 只需要 256 个元素长,因为这就是 char 的大小。
  • 我同意施文的观点。你的代码看起来不错。消除烦人的 gcc 警告的稍微更优雅的方法是将temp[(int)str[i]] 替换为temp[+str[i]]。这也将避免显式转换;最好尽可能避免强制转换。
  • 更正:除非你的字符是signed!
  • @JosephQuinsey 你能解释一下为什么在这种情况下必须对字符进行签名吗?

标签: c arrays string gcc argv


【解决方案1】:

在这里投射不会有性能损失。

但是,根据经验,通常最好尽可能避免显式转换。您可以通过例如更改来做到这一点:

   temp[(int)str[i]]

到:

   temp[+str[i]]

这将通过通常的算术转换起作用。

但是,您的代码还有另一个问题。你可能会问:为什么 gcc 会费心发出这样的烦人警告信息?

一个答案是他们只是喜欢让人讨厌。一个更好的猜测是,在大多数平台上charsigned——参见Is char signed or unsigned by default?——所以如果你的字符串碰巧有一个大于 127(即小于零)的 ASCII 字符,你就会有一个错误。

解决此问题的一种方法是替换:

   temp[(int)str[i]]

与:

   temp[str[i] + 128]

(并将int temp[10000] = {0} 更改为int temp[256 + 128] = {0})。无论char 的默认符号如何,这都将起作用。

【讨论】:

  • 当我测试这个时,它不会抱怨。但是,它将打印带有重复项的两个字符串。如果我将temp[str[i] + 128] 更改为temp[+str[i]],它将使其工作。
  • 您是否更改了temp[(int)str[i]]两个 实例和temp[(int)str2[i]]两个 实例?
  • 对不起,如果上面的评论很明显——我有时会犯愚蠢的错误。
  • 编辑: 关于 OP 的评论“该程序以 O(m * n) 运行,其中 m 是我们的第一个字符串,n 是我们的第二个字符串。”,时间其实是O(m + n)
  • 别担心,我们都是诚实的。在将两个实例都设置为temp[str[i] + 128]temp[+str[i]] 后,它现在可以正常工作了。我认为将一个实例更改为temp[+str[i]],将另一个实例更改为temp[str[i] + 128] 将对两者进行相同的操作。编辑:是的,您在O(m + n) 中是对的。我会改的。
【解决方案2】:

现在我真正的问题是,如何在不使用任何类型的强制转换的情况下获得相同的时间效率?

我不认为在 C 中进行强制转换会导致运行时损失。无论如何,C 中的所有内容都是一个数字。我相信它只是告诉编译器是的,你知道你使用了错误的类型并相信它没问题。

请注意,char 可以签名。负数有可能潜入其中。

这个程序以 O(m * n) 运行,其中 m 是我们的第一个字符串,n 是我们的第二个字符串。

不,它以 O(n) 运行。如果您为另一个字符串的每个字符迭代一个字符串,则为 O(m*n)。

for( int i = 0; i < strlen(str1); i++ ) {
    for( int j = 0; j < strlen(str2); j++ ) {
        ...
    }
}

但是你在两个独立的循环中一个接一个地循环遍历每个字符串。这是 O(m + n),也就是 O(n)。


继续改进。首先,temp 只需要保存char 范围,最多为256。让我们给它一个变量名来描述它的作用,chars_seen

最后,没有必要存储一个完整的整数。通常我们会使用bool from stdbool.h,但我们可以使用signed char 定义我们自己的,这是stdbool.h 可能会做的。我们确保将其包装在 #ifndef bool 中,因此我们使用系统提供的(如果可用),它比我们使用布尔值的类型更清楚。

#ifndef bool
  typedef signed char bool;
#endif
bool chars_seen[256] = {0};

您可以通过消除i 并直接增加指针来获得更多性能。不仅性能更高,而且这使得许多字符串和数组操作更简单。

for( ; *str != '\0'; str++ ) {
    if( !chars_seen[(size_t)*str] ) {
        chars_seen[(size_t)*str] = 1;
        write(1, str, 1);
    }
}

请注意,我正在转换为 size_t,而不是 int,因为这是索引的正确类型。

您也许可以通过使用后增量来减少触发,这是否有帮助将取决于您的编译器。

    if( !chars_seen[(size_t)*str]++ ) {
        write(1, str, 1);
    }

最后,为了避免重复您的代码并将其扩展为可以处理任意数量的字符串,我们可以编写一个函数来接收看到的字符集并显示一个字符串。我们会给编译器提示内联它,尽管它的用途有问题。

inline void display_chars_no_dups( const char *str, bool chars_seen[]) {
    for( ; *str != '\0'; str++ ) {
        if( !chars_seen[(size_t)*str]++ ) {
            write(1, str, 1);
        }
    }
}

然后main 分配所见字符的数组并根据需要多次调用该函数。

int main(int argc, char *argv[]) {
    bool chars_seen[256] = {0};

    for( int i = 1; i < argc; i++ ) {
      display_chars_no_dups( argv[i], chars_seen );
    }
    write(1, "\n", 1);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-08
    • 2018-06-09
    • 2021-07-02
    • 1970-01-01
    • 2014-05-20
    • 1970-01-01
    • 1970-01-01
    • 2012-04-08
    相关资源
    最近更新 更多