├── .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 | [](https://travis-ci.org/kRITZCREEK/purescript-matrices)
4 | [](https://www.versioneye.com/user/projects/57ca18f769d94900419ca37d)
5 |
6 |
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 |
--------------------------------------------------------------------------------