Можно ли создать функцию "generi c" в стандартном ML? - PullRequest
3 голосов
/ 05 августа 2020

Я хотел бы создать функцию remove_duplicates, которая принимает list любого типа (например, может быть int list, bool list, int list list или whatever list) и возвращает то же самое. список без дубликатов, возможно ли это в Standard ML?

Ответы [ 3 ]

3 голосов
/ 06 августа 2020

Возможна ли функция, которая принимает список любого типа и возвращает список без дубликатов в Стандартном ML?

Нет.

Чтобы определить, является ли дубликат другого, их значения должны быть сопоставимы. «Любой тип» или 'a в Стандартном ML не может считаться равным. Итак, хотя у вас не может быть val nub : 'a list -> 'a list, который удаляет дубликаты, вот четыре альтернативных варианта:

  1. Что предлагает @qouify, встроенный тип равенства ''a, так что все, что вы можете используйте = на:

    val nub : ''a list -> ''a list
    
  2. Что предлагает @kopecs, функцию, которая принимает оператор равенства в качестве параметра:

    val nub : ('a * 'a -> bool) -> 'a list -> 'a list
    

    Что является обобщением 1., так как здесь nub op= : ''a list -> ''a list. Это удобное решение, поскольку оно позволяет удалять не только дубликаты, но и избыточные представители произвольных классов эквивалентности, например, nub (fn (x, y) => (x mod 3) = (y mod 3)) сохранит только целые числа, отличные по модулю 3. Но его сложность составляет O (n²) . (-_-) ノ ⌒┻━┻

  3. Поскольку это O (n²) , nub считается вредным .

    Как также предлагается в статье, альтернативой является использование упорядочения, а не равенства, чтобы уменьшить сложность до O (n log n) . В то время как в Haskell это означает только изменение ограничения класса типа:

    nub    :: Eq a  => [a] -> [a]
    nubOrd :: Ord a => [a] -> [a]
    

    и настройку алгоритма, express это ограничение в SML становится немного сложнее. В то время как у нас do есть ''a для представления Eq a => a (что мы можем использовать = на нашем вводе), мы не имеем аналогичную поддержку специального синтаксиса для элементов, которые можно сравнить как меньше / равно / больше, и у нас также нет классов типов. У нас do есть следующий встроенный тип заказа:

    datatype order = LESS | EQUAL | GREATER
    

    , поэтому, если вам нравится решение kopecs, вариант с лучшим временем выполнения:

    val nubOrd : ('a * 'a -> order) -> 'a list -> 'a list
    

    , поскольку он может использовать что-то вроде математического набора ранее замеченных элементов, реализованного с использованием некоторого сбалансированного дерева поиска; n вставляет каждую сложность O (log n) занимает всего O (n log n) шагов.

  4. Одной из отличительных черт SML является его составная модульная система. Вместо использования полиморфизма параметри c и подачи функции nubOrd с функцией сравнения порядка вы можете создать модуль, который принимает другой модуль в качестве параметра ( функтор ).

    Во-первых, давайте определим подпись для модулей, которые представляют упорядочение типов:

    signature ORD =
    sig
      type t
      val compare : t * t -> order
    end
    

    (обратите внимание, что перед t нет '. )

    Это означает, что любой может создать struct ... end : ORD, указав t и соответствующую функцию compare для t s. Многие встроенные типы имеют предопределенные compare функции: int имеет Int.compare и real имеет Real.compare.

    Затем определите дерево- основанная структура данных набора; Я использовал двоичное дерево поиска и пропустил большинство функций, кроме тех, которые строго необходимы для выполнения этой задачи. В идеале вы могли бы расширить интерфейс и использовать более качественный тип дерева, например самобалансирующееся дерево. (К сожалению, поскольку вы пометили эти вопросы и ответы как SML / NJ и Moscow ML, я не был уверен, какой модуль использовать, поскольку они по-разному расширяют стандартную библиотеку, когда дело касается сбалансированных деревьев.)

    functor TreeSet (X : ORD) =
    struct
      type t = X.t
      datatype 'a tree = Leaf | Branch of 'a tree * 'a * 'a tree
    
      val empty = Leaf
    
      fun member (x, Leaf) = false
        | member (x, Branch (left, y, right)) =
            case X.compare (x, y) of
                 EQUAL => true
               | LESS => member (x, left)
               | GREATER => member (x, right)
    
      fun insert (x, Leaf) = Branch (Leaf, x, Leaf)
        | insert (x, Branch (left, y, right)) =
            case X.compare (x, y) of
                 EQUAL => Branch (left, y, right)
               | LESS  => Branch (insert (x, left), y, right)
               | GREATER => Branch (left, y, insert (x, right))
    end
    

    Наконец, функтор ListUtils содержит служебную функцию nubOrd. Функтор принимает структуру X : ORD точно так же, как функтор TreeSet. Он создает структуру XSet, специализируя функтор TreeSet с использованием того же модуля упорядочивания. Затем он использует это XSet для эффективно сохраняет запись элементов, которые он видел раньше.

    functor ListUtils (X : ORD) =
    struct
      structure XSet = TreeSet(X)
    
      fun nubOrd (xs : X.t list) =
        let
          val init = ([], XSet.empty)
          fun go (x, (ys, seen)) =
            if XSet.member (x, seen)
              then (ys, seen)
              else (x::ys, XSet.insert (x, seen))
        in rev (#1 (foldl go init xs))
        end
    end
    

    Использование этого функтора для удаления дубликатов в int list:

    structure IntListUtils = ListUtils(struct
                                         type t = int
                                         val compare = Int.compare
                                       end)
    
    val example = IntListUtils.nubOrd [1,1,2,1,3,1,2,1,3,3,2,1,4,3,2,1,5,4,3,2,1]
                                   (* [1,  2,  3,              4,      5] *)
    

    Цель всего этого беспорядка - nubOrd без прямого дополнительного параметра функции.

    К сожалению, чтобы это распространялось на int list list, вам необходимо создать функцию compare для этого типа, поскольку, в отличие от Int.compare, в стандартной библиотеке нет универсальной c функции. либо. (Здесь Haskell намного больше эргономики c.)

    Так что вы можете go и написать общую c, функцию сравнения лексикографических списков: если вы знаете, как сравнивать два элемента типа 'a, вы знаете, как сравнить два списка из них, независимо от типа элемента:

    fun listCompare _ ([], []) = EQUAL   (* empty lists are equal *)
      | listCompare _ ([], ys) = LESS    (* empty is always smaller than non-empty *)
      | listCompare _ (xs, []) = GREATER (* empty is always smaller than non-empty *)
      | listCompare compare (x::xs, y::ys) =
          case compare (x, y) of
               EQUAL => listCompare compare (xs, ys)
             | LESS => LESS
             | GREATER => GREATER
    

    А теперь,

    structure IntListListUtils = ListUtils(struct
                                             type t = int list
                                             val compare = listCompare Int.compare
                                           end)
    val example2 = IntListListUtils.nubOrd [[1,2,3],[1,2,3,2],[1,2,3]]
                                        (* [[1,2,3],[1,2,3,2]] *)
    

    Итак, хотя [1,2,3] и [1,2,3,2] содержат дубликаты, они не EQUAL, когда вы compare их. Но третий элемент соответствует EQUAL первому, поэтому он удаляется как дубликат.

Некоторые последние наблюдения:

  • Вы можете считать, что даже если каждый compare выполняется только O (log n) раз, один compare для некоторой сложной структуры данных, такой как (whatever * int) list list, может все равно будет дорого. Итак, еще одно улучшение, которое вы можете сделать здесь, - это кэширование результата каждого вывода compare, что на самом деле делает оператор Haskell nubOrdOn. ヽ (ಠ ل͜ ಠ) ノ

  • Функторный подход широко используется в библиотеке OCaml Base *1161* Jane Street . Быстрое решение заключалось в том, чтобы передавать функцию 'a * 'a -> order каждый раз, когда вы nub что-то делаете. Одна мораль, однако, заключается в том, что, хотя модульная система действительно добавляет многословия, если вы предоставите достаточное количество этого оборудования в стандартной библиотеке, это станет довольно удобным.

  • Если вы думаете, что улучшение от O (n²) до O (n log n) недостаточно, обратите внимание на Generi c нисходящую дискриминацию Фрица Хенглейна для сортировки и разделения за линейное время (2012) и Haskell пакета дискриминации Эдварда Кметта nub для O (n) nub.

2 голосов
/ 05 августа 2020

Да sml предоставляет полиморфизм для таких вещей. Во многих случаях вам фактически не важен тип элемента в ваших списках (или других структурах). Например, эта функция проверяет (уже присутствует в структуре List) на предмет существования элемента в списке:

fun exists _ [] = false
  | exists x (y :: l) = x = y orelse exists x l

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

2 голосов
/ 05 августа 2020

Да. Это возможно в SML за счет использования полиморфизма параметра c. Вам нужна функция самого общего типа 'a list -> 'a list, где 'a - это переменная типа (т. Е. Переменная, которая варьируется по типам), которая будет читаться как альфа.

Для некоторых более конкретных примеров того, как вы могли бы примените это (явная переменная типа после fun является необязательной):

fun 'a id (x : 'a) : 'a = x

Здесь у нас есть функция идентификации с типом 'a -> 'a.

Мы можем объявить аналогичные функции с некоторой степенью специализации типов, например

fun map _ [] = []
  | map f (x::xs) = f x :: map f xs

Где map имеет наиболее общий тип ('a -> 'b) -> 'a list -> 'b list, т.е. принимает два каррированных аргумента, один с некоторым типом функции, а другой с некоторым типом списка (согласуется с домена функции) и возвращает новый список с типом, заданным кодоменом функции.

Для вашей конкретной задачи c вы, вероятно, также захотите воспользоваться функцией равенства, чтобы определить, что такое " duplicate »или вы, вероятно, ограничитесь« типами равенства »(типы, которые можно сравнить с op=, представленные переменными типа с двумя ведущими построфы, например, ''a).

...