Существует ли реализация функциональной версии .NET StringBuilder с открытым исходным кодом (не GPL)?

Я ищу функциональную (например, необязательную) реализацию StringBuilder или эквивалентную. Я видел несколько реализаций функциональных массивов, но они изначально не поддерживают вставку. Бонус за открытый исходный код, не-(L?A?)GPL, бонус за F#, но при необходимости я могу перевести с Haskell/OCaml/SML.

Предложения по алгоритмам приветствуются.


person fmr    schedule 01.12.2011    source источник
comment
Какие функции и производительность вы хотите получить от StringBuilder? O(1) вставить и O(n) в массив?   -  person gradbot    schedule 01.12.2011
comment
Я не слишком требователен к производительности, но да, в идеале O(1) при вставке и O(n) при обратном преобразовании в seq/stream/array.   -  person fmr    schedule 01.12.2011
comment
Я сомневаюсь, что вставка O (1) в произвольном месте возможна для функциональной структуры данных. Что именно не так со стандартным строковым компоновщиком?   -  person John Palmer    schedule 01.12.2011
comment
Вы уверены, что StringBuilder.Insert равен O (1)? Я думаю, что это O (n) ... или вы имели в виду добавить?   -  person Mauricio Scheffer    schedule 01.12.2011
comment
Кроме того, что именно вы подразумеваете под функциональностью? Настойчивый?   -  person Mauricio Scheffer    schedule 01.12.2011
comment
@MauricioScheffer: функциональный означает неразрушающий. Операция вставки или добавления вернет новый StringBuilder, так что указатели на старый StringBuilder по-прежнему будут иметь то же значение, что и до операции. Подумайте о разнице между словарем (деструктивным) и функциональной картой. Я хочу такого же поведения в StringBuilder.   -  person fmr    schedule 01.12.2011
comment
@JohnPalmer: мне нужно, чтобы операции были неразрушающими.   -  person fmr    schedule 01.12.2011
comment
@fmr: такие структуры данных обычно называют «постоянными», а не «функциональными» ( en.wikipedia.org/ wiki/Persistent_data_structure ), хотя они, конечно, повсеместно (но не исключительно) используются в функциональном программировании.   -  person Mauricio Scheffer    schedule 01.12.2011
comment
@fmr: можете ли вы также уточнить свои требования к сложности? Вы уверены, что хотите вставить O (1) или вы имели в виду добавление O (1)?   -  person Mauricio Scheffer    schedule 01.12.2011
comment
Immutable — это другое имя, часто используемое для недеструктивных типов данных.   -  person gradbot    schedule 01.12.2011
comment
Выполнение вставки O (1) определенно возможно (см. Мой ответ). Однако вам придется пожертвовать производительностью других операций.   -  person Tomas Petricek    schedule 02.12.2011
comment
@fmr: подумав, что это несбыточная мечта, по предложению Томаса я реализовал его как связанный список. Код в моем ответе. Я довольно много тестировал его (используя вставки 1M), и он работает достаточно хорошо. Думаю, он соответствует вашим требованиям.   -  person Daniel    schedule 02.12.2011


Ответы (2)


Преимущество StringBuilder над string связано с минимальными ассигнованиями. Он предварительно выделяет буфер, чтобы избежать выделения для каждой вставки/добавления. Это требует изменчивости — какой-то объект должен владеть буфером (и изменять его).

Кстати, System.String уже подходит (насколько я могу судить) под ваше описание: он неизменяем и поддерживает конкатенацию, вставкаMSDN и удалениеMSDN.

ОБНОВИТЬ

Идея Томаса меня заинтриговала. Принимая его идею, вот что я придумал

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)

использование

let bldr = 
  StringBuilder.create "abcdef"
  |> StringBuilder.insert 1 "xyz"
  |> StringBuilder.append "123"
  |> StringBuilder.remove 1 2

bldr.ToString() //azbcdef123

Он постоянный, а вставка - O (1).

person Community    schedule 01.12.2011
comment
Вставка на самом деле не O (1), ее O (нет вставок) из-за проверки длины, я думаю, что O для ToString также слишком низкое, как минимум, вы эффективно сортируете по idx, что сделало бы его O ( п вход) - person John Palmer; 02.12.2011
comment
Ты прав. Вы можете отказаться от проверки длины при вставке и добавить ToString, чтобы сделать его O (1). Несмотря на то, что проверка делает это O (вставки), это очень эффективно. - person Daniel; 02.12.2011
comment
Я совершенно уверен, что string.Length - это O (1). - person Daniel; 02.12.2011
comment
есть рекурсивный шаг, вы суммируете все длины, что составляет длину O (n. вставок) - person John Palmer; 02.12.2011
comment
Вы правы, это O (вставки), но поскольку string.Length равно O (1), это очень дешево. - person Daniel; 02.12.2011
comment
@JohnPalmer: я изменил его, чтобы длина кэшировалась с каждым экземпляром. Вставки теперь O (1). - person Daniel; 02.12.2011

Я не знаю ни о какой реализации, которая делала бы именно то, что вы хотите. Однако я не думаю, что вы когда-либо сможете получить сложность O (1) вставки (по произвольному индексу) и сложность O (n) итерации по результатам.

Если вы готовы пожертвовать сложностью вставки, вы можете использовать только string, как предлагает Дэниел. С другой стороны, если вы готовы пожертвовать сложностью toString, вы можете создать неизменяемую структуру данных с вставкой O(1) в любом месте, используя список строк и индексов:

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]

Преобразование в строку немного сложнее. Однако я думаю, что вы можете получить сложность O (n log n), используя изменяемый LinkedList и вставляя отдельные символы в нужное место, повторяя вставки с конца. Использование LinkedList будет локализовано до toString, так что структура данных останется чисто функциональной.

person Tomas Petricek    schedule 01.12.2011
comment
Минимальное преобразование в строку будет как минимум O (n), так как вам придется перевернуть список, чтобы иметь дело с insertAt 0 "World", затем insertAt 0 "Hello", и тогда я думаю, что лучше всего будет использовать стабильную сортировку, я подозреваю, что лучший случай может быть O (n*log n * m), где есть m вставок, так как вам приходится иметь дело с перекрытиями - person John Palmer; 02.12.2011
comment
Я попытался реализовать вашу идею и включил ее в свой ответ. Это похоже на то, что вы имели в виду? - person Daniel; 02.12.2011