Как закрепить несколько списков в Haskell?

В функции python zip принимает произвольное количество списков и зашифровывает их вместе.

>>> l1 = [1,2,3]
>>> l2 = [5,6,7]
>>> l3 = [7,4,8]
>>> zip(l1,l2,l3)
[(1, 5, 7), (2, 6, 4), (3, 7, 8)]
>>> 

Как я могу zip объединить несколько списков в haskell?

Ответы

Ответ 1

Обобщение zip может быть достигнуто с помощью Аппликационная нотация. Это немного неприятно для использования из-за упаковки/распаковки newtype, но если вы делаете то, что не может быть сделано с помощью zipWithn для достаточно маленького n, вы, вероятно, уже находитесь на достаточно высоком уровне абстракции, где обозначение боли все равно отсутствуют.

Тип ZipList a, и его аппликативный экземпляр застегивает вместе списки. Например:

(+) <$> ZipList [1,2] <*> ZipList [3,4] == ZipList [4,6]

Это обобщает функции произвольной arity и типа с использованием частичного приложения:

(+) <$> ZipList [1,2]  :: ZipList (Int -> Int)

Посмотрите, как (+) применяется здесь частично?

Если вам не нравится добавлять ZipList и getZipList всюду, вы можете легко воссоздать обозначение:

(<$>) :: (a -> b) -> [a] -> [b]
(<$>) = map

(<*>) :: [a -> b] -> [a] -> [b]
(<*>) = zipWith ($)

Тогда обозначение для zipWith f a b c d ...:

f <$> a <*> b <*> c <*> d <*> ...

Аппликативная нотация - очень мощный и общий метод, который имеет гораздо более широкий охват, чем просто обобщенная застежка. Подробнее см. Typeclassopedia.

Ответ 2

Вы можете перенести список списков:

>>> import Data.List
>>> transpose [l1,l2,l3]
[[1,5,7],[2,6,4],[3,7,8]]

Ответ 3

Похоже, есть также zip3 (doc) и zip4 (doc) в Haskell. Но zipn кажется сложным из-за сильной системы типов. Вот хорошая дискуссия, которую я нашел во время моих исследований.

Ответ 4

GHC также поддерживает параллельные проверки списков:

{-# LANGUAGE ParallelListComp #-}

[(x,y) | x <- [1..3]
       | y <- ['a'..'c']
       ]

==> [(1,'a'),(2,'b'),(3,'c')]

Я просто проверил его до 26 параллельных переменных, что должно быть достаточно для всех практических целей.

Это немного хакерский (и нестандартный), хотя, если вы пишете что-то серьезное, ZipList может быть лучшим способом.

Ответ 5

Я думаю, что это, вероятно, наименее изящное решение, но для полноты нужно добавить, что такие вещи должны быть возможны с Template Haskell.

Фактически это было отражено в том, что я считаю исходной бумагой шаблона Haskell (поиск zipn в тексте): http://research.microsoft.com/en-us/um/people/simonpj/Papers/meta-haskell/meta-haskell.pdf

Но я думаю, что код никогда не работал, см. это: http://www.haskell.org/pipermail/template-haskell/2003-July/000126.html (фрагменты шаблона не реализованы).

Это не было реализовано в 2003 году, но оно все еще не реализовано сегодня: http://www.haskell.org/ghc/docs/7.6.1/html/users_guide/template-haskell.html (фрагменты шаблона не поддерживаются)

Однако существует реализация zipWithN с использованием шаблона haskell: http://www.haskell.org/haskellwiki/Template_Haskell#zipWithN

Я проверил, что он работает с этой тестовой программой:

{-# LANGUAGE TemplateHaskell #-}
import Zipn

main = do
    let l1 = [1,2,3]
    let l2 = [5,6,7]
    let l3 = [7,4,8]
    print $ $(zipWithN 3) (,,) l1 l2 l3

В модуле Zipn я вставил zipn, просто переименовал zipWithN для ясности (и не забудьте добавить прагма TemplateHaskell вверху). Заметим, что N фактически закодирован здесь дважды, потому что мне нужно было дать (,,) как функцию "с". Вам нужно будет изменить количество запятых в зависимости от N.

(,,) означает \a b c -> (a,b,c)

Я думаю, что кто-то с хорошими навыками Шаблона Хаскелла (который не мой случай в этой точке) мог бы сделать прямой zipN с использованием шаблона Haskell.

Ответ 6

Это нетривиально, но это выполнимо. См. этот пост в блоге. Я не знаю, было ли это сделано в какой-то библиотеке.

Вот другая версия, которая проще. На самом деле это можно было бы нарезать-n-вставить здесь:

{-# LANGUAGE MultiParamTypeClasses
           , FunctionalDependencies
           , FlexibleInstances
           , UndecidableInstances
           #-}

-- |
-- Module      :  Data.List.ZipWithN
-- Copyright   :  Copyright (c) 2009 wren ng thornton
-- License     :  BSD3
-- Maintainer  :  [email protected]
-- Stability   :  experimental
-- Portability :  non-portable (MPTCs, FunDeps,...)
--
-- Provides a polyvariadic 'map'/'zipWith' like the @[email protected] in Scheme.
-- For more details on this style of type hackery, see:
--
--    * Chung-chieh Shan, /A polyvariadic function of a non-regular/
--      /type (Int->)^N ([]^N e)->.../
--      <http://okmij.org/ftp/Haskell/polyvariadic.html#polyvartype-fn>
----------------------------------------------------------------
module Data.List.ZipWithN (ZipWithN(), zipWithN) where

-- | This class provides the necessary polymorphism. It is only
-- exported for the sake of giving type signatures.
--
-- Because we can't do functor composition without a lot of noise
-- from newtype wrappers, we use @[email protected] and @[email protected] to precompose the
-- direct/list functor with the reader functor and the return type.
class ZipWithN a gr kr | kr -> gr a where
    _zipWithN :: [a -> gr] -> [a] -> kr

instance ZipWithN a b [b] where
    _zipWithN = zipWith ($)

instance ZipWithN b gr kr => ZipWithN a (b -> gr) ([b] -> kr) where
    _zipWithN = (_zipWithN .) . zipWith ($)


-- | Polyadic version of 'map'/'zipWith'. The given type signature
-- isn't terribly helpful or intuitive. The /real/ type signature
-- is:
--
-- > zipWithN :: {forall a}^N. ({a->}^N  r) -> ({[a]->}^N  r)
--
-- Note that the @[email protected] type variables are meta and so are independent
-- from one another, despite being correlated in N across all
-- repetitions.
zipWithN :: (ZipWithN a gr kr) => (a -> gr) -> [a] -> kr
zipWithN = _zipWithN . repeat

Если вы только начинаете изучать Haskell, отложите его понимание в течение некоторого времени:)

Ответ 7

Обобщающий zipping на самом деле довольно прост. Вам просто нужно написать специализированные версии комбинаторов Applicative для ZipList:

z :: [a -> b] -> [a] -> [b]
z = zipWith ($)

infixl 4 `z`

Теперь вы можете скопировать столько списков, сколько хотите:

f <$> xs `z` ys `z` zs

или, альтернативно:

repeat f `z` xs `z` ys `z` zs

Ответ 8

Для определенного количества списков вы можете сделать что-то вроде этого:

> let l1 = [1,2,3]
> let l2 = "abc"
> let l3 = [10.0, 11.0, 12.0]
> let l4 = [True, False, False]

> [ (e1,e2,e3,e4) | (((e1,e2),e3),e4) <- zip (zip (zip l1 l2) l3) l4 ]
[(1,'a',10.0,True),(2,'b',11.0,False),(3,'c',12.0,False)]

Это не общая функция, а шаблон, который вы можете применить к другому количеству списков.

Ответ 9

Если все ваши данные одного типа, вы можете сделать:

import Data.List (transpose)

zipAllWith :: ([a] -> b) -> [[a]] -> [b]
zipAllWith _ []  = []
zipAllWith f xss = map f . transpose $ xss

zipAll = zipAllWith id

Пример:

> zipAll [[1, 2, 3], [4, 5, 6], [7, 8]]
[[1,4,7],[2,5,8],[3,6]]