Как заархивировать несколько списков в Haskell?
В питоне 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?
9 ответов
Обобщение zip может быть достигнуто с помощью аппликативной нотации. Это немного неприятно использовать из-за упаковки / распаковки нового типа, но если вы делаете что-то, чего нельзя сделать с помощью zipWithn
при достаточно малом n вы, вероятно, уже находитесь на достаточно высоком уровне абстракции, где нотационные боли все равно отсутствуют.
Тип является ZipList a
и его аппликативный экземпляр объединяет списки. Например:
(+) <$> ZipList [1,2] <*> ZipList [3,4] == ZipList [4,6]
Это обобщает функции произвольной арности и типа с использованием частичного применения:
(+) <$> 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 для больше на Аппликативной нотации.
Вы можете транспонировать список списков:
>>> import Data.List
>>> transpose [l1,l2,l3]
[[1,5,7],[2,6,4],[3,7,8]]
Похоже, что есть также zip3
( док) и zip4
( док) функция в Хаскеле. Но zipn кажется сложным из-за сильной системы типов. Вот хорошее обсуждение, которое я нашел во время моего исследования.
GHC также поддерживает параллельные списки:
{-# LANGUAGE ParallelListComp #-}
[(x,y) | x <- [1..3]
| y <- ['a'..'c']
]
==> [(1,'a'),(2,'b'),(3,'c')]
Я только что проверил до 26 параллельных переменных, которых должно быть достаточно для всех практических целей.
Это немного глупо (и нестандартно), поэтому, если вы пишете что-то серьезное, ZipList может быть лучшим способом.
Я думаю, что это, вероятно, наименее элегантное решение, предложенное, но ради полноты следует добавить, что такие вещи должны быть возможны с шаблоном Haskell.
На самом деле это было отражено в том, что я считаю оригинальной бумагой Template 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
Я проверил, что он работает с этой тестовой программой:
{-# 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)
Я предполагаю, что кто-то с хорошими навыками Template Haskell (что на данный момент не является моим делом) может сделать прямую zipN, используя Template Haskell.
Это нетривиально, но выполнимо. Смотрите этот пост в блоге. Я не знаю, было ли это сделано в какой-то библиотеке.
Вот еще одна версия, которая проще. Это можно на самом деле вырезать здесь:
{-# LANGUAGE MultiParamTypeClasses
, FunctionalDependencies
, FlexibleInstances
, UndecidableInstances
#-}
-- |
-- Module : Data.List.ZipWithN
-- Copyright : Copyright (c) 2009 wren ng thornton
-- License : BSD3
-- Maintainer : wren@community.haskell.org
-- Stability : experimental
-- Portability : non-portable (MPTCs, FunDeps,...)
--
-- Provides a polyvariadic 'map'/'zipWith' like the @map@ 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 @gr@ and @kr@ 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 @a@ 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, отложите понимание на некоторое время:)
Обобщение молнии на самом деле довольно просто. Вам просто нужно написать специализированные версии 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
Если все ваши данные одного типа, вы можете сделать:
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]]
Для определенного количества списков вы можете сделать что-то вроде этого:
> 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)]
Это не универсальная функция, а шаблон, который вы можете применить к другому количеству списков.