Удалить циклы из циклического / изменяемого списка в ocaml?

Я не уверен, как удалить циклы из изменяемого списка типа:

type 'a m_list = Nil | Cons of 'a * (('a m_list) ref)

Например, если бы у меня был список 3,2,2,1,2,1,2,1,..... я бы хотел получить 3,2,2,1.
Что я не могу понять, так это местоположение начальной циклической работы - у меня есть рекурсия, которая выглядит следующим образом, но я не могу понять, как обернуть это в рекурсивную функцию; очевидно, что здесь просто проверяются первые несколько терминов.

let remove list : unit =
  if is_cyclic list then match list with
    |Nil->()
    |Cons(_,v)-> match (!v) with
      |Nil->()
      |Cons(_,x)->match (!x) with
        |Nil->()
        |Cons(_,y)->match (!y) with
          |Nil->()
          |Cons(_,p) -> if is_cyclic (!p) then p:=Nil else ()

У меня есть функция is_cyclic, которая сообщает мне, есть ли у цикла m_list цикл или нет. Я хотел бы сделать это либо разрушительно (обновление ссылки), либо неразрушающе (создание нового списка).

Спасибо!

2 ответа

Основываясь на ответе Паскаля Куока на ваш предыдущий вопрос, вы можете попробовать что-то вроде этого:

let rec recurse list already_visited =
  match list with
    Nil -> ()
  | Cons(h, t) -> 
    if List.memq !t already_visited
    then t := Nil          
    else recurse !t (t :: already_visited)

let remove_cycles list = recurse list []

Это пересекает список, пока не достигнет конца или не посетит элемент дважды. Когда происходит последнее, вместо этого устанавливается последняя посещаемая ссылка Nil,

Вы можете заменить already_visited с другой структурой данных, если у вас очень большие списки.

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

Для этого измените is_cyclic вернуть 'a mlist ref вместо bool, Предполагая, что он может вернуть элемент в середине цикла, просмотрите исходный список и проверьте, находится ли каждый элемент в цикле. Это даст вам первый элемент в цикле.

Оттуда легко найти конец цикла - просто проходите цикл, пока не вернетесь к началу.

Что-то вроде этого:

let rec in_cycle x st cyc =
if cyc == x then true
else
    match !cyc with Nil -> false
    | Cons(_, t) when t == st -> false
    | Cons(_, t) -> in_cycle x st t

let rec find_start l cyc =
    if in_cycle l cyc cyc then l
    else
        match !l with Nil -> raise Not_found
        | Cons(_, t) -> find_start t cyc

let rec find_end st cyc =
    match !cyc with Nil -> raise Not_found
    | Cons(_, t) ->
        if t == st then cyc
        else find_end st t

(* ... *)
let cyc = is_cyclic list in
let st = find_start list cyc in
let e = (find_end st cyc) in
match !e with Nil -> failwith "Error"
| Cons(v, _) -> e := Cons(v, ref Nil)
Другие вопросы по тегам