Что правильный путь состоит в том, чтобы сделать изменяемые структуры данных (например, пропустите списки, вывихните деревья) в F#?

Что хороший путь состоит в том, чтобы реализовать изменяемые структуры данных в F#? Причина, которую я спрашиваю, состоит в том, потому что я хочу возвратиться и реализовать структуры данных, о которых я узнал в уроке алгоритмов, который я посещал в этом семестре (пропустите списки, вывихните деревья, деревья сплава, y-fast попытки, деревья van Emde Boas, и т.д.), который был чистым курсом теории без кодирования вообще, и я полагаю, что мог бы также попытаться изучить F#, в то время как я делаю его. Я знаю, что "должен" использовать деревья пальца для получения косой древовидной функциональности на функциональном языке, и что я должен сделать что-то с ленью для получения функциональности списка пропуска, и т.д., но я хочу закрепить основы, прежде чем я попытаюсь играть с чисто функциональными реализациями.

Существует много примеров того, как сделать функциональные структуры данных в F#, но нет очень о том, как сделать изменяемые структуры данных, таким образом, я запустил путем согласовывания двунаправленного связанного списка здесь во что-то, что позволяет, вставляет и удаляет где угодно. Мой план состоит в том, чтобы превратить это в список пропуска и затем использовать подобную структуру (дизъюнктное объединение записи) для древовидных структур, которые я хочу реализовать. Прежде чем я запущу на чем-то более существенном, существует ли лучший способ сделать изменяемые структуры как это в F#? Я должен просто использовать записи и не обеспокоиться дизъюнктным объединением? Я должен использовать класс вместо этого? Этот вопрос "даже неправильно"? Я должен делать изменяемые структуры в C# и не падение в F#, пока я не хочу сравнить их с их чисто функциональными дубликатами?

И, если DU записей - то, что я хочу, я, возможно, написал код ниже лучше или более идиоматически? Кажется, что существует большое дублирование здесь, но я не уверен, как избавиться от него.

module DoublyLinkedList =
    type 'a ll  = 
        | None
        | Node of 'a ll_node
    and 'a ll_node = {
        mutable Prev: 'a ll;
        Element : 'a ;
        mutable Next: 'a ll;
    }

    let insert x l = 
        match l with 
        | None -> Node({ Prev=None; Element=x; Next=None })
        | Node(node) ->
            match node.Prev with
                | None -> 
                    let new_node = { Prev=None; Element=x; Next=Node(node)}
                    node.Prev <- Node(new_node)
                    Node(new_node)
                | Node(prev_node) -> 
                    let new_node = { Prev=node.Prev; Element=x; Next=Node(node)}
                    node.Prev <- Node(new_node)
                    prev_node.Next <- Node(new_node)
                    Node(prev_node)

    let rec nth n l =
        match n, l with
        | _,None -> None
        | _,Node(node) when n > 0 -> nth (n-1) node.Next 
        | _,Node(node) when n < 0 -> nth (n+1) node.Prev 
        | _,Node(node) -> Node(node) //hopefully only when n = 0 :-)

    let rec printLinkedList head = 
        match head with
        | None -> ()
        | Node(x) -> 
            let prev = match x.Prev with
                        | None -> "-"
                        | Node(y) -> y.Element.ToString()
            let cur = x.Element.ToString()
            let next = match x.Next with
                        | None -> "-"
                        | Node(y) -> y.Element.ToString()
            printfn "%s, <- %s -> %s" prev cur next
            printLinkedList x.Next

11
задан dan 8 May 2010 в 20:45
поделиться

2 ответа

Я думаю, что использование размеченного объединения вместе с изменяемой записью - хороший подход. Дискриминационные союзы необходимы для сопоставления с образцом. Альтернативой использованию изменяемой записи может быть создание случая объединения с изменяемыми ссылочными ячейками :

// Note: using ´ instead of ' to avoid StackOverflow syntax confusion
type LinkedList<´T> =  
  | None 
  | Node of (LinkedList<´T> ref) * 'T * (LinkedList<´T> ref)

Это может привести к немного более простому коду. Например, функция insert будет выглядеть так (я не пробовал, но думаю, что это должно быть правильно):

let insert x l =  
  match l with  
  | None -> Node(ref None, x, ref None)
  | Node(prev, v, next) as node -> 
      match !prev with 
      | None ->  
         prev := Node(ref None, x, ref node) 
         !prev
      | Node(_, _, prevNextRef) ->  
         prevNextRef := Node(ref (!node.Prev), x, ref node)
         prev := !prevNextRef
         !prevNextRef

Однако я не думаю, что это делает код более лаконичным ( может быть, даже чуть менее читабельный). В любом случае вы можете определить активный шаблон, чтобы различать три случая в функции insert , используя одно выражение match . Ниже приводится исходная структура данных. Это просто экстрактор элементов, хранящихся в записи:

let (|NodeInfo|) node = (node.Prev, node.Element, node.Next)

Для получения дополнительной информации см. Активные шаблоны в MSDN . Тогда функция insert будет выглядеть так:

let insert x l =  
    match l with  
    | None -> Node({ Prev=None; Element=x; Next=None }) 
    | Node(NodeInfo(None, _, _) as node) ->
        let new_node = { Prev=None; Element=x; Next=Node(node)} 
        node.Prev <- Node(new_node) 
        Node(new_node) 
    | Node(NodeInfo(Node(prev_node), _, _) as node) ->            
        let new_node = { Prev=node.Prev; Element=x; Next=Node(node)} 
        node.Prev <- Node(new_node) 
        prev_node.Next <- Node(new_node) 
        Node(prev_node) 

[EDIT ] На самом деле то же самое можно было бы сделать, используя шаблоны для извлечения элементов записи, но я думаю, что активные шаблоны могут сделать код немного лучше.

6
ответ дан 3 December 2019 в 10:25
поделиться

Причина, по которой я спрашиваю, заключается в том, что я хочу вернуться и реализовать структуры данных, о которых я узнал в классе алгоритмов, который я посещал в этом семестре (списки пропусков, деревья сплайнов, деревья слияния, y-fast tries, деревья ван Эмде-Боаса и т.д.), который был чисто теоретическим курсом без какого-либо кодирования, и я подумал, что я мог бы также попытаться выучить F#, пока я это делаю.

Если это образовательное упражнение, то вы вполне можете обнаружить, что некоторые структуры данных (такие как списки пропусков) проще и познавательнее реализовать в их чисто функциональной форме. Окасаки является отличной ссылкой для элегантных чисто функциональных структур данных и очень познавателен (но имеет мало практической пользы).

Прежде чем я приступлю к чему-то более существенному, есть ли лучший способ сделать изменяемые структуры, подобные этой, в F#?

Вы работаете в правильном направлении. Если вам нужны примеры, посмотрите на реализацию mutable heap, написанную на OCaml Жаном-Кристофом Филлиатре. Есть много других замечательных примеров простых и эффективных структур данных с изменяемыми параметрами, использующих функциональное программирование в своих API, которые написаны на OCaml.

Должен ли я просто использовать записи и не беспокоиться о дискриминированном объединении?

В случае с изменяемыми структурами данных их части часто хранятся смежно в массиве. Именно поэтому они часто намного эффективнее своих чисто функциональных аналогов.

Должен ли я делать изменяемые структуры на C# и не погружаться в F#, пока не захочу сравнить их с чисто функциональными аналогами?

Нет. Не используйте F# так, как будто это чисто функциональный язык программирования. Весь смысл чисто функциональных языков (и причина их большей популярности) в том, что мутация полезна и ее не всегда следует избегать. Массивы и хэш-таблицы - отличные структуры данных. Мутирующие структуры данных, подобные тем, которые вы перечислили, также могут быть полезны.

3
ответ дан 3 December 2019 в 10:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: