Nate Glenn
Nate Glenn

Reputation: 6744

list of subarrays of an array

I am trying to implement a function stride n a where n is a stride length and a is an array. Given a call like stride 2 [| "a"; "b"; "c"; "d" |] it should return something like [ [|"a"; "b"|]; [|"c"; "d" |] ]. I am brand new to F# and don't know anything about using arrays in a functional language. I know what I've written is garbage, but it's a start:

let stride n (a : array 'a) =
    let rec r ind =
        if ind >= a.length()
        then
            []
        else 
            a[ind .. (ind + n - 1)]::r(ind + n)
    in
    r 0

(see also on dotnetfiddle). This does not compile. I to added the array 'a type parameter because the compiler couldn't find the length method, but this type parameter does not appear to be allowed.

For context, I am trying to get groups of letters from a string, so I plan to call this like stride 2 myString.ToCharArray().

Upvotes: 1

Views: 109

Answers (1)

Fyodor Soikin
Fyodor Soikin

Reputation: 80805

First of all, there is already an app for that - it's called Array.chunkBySize:

> Array.chunkBySize 2 [|1;2;3;4;5;6;7|]
val it : int [] [] = [|[|1; 2|]; [|3; 4|]; [|5; 6|]; [|7|]|]

And there are similar functions in the Seq and List module, so if your goal is to work with strings, I would consider using the Seq variant, since string already implements the seq interface:

> Seq.chunkBySize 2 "abcdefg";;
val it : seq<char []> =
  seq [[|'a'; 'b'|]; [|'c'; 'd'|]; [|'e'; 'f'|]; [|'g'|]]

But if you're interested in education rather than GSD, then here it is:

The logic of the code is fine, except you have a few purely syntactic mistakes and one logical.

First, the type "array of a" is not denoted array 'a. In general the F# notation for generic types is either T<'a> or 'a T, for example list<int> or int list. However, this doesn't work for arrays, because arrays are special. There is a type System.Array, but it's not generic, so you can't use it like that. Instead, the idea of an array is kind of baked into the CLR, so you have to use the special syntax, which is 'a[].

So: a : 'a[] instead of a : array 'a

Second while array does have a length property, it's capitalized (i.e. Length) and it's a property, not a method, so there shouldn't be parens after it.

So: a.Length instead of a.length()

However, that is not quite the F# way. Methods and properties are meh, functions are way better. The F# way would be to use the Array.length function.

So: Array.length a instead of a.length()

Bonus: if you do that, there is no need for the type annotation : 'a[], because the compiler can now figure it out from the type of Array.length.

Third, indexing of arrays, lists, and everything else that has an index, needs a dot before the opening bracket.

So: a.[ind .. (ind + n - 1)] instead of a[ind .. (ind + n - 1)]

Fourth, the in is not necessary in F#. You can just omit it.

With the above modifications, your program will work. But only on arrays whose length is a multiple of n. On all others you'll get an IndexOutOfRangeException. This is because you also have...

The logical mistake is that while you're checking that ind is within the array bounds, you're not checking that ind + n - 1 is as well. So you need a third case in your branch:

if ind >= Array.length a then
    []
elif ind + n - 1 >= Array.length a then
    a.[ind..] :: r (ind+n)
else
    a.[ind .. (ind+n-1)] :: r (ind+n)

Now this is ready for prime time.

Upvotes: 3

Related Questions