├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── package.json ├── src └── Matrix.purs └── test └── Main.purs /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /.psci* 6 | /src/.webpack.js 7 | .DS_Store 8 | /package-lock.json 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: trusty 3 | sudo: required 4 | node_js: 5 | - 10 6 | install: 7 | - npm i purescript pulp bower -g 8 | - bower i 9 | script: 10 | - pulp build && pulp test 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU Lesser General Public License version 3 2 | =========================================== 3 | 4 | Version 3, 29 June 2007 5 | 6 | Copyright © 2007 Free Software Foundation, Inc. 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this 9 | license document, but changing it is not allowed. 10 | 11 | This version of the GNU Lesser General Public License incorporates the 12 | terms and conditions of version 3 of the GNU General Public License, 13 | supplemented by the additional permissions listed below. 14 | 15 | **0. Additional Definitions.** 16 | 17 | As used herein, “this License” refers to version 3 of the GNU Lesser 18 | General Public License, and the “GNU GPL” refers to version 3 of the GNU 19 | General Public License. 20 | 21 | “The Library” refers to a covered work governed by this License, other 22 | than an Application or a Combined Work as defined below. 23 | 24 | An “Application” is any work that makes use of an interface provided by 25 | the Library, but which is not otherwise based on the Library. Defining a 26 | subclass of a class defined by the Library is deemed a mode of using an 27 | interface provided by the Library. 28 | 29 | A “Combined Work” is a work produced by combining or linking an 30 | Application with the Library. The particular version of the Library with 31 | which the Combined Work was made is also called the “Linked Version”. 32 | 33 | The “Minimal Corresponding Source” for a Combined Work means the 34 | Corresponding Source for the Combined Work, excluding any source code 35 | for portions of the Combined Work that, considered in isolation, are 36 | based on the Application, and not on the Linked Version. 37 | 38 | The “Corresponding Application Code” for a Combined Work means the 39 | object code and/or source code for the Application, including any data 40 | and utility programs needed for reproducing the Combined Work from the 41 | Application, but excluding the System Libraries of the Combined Work. 42 | 43 | **1. Exception to Section 3 of the GNU GPL.** 44 | 45 | You may convey a covered work under sections 3 and 4 of this License 46 | without being bound by section 3 of the GNU GPL. 47 | 48 | **2. Conveying Modified Versions.** 49 | 50 | If you modify a copy of the Library, and, in your modifications, a 51 | facility refers to a function or data to be supplied by an Application 52 | that uses the facility (other than as an argument passed when the 53 | facility is invoked), then you may convey a copy of the modified 54 | version: 55 | 56 | a. under this License, provided that you make a good faith effort to 57 | ensure that, in the event an Application does not supply the 58 | function or data, the facility still operates, and performs whatever 59 | part of its purpose remains meaningful, or 60 | 61 | b. under the GNU GPL, with none of the additional permissions of this 62 | License applicable to that copy. 63 | 64 | **3. Object Code Incorporating Material from Library Header Files.** 65 | 66 | The object code form of an Application may incorporate material from a 67 | header file that is part of the Library. You may convey such object code 68 | under terms of your choice, provided that, if the incorporated material 69 | is not limited to numerical parameters, data structure layouts and 70 | accessors, or small macros, inline functions and templates (ten or fewer 71 | lines in length), you do both of the following: 72 | 73 | a. Give prominent notice with each copy of the object code that the 74 | Library is used in it and that the Library and its use are covered 75 | by this License. 76 | 77 | b. Accompany the object code with a copy of the GNU GPL and this 78 | license document. 79 | 80 | **4. Combined Works.** 81 | 82 | You may convey a Combined Work under terms of your choice that, taken 83 | together, effectively do not restrict modification of the portions of 84 | the Library contained in the Combined Work and reverse engineering for 85 | debugging such modifications, if you also do each of the following: 86 | 87 | a. Give prominent notice with each copy of the Combined Work that the 88 | Library is used in it and that the Library and its use are covered 89 | by this License. 90 | 91 | b. Accompany the Combined Work with a copy of the GNU GPL and this 92 | license document. 93 | 94 | c. For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among these 96 | notices, as well as a reference directing the user to the copies of 97 | the GNU GPL and this license document. 98 | 99 | d. Do one of the following: 100 | 101 | 1. Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to recombine 104 | or relink the Application with a modified version of the Linked 105 | Version to produce a modified Combined Work, in the manner 106 | specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 2. Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time a 111 | copy of the Library already present on the user’s computer 112 | system, and (b) will operate properly with a modified version of 113 | the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e. Provide Installation Information, but only if you would otherwise be 117 | required to provide such information under section 6 of the GNU GPL, 118 | and only to the extent that such information is necessary to install 119 | and execute a modified version of the Combined Work produced by 120 | recombining or relinking the Application with a modified version of 121 | the Linked Version. (If you use option 4d0, the Installation 122 | Information must accompany the Minimal Corresponding Source and 123 | Corresponding Application Code. If you use option 4d1, you must 124 | provide the Installation Information in the manner specified by 125 | section 6 of the GNU GPL for conveying Corresponding Source.) 126 | 127 | **5. Combined Libraries.** 128 | 129 | You may place library facilities that are a work based on the Library 130 | side by side in a single library together with other library facilities 131 | that are not Applications and are not covered by this License, and 132 | convey such a combined library under terms of your choice, if you do 133 | both of the following: 134 | 135 | a. Accompany the combined library with a copy of the same work based on 136 | the Library, uncombined with any other library facilities, conveyed 137 | under the terms of this License. 138 | 139 | b. Give prominent notice with the combined library that part of it is a 140 | work based on the Library, and explaining where to find the 141 | accompanying uncombined form of the same work. 142 | 143 | **6. Revised Versions of the GNU Lesser General Public License.** 144 | 145 | The Free Software Foundation may publish revised and/or new versions of 146 | the GNU Lesser General Public License from time to time. Such new 147 | versions will be similar in spirit to the present version, but may 148 | differ in detail to address new problems or concerns. 149 | 150 | Each version is given a distinguishing version number. If the Library as 151 | you received it specifies that a certain numbered version of the GNU 152 | Lesser General Public License “or any later version” applies to it, you 153 | have the option of following the terms and conditions either of that 154 | published version or of any later version published by the Free Software 155 | Foundation. If the Library as you received it does not specify a version 156 | number of the GNU Lesser General Public License, you may choose any 157 | version of the GNU Lesser General Public License ever published by the 158 | Free Software Foundation. 159 | 160 | If the Library as you received it specifies that a proxy can decide 161 | whether future versions of the GNU Lesser General Public License shall 162 | apply, that proxy’s public statement of acceptance of any version is 163 | permanent authorization for you to choose that version for the Library. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | purescript-matrices 2 | == 3 | [![Build Status](https://travis-ci.org/kRITZCREEK/purescript-matrices.svg?branch=master)](https://travis-ci.org/kRITZCREEK/purescript-matrices) 4 | [![Dependency Status](https://www.versioneye.com/user/projects/57ca18f769d94900419ca37d/badge.svg?style=flat-square)](https://www.versioneye.com/user/projects/57ca18f769d94900419ca37d) 5 | 6 | purescript-matrices on Pursuit 8 | 9 | 10 | 11 | A two-dimensional Matrix type for PureScript with a simple and straightforward 12 | API. [Documentation is available on Pursuit](https://pursuit.purescript.org/packages/purescript-matrices) 13 | 14 | 15 | Inspired by https://github.com/eeue56/elm-flat-matrix. 16 | 17 | ## License 18 | 19 | Copyright 2016 Christoph Hegemann 20 | 21 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 22 | 23 | See the LICENSE file for further details. 24 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-matrices", 3 | "ignore": [ 4 | "**/.*", 5 | "node_modules", 6 | "bower_components", 7 | "output" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/kritzcreek/purescript-matrices.git" 12 | }, 13 | "license": "LGPL-3.0-or-later", 14 | "dependencies": { 15 | "purescript-arrays": "^5.3.1", 16 | "purescript-strings": "^4.0.1" 17 | }, 18 | "devDependencies": { 19 | "purescript-psci-support": "^4.0.0", 20 | "purescript-spec": "^4.0.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "purescript": "^0.13" 4 | }, 5 | "scripts": { 6 | "compile": "purs compile 'src/**/*.purs' 'test/**/*.purs' 'bower_components/*/src/**/*.purs'", 7 | "test": "npm run compile && node -e 'require(\"./output/Test.Main\").main()'" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Matrix.purs: -------------------------------------------------------------------------------- 1 | module Matrix 2 | ( Matrix() 3 | , height 4 | , width 5 | , repeat 6 | , fromArray 7 | , get 8 | , getRow 9 | , getColumn 10 | , rows 11 | , columns 12 | , prettyPrintMatrix 13 | , empty 14 | , isEmpty 15 | , set 16 | , modify 17 | , toIndexedArray 18 | , indexedMap 19 | , zipWith 20 | ) where 21 | 22 | 23 | import Prelude 24 | 25 | import Data.Array (mapMaybe, mapWithIndex) 26 | import Data.Array as Array 27 | import Data.Foldable (class Foldable, foldr, intercalate, maximum, foldMap, foldl) 28 | import Data.Maybe (fromMaybe, Maybe(..)) 29 | import Data.String as String 30 | import Data.String.CodeUnits as StringCU 31 | import Data.Traversable (class Traversable, all, sequenceDefault, traverse) 32 | import Data.Tuple (Tuple(Tuple)) 33 | import Data.Unfoldable (unfoldr) 34 | 35 | -- | A two-dimensional Matrix. Its Show instance is meant as a tool for 36 | -- | debugging/pretty-printing. 37 | newtype Matrix a = 38 | Matrix { size ∷ {x ∷ Int, y ∷ Int} 39 | , values ∷ Array a 40 | } 41 | 42 | instance showMatrix ∷ Show a ⇒ Show (Matrix a) where 43 | show = prettyPrintMatrix show 44 | 45 | instance eqMatrix ∷ Eq a ⇒ Eq (Matrix a) where 46 | eq m1 m2 = values m1 == values m2 47 | 48 | instance functorMatrix ∷ Functor Matrix where 49 | map = mapMatrix 50 | 51 | mapMatrix ∷ ∀ a b. (a → b) → Matrix a → Matrix b 52 | mapMatrix f (Matrix m) = 53 | Matrix m {values = map f m.values} 54 | 55 | instance foldableMatrix ∷ Foldable Matrix where 56 | foldr f b m = foldr f b (values m) 57 | foldl f b m = foldl f b (values m) 58 | foldMap f m = foldMap f (values m) 59 | 60 | instance traversableMatrix ∷ Traversable Matrix where 61 | traverse f = overValues (traverse f) 62 | sequence = sequenceDefault 63 | 64 | -- | Returns the height of a matrix. 65 | height ∷ ∀ a. Matrix a → Int 66 | height (Matrix m) = m.size.y 67 | 68 | -- | Returns the width of a matrix. 69 | width ∷ ∀ a. Matrix a → Int 70 | width (Matrix m) = m.size.x 71 | 72 | -- | Repeats the same value and creates a width × height `Matrix`. 73 | -- | 74 | -- | ```purescript 75 | -- | > repeat 2 3 "X" 76 | -- | "X", "X" 77 | -- | "X", "X" 78 | -- | "X", "X" 79 | -- | ``` 80 | repeat ∷ ∀ a. Int → Int → a → Matrix a 81 | repeat x y v = 82 | Matrix { size: {x, y} 83 | , values: Array.replicate (x * y) v 84 | } 85 | 86 | -- | The empty Matrix. 87 | empty ∷ ∀ a. Matrix a 88 | empty = Matrix {size: {x: 0, y: 0}, values: []} 89 | 90 | -- | Checks whether a Matrix is empty 91 | isEmpty ∷ ∀ a. Matrix a → Boolean 92 | isEmpty (Matrix {values: vs}) = Array.null vs 93 | 94 | -- | Constructs a Matrix from an Array of Arrays. Returns `Nothing` if the 95 | -- | dimensions don't line up. 96 | -- | 97 | -- | ```purescript 98 | -- | > fromMaybe empty (fromArray [[1,2,3], [4,5,6]]) 99 | -- | 1, 2, 3 100 | -- | 4, 5, 6 101 | -- | 102 | -- | > fromArray [[1,2,3], [4,5]] 103 | -- | Nothing 104 | -- | ``` 105 | fromArray ∷ ∀ a. Array (Array a) → Maybe (Matrix a) 106 | fromArray vals = 107 | let 108 | height' = Array.length vals 109 | width' = fromMaybe 0 (Array.head vals <#> Array.length) 110 | allSame = all ((width' == _) <<< Array.length) vals 111 | in 112 | if not allSame then 113 | Nothing 114 | else 115 | Just (Matrix {size: {x: width', y: height'}, values: Array.concat vals}) 116 | 117 | -- | Returns the value at column, row or `Nothing` if the index was out of 118 | -- | bounds. 119 | get ∷ ∀ a. Int → Int → Matrix a → Maybe a 120 | get x y m 121 | | x >= 0 && y >= 0 && x < width m && y < height m = 122 | (values m) Array.!! (y * (width m) + x) 123 | | otherwise = Nothing 124 | 125 | -- | Sets the value at column, row or returns `Nothing` if the index was out of 126 | -- | bounds. 127 | set ∷ ∀ a. Int → Int → a → Matrix a → Maybe (Matrix a) 128 | set x y new = modify x y (const new) 129 | 130 | -- | Applies the given function to the element at column, row or returns Nothing 131 | -- | if the index was out of bounds. 132 | modify ∷ ∀ a. Int → Int → (a → a) → Matrix a → Maybe (Matrix a) 133 | modify x y new m 134 | | x >= 0 && y >= 0 && x < width m && y < height m = 135 | overValues (Array.modifyAt (y * (width m) + x) new) m 136 | | otherwise = Nothing 137 | 138 | 139 | -- | Get the row at the given index. 140 | getRow ∷ ∀ a. Int → Matrix a → Maybe (Array a) 141 | getRow y m 142 | | y < 0 || y >= height m = Nothing 143 | | otherwise = 144 | let 145 | w = width m 146 | start = y * w 147 | end = start + w 148 | in 149 | Just (Array.slice start end (values m)) 150 | 151 | -- | Get the column at the given index. 152 | getColumn ∷ ∀ a. Int → Matrix a → Maybe (Array a) 153 | getColumn x m 154 | | x < 0 || x >= width m = Nothing 155 | | otherwise = 156 | let 157 | w = width m 158 | maxIndex = Array.length (values m) - 1 159 | indices = unfoldr (\ix → 160 | if ix > maxIndex then 161 | Nothing 162 | else 163 | Just (Tuple ix (ix + w))) x 164 | in 165 | traverse ((values m) Array.!! _) indices 166 | 167 | -- | Get all the rows in the matrix 168 | rows ∷ ∀ a. Matrix a → Array (Array a) 169 | rows m = 170 | 0 # unfoldr \rowIndex -> do 171 | row <- getRow rowIndex m 172 | pure (Tuple row (rowIndex + 1)) 173 | 174 | -- | Get all the columns in the matrix 175 | columns ∷ ∀ a. Matrix a → Array (Array a) 176 | columns m = 177 | 0 # unfoldr \columnIndex -> 178 | let 179 | oneColumn :: Array a 180 | oneColumn = 0 # unfoldr \rowIndex -> do 181 | el <- get columnIndex rowIndex m 182 | pure (Tuple el (rowIndex + 1)) 183 | in 184 | if Array.null oneColumn 185 | then Nothing 186 | else Just (Tuple oneColumn (columnIndex + 1)) 187 | 188 | -- | Convert a `Matrix` to an indexed Array 189 | toIndexedArray ∷ ∀ a. Matrix a → Array {x ∷ Int, y ∷ Int, value ∷ a} 190 | toIndexedArray m = 191 | let 192 | w = width m 193 | f ix a = { x: ix `mod` w 194 | , y: ix / w 195 | , value: a 196 | } 197 | in 198 | mapWithIndex f (values m) 199 | 200 | -- | Apply a function to every element in the given Matrix taking its indices 201 | -- | into account 202 | indexedMap ∷ ∀ a b. (Int → Int → a → b) → Matrix a → Matrix b 203 | indexedMap f m = 204 | Matrix { size: size m 205 | , values: map (\ {x, y, value} → f x y value) (toIndexedArray m) 206 | } 207 | 208 | -- | Combines two Matrices with the same dimensions by combining elements at the 209 | -- | same index with the given function. Returns Nothing on a dimension 210 | -- | mismatch. 211 | zipWith ∷ ∀ a b c. (a → b → c) → Matrix a → Matrix b → Maybe (Matrix c) 212 | zipWith f a b 213 | | width a /= width b || height a /= height b = Nothing 214 | | otherwise = 215 | Just $ Matrix { size: size a 216 | , values: Array.zipWith f (values a) (values b) 217 | } 218 | 219 | -- | Pretty prints a matrix using the given formatting function on every element 220 | prettyPrintMatrix ∷ ∀ a. (a → String) → Matrix a → String 221 | prettyPrintMatrix showElem m' 222 | | isEmpty m' = "()" 223 | | otherwise = 224 | let 225 | m = mapMatrix showElem m' 226 | w = width m 227 | h = height m 228 | columnsm = traverse (flip getColumn m) (Array.range 0 (w - 1)) 229 | acc = Array.replicate h "" 230 | in 231 | case columnsm of 232 | Nothing → "Dimensions error" 233 | Just columns' → 234 | intercalate "\n" 235 | (mapMaybe (String.stripSuffix (String.Pattern ", ")) 236 | (foldr appendColumn acc columns')) 237 | where 238 | appendColumn column acc = 239 | let 240 | maxLength = fromMaybe 0 $ maximum (map String.length column) 241 | app previous next = leftPad maxLength next <> ", " <> previous 242 | in 243 | Array.zipWith app acc column 244 | 245 | -- private 246 | 247 | leftPad ∷ Int → String → String 248 | leftPad x s = 249 | StringCU.fromCharArray (Array.replicate (x - (String.length s)) ' ') <> s 250 | 251 | values ∷ ∀ a. Matrix a → Array a 252 | values (Matrix m) = m.values 253 | 254 | size ∷ ∀ a. Matrix a → {x ∷ Int, y ∷ Int} 255 | size (Matrix m) = m.size 256 | 257 | overValues 258 | ∷ ∀ a b f. (Functor f) 259 | ⇒ (Array a → f (Array b)) 260 | → Matrix a 261 | → f (Matrix b) 262 | overValues f (Matrix m) = 263 | Matrix <$> {size: m.size, values: _} <$> f m.values 264 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Data.Maybe (fromMaybe, Maybe(Just)) 6 | import Effect (Effect) 7 | import Effect.Aff (launchAff_) 8 | import Matrix (repeat, zipWith, height, width, empty, fromArray, get, rows, columns) 9 | import Test.Spec (describe, it) 10 | import Test.Spec.Assertions (shouldEqual) 11 | import Test.Spec.Reporter.Console (consoleReporter) 12 | import Test.Spec.Runner (runSpec) 13 | 14 | main ∷ Effect Unit 15 | main = launchAff_ $ runSpec [consoleReporter] do 16 | describe "purescript-matrices" do 17 | describe "Creating matrices" do 18 | it "repeat" do 19 | let m = repeat 2 2 5 20 | Just m `shouldEqual` fromArray [[5, 5], [5, 5]] 21 | it "creates a matrix from an array" do 22 | let m = fromMaybe empty (fromArray [[1,2,3], [4,5,6]]) 23 | width m `shouldEqual` 3 24 | height m `shouldEqual` 2 25 | get 1 0 m `shouldEqual` Just 2 26 | describe "Operations on matrices" do 27 | it "zips two matrices" do 28 | let m = fromMaybe empty (zipWith (+) (repeat 2 2 1) (repeat 2 2 1)) 29 | m `shouldEqual` repeat 2 2 2 30 | it "get all rows from a matrix" do 31 | rows (repeat 2 3 1) `shouldEqual` [[1, 1], [1, 1], [1, 1]] 32 | it "get all columns from a matrix" do 33 | columns (repeat 2 3 1) `shouldEqual` [[1, 1, 1], [1, 1, 1]] 34 | --------------------------------------------------------------------------------