【问题标题】:C# recursive yield return not returning anythingC#递归收益返回不返回任何东西
【发布时间】:2020-10-05 15:11:46
【问题描述】:

问题可能在其他地方被问到,但我找不到解决问题的方法。问题不是特定于语言的,可以在 python 中提出同样的问题。任务是生成字符串列表的算法,如Enumerable.Range,但字符不仅限于 1、2、3...,还可以是任何字符序列。最简单的示例是:

测试用例 1
输入:

baseChars: ['a','b'],
所需字符串长度:2

输出:

['aa','ab','ba','bb']

测试用例 2

baseChars: ['a','b']
所需字符串长度:1

输出:

['a','b']

功能运行良好:

    static IList<string> baseChars = new List<string>() { "0", "1", "2", "3" };
    static void CharsRange1(string prefix, int pos)
    {
        if (pos == 1)
        {
            foreach (string s in baseChars)
            {
                Console.WriteLine(prefix + s);
            }
        }
        else
        {
            foreach (string s in baseChars)
            {
                CharsRange1(prefix + s, pos - 1);
            }
        }
    }

预期和实际输出(用逗号替换换行符以节省空间):

000, 001, 002, 003, 010, 011, 012, 013, 020, 021, 022, 023, 030, 031, 032, 033, 100, 101, 102, 103, 110, 111, 112, 113, 120, 121, 122, 123, 130, 131, 132, 133, 200, 201, 202, 203, 210, 211, 212, 213, 220, 221, 222、223、230、231、232、233、300、301、302、303、310、311、312、313、 320、321、322、323、330、331、332、333

问题是把这个函数封装成一个库,所以返回类型应该是IEnumerable&lt;string&gt;,这样即使输入很大,内存也不会爆炸。但我的代码无法返回任何内容

    static IEnumerable<string> CharsRange2(string prefix, int pos)
    {
        if (pos == 1)
        {
            foreach (string s in baseChars)
            {
                yield return prefix + s;
            }
        }
        else
        {
            foreach (string s in baseChars)
            {
                // here if i yield return then won't compile
                // i thought at the end of recursive loop it will return 
                CharsRange2(prefix + s, pos - 1);
            }
        }
    }

主要:

    static void Main(string[] args)
    {
        //CharsRange1("", 3);//working
        foreach (string s in CharsRange2("", 3))
        {
            Console.WriteLine(s);//nothing
        }
        Console.WriteLine("end");
        Console.ReadKey();
    }

有人可以帮忙吗?我已将我的代码放入github。如果您可以将我的实现更改为非递归但保留函数返回类型,也很高兴。

【问题讨论】:

  • CharsRange2(prefix + s, pos - 1); 所以你递归调用函数,嗯,忽略结果?我怀疑你的意思是foreach 并使用yield return
  • 您的问题描述也不是很好 - 因为您没有显示给定输入集的预期结果。我认为我了解您要做什么,但不是 100% 确定...
  • 与第一个bing.com/search?q=c%23+recursive+yield+return 结果stackoverflow.com/questions/2055927/… 相比,显示的代码非常混乱。您能否检查您的代码并确保它有意义...
  • 旁注:以任意基数计数确实是非常常见的竞争任务......这似乎是您要问的。您可能还想通过您的编辑来澄清这一点(或者澄清您想要的序列类型,如果它不计数)。
  • A (yield) return 只返回给直接调用者,而不是第一次调用递归链的人

标签: c# algorithm recursion permutation yield


【解决方案1】:

正如所指出的那样,呼叫CharsRange2(prefix + s, pos - 1); 未被使用。您需要嵌套 foreachyield 每个结果。

这是一个更基于Enumerable.Range 想法的替代方案。

从通用的碱基更换器开始:

public static IEnumerable<int> ToBase(this int x, int b)
{
    IEnumerable<int> ToBaseReverse()
    {
        if (x == 0)
        {
            yield return 0;
            yield break;
        }
        int z = x;
        while (z > 0)
        {
            yield return z % b;
            z = z / b;
        }
    }

    return ToBaseReverse().Reverse();
}

现在添加一个将其转换为特定数字集的方法:

public static string ToBase(this int number, string digits) =>
    String.Concat(number.ToBase(digits.Length).Select(x => digits[x]));

可以这样使用:

string result = 45.ToBase("0X2Y");
Console.WriteLine(result);

这给出了:

2YX

现在写Enumerable.Range(0, 10).Select(n =&gt; n.ToBase("0X2Y"))很简单。

这给了:

0, X, 2, Y, X0, XX, X2, XY, 20, 2X

这适用于所有非零数字,除了零本身之外不显示前导零。

【讨论】:

  • 很抱歉,您的回答似乎是将输入的 dec 数字转换为其他基数?我的问题只是关于生成连续序列(就像 000、001、... 002、010...111)。所以如果这些步骤包含一些计算(devid 和 mod),我猜性能可能会很差。
  • @LeiYang - 可能不会。 Divide 和 mod 非常快。这些操作非常接近 CPU 内核。像prefix + s 这样的字符串附加操作也非常慢。唯一的判断方法是运行两组代码并比较性能。
  • @LeiYang - 我做到了。事实证明,我的速度慢了大约 4 倍。我怀疑,根据我的经验,这与分水岭和 mod 无关,而更多的是 IEnumerable&lt;int&gt;Reverse()。所以,是时候尝试制作一个更快的版本了。 :-)
  • 感谢您澄清这一点。我最终得到了@Jeremy Lakeman 的解决方案,但更改了这样的函数public IEnumerable&lt;string&gt; Permutate(char[] strchars, int pos),其中init strchars[pos] 在每个循环中都会更新。并返回new string(strchars)。你认为这比旧版本的字符串+ 更快吗?
  • @LeiYang - 我需要查看代码,但唯一真正知道的方法是计时。
【解决方案2】:

选项 1,从递归调用中产生每个值。

    foreach (string s in baseChars)
        foreach (var r in CharsRange2(prefix + s, pos - 1))
            yield return r;

选项2,重用框架中内置的现有IEnumerable类型,完全避免yield返回;

    if (pos == 1)
        return baseChars.Select(s => prefix + s);
    else
        return baseChars.SelectMany(s => CharsRange2(prefix + s, pos - 1));

选项 3,使用嵌套循环而不是递归方法,留给读者作为练习。

【讨论】:

  • 谢谢!正是我想要的。只是对强大的 linq 感到惊讶。
猜你喜欢
  • 1970-01-01
  • 2023-03-28
  • 2011-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多