简体   繁体   English

递归连接以null结尾的字符串

[英]Concatenate null-terminated strings recursively

I'm inventing interview questions that require analysis of C++ code that does something simple with pointers and recursion. 我发明了面试问题,需要分析C ++代码,这些代码对指针和递归的操作很简单。 I tried to write strcat() in recursive manner: 我试图以递归方式编写strcat()

size_t mystrlen( const char* str )
{
    if( *str == 0 ) {
        return 0;
    }
    return 1 + mystrlen( str + 1 );
}

void mystrcpy( char* to, const char* from )
{
    if( ( *to = *from ) == 0 ) {
        return;
    }
    mystrcpy( to + 1, from + 1 );
}

void mystrcat( char* to, const char* from )
{
    mystrcpy( to + mystrlen( to ), from );
}

What I don't like here is that I have three functions and my strcat() is not very recursive - it just calls two other functions once. 我在这里不喜欢的是我有三个函数,而我的strcat()不是很递归的-它只调用另外两个函数。 Is there a way to rewrite that somehow to reduce the number of functions increasing use of recursion but without sacrificing much of code brevity? 有没有一种方法可以重写它,从而减少函数的数量,从而增加递归的使用,但又不牺牲太多的代码简洁性?

here's my example (the advantage is it has only one recursive call): 这是我的示例(优点是只有一个递归调用):

char * mystrcat(char *dest, const char *src){
    if(*dest == 0){
        if(*src == 0)   // end of recursion cond
            return dest;
        *dest = *src++; // actual copy
        dest[1]=0;      // zero out dest buf
    }
    mystrcat(dest+1,src); // advance one char
    return dest;
}

here's rough test code: 这是粗略的测试代码:

main(){
  char c[]={'a',0,'b','c',0};
  //char c[]={0,'b','c',0};
  mystrcat(c,"xy");
  //mystrcat(c,"");
  puts(c);
}

Well, I don't really see the point in trying to make strcat() "as recursive as possible", even though its just for an interview question. 好吧,尽管试图访问strcat()尽可能“递归”,但我并没有真正意义。 strcpy() is just a strlen() + strcat() and the implementation should reflect that. strcpy()只是一个strlen()+ strcat(),实现应该反映这一点。 If a candidate gave the solution you have sketched I would be more than happy - he/she showed that he/she understands 1. recursion and 2. using subroutines to implement more complex functionality. 如果候选人给出了您所概述的解决方案,我将不胜高兴-他/她表明他/她理解1.递归和2.使用子例程实现更复杂的功能。

How about: 怎么样:

void mystrcat( char* to, const char* from )
{
    if (*to == '\0')
        mystrcpy( to, from );
    else
        mystrcat( to + 1, from );     
 }

In a single function you could write: 在一个函数中,您可以编写:

// pre: to is a string that is zero-initiallized (all chars are '\0')
void mystrcat( char* to, const char* from )
{
   if ( *to ) mystrcat( to+1, from );
   else {
      if (*to = *from) mystrcat( to+1, from+1 );
   }
}

Note that it is intentionally ugly, it serves to purposes in two branches, it advances the to pointer in the first branch and it copies values in the other. 请注意,这是故意丑陋的,它在两个分支中用于目的,它在第一个分支中前进to指针,在另一个分支中复制值。 The if condition is intentionally ugly also as most usually you will flag a single = within a condition as a typo, but insist in that it is up, running and tested so that they must work the meaning out themselves. if条件也是有意丑陋的,因为通常情况下,您会在条件中将单个=标记为错字,但要坚持要求它已经启动,运行并经过测试,以便他们必须自己找出含义。

One other thing you might consider is adding a return statement like: 您可能考虑的另一件事是添加一个return语句,例如:

char* mystrcat2( char* to, const char* from )
{
   if ( *to ) return mystrcat2( to+1, from );
   else {
      if ( *to = *from ) return mystrcat2( to+1, from+1 );
      else return to;
   }
}

Now the interviewed has the second task of interpreting the result of the function when called on two strings, and you can discuss how the efficiency of this strcat version compares to the original both in terms of single string concatenations as well as multiple concatenations (take a look at this article by Joel Spolsky that among other things talks about the Scheimel paint algorithm). 现在,受访者的第二个任务是解释在两个字符串上调用时函数的结果,并且您可以在单个字符串串联和多个串联方面讨论该strcat版本的效率与原始版本的比较。请参阅Joel Spolsky 撰写的这篇文章 ,其中主要讨论了Scheimel绘制算法。 You can ask for the implementation of this function in terms of both the strcat and mystrcat : 您可以使用strcatmystrcat来要求该功能的实现:

// concatenates a set of strings into a buffer
//
// pre:  to has memory enough to hold all the contents
//       from[i] is a valid C string for all i smaller than n
void mymultiplecat( char* to, const char* from[], int n )
{
   for ( int i = 0; i < n; ++i ) {
      to = mystrcat( to, from[i] );
   }
}

I thought of another answer! 我想到了另一个答案!

void
mystrcat(char* to, const char* from)
{
    if (*to)
    {
        // keep looking for end of to
        mystrcat(to+1, from);
        return;
    }

    if (*from)
    {
        // make sure we get past first if
        to[1] = '\0';
        // call recursively until end of from found
        mystrcat(to+1, from+1);
        // after call, copy chars on way out
        *to = *from;
    }
}

No extra argument to hold state, and it is still O(n). 没有额外的参数来保持状态,它仍然是O(n)。

I'm not sure I could have come up with this while under the pressure of a job interview, though. 不过,我不确定在求职面试的压力下我能否提出这个建议。

I had an interview question once where I had to write a simple, recursive BSP tree traversal that was an actual, blanked-out function from a full program. 曾经有一个采访问题,我必须编写一个简单的,递归的BSP树遍历,这是整个程序中真正的空白功能。

I'd suggest that something like that would be a better way to gauge someone's grasp on recursion than inventing a problem that's not idiomatically solved with recursion in C++. 我建议,与发明一个C ++递归无法解决的问题相比,类似的方法可以更好地衡量某人对递归的掌握程度。 Plus it's exciting for entry level applicants when you run the program and they observe their code working flawlessly. 另外,对于入门级申请人来说,当您运行该程序并且他们的代码完美无缺地运行时,这是令人兴奋的。

If you insist on going with strcat, I think it could be more interesting if the task was to recursively concatenate an arbitrary number of strings taken from a linked list. 如果您坚持使用strcat,我认为如果任务是递归连接从链接列表中获取的任意数量的字符串,可能会更有趣。 Even if the data copy and string length operations aren't recursive, the traversal of the linked list could be, and I think it would be more interesting as you could actually do work on both the way down and on the way back up. 即使数据复制和字符串长度操作不是递归的,也可以遍历链接列表,并且我认为它会更有趣,因为您实际上可以在向下和向上进行工作。

I think you're done. 我想你完成了。 You have two lovely recursive functions, and a one-line call to them. 您有两个可爱的递归函数,以及对它们的单行调用。

I can only think of a hackish way to even do it in one function: 我只能想到一种骇人听闻的方法,甚至可以在一个功能中做到这一点:

void
mystrcat(char* to, const char* from, bool copy_now)
{
    if (!copy_now && *to)
    {
        mystrcat(to+1, from, false);
        return;
    }

    *to = *from;
    if (*to)
        mystrcat(to+1, from+1, true);
}

That almost doesn't suck if you are using C++ and you make copy_now an optional parameter with a default value of false . 如果您使用的是C ++,并且将copy_now设置为默认值为false的可选参数,则几乎不会copy_now Then the caller can pretend it doesn't have that extra bit of state. 然后,调用方可以假装它没有多余的状态。

But if the problem is hard enough to make you ask for help on StackOverflow, it's too hard to be a reasonable interview question. 但是,如果问题很难解决,无法在StackOverflow上寻求帮助,那么就很难成为一个合理的面试问题。 You could ask it just to try to make the person think out of the box, but don't make failure to get a good answer an automatic flunk. 您可以要求它只是想让这个人开箱即用,但不要因为自动失败而失败。

I suggest adding another lovely recursive problem: find the depth of a binary tree. 我建议添加另一个可爱的递归问题:找到二叉树的深度。

Given the following struct: 给定以下结构:

typedef struct s_node
{
    struct s_node *pLeft;
    struct s_node *pRight;
} NODE;

write a function 写一个函数

int tree_depth(NODE *pNode)

that returns the maximum depth of the tree. 返回树的最大深度。 I was actually asked that question in an interview once and I think it is just the right level of difficulty for an interview. 实际上,一次面试曾有人问过我这个问题,我认为这只是面试的适当难度。 The people you don't want to hire will have trouble with it, and the people you might want to hire will probably enjoy it. 您不想雇用的人会遇到麻烦,而您可能想雇用的人可能会喜欢它。

EDIT: I tested the function and found it had a bug. 编辑:我测试了该功能,发现它有一个错误。 This version has the bug fixed. 此版本已修复错误。

You want recursion and brevity? 您想要递归和简洁吗? Here's something that I think has both qualities without going too far into the code golf weeds (that's not to say it's not ugly - it is): 我认为有些东西在没有过多涉及高尔夫杂草的代码的情况下具有两种特质(这并不是说它并不难看-是的):

char* myStrcat( char* to, char const* from)
{
    if (!*from) return to;

    if (!*to) *to++ = *from++, *to-- = '\0';

    return myStrcat( to+1, from) - 1;
}

Note that this version has the added complexity of returning the destination pointer (as in the standard strcat() ). 请注意,此版本具有返回目标指针的复杂性(如标准strcat() )。

Here's a slightly more readable version that uses a little less 'pseudo-cleverness' like comma operators and decrementing after incrementing to restore the to pointer (steveha's comment prompted me to do this for some reason). 这是一个更具可读性的版本,它使用较少的“伪智能”,例如逗号运算符,并在递增以还原to指针后递减(steveha的注释促使我出于某种原因执行此操作)。 On second look at the various answers here is pretty much equivalent to catwalk's answer : 再看一下这里的各种答案,几乎等同于走秀的答案

char* myStrcat( char* to, char const* from)
{
    if (!*from) return to;  // terminal condition, no more chars to concatenate

    if (!*to) {
        // since we're at the end of the 'to' string, 
        //    append char from 'from'
        //    and 'consume' it from `from`
        *to = *from++;
        *(to+1) = '\0';
    }

    return myStrcat( to+1, from) - 1;
}

However, please don't associate my name with any of this code (I claim to have stolen it from someone else). 但是,请不要将我的名字与任何该代码关联(我声称是从别人那里偷来的)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM