【问题标题】:Trying to solve telephone word more elegantly with recursion尝试用递归更优雅地解决电话词
【发布时间】:2012-11-14 13:03:54
【问题描述】:

我查看了 Stack Overflow,但无法让任何东西正常工作。如果我错过了一个明显的帖子,我深表歉意。

我有一个学校问题,涉及获取电话号码,获取所有可能的单词组合,然后将其写入文本文件。我做到了,并获得了我的任务的全部功劳。我可以用七个嵌套循环来做到这一点,但这不是很优雅而且很死板。当我发现教科书的解决方案是七个嵌套循环时,我大吃一惊,完全失望。我的导师也没有任何答案。

我尝试了许多不同的方法,但无法将其拨入。我确定了递归和终止点,但始终无法使其正常工作。我可以生成字母序列,但远不及应该有多少。我把我的尝试注释掉了,这样你就可以看到我失败的思维过程了:)如果你有任何想法,请看看并告诉我。

public partial class TelephoneWorderizer : Form
{
    protected Dictionary<int, IEnumerable<string>> KeyMappings = new Dictionary<int, IEnumerable<string>>();
    protected string[][] ActiveLettersGroups = null;
    protected List<string> Words = new List<string>();
    protected List<string> RecursiveWords = new List<string>();
    protected int Iteration = 0;

    public TelephoneWorderizer()
    {
        InitializeComponent();

        this.KeyMappings = this.GetKeyMappings();
    }

    private void btnGetWords_Click(object sender, EventArgs e)
    {
        string textBoxContent = textBoxNumber.Text;

        int[] digits = this.GetPhoneNumbers(textBoxContent);

        List<string> words = this.GetWords(digits);

        using (StreamWriter writer = new StreamWriter(@"E:\words.txt"))
        {
            foreach (var word in words)
            {
                writer.WriteLine(word);
            }
        }

        textBoxNumber.Clear();
    }

    private List<string> GetWords(int[] digits)
    {
        List<string[]> letterGroupings = new List<string[]>();

        //digits array of numbers
        for (int i = 0, j = digits.Length; i < j; i++)
        {
            int digit = digits[i];

            //if the number has a letter group mapped to it
            if (this.KeyMappings.ContainsKey(digit))
            {
                // letters mapped to a given number
                letterGroupings.Add(this.KeyMappings[digit].ToArray());
            }
        }

        this.WordMakerLoop(letterGroupings);
        //this.WordMaker(letterGroupings);

        return this.Words;
        //return this.RecursiveWords;
    }

    //private void RecursionTest(string word, List<string[]> groups, int letterCtr, int groupCtr)
    //{
    //    string[] Group = groups[groupCtr];

    //    word += Group[letterCtr];

    //    letterCtr += 1;

    //    if (letterCtr < Group.Length - 1)
    //    {
    //        letterCtr = 0;
    //        groupCtr += 1;

    //        // Hit bottom
    //        if (groupCtr == groups.Count - 1)
    //        {
    //            groupCtr -= 1;
    //        }

    //        RecursionTest(word, groups, letterCtr, groupCtr);
    //    }
    //}

    private void WordMaker(List<string[]> letterCollections)
    {
        string newword = "";
        int numberLength = letterCollections.Count;

        this.ActiveLettersGroups = letterCollections.ToArray();

        string[] letterGroup = this.ActiveLettersGroups[0];

        this.RecursiveGetWords(newword, 0, 0);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="word"></param>
    /// <param name="groupIndex"></param>
    /// <param name="letterIndex"></param>
    private void RecursiveGetWords(string word, int groupIndex, int letterIndex)
    {

        /*
         * 
         * 
         * 
         */


        var numActiveLetterGroups = this.ActiveLettersGroups.Length;

        if (this.ActiveLettersGroups.Length > 0 && this.Iteration < numActiveLetterGroups)
        {
            if (groupIndex < numActiveLetterGroups)
            {
                var letters = this.ActiveLettersGroups[groupIndex]; // Picks the a letter group ex: A, B, C 

                if (letterIndex < letters.Length)
                {
                    //var letter1 = letters.Select(x => 
                    string letter = letters[letterIndex]; // Picks a letter from the group ex: A

                    word += letter;

                    this.RecursiveGetWords(word, groupIndex + 1, this.Iteration);
                }
                else
                {
                    //this.RecursiveWords.Add(word);
                    //word = "";

                    //this.RecursiveGetWords(word, 0, 1);
                }
            }
            else
            {
                this.RecursiveWords.Add(word);
                word = "";
                this.Iteration++;

                this.RecursiveGetWords(word, 0, this.Iteration);
            }
        }
    }

    #region
    private void WordMakerLoop(List<string[]> letterGroups)
    {
        string word = "";

        // array of string[]
        var newGroup = letterGroups.ToArray();

        //grabs a letter group
        for (int i = 0; i < newGroup.Length; i++)
        {
            var letterGroup1 = newGroup[i];

            //grabs a letter from group 1
            for (int j = 0; j < letterGroup1.Length; j++)
            {
                string letter1 = letterGroup1[j];

                if (i + 1 < newGroup.Length)
                {
                    var letterGroup2 = newGroup[i + 1];

                    //grabs a letter from group 2
                    for (int k = 0; k < letterGroup2.Length; k++)
                    {
                        string letter2 = letterGroup2[k];

                        if (i + 2 < newGroup.Length)
                        {
                            var letterGroup3 = newGroup[i + 2];

                            //grabs a letter from group 3
                            for (int l = 0; l < letterGroup3.Length; l++)
                            {
                                string letter3 = letterGroup3[l];

                                if (i + 3 < newGroup.Length)
                                {
                                    var letterGroup4 = newGroup[i + 3];

                                    //grabs a letter from group 4
                                    for (int m = 0; m < letterGroup4.Length; m++)
                                    {
                                        string letter4 = letterGroup4[m];

                                        if (i + 4 < newGroup.Length)
                                        {
                                            var letterGroup5 = newGroup[i + 4];

                                            //grabs a letter from group 5
                                            for (int n = 0; n < letterGroup5.Length; n++)
                                            {
                                                string letter5 = letterGroup5[n];

                                                if (i + 5 < newGroup.Length)
                                                {
                                                    var letterGroup6 = newGroup[i + 5];

                                                    //grabs a letter from group 6
                                                    for (int o = 0; o < letterGroup6.Length; o++)
                                                    {
                                                        string letter6 = letterGroup6[o];

                                                        if (i + 6 < newGroup.Length)
                                                        {
                                                            var letterGroup7 = newGroup[i + 6];

                                                            //grabs a letter from group 6
                                                            for (int p = 0; p < letterGroup7.Length; p++)
                                                            {
                                                                string letter7 = letterGroup7[p];

                                                                word = letter1 + letter2 + letter3 + letter4 + letter5 + letter6 + letter7;
                                                                this.Words.Add(word);
                                                                word = "";
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // Sanitizes input text and converts the string into and arra of int
    private int[] GetPhoneNumbers(string content)
    {
        int[] phoneNumbers = null;

        string cleanString = this.SanitizeString(content);

        int numbers;

        if (Int32.TryParse(cleanString, out numbers))
        {
            //phoneNumbers = this.GetIntArray(numbers).OfType<int>().ToList();
            phoneNumbers = this.GetIntArray(numbers);
        }

        return phoneNumbers;
    }

    // Removes potential unwanted characters from the phone number
    private string SanitizeString(string content)
    {
        content = content.Replace("-", "");
        content = content.Replace("(", "");
        content = content.Replace(")", "");

        return content;
    }

    //breaks a number into an array of its individual digits
    private int[] GetIntArray(int num)
    {
        List<int> listOfInts = new List<int>();

        while (num > 0)
        {
            listOfInts.Add(num % 10);
            num = num / 10;
        }

        listOfInts.Reverse();

        return listOfInts.ToArray();
    }

    //gets the mappings for the numerical values
    private Dictionary<int, IEnumerable<string>> GetKeyMappings()
    {
        List<string> alphabet = new List<string>() { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
        Dictionary<int, IEnumerable<string>> mappings = new Dictionary<int, IEnumerable<string>>();

        for (int i = 0; i < 10; i++)
        {
            string[] letters = null;
            switch (i)
            {
                case 0:
                case 1:
                    break;
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 8:
                    letters = alphabet.Take(3).ToArray();
                    mappings.Add(i, letters);
                    alphabet.RemoveRange(0, 3);
                    break;
                case 7:
                case 9:
                    letters = alphabet.Take(4).ToArray();
                    mappings.Add(i, letters);
                    alphabet.RemoveRange(0, 4);
                    break;
                default:
                    break;
            }
        }

        return mappings;
    }
    #endregion
}

让我强调一下,对于那些有疑问的人来说,学校作业已经结束。我想做得更好,更有效率。如果有帮助,我可以在 gitHub 上发布我的项目。

【问题讨论】:

  • 听起来也很适合Code Golf
  • 很棒的问题,很高兴看到这个问题,你的逻辑清晰,以及你自己以代码形式解决它的努力。经常有 cmets 询问更多信息或要求提出问题的人做出努力,这真是太棒了。为此 +1。

标签: c# linq loops recursion


【解决方案1】:

我现在懒得写任何代码,但你绝对应该能够通过递归而不是七个嵌套循环来做到这一点,事实上你应该能够设计一个可以在任何任意情况下工作的方法-长度电话号码。

基本思想是您将设计一个类似这样的递归方法:

void recurse(String phone, int index, StringBuilder sb)
{
   // Get the number at position phone[index]
   // Loop through the possible letters for that particular number (eg. A, B, C):
      // Add the letter to the StringBuilder
      // Call recurse(phone, index + 1, sb)
      // Subtract last letter from the StringBuilder
}

每次递归时,您都在处理下一个数字/字母位置。

当然,如果您遇到终止条件(sb 长度 == 电话长度),那么您只需将 StringBuilder 的当前值写入文件并返回即可,而不是递归。

希望这会有所帮助。

编辑:开始实际编写一些代码。就是这么简单,不需要 LINQ:

   class Program
   {
      static Dictionary<Char, String> DigitMap = new Dictionary<Char, String>()
      {
         {'0', "0"},
         {'1', "1"},
         {'2', "ABC"},
         {'3', "DEF"},
         {'4', "GHI"},
         {'5', "JKL"},
         {'6', "MNO"},
         {'7', "PQRS"},
         {'8', "TUV"},
         {'9', "WXYZ"}
      };

      static void Main(string[] args)
      {
         String phone = Regex.Replace("867-5309", "[^0-9]", "");
         recurse(phone, 0, new StringBuilder());
      }

      static void recurse(String phone, int index, StringBuilder sb)
      {
         // Terminating condition
         if (index == phone.Length)
         {
            Console.WriteLine(sb.ToString());
            return;
         }

         // Get digit and letters string
         Char digit = phone[index];
         String letters = DigitMap[digit];

         // Loop through all letter combinations for digit
         foreach (Char c in letters)
         {
            sb.Append(c);
            recurse(phone, index + 1, sb);
            sb.Length -= 1;
         }
      }
   }
}

在这段代码中(我制作了一个简单的控制台应用程序),我只是将单词写入控制台,但您可以将字符串添加到数组或将它们写入磁盘。

【讨论】:

    【解决方案2】:

    我假设您可能希望将每个数字转换为自身以及所有普通字母映射,以及将0 映射到+。所以我制作了这个字典来处理映射:

    var map = new Dictionary<char, string>()
    {
        { '0', "+0"},
        { '1', "1" },
        { '2', "2ABC"},
        { '3', "3DEF"},
        { '4', "4GHI"},
        { '5', "5JKL"},
        { '6', "6MNO"},
        { '7', "7PQRS"},
        { '8', "8TUV"},
        { '9', "9WXYZ"},
    };
    

    我的置换函数如下所示:

    Func<IEnumerable<char>, IEnumerable<IEnumerable<char>>> permutate = null;
    permutate = cs =>
    {
        var result = Enumerable.Empty<IEnumerable<char>>();
        if (cs.Any())
        {
            result = map[cs.First()].Select(c => new [] { c });
            if (cs.Skip(1).Any())
            {
                result =
                    from xs in result
                    from ys in permutate(cs.Skip(1))
                    select xs.Concat(ys);
            }
        }
        return result;
    };
    

    就是这样。

    你可以这样使用它:

    var digits = "(08) 8234-5678"
        .Where(x => char.IsDigit(x));
    
    var results =
        permutate(digits)
            .Select(x => new string(x.ToArray()));
    

    结果是一个字符串列表,其中每个字符串都是输入数字的排列。

    如果您不想将数字映射到数字,只需将它们从原始字典定义中取出,但您必须为数字 1 保留一个字符才能使其工作。

    让我知道这是否适合你。

    【讨论】:

      【解决方案3】:

      这是 Javaish 伪代码递归函数:

      void recWords(String number, int ind, String buf, Collection<String> result){
        if(ind==number.length()) {
          result.add(buf);
        } else {
          for(char letter : lettersForNumber(number.charAt(ind)) {
            recWords(number, ind+1, buf+letter, result);
          }
        }
      }
      

      在上面编写为 C# 并将其修改为您的代码之后的进一步练习:

      1. bufString 更改为一个共享StringBuilder
      2. 然后将其包装到类中并仅在递归中传递 ind,将其他作为类 成员变量。
      3. 然后最后将递归变成循环(提示:3 个嵌套循环,其中一个内部循环的迭代次数会增加)。

      关于我在想的非递归版本的注意事项:它的效率会低于递归,但让它变得有趣和值得作为练习编码的是,它将是 breadth-first,而这个递归版本是 @987654322 @。

      【讨论】:

        【解决方案4】:

        这是一个非递归的解决方案,

        • 给定一个数字,计算它的第一个单词
        • 给定一个单词,“递增”它以找到下一个单词
        public static class TelephoneFinder
        {
            static char[] firstDigits = new char[]
                           { '0', '1', 'A', 'D', 'G', 'J', 'M', 'P', 'T', 'W' };
            public static string First(string number)
            {
                char[] firstWord = new char[number.Length];
                for (int i = 0; i < number.Length; i++)
                {
                    var c = number.Substring(i, 1);
                    firstWord[i] = firstDigits[int.Parse(c)];
                }
                return new String(firstWord);
            }
        
            static Dictionary<char, char> rollovers = new Dictionary<char, char> { 
                { '1', '0' }, { '2', '1' }, { 'D', 'A' }, { 'G', 'D' }, { 'J', 'G' },
                { 'M', 'J' }, { 'P', 'M' }, { 'T', 'P' }, { 'W', 'T' }, { '[', 'W' } };
            public static string Next(string current)
            {
                char[] chars = current.ToCharArray();
                for (int i = chars.Length - 1; i >= 0; i--)
                {
                    // Increment current character
                    chars[i] = (char)((int)chars[i] + 1);
        
                    if (rollovers.ContainsKey(chars[i]))
                        // Roll current character over
                        // Will then continue on to next character
                        chars[i] = rollovers[chars[i]];
                    else
                        // Finish loop with current string
                        return new String(chars);
                }
                // Rolled everything over - end of list of words
                return null;
            }
        }
        

        称为例如

        string word = TelephoneFinder.First("867");
        while (!String.IsNullOrEmpty(word))
        {
            Console.WriteLine(word);
            word = TelephoneFinder.Next(word);
        }
        

        它可能需要一些整理,但它是一种通用的非递归解决方案,可以修改以适用于类似的“交叉产品”情况。

        【讨论】:

          【解决方案5】:

          对不起大家。这是我的第一篇 Stack Overflow 帖子,当我的问题的答案发布但从未收到时,我期待收到一封电子邮件。我只是想我的问题被吞入了堆栈溢出的深渊。

          与我一起工作的一组开发人员用大约 5 行代码就搞定了这个问题。目前我似乎找不到解决方案,但我认为它与 Enigmativity 发布的内容非常接近。我很肯定我们使用了排列。我会寻找我们使用的解决方案。谢谢大家。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-04-28
            • 1970-01-01
            • 1970-01-01
            • 2019-09-06
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多