Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. Other Discussions
  3. Clever Code
  4. LINQ gotcha I ran into

LINQ gotcha I ran into

Scheduled Pinned Locked Moved Clever Code
helpcsharplinqannouncement
13 Posts 8 Posters 4 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • K Kevin McFarlane

    This is a simplified version of a real problem I had at work on Friday. I have these numbers 5, 4, 12, 0, 8, 5, 14, 0, 37 I want to arrange them thus 4, 5, 5, 8, 12, 14, 37, 0, 0 i.e., numbers in ascending order but with 0s moved to end. Initially tried this

    Dim data() As Integer = {5, 4, 12, 0, 8, 5, 14, 0, 37}
    Dim numbers As New List(Of Integer)(data)
    Console.WriteLine("Before")
    For Each e In numbers
    Console.WriteLine(e)
    Next

    ' Want to get 5, 4, 12, 8, 5, 14, 37, 0, 0

    Dim extract = numbers.Where(Function(x) x = 0)
    Console.WriteLine("Extract")
    For Each e In extract
    Console.WriteLine(e)
    Next

    numbers.RemoveAll(Function(x) x = 0)
    numbers.Sort()
    numbers.AddRange(extract)
    Console.WriteLine("After")
    For Each e In numbers
    Console.WriteLine(e)
    Next

    But results were unexpected Before 5 4 12 0 8 5 14 0 37 Extract 0 0 After 4 5 5 8 12 14 37 i.e., extract is empty after numbers.RemoveAll. What I needed to do was this

    Dim data() As Integer = {5, 4, 12, 0, 8, 5, 14, 0, 37}
    Dim numbers As New List(Of Integer)(data)
    Console.WriteLine("Before")
    For Each e In numbers
    Console.WriteLine(e)
    Next

    ' Want to get 5, 4, 12, 8, 5, 14, 37, 0, 0

    Dim extract = numbers.Where(Function(x) x = 0).ToList()
    Console.WriteLine("Extract")
    For Each e In extract
    Console.WriteLine(e)
    Next

    numbers.RemoveAll(Function(x) x = 0)
    numbers.Sort()
    numbers.AddRange(extract)
    Console.WriteLine("After")
    For Each e In numbers
    Console.WriteLine(e)
    Next

    i.e., first take a copy of extract as a list before appending. New results Before 5 4 12 0 8 5 14 0 37 Extract 0 0 After 4 5 5 8 12 14 37 0 0 :-O Now tomorrow I should be able to fix my real code. Btw, if anyone has a more elegant solution to this feel free to offer it. I'm always willing to learn. :) Kevin

    Kevin

    E Offline
    E Offline
    Ed Poore
    wrote on last edited by
    #2

    Not in Visual Basic .NET but a more elegant solution using LINQ I would think is:

    int[] data = new int[] { 5, 4, 12, 0, 8, 5, 14, 0, 37 };
    var ordered = data.OrderBy(k => k);

    var nonZero = ordered.SkipWhile((value, index) => (value == 0));
    var zeroes = ordered.SkipWhile((value, index) => (value == 0));
    var finished = nonZero.Concat(zeroes);

    Or for a more compact version:

    var finished = ordered.SkipWhile((v, i) => (v == 0)).Concat(ordered.TakeWhile((v, i) => (v == 0)));


    I doubt it. If it isn't intuitive then we need to fix it. - Chris Maunder

    K S 2 Replies Last reply
    0
    • E Ed Poore

      Not in Visual Basic .NET but a more elegant solution using LINQ I would think is:

      int[] data = new int[] { 5, 4, 12, 0, 8, 5, 14, 0, 37 };
      var ordered = data.OrderBy(k => k);

      var nonZero = ordered.SkipWhile((value, index) => (value == 0));
      var zeroes = ordered.SkipWhile((value, index) => (value == 0));
      var finished = nonZero.Concat(zeroes);

      Or for a more compact version:

      var finished = ordered.SkipWhile((v, i) => (v == 0)).Concat(ordered.TakeWhile((v, i) => (v == 0)));


      I doubt it. If it isn't intuitive then we need to fix it. - Chris Maunder

      K Offline
      K Offline
      Kevin McFarlane
      wrote on last edited by
      #3

      Slick! :) Btw, in the first version you have a typo. The second SkipWhile should be TakeWhile. I translated this to VB and it works fine. I normally do C# but I'm currently in a short contract using VB. Some things seem not to carry across - or I haven't figured out how. In my real code I'll have to do this with objects, not numbers and the ordering will be by dates and times.

      Kevin

      E 1 Reply Last reply
      0
      • K Kevin McFarlane

        Slick! :) Btw, in the first version you have a typo. The second SkipWhile should be TakeWhile. I translated this to VB and it works fine. I normally do C# but I'm currently in a short contract using VB. Some things seem not to carry across - or I haven't figured out how. In my real code I'll have to do this with objects, not numbers and the ordering will be by dates and times.

        Kevin

        E Offline
        E Offline
        Ed Poore
        wrote on last edited by
        #4

        Kevin McFarlane wrote:

        in the first version you have a typo

        Oops, I switched them around to make more sense with what I'd called the variables but obviously missed one part of it. I'm sure there's an even better way but best I can think of at the moment.


        I doubt it. If it isn't intuitive then we need to fix it. - Chris Maunder

        1 Reply Last reply
        0
        • K Kevin McFarlane

          This is a simplified version of a real problem I had at work on Friday. I have these numbers 5, 4, 12, 0, 8, 5, 14, 0, 37 I want to arrange them thus 4, 5, 5, 8, 12, 14, 37, 0, 0 i.e., numbers in ascending order but with 0s moved to end. Initially tried this

          Dim data() As Integer = {5, 4, 12, 0, 8, 5, 14, 0, 37}
          Dim numbers As New List(Of Integer)(data)
          Console.WriteLine("Before")
          For Each e In numbers
          Console.WriteLine(e)
          Next

          ' Want to get 5, 4, 12, 8, 5, 14, 37, 0, 0

          Dim extract = numbers.Where(Function(x) x = 0)
          Console.WriteLine("Extract")
          For Each e In extract
          Console.WriteLine(e)
          Next

          numbers.RemoveAll(Function(x) x = 0)
          numbers.Sort()
          numbers.AddRange(extract)
          Console.WriteLine("After")
          For Each e In numbers
          Console.WriteLine(e)
          Next

          But results were unexpected Before 5 4 12 0 8 5 14 0 37 Extract 0 0 After 4 5 5 8 12 14 37 i.e., extract is empty after numbers.RemoveAll. What I needed to do was this

          Dim data() As Integer = {5, 4, 12, 0, 8, 5, 14, 0, 37}
          Dim numbers As New List(Of Integer)(data)
          Console.WriteLine("Before")
          For Each e In numbers
          Console.WriteLine(e)
          Next

          ' Want to get 5, 4, 12, 8, 5, 14, 37, 0, 0

          Dim extract = numbers.Where(Function(x) x = 0).ToList()
          Console.WriteLine("Extract")
          For Each e In extract
          Console.WriteLine(e)
          Next

          numbers.RemoveAll(Function(x) x = 0)
          numbers.Sort()
          numbers.AddRange(extract)
          Console.WriteLine("After")
          For Each e In numbers
          Console.WriteLine(e)
          Next

          i.e., first take a copy of extract as a list before appending. New results Before 5 4 12 0 8 5 14 0 37 Extract 0 0 After 4 5 5 8 12 14 37 0 0 :-O Now tomorrow I should be able to fix my real code. Btw, if anyone has a more elegant solution to this feel free to offer it. I'm always willing to learn. :) Kevin

          Kevin

          L Offline
          L Offline
          Lost User
          wrote on last edited by
          #5

          Fun, but I would just use Sort with a comparer anonymous method/delegate that sort zero above all other values..

          L 1 Reply Last reply
          0
          • L Lost User

            Fun, but I would just use Sort with a comparer anonymous method/delegate that sort zero above all other values..

            L Offline
            L Offline
            Lost User
            wrote on last edited by
            #6

            Yeah, that's what I'd also do. Makes the code more readable than the LINQ-Version.

            1 Reply Last reply
            0
            • K Kevin McFarlane

              This is a simplified version of a real problem I had at work on Friday. I have these numbers 5, 4, 12, 0, 8, 5, 14, 0, 37 I want to arrange them thus 4, 5, 5, 8, 12, 14, 37, 0, 0 i.e., numbers in ascending order but with 0s moved to end. Initially tried this

              Dim data() As Integer = {5, 4, 12, 0, 8, 5, 14, 0, 37}
              Dim numbers As New List(Of Integer)(data)
              Console.WriteLine("Before")
              For Each e In numbers
              Console.WriteLine(e)
              Next

              ' Want to get 5, 4, 12, 8, 5, 14, 37, 0, 0

              Dim extract = numbers.Where(Function(x) x = 0)
              Console.WriteLine("Extract")
              For Each e In extract
              Console.WriteLine(e)
              Next

              numbers.RemoveAll(Function(x) x = 0)
              numbers.Sort()
              numbers.AddRange(extract)
              Console.WriteLine("After")
              For Each e In numbers
              Console.WriteLine(e)
              Next

              But results were unexpected Before 5 4 12 0 8 5 14 0 37 Extract 0 0 After 4 5 5 8 12 14 37 i.e., extract is empty after numbers.RemoveAll. What I needed to do was this

              Dim data() As Integer = {5, 4, 12, 0, 8, 5, 14, 0, 37}
              Dim numbers As New List(Of Integer)(data)
              Console.WriteLine("Before")
              For Each e In numbers
              Console.WriteLine(e)
              Next

              ' Want to get 5, 4, 12, 8, 5, 14, 37, 0, 0

              Dim extract = numbers.Where(Function(x) x = 0).ToList()
              Console.WriteLine("Extract")
              For Each e In extract
              Console.WriteLine(e)
              Next

              numbers.RemoveAll(Function(x) x = 0)
              numbers.Sort()
              numbers.AddRange(extract)
              Console.WriteLine("After")
              For Each e In numbers
              Console.WriteLine(e)
              Next

              i.e., first take a copy of extract as a list before appending. New results Before 5 4 12 0 8 5 14 0 37 Extract 0 0 After 4 5 5 8 12 14 37 0 0 :-O Now tomorrow I should be able to fix my real code. Btw, if anyone has a more elegant solution to this feel free to offer it. I'm always willing to learn. :) Kevin

              Kevin

              L Offline
              L Offline
              Lutoslaw
              wrote on last edited by
              #7

              As Harold has suggested, it's better to just sort it using a simple comrasion formula defined as below:

              (a, b) => a == 0 ? 1 : a - b

              There is no ?: in VB, so

              if (a == 0)
              return 1;
              else
              return a - b;

              Array.Sort(numbers, (a, b) => a == 0 ? 1 : a - b);

              In the Real World:

              Array.Sort(data, (a, b) => a.Equals(ZERO_DEFINITION) ? 1 : a.CompareTo(b);

              EDIT: Looking at your code, It seems that a VB version would be somthing like his:

              Array.Sort(numbers,
              Function(a, b)
              If a == 0 Then
              return 1
              else
              return a - b);

              Greetings - Gajatko Portable.NET is part of DotGNU, a project to build a complete Free Software replacement for .NET - a system that truly belongs to the developers.

              A 1 Reply Last reply
              0
              • E Ed Poore

                Not in Visual Basic .NET but a more elegant solution using LINQ I would think is:

                int[] data = new int[] { 5, 4, 12, 0, 8, 5, 14, 0, 37 };
                var ordered = data.OrderBy(k => k);

                var nonZero = ordered.SkipWhile((value, index) => (value == 0));
                var zeroes = ordered.SkipWhile((value, index) => (value == 0));
                var finished = nonZero.Concat(zeroes);

                Or for a more compact version:

                var finished = ordered.SkipWhile((v, i) => (v == 0)).Concat(ordered.TakeWhile((v, i) => (v == 0)));


                I doubt it. If it isn't intuitive then we need to fix it. - Chris Maunder

                S Offline
                S Offline
                singh iz king
                wrote on last edited by
                #8

                What about ?

                var finished = ordered.Where(n => n != 0).Concat(ordered.Where(n => n == 0))

                What is the benefit of using SkipWhile and TakeWhile over just using the Where clause?

                E 1 Reply Last reply
                0
                • S singh iz king

                  What about ?

                  var finished = ordered.Where(n => n != 0).Concat(ordered.Where(n => n == 0))

                  What is the benefit of using SkipWhile and TakeWhile over just using the Where clause?

                  E Offline
                  E Offline
                  Ed Poore
                  wrote on last edited by
                  #9

                  I would assume that the SkipWhile and TakeWhile will be a bit faster because Where is going to compare every single element in the array. Using TakeWhile it'll compare n+1 where n is the number of 0s. Then SkipWhile will compare n+1 again until it hits a non-zero and then copies everything else. Since we know that the 0s in finished will be at the beginning there's no need to compare all the elements in the array. I think anyway (I'm not that much of an expert on LINQ) :rolleyes:


                  I doubt it. If it isn't intuitive then we need to fix it. - Chris Maunder

                  1 Reply Last reply
                  0
                  • K Kevin McFarlane

                    This is a simplified version of a real problem I had at work on Friday. I have these numbers 5, 4, 12, 0, 8, 5, 14, 0, 37 I want to arrange them thus 4, 5, 5, 8, 12, 14, 37, 0, 0 i.e., numbers in ascending order but with 0s moved to end. Initially tried this

                    Dim data() As Integer = {5, 4, 12, 0, 8, 5, 14, 0, 37}
                    Dim numbers As New List(Of Integer)(data)
                    Console.WriteLine("Before")
                    For Each e In numbers
                    Console.WriteLine(e)
                    Next

                    ' Want to get 5, 4, 12, 8, 5, 14, 37, 0, 0

                    Dim extract = numbers.Where(Function(x) x = 0)
                    Console.WriteLine("Extract")
                    For Each e In extract
                    Console.WriteLine(e)
                    Next

                    numbers.RemoveAll(Function(x) x = 0)
                    numbers.Sort()
                    numbers.AddRange(extract)
                    Console.WriteLine("After")
                    For Each e In numbers
                    Console.WriteLine(e)
                    Next

                    But results were unexpected Before 5 4 12 0 8 5 14 0 37 Extract 0 0 After 4 5 5 8 12 14 37 i.e., extract is empty after numbers.RemoveAll. What I needed to do was this

                    Dim data() As Integer = {5, 4, 12, 0, 8, 5, 14, 0, 37}
                    Dim numbers As New List(Of Integer)(data)
                    Console.WriteLine("Before")
                    For Each e In numbers
                    Console.WriteLine(e)
                    Next

                    ' Want to get 5, 4, 12, 8, 5, 14, 37, 0, 0

                    Dim extract = numbers.Where(Function(x) x = 0).ToList()
                    Console.WriteLine("Extract")
                    For Each e In extract
                    Console.WriteLine(e)
                    Next

                    numbers.RemoveAll(Function(x) x = 0)
                    numbers.Sort()
                    numbers.AddRange(extract)
                    Console.WriteLine("After")
                    For Each e In numbers
                    Console.WriteLine(e)
                    Next

                    i.e., first take a copy of extract as a list before appending. New results Before 5 4 12 0 8 5 14 0 37 Extract 0 0 After 4 5 5 8 12 14 37 0 0 :-O Now tomorrow I should be able to fix my real code. Btw, if anyone has a more elegant solution to this feel free to offer it. I'm always willing to learn. :) Kevin

                    Kevin

                    C Offline
                    C Offline
                    Charles E Wagner Jr
                    wrote on last edited by
                    #10

                    Kevin what you see as a subtle bug is a typical mistake made by programmers new to Linq that haven't read the MSDN Linq documentation(specifically Deferred Query Evaluation). The method call here causes immediate execution from within the ToList method. Dim extract = numbers.Where(Function(x) x = 0).ToList()

                    Charles E. Wagner Jr.

                    K 1 Reply Last reply
                    0
                    • C Charles E Wagner Jr

                      Kevin what you see as a subtle bug is a typical mistake made by programmers new to Linq that haven't read the MSDN Linq documentation(specifically Deferred Query Evaluation). The method call here causes immediate execution from within the ToList method. Dim extract = numbers.Where(Function(x) x = 0).ToList()

                      Charles E. Wagner Jr.

                      K Offline
                      K Offline
                      Kevin McFarlane
                      wrote on last edited by
                      #11

                      Yeah, I remember coming across that a while back but didn't really take it in.

                      1 Reply Last reply
                      0
                      • K Kevin McFarlane

                        This is a simplified version of a real problem I had at work on Friday. I have these numbers 5, 4, 12, 0, 8, 5, 14, 0, 37 I want to arrange them thus 4, 5, 5, 8, 12, 14, 37, 0, 0 i.e., numbers in ascending order but with 0s moved to end. Initially tried this

                        Dim data() As Integer = {5, 4, 12, 0, 8, 5, 14, 0, 37}
                        Dim numbers As New List(Of Integer)(data)
                        Console.WriteLine("Before")
                        For Each e In numbers
                        Console.WriteLine(e)
                        Next

                        ' Want to get 5, 4, 12, 8, 5, 14, 37, 0, 0

                        Dim extract = numbers.Where(Function(x) x = 0)
                        Console.WriteLine("Extract")
                        For Each e In extract
                        Console.WriteLine(e)
                        Next

                        numbers.RemoveAll(Function(x) x = 0)
                        numbers.Sort()
                        numbers.AddRange(extract)
                        Console.WriteLine("After")
                        For Each e In numbers
                        Console.WriteLine(e)
                        Next

                        But results were unexpected Before 5 4 12 0 8 5 14 0 37 Extract 0 0 After 4 5 5 8 12 14 37 i.e., extract is empty after numbers.RemoveAll. What I needed to do was this

                        Dim data() As Integer = {5, 4, 12, 0, 8, 5, 14, 0, 37}
                        Dim numbers As New List(Of Integer)(data)
                        Console.WriteLine("Before")
                        For Each e In numbers
                        Console.WriteLine(e)
                        Next

                        ' Want to get 5, 4, 12, 8, 5, 14, 37, 0, 0

                        Dim extract = numbers.Where(Function(x) x = 0).ToList()
                        Console.WriteLine("Extract")
                        For Each e In extract
                        Console.WriteLine(e)
                        Next

                        numbers.RemoveAll(Function(x) x = 0)
                        numbers.Sort()
                        numbers.AddRange(extract)
                        Console.WriteLine("After")
                        For Each e In numbers
                        Console.WriteLine(e)
                        Next

                        i.e., first take a copy of extract as a list before appending. New results Before 5 4 12 0 8 5 14 0 37 Extract 0 0 After 4 5 5 8 12 14 37 0 0 :-O Now tomorrow I should be able to fix my real code. Btw, if anyone has a more elegant solution to this feel free to offer it. I'm always willing to learn. :) Kevin

                        Kevin

                        M Offline
                        M Offline
                        Mohammad Dayyan
                        wrote on last edited by
                        #12

                        I've thought we shouldn't post programming questions here :rolleyes:

                        1 Reply Last reply
                        0
                        • L Lutoslaw

                          As Harold has suggested, it's better to just sort it using a simple comrasion formula defined as below:

                          (a, b) => a == 0 ? 1 : a - b

                          There is no ?: in VB, so

                          if (a == 0)
                          return 1;
                          else
                          return a - b;

                          Array.Sort(numbers, (a, b) => a == 0 ? 1 : a - b);

                          In the Real World:

                          Array.Sort(data, (a, b) => a.Equals(ZERO_DEFINITION) ? 1 : a.CompareTo(b);

                          EDIT: Looking at your code, It seems that a VB version would be somthing like his:

                          Array.Sort(numbers,
                          Function(a, b)
                          If a == 0 Then
                          return 1
                          else
                          return a - b);

                          Greetings - Gajatko Portable.NET is part of DotGNU, a project to build a complete Free Software replacement for .NET - a system that truly belongs to the developers.

                          A Offline
                          A Offline
                          Al Beback
                          wrote on last edited by
                          #13

                          gajatko wrote:

                          There is no ?: in VB, soif (a == 0) return 1;else return a - b;

                          VB has the IIf[^] function ("Inline If"), which is not short-circuited like the ternary operator, but for this simple case works just fine:

                          Array.Sort(number, Function(a, b) IIf(a = 0, 1, a - b))

                          ShamWow

                          1 Reply Last reply
                          0
                          Reply
                          • Reply as topic
                          Log in to reply
                          • Oldest to Newest
                          • Newest to Oldest
                          • Most Votes


                          • Login

                          • Don't have an account? Register

                          • Login or register to search.
                          • First post
                            Last post
                          0
                          • Categories
                          • Recent
                          • Tags
                          • Popular
                          • World
                          • Users
                          • Groups