LINQ gotcha I ran into
-
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)
Nextnumbers.RemoveAll(Function(x) x = 0)
numbers.Sort()
numbers.AddRange(extract)
Console.WriteLine("After")
For Each e In numbers
Console.WriteLine(e)
NextBut 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)
Nextnumbers.RemoveAll(Function(x) x = 0)
numbers.Sort()
numbers.AddRange(extract)
Console.WriteLine("After")
For Each e In numbers
Console.WriteLine(e)
Nexti.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
-
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)
Nextnumbers.RemoveAll(Function(x) x = 0)
numbers.Sort()
numbers.AddRange(extract)
Console.WriteLine("After")
For Each e In numbers
Console.WriteLine(e)
NextBut 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)
Nextnumbers.RemoveAll(Function(x) x = 0)
numbers.Sort()
numbers.AddRange(extract)
Console.WriteLine("After")
For Each e In numbers
Console.WriteLine(e)
Nexti.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
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
-
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
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
-
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
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
-
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)
Nextnumbers.RemoveAll(Function(x) x = 0)
numbers.Sort()
numbers.AddRange(extract)
Console.WriteLine("After")
For Each e In numbers
Console.WriteLine(e)
NextBut 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)
Nextnumbers.RemoveAll(Function(x) x = 0)
numbers.Sort()
numbers.AddRange(extract)
Console.WriteLine("After")
For Each e In numbers
Console.WriteLine(e)
Nexti.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
-
Fun, but I would just use Sort with a comparer anonymous method/delegate that sort zero above all other values..
-
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)
Nextnumbers.RemoveAll(Function(x) x = 0)
numbers.Sort()
numbers.AddRange(extract)
Console.WriteLine("After")
For Each e In numbers
Console.WriteLine(e)
NextBut 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)
Nextnumbers.RemoveAll(Function(x) x = 0)
numbers.Sort()
numbers.AddRange(extract)
Console.WriteLine("After")
For Each e In numbers
Console.WriteLine(e)
Nexti.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
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.
-
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
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?
-
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?
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
-
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)
Nextnumbers.RemoveAll(Function(x) x = 0)
numbers.Sort()
numbers.AddRange(extract)
Console.WriteLine("After")
For Each e In numbers
Console.WriteLine(e)
NextBut 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)
Nextnumbers.RemoveAll(Function(x) x = 0)
numbers.Sort()
numbers.AddRange(extract)
Console.WriteLine("After")
For Each e In numbers
Console.WriteLine(e)
Nexti.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
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.
-
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.
Yeah, I remember coming across that a while back but didn't really take it in.
-
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)
Nextnumbers.RemoveAll(Function(x) x = 0)
numbers.Sort()
numbers.AddRange(extract)
Console.WriteLine("After")
For Each e In numbers
Console.WriteLine(e)
NextBut 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)
Nextnumbers.RemoveAll(Function(x) x = 0)
numbers.Sort()
numbers.AddRange(extract)
Console.WriteLine("After")
For Each e In numbers
Console.WriteLine(e)
Nexti.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
I've thought we shouldn't post programming questions here :rolleyes:
-
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.