3
\$\begingroup\$

I need to split a string into parts of varying predefined lengths, given as a sequence of integers. After a first imperative attempt using a sequence expression and a reference cell, I have now come up with this implementation that uses Seq.scan and doesn't need mutability:

let segmentString lengths (input : string) =
 let folder (start, parts) lengthHere = 
 let partHere = input.Substring(start, lengthHere)
 start + lengthHere, partHere :: parts
 let results = lengths |> Seq.scan folder (0, [])
 let _, resultParts = Seq.last results
 List.rev resultParts 

This works and is probably "good enough", but I wonder whether there are things that could be improved or more idiomatic (other than error handling and allowing more generic inputs than strings; I left those out because they are not a concern right now). What especially bugs me is the need to reverse the results list at the end, but obviously, I could only add the new part as the head of the aggregate list in the folder function.

200_success
145k22 gold badges190 silver badges478 bronze badges
asked Sep 7, 2014 at 23:25
\$\endgroup\$
1
  • \$\begingroup\$ What should it do when the sum of lengths is smaller than the length of input? Just ignore the rest of the input, like your code does? \$\endgroup\$ Commented Sep 13, 2014 at 16:23

2 Answers 2

4
\$\begingroup\$

This is a pretty good solution but like you say, the reverse is annoying, so let's see if we can fix that.

We want a sequence of tuples (start, length) that we can pass to Substring. We already have the lengths, so we just need to figure out the starts. The skeleton of our solution looks like this:

let segmentString lengths (input : string) =
 let segments = Seq.zip ??? lengths
 Seq.map (input.Substring : int * int -> string) segments

So where does a segment start? It starts at the sum of the previous lengths. We can use scan (+) 0 to calculate the partial sums of the lengths.

This gives us the final solution

let segmentString lengths (input : string) =
 let segments = Seq.zip (Seq.scan (+) 0 lengths) lengths
 Seq.map (input.Substring : int * int -> string) segments
answered Sep 8, 2014 at 2:20
\$\endgroup\$
1
  • \$\begingroup\$ That is the more functional solution I was looking for, thanks! \$\endgroup\$ Commented Sep 13, 2014 at 14:10
0
\$\begingroup\$

You can avoid that reversing by using manual recursion:

let segmentString lengths (input : string) =
 let rec segmentString' start = function
 | [] -> []
 | length::lengthsTail ->
 let segment = input.Substring(start, length)
 let segmentsTail = segmentString' (start + length) lengthsTail
 segment::segmentsTail
 segmentString' 0 (List.ofSeq lengths)

I'm generally not in favor of using recursion directly, most of the time, you should use higher-order functions instead. But here, that higher-order function would be a hybrid between fold and foldBack (state has to be computed front to back, but result back to front), so I think that recursion makes sense here.

answered Sep 13, 2014 at 16:47
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.