Как пересечь два списка в 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 (синтаксис), но, как правило, вы можете сделать это двумя способами:

  1. Если ваш язык поддерживает Set-datastructure, преобразуйте оба списка в наборы и используйте операцию set-intersection.

  2. в более общем плане: сортировка обоих списков, а затем сканирование отсортированных списков, что делает поиск дубликатов гораздо более эффективным. Вы берете 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]