我刚刚开始使用Rob Miles的C#编程黄皮书和他网站上的一些相关实验练习来学习C#。我做了其中一个,并提出了可行的解决方案。在Miles的书中,他说嵌套if和else括号很笨拙,并介绍了切换方法。但是switch显然不适用于布尔运算符,例如x <201,所以我不能使用它。我可以只拥有独立的,非嵌套的if语句,尽管它们的条件必须更长。还有其他想法吗?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


class Program
{
    static int GetIntNumber(int min, int max, string prompt, string inputPrompt)
    {
        int number;

        Console.Write(prompt);
        do  //loops as long as input is not between min and max
        {
            while (!int.TryParse(Console.ReadLine(), out number)) //loops as long as the input is not an integer
            {
                Console.Write("\nYou must enter an integer number!: ");
            }
            if (number < min || number > max) Console.Write(inputPrompt); //<--| write input prompt if number out of range
        } while (number < min || number > max);

        return number;
    }

    static void printGrade(string grade)
    {
        Console.WriteLine("\nYour potato is grade " + grade);
    }

    static void Main()
    {

        do
        {
            int weight = GetIntNumber(0, 1500, "\nEnter the weight of the potato in grams: ", "\nEnter an integer between 0 and 1500: ");

            if (weight < 201)
            {
                printGrade("X");
            }
            else
            {
                if (weight < 401)
                {
                    printGrade("A");
                }
                else
                {
                    if (weight < 801)
                    {
                        printGrade("B");
                    }
                    else
                        printGrade("Z");
                }
            }
            Console.WriteLine("\nPress any key to check the grade of another potato.");
        } while (Console.ReadKey(true).Key != ConsoleKey.Escape );

        Console.Read();

    }
}


评论

我对C#一无所知,这就是为什么我只留下评论,但是该语言肯定支持三元运算符吗? printGrade(重量<201?“ X”:重量<401?“ A”:重量<801?“ B”:“ Z”)

x <201是布尔变量表达式。它返回true或false或0或1。不能将其评估为一系列数字。因为0和1的集合中何时是3?

#1 楼

您非常正确地认为switch语句不能采用这样的布尔运算符:switch语句用于构建跳转表,并且只能使用常量来这样做。但是,您可以使用Dictionary和一些LINQ轻松地为您的问题提供一个相当可靠的解决方案。

由于您为问题初学者添加了标签,因此我会很友善地进行解释。

我们要做的第一件事是制作一个Dictionary<int, string>,它表示每个年级的最低值。

var gradeMap = new Dictionary<int, string>
{
    [0] = "X",
    [201] = "A",
    [401] = "B",
    [801] = "Z",
};


因此,这意味着如果该值是>= 801 ,然后是Z,依此类推。

接下来,我们将在LINQ中将整个操作简单地单行处理:

gradeMap.OrderByDescending(x => x.Key).FirstOrDefault(x => x.Key <= testWeight).Value



这是怎么回事? LINQ是语言集成查询,如果您曾经使用过SQL,则可能会认识到Query位,从本质上讲,LINQ允许您构建外观和行为类似于对IEnumerable对象(Dictionary是对象)的查询的代码。

因此,如果逐步进行操作,我们可以从以下字典开始:

0: X
201: A
401: B
801: Z


gradeMap.OrderByDescending(x => x.Key)位将首先占用整个gradeMap字典,然后按键值从高到低的顺序对其进行排序:

801: Z
401: B
201: A
0: X


接下来,我们执行FirstOrDefault(x => x.Key <= testWeight),它将循环遍历所有元素(在字典中的倒序排列),然后找到与我们的表达式匹配的第一个(x.Key <= testWeight)。 x.Key是字典键(我们的int值)的引用:

801: Z (Skipped)
401: B (Skipped)
201: A (Matched)


所以,现在我们有了字典中的201: A项,最后一部分是获得.Value并返回其中的string部分:

A


中提琴,我们有我们的价值。 :)


我们可以将其组合在一起并得到:

var testWeight = 205;
var grade = gradeMap.OrderByDescending(x => x.Key).FirstOrDefault(x => x.Key <= testWeight).Value;


所以,我们要做的最后一件事就是组合与您原来的程序一起使用:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

class Program
{
    static int GetIntNumber(int min, int max, string prompt, string inputPrompt)
    {
        int number;

        Console.Write(prompt);
        do  //loops as long as input is not between min and max
        {
            while (!int.TryParse(Console.ReadLine(), out number)) //loops as long as the input is not an integer
            {
                Console.Write("\nYou must enter an integer number!: ");
            }
            if (number < min || number > max) Console.Write(inputPrompt); //<--| write input prompt if number out of range
        } while (number < min || number > max);

        return number;
    }

    static void printGrade(string grade)
    {
        Console.WriteLine("\nYour potato is grade " + grade);
    }

    static void Main()
    {
        do
        {
            var gradeMap = new Dictionary<int, string>
            {
                [0] = "X",
                [201] = "A",
                [401] = "B",
                [801] = "Z",
            };

            int weight = GetIntNumber(0, 1500, "\nEnter the weight of the potato in grams: ", "\nEnter an integer between 0 and 1500: ");

            printGrade(gradeMap.OrderByDescending(x => x.Key).FirstOrDefault(x => x.Key <= weight).Value);

            Console.WriteLine("\nPress any key to check the grade of another potato.");
        } while (Console.ReadKey(true).Key != ConsoleKey.Escape );

        Console.Read();
    }
}


下一个最好的方法是将LINQ逻辑提取到新方法中:

public string GetGrade(int weight)
{
    var gradeMap = new Dictionary<int, string>
    {
        [0] = "X",
        [201] = "A",
        [401] = "B",
        [801] = "Z",
    };

    return gradeMap.OrderByDescending(x => x.Key).FirstOrDefault(x => x.Key <= weight).Value;
}


然后我们的Main方法变为:

static void Main()
{
    do
    {
        int weight = GetIntNumber(0, 1500, "\nEnter the weight of the potato in grams: ", "\nEnter an integer between 0 and 1500: ");

        printGrade(GetGrade(weight));

        Console.WriteLine("\nPress any key to check the grade of another potato.");
    } while (Console.ReadKey(true).Key != ConsoleKey.Escape );

    Console.Read();
}


现在,无论出于何种原因,如果您需要添加新成绩,这都是微不足道的。


另外:如果您是未使用C#6.0的情况下,您必须将var gradeMap替换为以下内容:

var gradeMap = new Dictionary<int, string>
{
    {0, "X"},
    {201, "A"},
    {401, "B"},
    {801, "Z"}
};


到目前为止,一切都很好,希望您能继续改进并了解有关C#和范例的更多信息它拥有。 :)


t3chb0t的回答也非常好,我建议也仔细阅读(尤其是有关方法重载的内容)。

评论


\ $ \ begingroup \ $
尽管我对此没有技术上的异议,但这只是一个刚起步的人,我真的不认为这是适合他们情况的合适答案。
\ $ \ endgroup \ $
–Loren Pechtel
16年11月7日在1:57

\ $ \ begingroup \ $
@LorenPechtel虽然这可能不是特定用户的最佳答案,但我们仍应努力让那些入门的用户(至少)了解他们将来可能会遇到的高级工具,以使他们以了解话题探索的方向。如果有解决问题的更好方法(这是尝试解决的问题),那么至少应将其提供给将来的读者。
\ $ \ endgroup \ $
– Der Kommissar
16年11月7日2:00

\ $ \ begingroup \ $
嘿,这很有趣,感谢您的答复。我或多或少了解它是如何工作的。作为初学者,我面临的挑战当然是要记住的语法数量...但是将来我会尽量记住这一点。
\ $ \ endgroup \ $
– S. Elliot Perez
16年11月7日在7:50

\ $ \ begingroup \ $
@ S.ElliotPerez每个人都从一个初学者开始,诀窍是不要失去兴趣。希望您能顺利成为一名强大的C#程序员。 :)
\ $ \ endgroup \ $
– Der Kommissar
16年11月7日在16:47

#2 楼

您可以将else if放在一起。
我怀疑这会更快吗,但对我来说它更干净,我喜欢偶数

if (weight <= 200)
{
    printGrade("X");
}
else if (weight <= 400)
{
    printGrade("A");
}
else if (weight <= 800)
{
    printGrade("B");
}
else
{
    printGrade("Z");
}


#3 楼

有什么可以改进的地方?

您可以在几个地方改进您的代码。让我们从GetIntNumber开始。


对重复的条件使用辅助变量。
使用辅助变量赋予条件含义。最好写评论。
使用积极的条件,并在必要时将其取反。通常,它们更容易理解。

借助良好的辅助变量,您将不需要注释,因为现在变量名称已经说明了while的运行时间。改进后的代码如下所示:

static int GetIntNumber(int min, int max, string prompt, string inputPrompt)
{
    int number;
    Console.Write(prompt);
    bool inputInRange;

    do
    {
        while (!int.TryParse(Console.ReadLine(), out number)) //loops as long as the input is not an integer
        {
            Console.Write("\nYou must enter an integer number!: ");
        }

        inputInRange = number >= min && number <= max;

        if (!inputInRange)
        {
            Console.Write(inputPrompt);
        }
    } while (!inputInRange);

    return number;
}



方法重载

现在,让我们对Main进行一些处理,当前包含重要的逻辑。

您可以创建另一种PrintGrade方法,但是该方法将需要int。它将封装打印级逻辑,并使用return避免嵌套或丑陋。

其他建议:


使用PascalCase作为方法。
使用常量而不是幻数。

示例:

static class WeightGradeMaxValue
{
    public const int X = 200;
    public const int A = 400;
    public const int B = 800;
    public const int Z = -1; // -1 might indicate no limit
}

static void PrintGrade(int weight) 
{

    if (weight <= WeightGradeMaxValue.X)
    {
        PrintGrade("X");
        return;
    }

    if (weight <= WeightGradeMaxValue.A)
    {
        PrintGrade("A");
        return;
    }

    if (weight <= WeightGradeMaxValue.B)
    {
        PrintGrade("B");
        return;
    }

    PrintGrade("Z");
}


您的if/else现在很短:

static void Main()
{
    do
    {
        int weight = GetIntNumber(0, 1500, "\nEnter the weight of the potato in grams: ", "\nEnter an integer between 0 and 1500: ");

        PrintGrade(weight);

        Console.WriteLine("\nPress any key to check the grade of another potato.");
    } while (Console.ReadKey(true).Key != ConsoleKey.Escape);

    Console.Read();
}



更高级的解决方案

即使有所有建议的改进,仍然还有更多的余地。

可以将符号封装为新类型:

class Grade
{
    public Grade(string symbol, int minWeight) 
    {
        Symbol = symbol;
        MinWeight = minWeight;
    }

    public string Symbol { get; }

    public int MinWeight { get; }

    public bool Includes(int weight)
    {
        return weight > MinWeight;
    }   
}


现在,该新类型可以检查重量是否在其范围内。这将帮助您搜索特定的成绩。

通常很容易理解正逻辑,因此我将其翻转。我没有将最大权重设置为最小权重,而是使之可以使用所有等级,并且逻辑变得更加明显,因为您现在检查的是该等级的权重,而不是其他等级的权重。

现在非常容易创建新成绩,我们通过Main对数组进行降序预排序,这样您就不必每次都这样做。

private static readonly Grade[] Grades = new Grade[]
{
    new Grade("X", 0),
    new Grade("A", 200),
    new Grade("B", 400),
    new Grade("Z", 800),
}
.OrderByDescending(x => x.MinWeight)
.ToArray();


现在,搜索成绩就像编写一个LINQ表达式一样简单-它也很容易阅读,因为逻辑封装在MinWeight方法中:

static Grade GetGrade(int weight)
{
    return Grades.FirstOrDefault(x => x.Includes(weight)) ?? Grades.Last();
}


Includes(null-coalescing运算符)表示如果在未找到任何内容的情况下第一部分返回??,则使用第二部分或更详细地讲:


它返回左操作数,如果操作数不为空;


最后一步是交换旧的


PrintGrade(weight);



用于新产品

PrintGrade(GetGrade(weight).Symbol);


评论


\ $ \ begingroup \ $
谢谢您的答复。我对为什么PrintGrade方法(在第二行代码中)包含其自身的实例感到有些困惑。同样,按照定义,它以整数作为值,但是您给它一个字符串作为参数...
\ $ \ endgroup \ $
– S. Elliot Perez
16年11月7日在8:23

\ $ \ begingroup \ $
@ S.ElliotPerez称为方法重载。您可以使用相同名称的多个方法,只要它们的参数不同即可。
\ $ \ endgroup \ $
–t3chb0t
16年11月7日在8:24

#4 楼

一些建议:


为什么要计算两次(number < min || number > max)

如果提取要求的公式即
if,则可以避免if-else (weight-1)/ 200
== 0 =>'X'
== 1 =>'A'
== 2 =>'B'
== 3 =>'B'
其他=='Z'

因此,您可以拥有一个数组

grades[5] = {'X','A','B','B','Z'}
factor = (weight-1)/200;
if(factor>=4) { factor = 4;}
return grade[factor];


我知道它的有点不可理解,但它很简短:)


其他选择是switch

只是实现了一个更简单的解决方案。
-使用带有键代表的字典每个范围的上限和具有该范围值的值,即

   valuesForRange[200] = 'X';
   valuesForRange[400] = 'A';
   valuesForRange[800] = 'B';
   valuesForRange[1500] = 'Z';


遍历键(如数组),并在匹配的任何地方获取值。

注意:您将误用字典来充当不同数据类型的2D数组。

评论


\ $ \ begingroup \ $
嘿,我将按照长度/复杂度的顺序进行所有这些操作。您的解决方案非常好且紧凑,谢谢。加上一些评论,它会更容易理解...唯一的问题是,在这种情况下,如果范围值的间隔不均匀,使用起来会变得更加困难。例如,如果它是类似“ X” = 0-257 gm,“ A” = 258-420 gm,“ B” = 421-545 gm之类的东西,那么我可能不得不寻找其他解决方案。
\ $ \ endgroup \ $
– S. Elliot Perez
16年11月7日在7:31

\ $ \ begingroup \ $
“我知道它有点难以理解,但它很短”-您应该始终主张理解而不是简洁。您从代码“简短”和难以理解中获得了任何好处,除了想知道“这又会做什么?”几天后查看时。
\ $ \ endgroup \ $
– 404
16年7月7日在13:38

\ $ \ begingroup \ $
绝对不要为了简洁而妥协自己的解释性代码,我对此表示赞同。该解决方案适合于所放置的要求:) @Elliot:我添加了另一个解决方案以使其简洁。
\ $ \ endgroup \ $
– Thepace
16年11月7日在16:01