views:

85

answers:

2

I have a question. I have a class of Cars that I need to display in a simpli-ish string if they will be sold or not base on their number.

So like this:

Public Class Car
    Property Index As Integer
    Property Sell As Boolean
End Class
Public Class Cars
    Property Vehicles As New List(Of Car) From {
                    {New Car With {.Index = 1, .Sell = True}},
                    {New Car With {.Index = 2, .Sell = False}},
                    {New Car With {.Index = 3, .Sell = True}},
                    {New Car With {.Index = 4, .Sell = True}},
                    {New Car With {.Index = 5, .Sell = True}},
                    {New Car With {.Index = 6, .Sell = False}},
                    {New Car With {.Index = 7, .Sell = True}},
                    {New Car With {.Index = 8, .Sell = True}},
                    {New Car With {.Index = 9, .Sell = False}},
                    {New Car With {.Index = 10, .Sell = False}},
                    {New Car With {.Index = 11, .Sell = True}}}
End Class

I'd like to display a simple string like this: Cars to be sold: 1, 3-5, 7-8, 11, which is based of the .Sell value.

Is there some kind of heuristic to create this kind of string in .NET or is it just a bunch of for/each and if/then and redimming of arrays?

A: 

I would do this:

Dim list_sold = Vehicles.Where(Function(x As Car) x.Sell = True)
Dim list_index = list_sold.Select(Function(x As Car) x.Index.ToString())

Console.WriteLine("Cars to be sold: {0}", String.Join(", ", list_index))
jalexiou
This gives a comma separated output but doesn't group the neighboring indices together.
Ahmad Mageed
I see that now. I did not notice originally the 3-5 grouping.
jalexiou
+4  A: 

LINQ would definitely simplify the solution. Without LINQ you could use a StringBuilder and iterate over the list while comparing neighboring items and build up the string. That would be more ideal rather than redimming arrays and such.

Here's a LINQ solution:

Dim query = vehicles.Where(Function(c) c.Sell) _
                    .OrderBy(Function(c) c.Index) _
                    .Select(Function(c, i) New With { .Car = c, .Diff = c.Index - vehicles(i).Index }) _
                    .GroupBy(Function(item) item.Diff) _
                    .Select(Function(item) item.First().Car.Index.ToString() &
                        If(item.Count() > 1, "-" & item.Last().Car.Index.ToString(), ""))

Console.WriteLine("Cars to be sold: " & String.Join(", ", query.ToArray()))

With .NET 4 you can drop the ToArray() call since String.Join has an overload that accepts an IEnumerable<T>.

The code filters out cars with a Sell value of True. It is important for the list to be in order of Car.Index for this to work properly, hence the OrderBy. The logic behind determining consecutive items is to compare neighboring items and group them based on their index difference. If there's a difference of 1 then they are neighbors. So the second Select projects into an anonymous type that stores the Car and the Diff based on the current index minus the previous car's index. The GroupBy groups all the differences together. The last Select builds the range. If the Count is greater than 1 we put a dash between the first and last group items. Otherwise one item exists and we select it as it is. Finally we use String.Join to return the list of values separated by commas.

Ahmad Mageed
I'm impressed, this is really clean and concise. Linq is definetely the way to go. Thanks so much!
WinnerWinnerChickenDinner