Question Issue With Looping Through an Array

digitaldrew

Well-known member
Joined
Nov 10, 2012
Messages
151
Programming Experience
Beginner
I've got a richtextbox which will have different amounts of items in it. Sometime it may have 6, sometimes it may have 12..etc. I've taken all those items and put them into one array which we'll call Array1. What I want to do now is take 10 items from that array and put them into a separate array (Array2) which I will then take and pass within a web request.

The issue I'm having is that I always want Array2 to always have 10 items. If Array1 only has 5 then I want it to put those 5 in there twice. If it has 15 I want it to send the first request with the first 10 and the second request with the remaining 5 plus 5 from the first 10, and so on. It should constantly cycle through Array1 and pull the next 10 items, if it reaches the end then it goes and takes them from the beginning.

I've done some experimenting and if I use this code
VB.NET:
Dim Array2 = Array1.Cast(Of String).Take(10).ToArray()
I'm able to get the first 10 items, but that's it. If there are more than 10 items in the richtextbox those will never be touched.

I also tried this code
VB.NET:
Dim Array2 = Array1.Cast(Of String).Skip(10).Take(10).ToArray()
This will try and skip 10 right from the get go so if there are less than 10 items in the richtextbox it won't take anything.

If I try setting up a loop, for example:
VB.NET:
Dim i As Integer = 0

While Something > Something
Dim Array2 = Array1.Cast(Of String).Skip(i * 10).Take(10).ToArray()

'do some work here

i = i + 1
End While
This will take the first 10 no problem, but if there are only 12 items in the richtextbox then it only takes 2 during the second request and stops after that because it's hit the end of the array.

Is what I'm trying to do even possible? No matter what I can't figure out how to get one array to constantly grab 10 items from another and start at the beginning if necessary. Any help/input would be appreciated!
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,439
Location
Norway
Programming Experience
10+
The Cycle iterator posted by Nick Udell here c# - Cycle through an IEnumerable - Code Review Stack Exchange is probably the best solution, but in VB it requires version 2012 (.Net 4.5) or higher to use Iterator (Visual Basic) and Yield Statement (Visual Basic). Translation is this:
Module Extensions
    <Runtime.CompilerServices.Extension> Public Iterator Function Cycle(Of T)(list As IEnumerable(Of T), Optional index As Integer = 0) As IEnumerable(Of T)
        Dim count = list.Count()
        index = index Mod count

        Do
            Yield list.ElementAt(index)
            index = (index + 1) Mod count
        Loop
    End Function
End Module

You can then use Linq code like array.Cycle.Take(10) and array.Cycle.Skip(10).Take(10).
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,439
Location
Norway
Programming Experience
10+
Here is also an example that is not using Iterator/Yield:
        Dim data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}

        Dim c As New Cyclable(Of Integer)(data)
        Dim selection = c.Skip(10).Take(10)

The attachment contains a Cyclable class, this is just a wrapper for the also included CycleEnumerator class that enumerates cyclic. These classes implement IEnumerable(Of T) and IEnumerator(Of T) interfaces. This is the same as how the System.Linq.Enumerable class works. As you can see this is a little bit more work to achieve the same as in previous post, although most of the code is generated by the IDE for the interface requirements.

Update, the missing link is this extension:
Module Extensions
    <Runtime.CompilerServices.Extension()>
    Public Function Circular(Of T)(source As IEnumerable(Of T)) As IEnumerable(Of T)
        Return New Cyclable(Of T)(source)
    End Function
End Module

Now you can use it directly in expressions like this:
Dim selection = data.Circular.Skip(10).Take(10)
 

Attachments

  • Cyclable.zip
    663 bytes · Views: 16
Last edited:

digitaldrew

Well-known member
Joined
Nov 10, 2012
Messages
151
Programming Experience
Beginner
Thanks for the awesome replies and sample code JohnH! I'm actually using .NET Framework 4, not 4.5 so I wasn't sure if the previous example would work or not. There was another solution someone suggested on a separate board that uses uses the enumerator to loop over the array 10 times. If it reaches the end, it creates a new enumerator so it starts over at the beginning. This method would mean the array can never have zero elements in it, but I still think would work.

VB.NET:
Module Module1

    Sub Main()
        For i As Integer = 0 To 10
            Dim testArray(i) As Integer
            For j As Integer = 0 To testArray.Length - 1
                testArray(j) = j
            Next

            Console.Write("Test with {0} elements: ", i + 1)
            Dim tenItems = ForceTenItems(testArray)
            For Each item In tenItems
                Console.Write("{0}, ", item)
            Next
            Console.WriteLine()
        Next
    End Sub

    Function ForceTenItems(ByVal input() As Integer) As Integer()
        Dim output(9) As Integer

        Dim enumerator = input.GetEnumerator()
        For i As Integer = 0 To 9
            If Not enumerator.MoveNext() Then
                enumerator = input.GetEnumerator()
                enumerator.MoveNext()
            End If

            output(i) = CInt(enumerator.Current)
        Next

        Return output
    End Function

End Module

But, I think your second example is probably more efficient. I might give both a try.. Thanks again JohnH!
 

digitaldrew

Well-known member
Joined
Nov 10, 2012
Messages
151
Programming Experience
Beginner
Here is also an example that is not using Iterator/Yield:
        Dim data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}

        Dim c As New Cyclable(Of Integer)(data)
        Dim selection = c.Skip(10).Take(10)

The attachment contains a Cyclable class, this is just a wrapper for the also included CycleEnumerator class that enumerates cyclic. These classes implement IEnumerable(Of T) and IEnumerator(Of T) interfaces. This is the same as how the System.Linq.Enumerable class works. As you can see this is a little bit more work to achieve the same as in previous post, although most of the code is generated by the IDE for the interface requirements.

Was just testing this code JohnH, but it leads me to another error.

After I create the array with 10 items I'm trying to put them into a string. The string will contain <sometext>Array Item</sometext>...I need to format it this way before I send it via web request. I was previously doing that with this code
VB.NET:
Dim s As String = String.Concat(Enumerable.Range(0, Array1.Count).Select(Function(x) "<sometext>" & page(x).ToArray & "</sometext>"))

So, with your code I was trying..
VB.NET:
Dim c As New Cyclable(Of Integer)(Array1)
Dim selection = c.Skip(10).Take(10)
Dim pageCnt As Integer = selection.Count

Dim s As String = String.Concat(Enumerable.Range(0, selection.Count).Select(Function(x) "<sometext>" & page(x).ToArray & "</sometext>"))

But when trying to do the above, I get an error:
Overload resolution failed because no accessible 'Select' can be called with these arguments.
 

digitaldrew

Well-known member
Joined
Nov 10, 2012
Messages
151
Programming Experience
Beginner
In your second code block you use selection.Count instead of array1.Count, so nothing is really different there. The return value is of course 10 since you used Take(10).

it doesn't seam to matter whether I use my main array, or the selection array:
image.png
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,439
Location
Norway
Programming Experience
10+
I'm actually using .NET Framework 4, not 4.5
I noticed that from you forum profile, but many users don't update this information, and that is also why I explained version requirement for post 2.
By the way I updated the other solution in post 3 with an extension that makes its usage more like Linq.

Also a note about your code indexing "selection(x)", when selection is a Linq query that will make it execute each time you index it which is unefficient. In such cases you should cache the query result by for example converting it to an array first:
Dim selection = Array1.Circular.Skip(10).Take(10).ToArray

Now when you index selection(x) it will just get an array element and not execute the Linq query again. This is explained in this article: Query Execution
 
Top Bottom