Skip to content

Commit

Permalink
Add transpose and transpose' to NonEmptyArray (#228)
Browse files Browse the repository at this point in the history
* Add transpose to Data.Array

Implementation courtesy of Jordan Martinez.

* Update CHANGELOG

* Improve layout of doc comments

* Add transpose and transpose' to NonEmptyArray

* Add dependency on safe-coerce

and implement the functions that transform 2D arrays between Array and
NonEmptyArray representations to use coerce.

* inline what were the two coerce2D functions

* Alter the signature of transpose'

so that it returns a Maybe.  This is necessary in order to handle the
edge case of transpose` (NonEmptyArray [ [] ]).
  • Loading branch information
newlandsvalley authored Aug 6, 2022
1 parent e5d80f3 commit 43e0284
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
1 change: 1 addition & 0 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
41 changes: 41 additions & 0 deletions src/Data/Array/NonEmpty.purs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ module Data.Array.NonEmpty
, foldMap1
, fold1
, intercalate
, transpose
, transpose'
, scanl
, scanr

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
19 changes: 19 additions & 0 deletions test/Test/Data/Array/NonEmpty.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 43e0284

Please sign in to comment.