Skip to content

Commit 6ba5987

Browse files
committed
feat: Allow single number or an Array of index specifications in subset()
* A single number is now interpreted as the entire "layer" at that position; adds `layer` method to Matrix for efficient support of this convention. * An array is just passed to index * A string form of a Range is now allowed in the index; the lower and upper limits may be elided, allowing `':'` as a wildcard for an entire dimension. * Also removes unnecessary dependence of set functions on Index. * Now passes all doc example testing for `subset`
1 parent f8d7d1d commit 6ba5987

File tree

23 files changed

+289
-146
lines changed

23 files changed

+289
-146
lines changed

src/expression/transform/subset.transform.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import { factory } from '../../utils/factory.js'
22
import { errorTransform } from './utils/errorTransform.js'
3-
import { createSubset } from '../../function/matrix/subset.js'
3+
import { createSubset, dependencies } from '../../function/matrix/subset.js'
44

55
const name = 'subset'
6-
const dependencies = ['typed', 'matrix', 'zeros', 'add']
76

8-
export const createSubsetTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, zeros, add }) => {
9-
const subset = createSubset({ typed, matrix, zeros, add })
7+
export const createSubsetTransform = /* #__PURE__ */ factory(name, dependencies, provided => {
8+
const subset = createSubset(provided)
109

1110
/**
1211
* Attach a transform function to math.subset
1312
* Adds a property transform containing the transform function.
1413
*
1514
* This transform creates a range which includes the end value
1615
*/
17-
return typed('subset', {
16+
return provided.typed('subset', {
1817
'...any': function (args) {
1918
try {
2019
return subset.apply(null, args)

src/function/matrix/subset.js

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,35 @@ import { DimensionError } from '../../error/DimensionError.js'
66
import { factory } from '../../utils/factory.js'
77

88
const name = 'subset'
9-
const dependencies = ['typed', 'matrix', 'zeros', 'add']
9+
export const dependencies = ['typed', 'matrix', 'zeros', 'add', 'index', 'size']
1010

11-
export const createSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, zeros, add }) => {
11+
export const createSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, zeros, add, index, size }) => {
1212
/**
1313
* Get or set a subset of a matrix or string.
1414
*
15+
* The second argument should be a specification of the desired subset of
16+
* the first argument. Therefore, the second argument is typically an Index
17+
* object produced by the `index` function, which see (in short, for each
18+
* dimension of the matrix, the Index specifies one position or a list or
19+
* range of positions to include in the subset).
20+
*
21+
* For convenience, the second argument may be simply a number n, in which
22+
* case the subset is the entire section of one dimension lower than the
23+
* given matrix, at position n. In other words, it corresponds to the
24+
* entry of a vector at position n, or the row of a 2D matrix at position
25+
* n, etc.
26+
*
27+
* Furthermore, it can also be an array of appropriate arguments to the
28+
* `index` function, in which case it will be passed to the `index` function
29+
* for you. Beware, though: in the case of a 1d vector v,
30+
* `math.subset(v, [2, 3])` will not therefore return the elements at
31+
* positions 2 and 3, because passing those arguments to `index` would
32+
* attempt to index the first dimension of v by and its nonexistent second
33+
* dimension by 3. You can call `math.subset(v, [[2, 3]])` to obtain the
34+
* elements at positions 2 and 3, because now the inner `[2, 3]` will be
35+
* interpreted as the list of positions with which to index into the first
36+
* dimension.
37+
*
1538
* Syntax:
1639
* math.subset(value, index) // retrieve a subset
1740
* math.subset(value, index, replacement [, defaultValue]) // replace a subset
@@ -20,23 +43,29 @@ export const createSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed
2043
*
2144
* // get a subset
2245
* const d = [[1, 2], [3, 4]]
23-
* math.subset(d, math.index(1, 0)) // returns 3
24-
* math.subset(d, math.index([0, 1], [1])) // returns [[2], [4]]
25-
* math.subset(d, math.index([false, true], [0])) // returns [[3]]
46+
* math.subset(d, math.index(1, 0)) // returns 3 ...
47+
* math.subset(d, [1, 0]) // returns 3 ...
48+
* math.subset(d, math.index([0, 1], [1])) // Array [[2], [4]] ...
49+
* math.subset(d, [[0, 1], [1]]) // Array [[2], [4]] ...
50+
* math.subset(d, math.index([false, true], [0])) // Array [[3]] ...
51+
* math.subset(d, [[false, true], 0]) // Array [3] ...
52+
* math.subset(d, 1) // Array [3, 4]
2653
*
2754
* // replace a subset
2855
* const e = []
29-
* const f = math.subset(e, math.index(0, [0, 2]), [5, 6]) // f = [[5, 0, 6]]
30-
* const g = math.subset(f, math.index(1, 1), 7, 0) // g = [[5, 0, 6], [0, 7, 0]]
31-
* math.subset(g, math.index([false, true], 1), 8) // returns [[5, 0, 6], [0, 8, 0]]
56+
* const f = math.subset(e, math.index(0, [0, 2]), [5, 6])
57+
* f // Array [[5, 0, 6]] ...
58+
* const g = math.subset(f, math.index(1, 1), 7, 0)
59+
* g // Array [[5, 0, 6], [0, 7, 0]] ...
60+
* math.subset(g, math.index([false, true], 1), 8) // Array [[5, 0, 6], [0, 8, 0]]
3261
*
3362
* // get submatrix using ranges
3463
* const M = [
3564
* [1, 2, 3],
3665
* [4, 5, 6],
3766
* [7, 8, 9]
3867
* ]
39-
* math.subset(M, math.index(math.range(0,2), math.range(0,3))) // [[1, 2, 3], [4, 5, 6]]
68+
* math.subset(M, math.index(math.range(0,2), math.range(0,3))) // Array [[1, 2, 3], [4, 5, 6]]
4069
*
4170
* See also:
4271
*
@@ -75,6 +104,23 @@ export const createSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed
75104

76105
'string, Index': _getSubstring,
77106

107+
// Allow single number index to get layer:
108+
'Matrix, number': function (M, position) {
109+
return M.layer(position)
110+
},
111+
112+
'Array, number': function (A, position) {
113+
return A[position]
114+
},
115+
116+
'string, number': function (s, position) {
117+
return s.charAt(position)
118+
},
119+
120+
// Otherwise pass second array argument to index function for convenience
121+
'Matrix | Array | Object | string, Array': typed.referToSelf(
122+
self => (v, i) => self(v, index(...i))),
123+
78124
// set subset
79125
'Matrix, Index, any, any': function (value, index, replacement, defaultValue) {
80126
if (isEmptyIndex(index)) { return value }
@@ -89,19 +135,31 @@ export const createSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed
89135
}
90136
}),
91137

92-
'Array, Index, any': typed.referTo('Matrix, Index, any, any', function (subsetRef) {
93-
return function (value, index, replacement) {
94-
return subsetRef(matrix(value), index, replacement, undefined).valueOf()
95-
}
96-
}),
97-
98-
'Matrix, Index, any': typed.referTo('Matrix, Index, any, any', function (subsetRef) {
99-
return function (value, index, replacement) { return subsetRef(value, index, replacement, undefined) }
100-
}),
101-
102138
'string, Index, string': _setSubstring,
103139
'string, Index, string, string': _setSubstring,
104-
'Object, Index, any': _setObjectProperty
140+
'Object, Index, any': _setObjectProperty,
141+
142+
// fourth argument defaults to undefined:
143+
'Matrix | Array, Index | Array | number, any': typed.referToSelf(
144+
self => (v, pos, rep) => self(v, pos, rep, undefined)
145+
),
146+
147+
// Allow 2nd index to be a number:
148+
'Matrix | Array | Object | string, number, any, any': typed.referToSelf(
149+
self => (v, pos, rep, def) => {
150+
const ix = [pos]
151+
let wildcards = size(v).length
152+
while (--wildcards > 0) ix.push(':')
153+
return self(v, index(...ix), rep, def)
154+
}),
155+
156+
'string, number, string': typed.referTo(
157+
'string, Index, string', sis => (s, n, rep) => sis(s, index(n), rep)),
158+
159+
// Or allow 2nd argument to be an array of arguments to index
160+
'Matrix | Array | Object | string, Array, any, any': typed.referToSelf(
161+
self => (v, ixes, rep, def) => self(v, index(...ixes), rep, def)
162+
)
105163
})
106164

107165
/**

src/function/set/setCartesian.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { flatten } from '../../utils/array.js'
22
import { factory } from '../../utils/factory.js'
33

44
const name = 'setCartesian'
5-
const dependencies = ['typed', 'size', 'subset', 'compareNatural', 'Index', 'DenseMatrix']
5+
const dependencies = ['typed', 'size', 'subset', 'compareNatural']
66

7-
export const createSetCartesian = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural, Index, DenseMatrix }) => {
7+
export const createSetCartesian = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural }) => {
88
/**
99
* Create the cartesian product of two (multi)sets.
1010
* Multi-dimension arrays will be converted to single-dimension arrays
@@ -30,10 +30,10 @@ export const createSetCartesian = /* #__PURE__ */ factory(name, dependencies, ({
3030
return typed(name, {
3131
'Array | Matrix, Array | Matrix': function (a1, a2) {
3232
let result = []
33-
34-
if (subset(size(a1), new Index(0)) !== 0 && subset(size(a2), new Index(0)) !== 0) { // if any of them is empty, return empty
35-
const b1 = flatten(Array.isArray(a1) ? a1 : a1.toArray()).sort(compareNatural)
36-
const b2 = flatten(Array.isArray(a2) ? a2 : a2.toArray()).sort(compareNatural)
33+
// if either is empty, return empty
34+
if (size(a1)[0] !== 0 && size(a2)[0] !== 0) {
35+
const b1 = flatten(a1.valueOf()).sort(compareNatural)
36+
const b2 = flatten(a2.valueOf()).sort(compareNatural)
3737
result = []
3838
for (let i = 0; i < b1.length; i++) {
3939
for (let j = 0; j < b2.length; j++) {
@@ -42,11 +42,11 @@ export const createSetCartesian = /* #__PURE__ */ factory(name, dependencies, ({
4242
}
4343
}
4444
// return an array, if both inputs were arrays
45-
if (Array.isArray(a1) && Array.isArray(a2)) {
46-
return result
45+
if (Array.isArray(a1)) {
46+
if (Array.isArray(a2)) return result
47+
return a2.create(result)
4748
}
48-
// return a matrix otherwise
49-
return new DenseMatrix(result)
49+
return a1.create(result)
5050
}
5151
})
5252
})

src/function/set/setDifference.js

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { flatten, generalize, identify } from '../../utils/array.js'
22
import { factory } from '../../utils/factory.js'
33

44
const name = 'setDifference'
5-
const dependencies = ['typed', 'size', 'subset', 'compareNatural', 'Index', 'DenseMatrix']
5+
const dependencies = ['typed', 'size', 'subset', 'compareNatural']
66

7-
export const createSetDifference = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural, Index, DenseMatrix }) => {
7+
export const createSetDifference = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural }) => {
88
/**
99
* Create the difference of two (multi)sets: every element of set1, that is not the element of set2.
1010
* Multi-dimension arrays will be converted to single-dimension arrays before the operation.
@@ -28,15 +28,14 @@ export const createSetDifference = /* #__PURE__ */ factory(name, dependencies, (
2828
*/
2929
return typed(name, {
3030
'Array | Matrix, Array | Matrix': function (a1, a2) {
31-
let result
32-
if (subset(size(a1), new Index(0)) === 0) { // empty-anything=empty
33-
result = []
34-
} else if (subset(size(a2), new Index(0)) === 0) { // anything-empty=anything
35-
return flatten(a1.toArray())
36-
} else {
37-
const b1 = identify(flatten(Array.isArray(a1) ? a1 : a1.toArray()).sort(compareNatural))
38-
const b2 = identify(flatten(Array.isArray(a2) ? a2 : a2.toArray()).sort(compareNatural))
39-
result = []
31+
let result = []
32+
// empty - anything = empty
33+
if (size(a1)[0] !== 0) {
34+
if (size(a2)[0] === 0) { // anything - empty = anything
35+
return flatten(a1.valueOf())
36+
}
37+
const b1 = identify(flatten(a1.valueOf()).sort(compareNatural))
38+
const b2 = identify(flatten(a2.valueOf()).sort(compareNatural))
4039
let inb2
4140
for (let i = 0; i < b1.length; i++) {
4241
inb2 = false
@@ -51,12 +50,13 @@ export const createSetDifference = /* #__PURE__ */ factory(name, dependencies, (
5150
}
5251
}
5352
}
53+
result = generalize(result) // remove the identifiers
5454
// return an array, if both inputs were arrays
55-
if (Array.isArray(a1) && Array.isArray(a2)) {
56-
return generalize(result)
55+
if (Array.isArray(a1)) {
56+
if (Array.isArray(a2)) return result
57+
return a2.create(result)
5758
}
58-
// return a matrix otherwise
59-
return new DenseMatrix(generalize(result))
59+
return a1.create(result)
6060
}
6161
})
6262
})

src/function/set/setDistinct.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { factory } from '../../utils/factory.js'
44
const name = 'setDistinct'
55
const dependencies = ['typed', 'size', 'subset', 'compareNatural', 'Index', 'DenseMatrix']
66

7-
export const createSetDistinct = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural, Index, DenseMatrix }) => {
7+
export const createSetDistinct = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural }) => {
88
/**
99
* Collect the distinct elements of a multiset.
1010
* A multi-dimension array will be converted to a single-dimension array before the operation.
@@ -22,16 +22,14 @@ export const createSetDistinct = /* #__PURE__ */ factory(name, dependencies, ({
2222
* setMultiplicity
2323
*
2424
* @param {Array | Matrix} a A multiset
25-
* @return {Array | Matrix} A set containing the distinc elements of the multiset
25+
* @return {Array | Matrix} A set containing the distinct elements of the multiset
2626
*/
2727
return typed(name, {
2828
'Array | Matrix': function (a) {
29-
let result
30-
if (subset(size(a), new Index(0)) === 0) { // if empty, return empty
31-
result = []
32-
} else {
33-
const b = flatten(Array.isArray(a) ? a : a.toArray()).sort(compareNatural)
34-
result = []
29+
const result = []
30+
// if empty, return empty
31+
if (size(a)[0] !== 0) {
32+
const b = flatten(a.valueOf()).sort(compareNatural)
3533
result.push(b[0])
3634
for (let i = 1; i < b.length; i++) {
3735
if (compareNatural(b[i], b[i - 1]) !== 0) {
@@ -44,7 +42,7 @@ export const createSetDistinct = /* #__PURE__ */ factory(name, dependencies, ({
4442
return result
4543
}
4644
// return a matrix otherwise
47-
return new DenseMatrix(result)
45+
return a.create(result)
4846
}
4947
})
5048
})

src/function/set/setIntersect.js

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { flatten, generalize, identify } from '../../utils/array.js'
22
import { factory } from '../../utils/factory.js'
33

44
const name = 'setIntersect'
5-
const dependencies = ['typed', 'size', 'subset', 'compareNatural', 'Index', 'DenseMatrix']
5+
const dependencies = ['typed', 'size', 'subset', 'compareNatural']
66

7-
export const createSetIntersect = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural, Index, DenseMatrix }) => {
7+
export const createSetIntersect = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural }) => {
88
/**
99
* Create the intersection of two (multi)sets.
1010
* Multi-dimension arrays will be converted to single-dimension arrays before the operation.
@@ -28,13 +28,11 @@ export const createSetIntersect = /* #__PURE__ */ factory(name, dependencies, ({
2828
*/
2929
return typed(name, {
3030
'Array | Matrix, Array | Matrix': function (a1, a2) {
31-
let result
32-
if (subset(size(a1), new Index(0)) === 0 || subset(size(a2), new Index(0)) === 0) { // of any of them is empty, return empty
33-
result = []
34-
} else {
35-
const b1 = identify(flatten(Array.isArray(a1) ? a1 : a1.toArray()).sort(compareNatural))
36-
const b2 = identify(flatten(Array.isArray(a2) ? a2 : a2.toArray()).sort(compareNatural))
37-
result = []
31+
let result = []
32+
// if both are nonempty, we must compute the intersection
33+
if (size(a1)[0] !== 0 && size(a2)[0] !== 0) {
34+
const b1 = identify(flatten(a1.valueOf()).sort(compareNatural))
35+
const b2 = identify(flatten(a2.valueOf()).sort(compareNatural))
3836
for (let i = 0; i < b1.length; i++) {
3937
for (let j = 0; j < b2.length; j++) {
4038
if (compareNatural(b1[i].value, b2[j].value) === 0 && b1[i].identifier === b2[j].identifier) { // the identifier is always a decimal int
@@ -44,12 +42,13 @@ export const createSetIntersect = /* #__PURE__ */ factory(name, dependencies, ({
4442
}
4543
}
4644
}
45+
result = generalize(result) // remove the identifiers
4746
// return an array, if both inputs were arrays
48-
if (Array.isArray(a1) && Array.isArray(a2)) {
49-
return generalize(result)
47+
if (Array.isArray(a1)) {
48+
if (Array.isArray(a2)) return result
49+
return a2.create(result)
5050
}
51-
// return a matrix otherwise
52-
return new DenseMatrix(generalize(result))
51+
return a1.create(result)
5352
}
5453
})
5554
})

src/function/set/setIsSubset.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { flatten, identify } from '../../utils/array.js'
22
import { factory } from '../../utils/factory.js'
33

44
const name = 'setIsSubset'
5-
const dependencies = ['typed', 'size', 'subset', 'compareNatural', 'Index']
5+
const dependencies = ['typed', 'size', 'subset', 'compareNatural']
66

7-
export const createSetIsSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural, Index }) => {
7+
export const createSetIsSubset = /* #__PURE__ */ factory(name, dependencies, ({ typed, size, subset, compareNatural }) => {
88
/**
99
* Check whether a (multi)set is a subset of another (multi)set. (Every element of set1 is the element of set2.)
1010
* Multi-dimension arrays will be converted to single-dimension arrays before the operation.
@@ -28,9 +28,10 @@ export const createSetIsSubset = /* #__PURE__ */ factory(name, dependencies, ({
2828
*/
2929
return typed(name, {
3030
'Array | Matrix, Array | Matrix': function (a1, a2) {
31-
if (subset(size(a1), new Index(0)) === 0) { // empty is a subset of anything
31+
if (size(a1)[0] === 0) { // empty is a subset of anything
3232
return true
33-
} else if (subset(size(a2), new Index(0)) === 0) { // anything is not a subset of empty
33+
}
34+
if (size(a2)[0] === 0) { // anything nonempty is not a subset of empty
3435
return false
3536
}
3637
const b1 = identify(flatten(Array.isArray(a1) ? a1 : a1.toArray()).sort(compareNatural))

0 commit comments

Comments
 (0)