×

Loading...
Ad by
Ad by

Sometimes, greedy operators are also important.

For example, we want to remove some selected items from a ListBox (similar in any collection object). In traditional code, it has to loop backward thro whole items

for (int i = ListBox1.Items.Count - 1; i >= 0; i--)
{
if (ListBox1.Items[i].Selected)
{
ListBox1.Items.Remove(ListBox1.Items[i]);
}
}

Benefit from LINQ greedy operator, the code can be simplified as following

foreach (ListItem item in ListBox1.Items.Cast<ListItem>().Where(i => i.Selected).ToList())
{
ListBox1.Items.Remove(item);
}

Here, greedy operator ToList() is important. Missing it will result in error.
Report

Replies, comments and Discussions:

  • 工作学习 / 学科技术讨论 / 朋友最近找.net工作遇到的几道面试题,比较新鲜,给找工作的IT同行
    本文发表在 rolia.net 枫下论坛现在越来越多的公司开始转到.net 3.5,LINQ似乎是必考的题目,这些题和LINQ有关

    1) 有一个数组
    string[] a = new string[] {"a1","b1","c1","d1","e1"}
    要求打印出以下的效果,用包括LINQ在内的最简单的方法完成
    "a1-b1-c1-d1-e1"

    朋友没用LINQ,用的是
    string b = "";
    for(int i=0;i<a.Length;i++)
    {
    if(i!=a.Length-1)
    {
    b+=a[i]+"-";
    }
    else
    {
    b+=a[i];
    }
    }
    Console.WriteLine(b);

    考官既然提到LINQ,我估计他是想考LINQ的.Aggregate()方法,这应该不叫LINQ,是一个扩展方法而已,最简单的是这种写法

    string b = a.Aggregate((a1,b1)=>a1+"-"+b1);
    Console.WriteLine(b);

    还有一些题,比如把数组 "to-toronto","mo-montrol","la-Los Angeles"
    变成一个Directory<string, string> dir
    里面内容是
    dir[to] = toronto
    dir[mo] = montrol
    dir[la] = Los Angeles
    同样要求用包括LINQ在内的最简单的方法完成

    按常规写法用一个loop当然可以搞定,但我猜考官可能是要考.ToDirectory() 方法,应该这么写

    string[] a = new string[] {"to-toronto","mo-montrol","la-Los Angeles" }
    var dir = a.ToDictionary(s => s.Split('-').First(), s => s.Split('-').Last());

    总之朋友讲给我的linq题都是考一些基本知识,但这2个扩展方法平时人们不是很常用,考官可能是测试你对linq的熟练程度。更多精彩文章及讨论,请光临枫下论坛 rolia.net
    • The aggregate operator usage is very novel. Normally aggregate operators are primarily used for numeric accumulative operation.
      • 哈,深蓝终于出现了,正好有个问题问你。那个PLINQ不是有3种访问方法吗 Pipelined, stop-and-go, Inverted Enumeration 我看资料说Inverted Enumeration速度最快,可是我的测试结果却是Pipelined速度最快,这个你有经验吗?到底是怎么回事?
        • Sorry I don’t test them. Recently, I have been being fully overloaded.
    • string.Join("-", a);
      • Join是个好方法,但是只能用于string[],aggregate 更通用一些,可以用于所有IEnumerable<>的东西啦
    • 再举一个面试题,和.Net 2.0的 yield 有关
      本文发表在 rolia.net 枫下论坛有这么一段程序,要求写出打印结果

      static void Main(string[] args)
      {
      try
      {
      var result = GetNames();
      foreach (var s in result)
      {
      Console.WriteLine(s);
      }
      }
      catch(Exception e)
      {
      Console.WriteLine(e.ToString());
      }
      }

      static IEnumerable<string> GetNames()
      {
      yield return "Wang";
      yield return "Li";
      yield return "Zhang";
      throw new Exception("Bad Name");
      }


      打印结果是什么呢?大部分人(包括我)的第一感觉就是直接打印出“Bad Name”, 呵呵,其实结果是这个样子

      Wang
      Li
      Zhang
      System.Exception: Bad Name .....

      为什么会这样?为什么不直接抛出异常呢?出这道题的考官很坏,这涉及到yield的特点,这个特点很多程序员很少注意
      ,那就是系统每次遇到一个yield,就会返回主调函数它后面的return值,等主调函数处理完毕之后再往下执行返回下一个yield return的值。也就是说
      static IEnumerable<string> GetNames() 函数并不是首先生成里面所有可能的IEnumerable<string>,然后再返回这个IEnumerable<string>,而是动态的,一个一个返回,所以它发现不了最后面的那个throw exceptions

      这个特点和LINQ的Lazy Operation很像,以后有时间说说LINQ的Lazy Operation更多精彩文章及讨论,请光临枫下论坛 rolia.net
      • 问个知其所以然的问题:为什么我们需要yield这个命令。
        • 用yield的好处可能主要是这样
          系统一旦碰到yield就会返回后面的值,而不是等到所有的值都生成完毕才返回,这对提高系统性能有好处,比如如下两段代码

          IEnumerable<string> GetNames()
          {
          for(int i=0;i<1000000000000000000000000000000000000;i++)
          {
          yield return i.toString();
          }
          }



          IEnumerable<string> GetNames()
          {
          List<string> result = new List<string>();
          for(int i=0;i<1000000000000000000000000000000000000;i++)
          {
          result.Add(i.toString());
          }

          return result;
          }

          这2段代码所返回的结果是完全一样的,不同的是第一段使用yield的代码会马上一个一个地返回i.toString(),用户看不到有什么停顿,而且对内存的开销极小。但是第二段代码系统会生成所有的array之后才会返回这个array值。时间会很长,用户会明显感觉到有停顿
          ,并且需要大量的内存记住这个返回的array值

          这就是用yield的好处,可以立即的、实时的处理foreach里面的items。
          • 这个例子举得太烂了,用好一点的例子来说明一下yield的用法
            本文发表在 rolia.net 枫下论坛程序如下:

            static void Main(string[] args)
            {
            foreach (string num in getNumbers())
            {
            Console.WriteLine("I am geting numbers: " + num.ToString());
            }
            }

            static IEnumerable<string> getNumbers()
            {
            for (int i = 0; i < 3; i++)
            {
            Console.WriteLine(i.ToString());
            yield return i.ToString();
            }
            }

            打印出的结果是这个样子
            0
            I am geting numbers: 0
            1
            I am geting numbers: 1
            2
            I am geting numbers: 2

            很明显,yield返回一个数,然后主程序处理一下,然后yield在返回一个数,主程序再处理一下.......etc

            如果改成这个样子

            static void Main(string[] args)
            {
            foreach (string num in getNumbers().ToArray())
            {
            Console.WriteLine("I am geting numbers: " + num.ToString());
            }
            }

            static IEnumerable<string> getNumbers()
            {
            for (int i = 0; i < 3; i++)
            {
            Console.WriteLine(i.ToString());
            yield return i.ToString();
            }
            }

            结果就会变为:

            0
            1
            2
            I am geting numbers: 0
            I am geting numbers: 1
            I am geting numbers: 2

            哈哈,因为我加了getNumbers().ToArray(),这时系统就会显示的把getNumbers()的结果变为一个存储在内存中的Array,所以必须先要遍历getNumbers(),就会形成上面的结果更多精彩文章及讨论,请光临枫下论坛 rolia.net
          • 对不起,我老提不同意见。
            这个应该用多线程很容易实现,而且多线程可以中断后续的处理,比如你这个例子中,处理到1000时程序逻辑上发现不需再往下处理就结束了,yield能做到吗?(我是真不知道,向你学习才提的问题。)
            • 俺没看懂你的多线成是什么意思,但根据你的描述,用yield应该这么来写
              static void Main(string[] args)
              {
              int counter = 0;
              foreach (var num in getNumbers())
              {
              if (counter++ > 1000) break;
              Console.WriteLine("I am geting numbers: " + num.ToString());
              }
              }

              static IEnumerable<string> getNumbers()
              {
              int i = 0;
              while (true)
              {
              yield return i++.ToString();
              }
              }
              • 难怪你对AsParallel推崇倍加。我的意思是在static IEnumerable<string> getNumbers() 里面,不过看你的答案,我猜yield可以做到。
            • 对了,我记得VB里没有 yield return 这个关键字,很遗憾了,VB程序员享受不到C#的这点好处了
              • Yield is not a feature of the .Net runtime. It is just a C# language feature which gets compiled into simple IL code by the C# compiler.
              • 从功能上用backgroundWorker class也能轻松做到,不过形式上就不是IEnumerable和yield了。backgroundWorker 更强大,它可以每次返回不同type的值。
        • 而且,通过我对Linq中where, select等等的测试,无论我查询或者选择多大的数据量,Linq总是能在第一时间开始返回IEnumerable结果,所以俺猜想Linq中的Where, Select等等里面全都是用的yield
        • Typically, yield return uses lazily generating a sequence of objects.
          It just generates the next item on demand.

          It means that yield return itself doesn’t create actual iterator. Only when the sequence is traversed, it goes thro each item.

          One benefit is as follows.

          One sequence might be infinite. If it requires return an infinite sequence, it’s impossible. However, when you call its top items, e.g. first 1000 (or some other conditions), you can get final result without caring about the infinite.
          • 继续挖根究底:那在什么情况下,我们需要使用infinite sequence?天文应用?银行计算,还是其他的?
            • It’s just extreme example and program concept.
              yield return just tells .NET framework don’t run it until its sequence is actually traversed.

              LZ’s interview question is also good example. If you loop thro whole GetNames method, it throws exception. However, if you run it in following, it runs properly.

              var q = GetNames().Where(n => n.EndWith(“ang”));
              foreach (var e in q)
              {

              }
      • Sometimes, greedy operators are also important.
        For example, we want to remove some selected items from a ListBox (similar in any collection object). In traditional code, it has to loop backward thro whole items

        for (int i = ListBox1.Items.Count - 1; i >= 0; i--)
        {
        if (ListBox1.Items[i].Selected)
        {
        ListBox1.Items.Remove(ListBox1.Items[i]);
        }
        }

        Benefit from LINQ greedy operator, the code can be simplified as following

        foreach (ListItem item in ListBox1.Items.Cast<ListItem>().Where(i => i.Selected).ToList())
        {
        ListBox1.Items.Remove(item);
        }

        Here, greedy operator ToList() is important. Missing it will result in error.
        • 在for each里做remove应该会有问题的。
          • Of cource, if without ToList().
            • 唔,指针。
              • Unlike lazy operator, greedy operator generates a status result set that is not affected by changing original data.
                • 我们本着共同学习,我玩了一个上午才发现的,如果这个ListBox1.Items里有多个同样值的listItem,用remove会有问题,得用removeat.
                  • If I do it, I’ll filter out dups when adding/binding data source into the listbox.
    • 再举一个小例子,用C#的 params 关键字简化编程
      params关键字是和 ref, out 属于一类的,但是用params有时可以让变成更轻松一些,看一下这个例子

      string a = ".....ooooo;;;;;";
      如果在a后面打一个.Trim(,那么系统会弹出Trim的重载方法,其中一个重载方法是
      a.Trim(params char[] trimChars);

      它的作用是把字符串两边指定不用的字符删掉,如果要删掉 .和; , 我以前这么写
      a.Trim(new char[] {'.',';'})

      而使用了params修饰方法参数后,我们可以直接使用一组对象作为参数
      a.Trim('.',';');

      很方便,我再也不用打入那一大堆讨厌的new char[] {},或者 new string[] {}了
      • 呵呵,这次可不是C#的专利,不知为何要加这个keyword,"a.Trim(char[] trimChars);"不是也把意思表达啦。VB.NET是a.Trim(trimChars As char())。
      • 呵呵,再看了一下,明白你的意思。
      • C++里有个...
    • LINQ has been dead for while,难道还有人用吗?