diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c96efc1..2f47ebec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Breaking changes: New features: - Added `transpose` to `Array` (#225 by @newlandsvalley and @JordanMartinez) +- Added `transpose` and `transpose' `to `Array.NonEmpty` (#227 by @newlandsvalley and @JordanMartinez) Bugfixes: diff --git a/bower.json b/bower.json index 0bb4b9df..bc2bff74 100644 --- a/bower.json +++ b/bower.json @@ -22,6 +22,7 @@ "purescript-nonempty": "^7.0.0", "purescript-partial": "^4.0.0", "purescript-prelude": "^6.0.0", + "purescript-safe-coerce": "^2.0.0", "purescript-st": "^6.0.0", "purescript-tailrec": "^6.0.0", "purescript-tuples": "^7.0.0", diff --git a/src/Data/Array/NonEmpty.purs b/src/Data/Array/NonEmpty.purs index 332cd695..35a8e3a5 100644 --- a/src/Data/Array/NonEmpty.purs +++ b/src/Data/Array/NonEmpty.purs @@ -64,6 +64,8 @@ module Data.Array.NonEmpty , foldMap1 , fold1 , intercalate + , transpose + , transpose' , scanl , scanr @@ -133,6 +135,7 @@ import Data.Tuple (Tuple(..)) import Data.Unfoldable (class Unfoldable) import Data.Unfoldable1 (class Unfoldable1, unfoldr1) import Partial.Unsafe (unsafePartial) +import Safe.Coerce (coerce) import Unsafe.Coerce (unsafeCoerce) -- | Internal - adapt an Array transform to NonEmptyArray @@ -361,6 +364,44 @@ fold1 = F.fold1 intercalate :: forall a. Semigroup a => a -> NonEmptyArray a -> a intercalate = F.intercalate +-- | The 'transpose' function transposes the rows and columns of its argument. +-- | For example, +-- | +-- | ```purescript +-- | transpose +-- | (NonEmptyArray [ NonEmptyArray [1, 2, 3] +-- | , NonEmptyArray [4, 5, 6] +-- | ]) == +-- | (NonEmptyArray [ NonEmptyArray [1, 4] +-- | , NonEmptyArray [2, 5] +-- | , NonEmptyArray [3, 6] +-- | ]) +-- | ``` +-- | +-- | If some of the rows are shorter than the following rows, their elements are skipped: +-- | +-- | ```purescript +-- | transpose +-- | (NonEmptyArray [ NonEmptyArray [10, 11] +-- | , NonEmptyArray [20] +-- | , NonEmptyArray [30, 31, 32] +-- | ]) == +-- | (NomEmptyArray [ NonEmptyArray [10, 20, 30] +-- | , NonEmptyArray [11, 31] +-- | , NonEmptyArray [32] +-- | ]) +-- | ``` +transpose :: forall a. NonEmptyArray (NonEmptyArray a) -> NonEmptyArray (NonEmptyArray a) +transpose = + (coerce :: (Array (Array a)) -> (NonEmptyArray (NonEmptyArray a))) + <<< A.transpose <<< coerce + +-- | `transpose`' is identical to `transpose` other than that the inner arrays are each +-- | a standard `Array` and not a `NonEmptyArray`. However, the result is wrapped in a +-- | `Maybe` to cater for the case where the inner `Array` is empty and must return `Nothing`. +transpose' :: forall a. NonEmptyArray (Array a) -> Maybe (NonEmptyArray (Array a)) +transpose' = fromArray <<< A.transpose <<< coerce + scanl :: forall a b. (b -> a -> b) -> b -> NonEmptyArray a -> NonEmptyArray b scanl f x = unsafeAdapt $ A.scanl f x diff --git a/test/Test/Data/Array/NonEmpty.purs b/test/Test/Data/Array/NonEmpty.purs index 7f2a9fca..0ee98550 100644 --- a/test/Test/Data/Array/NonEmpty.purs +++ b/test/Test/Data/Array/NonEmpty.purs @@ -397,6 +397,25 @@ testNonEmptyArray = do log "traverse1 should work" assert $ traverse1 Just (fromArray [1, 2, 3, 4]) == NEA.fromArray [1, 2, 3, 4] + log "transpose swaps rows and columns for a regular two-dimension array" + assert $ NEA.transpose (fromArray [ (fromArray [1,2,3]), (fromArray [4,5,6]), (fromArray [7,8,9])]) == + (fromArray [ (fromArray [1,4,7]), (fromArray [2,5,8]), (fromArray [3,6,9])]) + + log "transpose skips elements when rows don't match" + assert $ NEA.transpose (fromArray [ (fromArray [10,11]), (fromArray [20]), (fromArray [30,31,32])]) == + (fromArray [ (fromArray [10,20,30]), (fromArray [11,31]), (fromArray [32])]) + + log "transpose' handles the singleton empty array" + assert $ NEA.transpose' (fromArray [ [] ]) == (Nothing :: Maybe (NEA.NonEmptyArray (Array Int))) + + log "transpose' swaps rows and columns for a regular two-dimension array" + assert $ NEA.transpose' (fromArray [[1,2,3], [4,5,6], [7,8,9]]) == + (Just $ fromArray [[1,4,7], [2,5,8], [3,6,9]]) + + log "transpose' skips elements when rows don't match" + assert $ NEA.transpose' (fromArray [[10,11], [20], [30,31,32]]) == + (Just $ fromArray [[10,20,30], [11,31], [32]]) + odd :: Int -> Boolean odd n = n `mod` 2 /= zero