如果我对元素的唯一了解是它们在列表中出现的位置,是否有任何方法可以交换列表中的两个元素。
更具体地说,我正在寻找这样的东西:
swapElementsAt :: Int -> Int -> [Int] -> [Int]
表现得像那样:
> swapElementsAt 1 3 [5,4,3,2,1] -- swap the first and third elements
[3,4,5,2,1]
我认为Haskell中可能存在内置函数,但我无法找到它。
答案 0 :(得分:15)
警告:微积分。我并不认真对待这个答案,因为它更像是一个大锤。但是这是一把我用得很大的大锤,所以为什么不进行一些运动呢?除了这个问题可能比提问者想知道的更多,我为此道歉。这是试图挖掘已经提出的明智答案背后的深层结构。
可微分函子类至少提供以下各个部分。
class (Functor f, Functor (D f)) => Diff (f :: * -> *) where
type D f :: * -> *
up :: (I :*: D f) :-> f
down :: f :-> (f :.: (I :*: D f))
我想我最好解开其中的一些定义。它们是组合仿函数的基本工具包。这件事
type (f :-> g) = forall a. f a -> g a
缩写容器上操作的多态函数类型。
以下是容器的常量,标识,组成,总和和产品。
newtype K a x = K a deriving (Functor, Foldable, Traversable, Show)
newtype I x = I x deriving (Functor, Foldable, Traversable, Show)
newtype (f :.: g) x = C {unC :: f (g x)} deriving (Functor, Foldable, Traversable, Show)
data (f :+: g) x = L (f x) | R (g x) deriving (Functor, Foldable, Traversable, Show)
data (f :*: g) x = f x :*: g x deriving (Functor, Foldable, Traversable, Show)
D
通过通常的微积分规则来计算仿函数的导数。它告诉我们如何为元素表示单孔上下文。让我们再次阅读这些操作的类型。
up :: (I :*: D f) :-> f
我们可以从{em>一个元素和f
中该元素的上下文中创建一个完整的f
。它是“向上”,因为我们在层次结构中向上导航,专注于整体而不是一个元素。
down :: f :-> (f :.: (I :*: D f))
与此同时,我们可以使用 上下文来装饰可区分仿函数结构中的每个元素,计算特别是“向下”到一个元素的所有方法。
我会将基本组件的Diff
个实例留到此答案的末尾。对于我们得到的列表
instance Diff [] where
type D [] = [] :*: []
up (I x :*: (xs :*: ys)) = xs ++ x : ys
down [] = C []
down (x : xs) = C ((I x :*: ([] :*: xs)) :
fmap (id *:* ((x :) *:* id)) (unC (down xs)))
,其中
(*:*) :: (f a -> f' a) -> (g a -> g' a) -> (f :*: g) a -> (f' :*: g') a
(ff' *:* gg') (f :*: g) = ff' f :*: gg' g
所以,例如,
> unC (down [0,1,2])
[I 0 :*: ([] :*: [1,2]),I 1 :*: ([0] :*: [2]),I 2 :*: ([0,1] :*: [])]
依次选择每个元素在上下文中。
如果f
也是Foldable
,我们会得到一个广义!!
运算符......
getN :: (Diff f, Foldable f) => f x -> Int -> (I :*: D f) x
getN f n = foldMap (: []) (unC (down f)) !! n
...我们得到了元素的上下文以及元素本身的额外奖励。
> getN "abcd" 2
I 'c' :*: ("ab" :*: "d")
> getN ((I "a" :*: I "b") :*: (I "c" :*: I "d")) 2
I "c" :*: R ((I "a" :*: I "b") :*: L (K () :*: I "d"))
如果我们想要一个仿函数提供两个元素的交换,最好是两次可微分,并且它的衍生物最好也可以折叠。到此为止。
swapN :: (Diff f, Diff (D f), Foldable f, Foldable (D f)) =>
Int -> Int -> f x -> f x
swapN i j f = case compare i j of
{ LT -> go i j ; EQ -> f ; GT -> go j i } where
go i j = up (I y :*: up (I x :*: f'')) where
I x :*: f' = getN f i -- grab the left thing
I y :*: f'' = getN f' (j - 1) -- grab the right thing
现在很容易抓住两个元素并以相反的方式将它们插回去。如果我们对位置进行编号,我们只需要注意删除元素重新编号的方式。
> swapN 1 3 "abcde"
"adcbe"
> swapN 1 2 ((I "a" :*: I "b") :*: (I "c" :*: I "d"))
(I "a" :*: I "c") :*: (I "b" :*: I "d")
与往常一样,你没有在有趣的编辑操作下挖掘太多,以找到工作中的差异结构。
为了完整性。以下是涉及的其他实例。
instance Diff (K a) where -- constants have zero derivative
type D (K a) = K Void
up (_ :*: K z) = absurd z
down (K a) = C (K a)
instance Diff I where -- identity has unit derivative
type D I = K ()
up (I x :*: K ()) = I x
down (I x) = C (I (I x :*: K ()))
instance (Diff f, Diff g) => Diff (f :+: g) where -- commute with +
type D (f :+: g) = D f :+: D g
up (I x :*: L f') = L (up (I x :*: f'))
up (I x :*: R g') = R (up (I x :*: g'))
down (L f) = C (L (fmap (id *:* L) (unC (down f))))
down (R g) = C (R (fmap (id *:* R) (unC (down g))))
instance (Diff f, Diff g) => Diff (f :*: g) where -- product rule
type D (f :*: g) = (D f :*: g) :+: (f :*: D g)
up (I x :*: (L (f' :*: g))) = up (I x :*: f') :*: g
up (I x :*: (R (f :*: g'))) = f :*: up (I x :*: g')
down (f :*: g) = C (fmap (id *:* (L . (:*: g))) (unC (down f))
:*: fmap (id *:* (R . (f :*:))) (unC (down g)))
instance (Diff f, Diff g) => Diff (f :.: g) where -- chain rule
type D (f :.: g) = (D f :.: g) :*: D g
up (I x :*: (C f'g :*: g')) = C (up (I (up (I x :*: g')) :*: f'g))
down (C fg) = C (C (fmap inner (unC (down fg)))) where
inner (I g :*: f'g) = fmap wrap (unC (down g)) where
wrap (I x :*: g') = I x :*: (C f'g :*: g')
答案 1 :(得分:5)
Haskell没有这样的功能,主要是因为它有点不起作用。你究竟想要实现什么目标?
您可以实现自己的版本(可能有一种更惯用的方式来编写它)。请注意,我假设'\0'
,但扩展函数以正确处理其他情况是微不足道的:
strlen()
答案 2 :(得分:5)
这里有几个可行的答案,但我认为更有惯用的haskell示例会很有用。
本质上,我们使用原始列表压缩无限序列的自然数,以在结果对的第一个元素中包含排序信息,然后我们使用简单的右折叠(catamorphism)来使用右边的列表和创建一个新列表,但这次交换了正确的元素。我们最终提取所有第二个元素,丢弃包含排序的第一个元素。
在这种情况下,索引是从零开始的(与Haskell的典型索引一致)并且指针必须在范围内,否则您将获得异常(如果您更改结果,这可以很容易地防止输入Maybe [a])。
swapTwo :: Int -> Int -> [a] -> [a]
swapTwo f s xs = map snd . foldr (\x a ->
if fst x == f then ys !! s : a
else if fst x == s then ys !! f : a
else x : a) [] $ ys
where ys = zip [0..] xs
还有一个衬垫,只需一次通过交换(将foldr和map的功能组合成一个zipWith):
swapTwo' f s xs = zipWith (\x y ->
if x == f then xs !! s
else if x == s then xs !! f
else y) [0..] xs
答案 3 :(得分:4)
我是如何解决的:
swapElementsAt :: Int -> Int -> [a] -> [a]
swapElementsAt a b list = list1 ++ [list !! b] ++ list2 ++ [list !! a] ++ list3
where list1 = take a list;
list2 = drop (succ a) (take b list);
list3 = drop (succ b) list
这里我使用了第0位置的约定。我的函数需要一个< = b。
我最喜欢的程序是take a list
行。
编辑:如果你想获得更多这么酷的线条,请看这段代码:
swapElementsAt :: Int -> Int -> [a] -> [a]
swapElementsAt a another list = list1 ++ [list !! another] ++ list2 ++ [list !! a] ++ list3
where list1 = take a list;
list2 = drop (succ a) (take another list);
list3 = drop (succ another) list
答案 4 :(得分:3)
这是一个奇怪的事情,但这应该有效,除了我必须修复的一个一个错误,因为我在手机上写这个。这个版本避免了超过必要的时间超过列表的相同段。
swap' :: Int -> Int -> [a] -> [a]
swap' first second lst = beginning ++ [y] ++ middle ++ [x] ++ end
where
(beginning, (x : r)) = splitAt first lst
(middle, (y : end)) = splitAt (second - first - 1) r
swap x y | x == y = id
| otherwise = swap' (min x y) (max x y)
答案 5 :(得分:3)
一阶一遍交换
swap 1 j l = let (jth,ith:l') = swapHelp j l ith in jth:l'
swap j 1 l = swap 1 j l
swap i j (h:t) = h : swap (i-1) (j-1) t
swapHelp 1 (h:t) x = (h,x:t)
swapHelp n (h:t) x = (y,h:t') where
(y, t') = swapHelp (n-1) t x
答案 6 :(得分:1)
还有一个递归解决方案:
setElementAt :: a -> Int -> [a] -> [a]
setElementAt a 0 (_:tail) = a:tail
setElementAt a pos (b:tail) = b:(setElementAt a (pred pos) tail)
swapElementsAt :: Int -> Int -> [a] -> [a]
swapElementsAt 0 b list@(c:tail) = (list !! b):(setElementAt c (pred b) tail)
swapElementsAt a b (c:tail) = c:(swapElementsAt (pred a) (pred b) tail)
答案 7 :(得分:1)
我非常喜欢@dfeuer的解决方案。然而,通过砍伐森林仍有优化空间:
swap' :: Int -> Int -> [a] -> [a]
swap' first second lst = beginning $ [y] ++ (middle $ [x] ++ end)
where
(beginning, (x : r)) = swapHelp first lst
(middle, (y : end)) = swapHelp (second - first - 1) r
swapHelp :: Int -> [a] -> ([a] -> [a],[a])
swapHelp 0 l = ( id , l)
swapHelp n (h:t) = ((h:).f , r) where
( f , r) = swapHelp (n-1) t
答案 8 :(得分:0)
对于位置交换,使用更复杂的折叠函数,我将最小(min
)索引的值更改为Greates (xs!!(y-ii))
的值,然后将最大索引的值保留在温度,直到找到索引(max
)。
我使用min
和max
来确保以正确的顺序遇到索引,否则我将不得不在folds
函数中添加更多检查和条件。
folds _ _ _ _ [] = []
folds i z y tmp (x:xs)
| i == z = (xs!!(y-ii)):folds ii z y x xs
| i == y = tmp:folds ii z y 0 xs
| otherwise = x:folds ii z y tmp xs
where
ii = i+1
swapElementsAt x y xs = folds 0 a b 0 xs
where
a = min x y
b = max x y
结果
> swapElementsAt 0 1 [1,1,1,3,4,9]
[1,1,1,3,4,9]
> swapElementsAt 0 5 [1,1,1,3,4,9]
[9,1,1,3,4,1]
> swapElementsAt 3 1 [1,1,1,3,4,5]
[1,3,1,1,4,5]
> swapElementsAt 1 3 [1,1,1,3,4,5]
[1,3,1,1,4,5]
> swapElementsAt 5 4 [1,1,1,3,4,5]
[1,1,1,3,5,4]