Разбиение списка на список возможных кортежей

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

pairs ["cat","dog","mouse"]

в результате

[("cat","dog"), ("cat","mouse"), ("dog","cat"), ("dog","mouse"), ("mouse","cat"), ("mouse","dog")]

мне удалось первые два, но не уверен, как получить остальные.

вот что у меня пока есть:

pairs :: [a] -> [(a,a)]
pairs (x:xs) = [(m,n) | m <- [x], n <- xs]

6 ответов


вы можете использовать список понимание:

allpairs :: Eq a => [a] -> [(a,a)]
allpairs xs = [ (x1,x2) | x1 <- xs, x2 <- xs, x1 /= x2 ]

этот ответ состоит из двух частей. Первая часть непосредственно касается этого вопроса. Вторая часть идет по касательной (буквально), чтобы копаться в математике за первой частью: это может оказаться трудным материалом с ограниченным интересом, но я думал, что несколько экстремистов могут наслаждаться этим.

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

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

picks :: [x] -> [(x, [x])]
picks [] = []
picks (x : xs) = (x, xs) : [(y, x : ys) | (y, ys) <- picks xs]

обратите внимание, что map fst . picks = id, так что выбранный элемент в каждой позиции результата является элементом из этой позиции в исходном списке: это то, что я имел в виду под "украшает".

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

allPairs :: [x] -> [(x, x)]
allPairs xs = [(y, z) | (y, ys) <- picks xs, z <- ys]

так же легко достать тройки, взяв picks два раза.

allTriples :: [x] -> [(x, x, x)]
allTriples ws = [(x, y, z) | (x, xs) <- picks ws, (y, ys) <- picks xs, z <- ys]

для единообразия почти заманчиво сделать код немного менее эффективным, написав (z, _) <- picks ys, а не z <- ys в обоих.

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

Picks> allPairs ["cat", "cat"]
[("cat","cat"),("cat","cat")]

To избежать этого, не стесняйтесь использовать allPairs . nub, который удаляет дубликаты перед выбором и требует еще больше Eq экземпляр для типа элемента.


для экстремистов только: контейнеры, исчисление, comonads и комбинаторика Ахой!

picks является одним из примеров более общей конструкции, возникающей из дифференциального исчисления. Это забавный факт, что для любого заданного containery рода функтор f, его математическая производная, ∂f, представляет f-структуры с одним удаленным элементом. Например,

newtype Trio x = Trio (x, x, x)   -- x^3

и производное

data DTrio x = Left3 ((), x, x) | Mid3 (x, (), x) | Right3 (x, x, ())  -- 3*x^2

с этой конструкцией может быть связан ряд операций. Представьте, что мы действительно можем использовать ∂ (и мы можем кодировать его, используя семейства типов). Тогда мы могли бы сказать

data InContext f x = (:-) {selected :: x, context :: ∂f x}

чтобы дать тип выбранных элементов, украшенных контекстом. Мы должны, конечно, ожидать, что операция

plug :: InContext f x -> f x   -- putting the element back in its place

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

мы также должны ожидать InContext f быть комонадой, с

counit :: InContext f x -> x
counit = selected

проецирование выбранного элемента и

cojoin :: InContext f x -> InContext f (InContext f x)

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

picks :: f x -> f (InContext f x)

должны украсить каждый x - элемент на входе f-структура с ее контекстом. Мы должны ожидать, что

fmap selected . picks = id

какой закон у нас был раньше, но также

fmap plug (picks fx) = fmap (const fx) fx

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

picks :: [x] -> [(x, [x])]

украшение каждого элемента не совсем с чем-то вроде его контекста: только из списка других элементов вы не можете видеть, где находится "отверстие". По правде говоря,

∂[] x = ([x], [x])

разделение списка элементов перед отверстием от элементов после отверстия. Возможно, я должен был написано

picks :: [x] -> [(x, ([x], [x]))]
picks [] = []
picks (x : xs) = (x, ([], xs)) : [(y, (x : ys, ys')) | (y, (ys, ys')) <- picks xs]

и это, безусловно, очень полезная операция.

но то, что происходит на самом деле, вполне разумно и только небольшое злоупотребление. В коде, который я изначально написал, Я локально принимаю [] представлять конечное сумки или ненумерованный список. Сумки-Это списки без понятия конкретной позиции, поэтому, если вы выбираете один элемент, его контекст-это просто сумка остальных элементов. Действительно

∂Bag = Bag   -- really? why?

так правильное понятие picks действительно

picks :: Bag x -> Bag (x, Bag x)

представляют Bag by [] и вот что у нас было. Более того, для сумок,plug это просто (:) и, вплоть до равенства мешков (т. е. перестановки), второй закон для picks тут удерживайте.

другой способ взглянуть на сумки - это серия power. Сумка-это выбор кортежей любого размера, со всеми возможными перестановками (n! в размере n) определены. Так что мы можем запишите его комбинаторно как большую сумму степеней, пропорциональных факториалам, потому что вы должны разделить на x^n на n! чтобы учесть тот факт, что каждый из n! заказы, в которых вы могли бы выбрать x, дают вам ту же сумку.

 Bag x = 1 + x + x^2/2! + x^3/3! + ...

так

∂Bag x = 0 + 1 + x      + x^2/2! + ...

смещение серии в сторону. В самом деле, вы вполне могли узнать серию power для Bag как это для exp (или e ^x), который славится своей собственной производное.

так, фух! Там вы идете. Операция, естественно возникающая из интерпретации типа данных экспоненциальной функции, будучи ее собственной производной, является удобной частью набора для решения проблем, основанных на выборе без замены.


мой подход, который несколько похож на другие. Это не требует Eq.

allpairs :: [t] -> [(t,t)]
allpairs [] = []
allpairs [_] = []
allpairs (x:xs) = concatMap (\y -> [(x,y),(y,x)]) xs ++ allpairs xs

еще одна возможность-использовать монадическую нотацию:

pairs :: (Eq a) => [a] -> [(a,a)]
pairs l = do
    x <- l
    y <- l
    guard (x /= y)
    return (x, y)

(самый общий тип для этого определения pairs будет (MonadPlus m, Eq a) => m a -> m (a,a) но я считаю, что нет экземпляра MonadPlus кроме [] для которого это имело бы смысл.)


import Control.Applicative

pairs xs = filter (uncurry (/=)) $ (,) <$> xs <*> xs

pairs = (filter.uncurry) (/=) . (join.liftA2) (,)