twisty-0.1.0: Twisty/Wreath.hs
{-
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- | Defines the 'Wreath' type, which combines a permutation of a bounded enum
-- with a way to twist each enum element as it's permuted. This embodies the
-- group theoretic notion of \"wreath product.\"
module Twisty.Wreath
( Wreath
, WreathEntry(..)
, WreathPermutable
, WreathTwist
, numIndicesMoved
, toCycles
, fromCycles
, optShowCycles
, showEmptyParens
, fromOptCycles
)
where
import Twisty.Group
import Twisty.Lists
import Twisty.Strings
import Data.Array
import Data.Ix (Ix)
import Data.List (foldl')
import qualified Data.Map as Map
import Data.Monoid (Monoid, mappend, mempty)
import Data.Ord (comparing)
import qualified Data.Set as Set
-- | A class for types that can be permuted in a 'Wreath'. Such types must be
-- bounded, indexable enums with equality testing and order. And they must have
-- a corresponding type for the ways they can be \"twisted.\"
class (Enum a, Bounded a, Ix a, Eq a, Ord a,
Group (WreathTwist a), Eq (WreathTwist a), Ord (WreathTwist a))
=> WreathPermutable a where
-- | The twist group that corresponds to the permuted type.
type WreathTwist a
-- | A wreath product is a way to factor a group into two parts, a permutation
-- and some other subgroup, which we designate the \"twist\" group. For
-- example, the corner pieces of a Rubik's cube are permuted by each move, but
-- they are also twisted.
newtype (WreathPermutable a) => Wreath a = Wreath (Array a (Entry a))
instance (WreathPermutable a) => Eq (Wreath a) where
Wreath arr1 == Wreath arr2 = trim arr1 == trim arr2
instance (WreathPermutable a) => Ord (Wreath a) where
Wreath arr1 `compare` Wreath arr2 = comparing trim arr1 arr2
-- | A WreathEntry combines the target value and the twist for the source value.
newtype (WreathPermutable a) => WreathEntry a = Entry (a, WreathTwist a)
deriving instance (WreathPermutable a) => Eq (WreathEntry a)
deriving instance (WreathPermutable a) => Ord (WreathEntry a)
-- | A short name for internal use.
type Entry = WreathEntry
instance (WreathPermutable a, Show a, Show (WreathTwist a)) => Show (WreathEntry a) where
showsPrec n (Entry (a, t)) = showsPrec n a . showsPrec n t
-- | Look up the entry for a value within a wreath.
getEntry :: (WreathPermutable a) => Wreath a -> a -> Entry a
getEntry (Wreath arr) a = if inRange (bounds arr) a then arr!a else Entry (a, one)
-- | Chain an entry through a wreath.
chainEntry :: (WreathPermutable a) => Wreath a -> Entry a -> Entry a
chainEntry w !(Entry (a, t)) = let !(Entry (a', t')) = getEntry w a in Entry (a', t $* t')
-- | The array we use for empty wreaths.
emptyWreathArray :: (WreathPermutable a) => Array a (Entry a)
emptyWreathArray = listArray (maxBound, pred maxBound) []
-- | Wreaths are Monoids: the identity is the identity permutation, and the
-- append operation is composition of permutations.
instance (WreathPermutable a) => Monoid (Wreath a) where
mempty = Wreath emptyWreathArray
mappend w1@(Wreath arr1) w2@(Wreath arr2) = Wreath comp
where comp = listArray nb [chainEntry w2 (getEntry w1 a) | a <- [fst nb..snd nb]]
nb = bounds arr1 `union` bounds arr2
union (b11, b12) (b21, b22) = (min b11 b21, max b12 b22)
-- | Trims the bounds of an array of entries so it consists only of those
-- elements that are either moved or twisted.
trim :: (WreathPermutable a) => Array a (Entry a) -> Array a (Entry a)
trim = trimDown . trimUp
where trimUp arr = subarray arr (minMoved arr, snd (bounds arr))
trimDown arr = subarray arr (fst (bounds arr), maxMoved arr)
subarray arr nb@(n1, n2)
| nb == bounds arr = arr
| n1 > n2 = emptyWreathArray
| otherwise = listArray nb [arr!a | a <- [n1..n2]]
-- minMoved returns the upper bound if nothing below it moves.
minMoved arr = let (b1, b2) = bounds arr in mm b1 b2
where mm b1 b2 = if b1 >= b2 || arr `moves` b1 then b1
else mm (succ b1) b2
-- maxMoved returns the pred of the lower bound if nothing above the
-- lower bound moves and the lower bound doesn't move either.
maxMoved arr = let (b1, b2) = bounds arr in mm b1 b2
where mm b1 b2 = if b2 < b1 || arr `moves` b2 then b2
else mm b1 (pred b2)
moves arr a = arr!a /= Entry (a, one)
-- | Wreaths are Groups: the inverse is the inverse permutation with all the
-- twists also inverted.
instance (WreathPermutable a) => Group (Wreath a) where
ginvert (Wreath arr) = Wreath (if Map.null invMap then emptyWreathArray else trim inv)
where invMap = Map.fromList [(tgt, Entry (src, ginvert t)) | (src, Entry (tgt, t)) <- assocs arr]
inv = listArray (b1, b2) [Map.findWithDefault (Entry (a, one)) a invMap | a <- [b1..b2]]
(b1, _) = Map.findMin invMap
(b2, _) = Map.findMax invMap
instance (WreathPermutable a, Show a, Show (WreathTwist a)) => Show (Wreath a) where
showsPrec _ = fromOptCycles . optShowCycles
-- | Counts the number of indices that are moved by the given wreath. Ignores
-- the twists: an index that is twisted in place does not add to the count.
numIndicesMoved :: (WreathPermutable a) => Wreath a -> Int
numIndicesMoved (Wreath arr) = foldl' f 0 (assocs arr)
where f count (src, Entry (tgt, _))
| src == tgt = count
| otherwise = succ count
-- | Converts a wreath into disjoint cycles.
toCycles :: forall a. (WreathPermutable a) => Wreath a -> [[Entry a]]
toCycles (Wreath arr) =
let (cs, _) = foldl' findCycle ([], Set.empty) (range (bounds arr)) in reverse cs
where findCycle (cs, seen) src =
if src `Set.member` seen then (cs, seen)
else let e@(Entry (tgt, t)) = arr ! src
in if tgt == src && t == one then (cs, seen)
else let (c, srce, seen') = cycle src e seen in ((srce:c):cs, seen')
cycle hd e@(Entry (a, t)) seen =
if a == hd then ([], e, seen)
else let (c, srce, seen') = cycle hd (arr!a) (Set.insert a seen)
in (e:c, srce, seen')
-- | Converts a list of cycles into a wreath.
fromCycles :: (WreathPermutable a) => [[Entry a]] -> Wreath a
fromCycles cs = Wreath (trim arr)
where arr = base // concatMap fromCycle cs
base = array (minBound, maxBound) [(a, Entry (a, one)) | a <- [minBound..maxBound]]
fromCycle es = zip (map getItem es) (rotate 1 es)
getItem (Entry (a, _)) = a
-- | Optionally shows a wreath as its disjoint cycles; an empty wreath returns
-- Nothing.
optShowCycles :: (WreathPermutable a, Show a, Show (WreathTwist a)) => Wreath a -> OptS
optShowCycles w = showCycles' (toCycles w)
where showCycles' [] = Nothing
showCycles' [c] = toOptS $ showParen True (showEntries c)
showCycles' (c:cs) = showCycles' [c] $* showCycles' cs
showEntries [e] = shows e
showEntries (e:es) = shows e . showChar ' ' . showEntries es
-- | Shows a pair of empty parentheses.
showEmptyParens :: ShowS
showEmptyParens = showString "()"
-- | Converts an OptS of cycles into a ShowS.
fromOptCycles :: OptS -> ShowS
fromOptCycles = fromOptS showEmptyParens