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 3 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 Offline
    K Offline
    Kevin McFarlane
    wrote on last edited by
    #1

    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 L L C M 5 Replies 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

      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