I\'m looking for a functional (as in, non-imperative) implementation of StringBuilder or equivalent. I\'ve seen a couple of functional arrays implementation, but they don\'t
StringBuilder
s advantage over string
is due to minimizing allocations. It pre-allocates a buffer to avoid allocating for every insert/append. This requires mutability--some object must own (and mutate) the buffer.
Incidentally, System.String
already fits (what I can make of) your description: it's immutable and supports concatenation, insertionMSDN, and removalMSDN.
Tomas' idea intrigued me. Taking his idea, here's what I came up with
type StringBuilder =
private
| Empty
| StringBuilder of int * string * int * StringBuilder
member this.Length =
match this with
| Empty -> 0
| StringBuilder(_, _, n, _) -> n
override this.ToString() =
let rec rev acc = function
| Empty -> acc
| StringBuilder(idx, str, _, bldr) -> rev ((idx, str)::acc) bldr
let buf = ResizeArray(this.Length)
for idx, str in rev [] this do buf.InsertRange(idx, str)
System.String(buf.ToArray())
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
[<RequireQualifiedAccess>]
module StringBuilder =
let empty = Empty
let length (bldr:StringBuilder) = bldr.Length
let insert index str bldr =
if index < 0 || index > (length bldr) then invalidArg "index" "out of range"
StringBuilder(index, str, str.Length + bldr.Length, bldr)
let create str = insert 0 str empty
let append str bldr = insert (length bldr) str bldr
let remove index count (bldr:StringBuilder) = create <| bldr.ToString().Remove(index, count)
Usage
let bldr =
StringBuilder.create "abcdef"
|> StringBuilder.insert 1 "xyz"
|> StringBuilder.append "123"
|> StringBuilder.remove 1 2
bldr.ToString() //azbcdef123
It's persistent and insertion is O(1).
I don't know about any implementation that would do exactly what you want. However, I don't think you can ever get O(1) complexity of inserting (at arbitrary index) and O(n) complexity of iteration over the results.
If you're happy to sacrifice the complexity of insertion, then you can use just string
as Daniel suggests. On the other side, if you're willing to sacrifice the complexity of toString
, then you can make immutable data structure with O(1) insertion at any location by using a list of strings and indices:
type InsertList = IL of (int * string) list
// Insert string 'str' at the specified index
let insertAt idx str (IL items) = IL (idx, str)::items
// Create insert list from a string
let ofString str = IL [str]
The conversion to string is a bit more tricky. However, I think you can get O(n log n) complexity by using a mutable LinkedList
and inserting individual characters at the right location by iterating over the insertions from the end. The use of LinkedList
is going to be locallized to toString
, so the data structure is still purely functional.