Как пересечь два списка в OCaml?
когда у меня есть два списка в OCaml, например
e1 = [3; 4; 5; 6; 7]
и
e2 = [1; 3; 5; 7; 9]
есть ли эффективный способ, чтобы получить пересечение этих двух списков? Т. е.:
[3; 5; 7]
потому что мне не нравится сканировать каждый элемент в списке e2 для каждого элемента в списке e1, создавая большой Oh Порядка N^2.
5 ответов
как сказали Франк и Реми, преобразование ваших списков в наборы (из набора модулей stdlib) стоит N log(n), а затем наборы обеспечивают линейную реализацию пересечения. Франк также упомянул эквивалентную альтернативу сортировки списков,а затем их синхронного прохождения. Они примерно одинаковы (и, кстати, в обоих случаях вам нужно иметь возможность обеспечить полный порядок элементов в ваших списках).
если пересечения являются важной частью вашего алгоритма и вы хотите, чтобы они были быстрее в случае двух наборов элементов, которые лишь немного отличаются, вам нужно перейти к mergeable структура, такая как деревья Патриции. См. файлы pt*
в http://www.lri.fr / ~filliatr / ftp / ocaml/ds/ .
Если вам нужно, чтобы пересечение было быстрым во всех случаях, у вас есть возможность использовать хэш-деревья Патриции. Хэш-консинг помогает распознавать структурно идентичные под-деревья и помогает создавать эффективные кэши для предыдущие операции, сделав сравнение дешевым.
деревья Patricia не могут использовать произвольный тип в качестве ключа (обычно они представлены с ints в качестве ключей). Но иногда можно обойти это ограничение путем нумерации при создании каждого значения, которое вы собираетесь использовать в качестве ключа.
мой OCaml не лучший, но я взломал эту функцию вместе, которая будет пересекать отсортированные списки:
let rec intersect l1 l2 =
match l1 with [] -> []
| h1::t1 -> (
match l2 with [] -> []
| h2::t2 when h1 < h2 -> intersect t1 l2
| h2::t2 when h1 > h2 -> intersect l1 t2
| h2::t2 -> (
match intersect t1 t2 with [] -> [h1]
| h3::t3 as l when h3 = h1 -> l
| h3::t3 as l -> h1::l
)
);;
который должен работать в O (n+m) времени. В основном он проверяет первый элемент каждого списка. Если они равны, он сохраняет результат рекурсивного вызова их хвостов, а затем проверяет, равен ли глава сохраненного результата головкам списков. Если это не так, он вставляет его, иначе это дубликат, и он игнорирует его.
если они не равный, он просто продвигает тот, который меньше.
Я не знаю OCaml (синтаксис), но, как правило, вы можете сделать это двумя способами:
Если ваш язык поддерживает Set-datastructure, преобразуйте оба списка в наборы и используйте операцию set-intersection.
в более общем плане: сортировка обоих списков, а затем сканирование отсортированных списков, что делает поиск дубликатов гораздо более эффективным. Вы берете N log (n) для сортировки и можете найти дубликаты в линейном времени затем.
Как предложил @Frank, вы можете использовать наборы для решения этой проблемы, хотя это не лучший ответ, но вот короткий список кода, демонстрирующий, как это может быть достигнуто в OCaml:
module Int_set = Set.Make (struct
type t = int
let compare = compare
end);;
(* iters through a list to construct a set*)
let set_of_list = List.fold_left (fun acc x -> Int_set.add x acc) Int_set.empty;;
let e1 = [3; 4; 5; 6; 7];;
let e2 = [1; 3; 5; 7; 9];;
let s1 = set_of_list e1;;
let s2 = set_of_list e2;;
(*result*)
let s3 = Int_set.inter s1 s2;;
(*testing output*)
Int_set.iter (fun elt -> print_int elt;print_string "\n") s3;;
выход :
3
5
7
- : unit = ()
если ваши списки содержат только целые числа ограниченного размера, есть также решение в O (n):
1.) Создайте массив логических значений размера вашего наибольшего целочисленного значения плюс 1 в ваших исходных списках (например, в вашем примере "9+1"); Установите для всех полей значение false;
let m = Array.create 10 false
->[|false; false; false; false; false; false; false; false; false; false|]
2.) Повторите первый список: для каждого элемента, с которым вы сталкиваетесь, установите логическое значение с соответствующим смещением в "true"; в вашем примере это будет доходность
List.iter (fun x -> m.(x) <- true) e1
->[|false; false; false; true; true; true; true; true; false; false|]
3.) Фильтровать по второму списку, сохраняя только те элементы, для которых соответствующее поле в массиве true
List.filter (fun x -> m.(x) = true) e2
->[3; 5; 7]