• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lunarmodules / Penlight / 7742759193

01 Feb 2024 02:24PM UTC coverage: 88.938% (-0.7%) from 89.675%
7742759193

push

github

web-flow
docs: Fix typos (#462)

7 of 7 new or added lines in 2 files covered. (100.0%)

120 existing lines in 14 files now uncovered.

5443 of 6120 relevant lines covered (88.94%)

256.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.45
/lua/pl/array2d.lua
1
--- Operations on two-dimensional arrays.
2
-- See @{02-arrays.md.Operations_on_two_dimensional_tables|The Guide}
3
--
4
-- The size of the arrays is determined by using the length operator `#` hence
5
-- the module is not `nil` safe, and the usual precautions apply.
6
--
7
-- Note: all functions taking `i1,j1,i2,j2` as arguments will normalize the
8
-- arguments using `default_range`.
9
--
10
-- Dependencies: `pl.utils`, `pl.tablex`, `pl.types`
11
-- @module pl.array2d
12

13
local tonumber,tostring,io,ipairs,string,table =
14
    _G.tonumber,_G.tostring,_G.io,_G.ipairs,_G.string,_G.table
12✔
15
local setmetatable,getmetatable = setmetatable,getmetatable
12✔
16

17
local tablex = require 'pl.tablex'
12✔
18
local utils = require 'pl.utils'
12✔
19
local types = require 'pl.types'
12✔
20
local imap,tmap,reduce,keys,tmap2,tset,index_by = tablex.imap,tablex.map,tablex.reduce,tablex.keys,tablex.map2,tablex.set,tablex.index_by
12✔
21
local remove = table.remove
12✔
22
local splitv,fprintf,assert_arg = utils.splitv,utils.fprintf,utils.assert_arg
12✔
23
local byte = string.byte
12✔
24
local stdout = io.stdout
12✔
25
local min = math.min
12✔
26

27

28
local array2d = {}
12✔
29

30
local function obj (int,out)
31
    local mt = getmetatable(int)
78✔
32
    if mt then
78✔
33
        setmetatable(out,mt)
×
34
    end
35
    return out
78✔
36
end
37

38
local function makelist (res)
39
    return setmetatable(res, require('pl.List'))
126✔
40
end
41

42
--- return the row and column size.
43
-- Size is calculated using the Lua length operator #, so usual precautions
44
-- regarding `nil` values apply.
45
-- @array2d a a 2d array
46
-- @treturn int number of rows (`#a`)
47
-- @treturn int number of cols (`#a[1]`)
48
function array2d.size (a)
12✔
49
    assert_arg(1,a,'table')
192✔
50
    return #a,#a[1]
192✔
51
end
52

53
do
54
    local function index (t,k)
55
        return t[k]
234✔
56
    end
57

58
    --- extract a column from the 2D array.
59
    -- @array2d a 2d array
60
    -- @param j column index
61
    -- @return 1d array
62
    function array2d.column (a,j)
12✔
63
        assert_arg(1,a,'table')
60✔
64
        return makelist(imap(index,a,j))
80✔
65
    end
66
end
67
local column = array2d.column
12✔
68

69
--- extract a row from the 2D array.
70
-- Added in line with `column`, for read-only purposes directly
71
-- accessing a[i] is more performant.
72
-- @array2d a 2d array
73
-- @param i row index
74
-- @return 1d array (copy of the row)
75
function array2d.row(a,i)
12✔
76
    assert_arg(1,a,'table')
48✔
77
    local row = a[i]
48✔
78
    local r = {}
48✔
79
    for n,v in ipairs(row) do
168✔
80
        r[n] = v
120✔
81
    end
82
    return makelist(r)
48✔
83
end
84

85
--- map a function over a 2D array
86
-- @func f a function of at least one argument
87
-- @array2d a 2d array
88
-- @param arg an optional extra argument to be passed to the function.
89
-- @return 2d array
90
function array2d.map (f,a,arg)
12✔
91
    assert_arg(2,a,'table')
6✔
92
    f = utils.function_arg(1,f)
8✔
93
    return obj(a,imap(function(row) return imap(f,row,arg) end, a))
20✔
94
end
95

96
--- reduce the rows using a function.
97
-- @func f a binary function
98
-- @array2d a 2d array
99
-- @return 1d array
100
-- @see pl.tablex.reduce
101
function array2d.reduce_rows (f,a)
12✔
102
    assert_arg(1,a,'table')
12✔
103
    return tmap(function(row) return reduce(f,row) end, a)
54✔
104
end
105

106
--- reduce the columns using a function.
107
-- @func f a binary function
108
-- @array2d a 2d array
109
-- @return 1d array
110
-- @see pl.tablex.reduce
111
function array2d.reduce_cols (f,a)
12✔
112
    assert_arg(1,a,'table')
6✔
113
    return tmap(function(c) return reduce(f,column(a,c)) end, keys(a[1]))
32✔
114
end
115

116
--- reduce a 2D array into a scalar, using two operations.
117
-- @func opc operation to reduce the final result
118
-- @func opr operation to reduce the rows
119
-- @param a 2D array
120
function array2d.reduce2 (opc,opr,a)
12✔
121
    assert_arg(3,a,'table')
6✔
122
    local tmp = array2d.reduce_rows(opr,a)
6✔
123
    return reduce(opc,tmp)
6✔
124
end
125

126
--- map a function over two arrays.
127
-- They can be both or either 2D arrays
128
-- @func f function of at least two arguments
129
-- @int ad order of first array (`1` if `a` is a list/array, `2` if it is a 2d array)
130
-- @int bd order of second array (`1` if `b` is a list/array, `2` if it is a 2d array)
131
-- @tab a 1d or 2d array
132
-- @tab b 1d or 2d array
133
-- @param arg optional extra argument to pass to function
134
-- @return 2D array, unless both arrays are 1D
135
function array2d.map2 (f,ad,bd,a,b,arg)
12✔
136
    assert_arg(1,a,'table')
18✔
137
    assert_arg(2,b,'table')
18✔
138
    f = utils.function_arg(1,f)
24✔
139
    if ad == 1 and bd == 2 then
18✔
140
        return imap(function(row)
10✔
141
            return tmap2(f,a,row,arg)
12✔
142
        end, b)
6✔
143
    elseif ad == 2 and bd == 1 then
12✔
144
        return imap(function(row)
10✔
145
            return tmap2(f,row,b,arg)
12✔
146
        end, a)
6✔
147
    elseif ad == 1 and bd == 1 then
6✔
148
        return tmap2(f,a,b)
×
149
    elseif ad == 2 and bd == 2 then
6✔
150
        return tmap2(function(rowa,rowb)
10✔
151
            return tmap2(f,rowa,rowb,arg)
12✔
152
        end, a,b)
6✔
153
    end
154
end
155

156
--- cartesian product of two 1d arrays.
157
-- @func f a function of 2 arguments
158
-- @array t1 a 1d table
159
-- @array t2 a 1d table
160
-- @return 2d table
161
-- @usage product('..',{1,2},{'a','b'}) == {{'1a','2a'},{'1b','2b'}}
162
function array2d.product (f,t1,t2)
12✔
163
    f = utils.function_arg(1,f)
16✔
164
    assert_arg(2,t1,'table')
12✔
165
    assert_arg(3,t2,'table')
12✔
166
    local res = {}
12✔
167
    for i,v in ipairs(t2) do
48✔
168
        res[i] = tmap(f,t1,v)
48✔
169
    end
170
    return res
12✔
171
end
172

173
--- flatten a 2D array.
174
-- (this goes over columns first.)
175
-- @array2d t 2d table
176
-- @return a 1d table
177
-- @usage flatten {{1,2},{3,4},{5,6}} == {1,2,3,4,5,6}
178
function array2d.flatten (t)
12✔
179
    local res = {}
18✔
180
    local k = 1
18✔
181
    local rows, cols = array2d.size(t)
18✔
182
    for r = 1, rows do
66✔
183
        local row = t[r]
48✔
184
        for c = 1, cols do
144✔
185
            res[k] = row[c]
96✔
186
            k = k + 1
96✔
187
        end
188
    end
189
    return makelist(res)
18✔
190
end
191

192
--- reshape a 2D array. Reshape the array by specifying a new nr of rows.
193
-- @array2d t 2d array
194
-- @int nrows new number of rows
195
-- @bool co use column-order (Fortran-style) (default false)
196
-- @return a new 2d array
197
function array2d.reshape (t,nrows,co)
12✔
198
    local nr,nc = array2d.size(t)
30✔
199
    local ncols = nr*nc / nrows
30✔
200
    local res = {}
30✔
201
    local ir,ic = 1,1
30✔
202
    for i = 1,nrows do
132✔
203
        local row = {}
102✔
204
        for j = 1,ncols do
414✔
205
            row[j] = t[ir][ic]
312✔
206
            if not co then
312✔
207
                ic = ic + 1
144✔
208
                if ic > nc then
144✔
209
                    ir = ir + 1
36✔
210
                    ic = 1
36✔
211
                end
212
            else
213
                ir = ir + 1
168✔
214
                if ir > nr then
168✔
215
                    ic = ic + 1
66✔
216
                    ir = 1
66✔
217
                end
218
            end
219
        end
220
        res[i] = row
102✔
221
    end
222
    return obj(t,res)
30✔
223
end
224

225
--- transpose a 2D array.
226
-- @array2d t 2d array
227
-- @return a new 2d array
228
function array2d.transpose(t)
12✔
229
  assert_arg(1,t,'table')
12✔
230
  local _, c = array2d.size(t)
12✔
231
  return array2d.reshape(t,c,true)
12✔
232
end
233

234
--- swap two rows of an array.
235
-- @array2d t a 2d array
236
-- @int i1 a row index
237
-- @int i2 a row index
238
-- @return t (same, modified 2d array)
239
function array2d.swap_rows (t,i1,i2)
12✔
240
    assert_arg(1,t,'table')
6✔
241
    t[i1],t[i2] = t[i2],t[i1]
6✔
242
    return t
6✔
243
end
244

245
--- swap two columns of an array.
246
-- @array2d t a 2d array
247
-- @int j1 a column index
248
-- @int j2 a column index
249
-- @return t (same, modified 2d array)
250
function array2d.swap_cols (t,j1,j2)
12✔
251
    assert_arg(1,t,'table')
6✔
252
    for _, row in ipairs(t) do
24✔
253
        row[j1],row[j2] = row[j2],row[j1]
18✔
254
    end
255
    return t
6✔
256
end
257

258
--- extract the specified rows.
259
-- @array2d t 2d array
260
-- @tparam {int} ridx a table of row indices
261
-- @return a new 2d array with the extracted rows
262
function array2d.extract_rows (t,ridx)
12✔
263
    return obj(t,index_by(t,ridx))
8✔
264
end
265

266
--- extract the specified columns.
267
-- @array2d t 2d array
268
-- @tparam {int} cidx a table of column indices
269
-- @return a new 2d array with the extracted columns
270
function array2d.extract_cols (t,cidx)
12✔
271
    assert_arg(1,t,'table')
6✔
272
    local res = {}
6✔
273
    for i = 1,#t do
30✔
274
        res[i] = index_by(t[i],cidx)
32✔
275
    end
276
    return obj(t,res)
6✔
277
end
278

279
--- remove a row from an array.
280
-- @function array2d.remove_row
281
-- @array2d t a 2d array
282
-- @int i a row index
283
array2d.remove_row = remove
12✔
284

285
--- remove a column from an array.
286
-- @array2d t a 2d array
287
-- @int j a column index
288
function array2d.remove_col (t,j)
12✔
289
    assert_arg(1,t,'table')
6✔
290
    for i = 1,#t do
30✔
291
        remove(t[i],j)
24✔
292
    end
293
end
294

295
do
296
    local function _parse (s)
297
        local r, c = s:match 'R(%d+)C(%d+)'
78✔
298
        if r then
78✔
299
            r,c = tonumber(r),tonumber(c)
18✔
300
            return r,c
18✔
301
        end
302
        c,r = s:match '(%a+)(%d+)'
60✔
303
        if c then
60✔
304
            local cv = 0
60✔
305
            for i = 1, #c do
132✔
306
              cv = cv * 26 + byte(c:sub(i,i)) - byte 'A' + 1
96✔
307
            end
308
            return tonumber(r), cv
60✔
309
        end
UNCOV
310
        error('bad cell specifier: '..s)
×
311
    end
312

313
    --- parse a spreadsheet range or cell.
314
    -- The range/cell can be specified either as 'A1:B2' or 'R1C1:R2C2' or for
315
    -- single cells as 'A1' or 'R1C1'.
316
    -- @string s a range (case insensitive).
317
    -- @treturn int start row
318
    -- @treturn int start col
319
    -- @treturn int end row (or `nil` if the range was a single cell)
320
    -- @treturn int end col (or `nil` if the range was a single cell)
321
    function array2d.parse_range (s)
12✔
322
        assert_arg(1,s,'string')
48✔
323
        s = s:upper()
64✔
324
        if s:find ':' then
48✔
325
            local start,finish = splitv(s,':')
30✔
326
            local i1,j1 = _parse(start)
30✔
327
            local i2,j2 = _parse(finish)
30✔
328
            return i1,j1,i2,j2
30✔
329
        else -- single value
330
            local i,j = _parse(s)
18✔
331
            return i,j
18✔
332
        end
333
    end
334
end
335

336
--- get a slice of a 2D array.
337
-- Same as `slice`.
338
-- @see slice
339
function array2d.range (...)
12✔
340
    return array2d.slice(...)
6✔
341
end
342

343
local default_range do
12✔
344
    local function norm_value(v, max)
345
        if not v then return v end
456✔
346
        if v < 0 then
456✔
347
            v = max + v + 1
36✔
348
        end
349
        if v < 1 then v = 1 end
456✔
350
        if v > max then v = max end
456✔
351
        return v
456✔
352
    end
353

354
    --- normalizes coordinates to valid positive entries and defaults.
355
    -- Negative indices will be counted from the end, too low, or too high
356
    -- will be limited by the array sizes.
357
    -- @array2d t a 2D array
358
    -- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
359
    -- @tparam[opt=1] int j1 start col
360
    -- @tparam[opt=N] int i2 end row
361
    -- @tparam[opt=M] int j2 end col
362
    -- @see parse_range
363
    -- @return i1, j1, i2, j2
364
    function array2d.default_range (t,i1,j1,i2,j2)
12✔
365
        if (type(i1) == 'string') and not (j1 or i2 or j2) then
114✔
366
            i1, j1, i2, j2 = array2d.parse_range(i1)
8✔
367
        end
368
        local nr, nc = array2d.size(t)
114✔
369
        i1 = norm_value(i1 or 1, nr)
152✔
370
        j1 = norm_value(j1 or 1, nc)
152✔
371
        i2 = norm_value(i2 or nr, nr)
152✔
372
        j2 = norm_value(j2 or nc, nc)
152✔
373
        return i1,j1,i2,j2
114✔
374
    end
375
    default_range = array2d.default_range
12✔
376
end
377

378
--- get a slice of a 2D array. Note that if the specified range has
379
-- a 1D result, the rank of the result will be 1.
380
-- @array2d t a 2D array
381
-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
382
-- @tparam[opt=1] int j1 start col
383
-- @tparam[opt=N] int i2 end row
384
-- @tparam[opt=M] int j2 end col
385
-- @see parse_range
386
-- @return an array, 2D in general but 1D in special cases.
387
function array2d.slice (t,i1,j1,i2,j2)
12✔
388
    assert_arg(1,t,'table')
30✔
389
    i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
40✔
390
    local res = {}
30✔
391
    for i = i1,i2 do
90✔
392
        local val
393
        local row = t[i]
60✔
394
        if j1 == j2 then
60✔
395
            val = row[j1]
30✔
396
        else
397
            val = {}
30✔
398
            for j = j1,j2 do
96✔
399
                val[#val+1] = row[j]
66✔
400
            end
401
        end
402
        res[#res+1] = val
60✔
403
    end
404
    if i1 == i2 then res = res[1] end
30✔
405
    return obj(t,res)
30✔
406
end
407

408
--- set a specified range of an array to a value.
409
-- @array2d t a 2D array
410
-- @param value the value (may be a function, called as `val(i,j)`)
411
-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
412
-- @tparam[opt=1] int j1 start col
413
-- @tparam[opt=N] int i2 end row
414
-- @tparam[opt=M] int j2 end col
415
-- @see parse_range
416
-- @see tablex.set
417
function array2d.set (t,value,i1,j1,i2,j2)
12✔
418
    i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
16✔
419
    local i = i1
12✔
420
    if types.is_callable(value) then
16✔
421
        local old_f = value
6✔
422
        value = function(j)
423
            return old_f(i,j)
36✔
424
        end
425
    end
426
    while i <= i2 do
36✔
427
        tset(t[i],value,j1,j2)
24✔
428
        i = i + 1
24✔
429
    end
430
end
431

432
--- write a 2D array to a file.
433
-- @array2d t a 2D array
434
-- @param f a file object (default stdout)
435
-- @string fmt a format string (default is just to use tostring)
436
-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
437
-- @tparam[opt=1] int j1 start col
438
-- @tparam[opt=N] int i2 end row
439
-- @tparam[opt=M] int j2 end col
440
-- @see parse_range
441
function array2d.write (t,f,fmt,i1,j1,i2,j2)
12✔
442
    assert_arg(1,t,'table')
12✔
443
    f = f or stdout
12✔
444
    local rowop
445
    if fmt then
12✔
446
        rowop = function(row,j) fprintf(f,fmt,row[j]) end
140✔
447
    else
UNCOV
448
        rowop = function(row,j) f:write(tostring(row[j]),' ') end
×
449
    end
450
    local function newline()
451
        f:write '\n'
36✔
452
    end
453
    array2d.forall(t,rowop,newline,i1,j1,i2,j2)
12✔
454
end
455

456
--- perform an operation for all values in a 2D array.
457
-- @array2d t 2D array
458
-- @func row_op function to call on each value; `row_op(row,j)`
459
-- @func end_row_op function to call at end of each row; `end_row_op(i)`
460
-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
461
-- @tparam[opt=1] int j1 start col
462
-- @tparam[opt=N] int i2 end row
463
-- @tparam[opt=M] int j2 end col
464
-- @see parse_range
465
function array2d.forall (t,row_op,end_row_op,i1,j1,i2,j2)
12✔
466
    assert_arg(1,t,'table')
24✔
467
    i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
32✔
468
    for i = i1,i2 do
102✔
469
        local row = t[i]
78✔
470
        for j = j1,j2 do
282✔
471
            row_op(row,j)
204✔
472
        end
473
        if end_row_op then end_row_op(i) end
78✔
474
    end
475
end
476

477
---- move a block from the destination to the source.
478
-- @array2d dest a 2D array
479
-- @int di start row in dest
480
-- @int dj start col in dest
481
-- @array2d src a 2D array
482
-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
483
-- @tparam[opt=1] int j1 start col
484
-- @tparam[opt=N] int i2 end row
485
-- @tparam[opt=M] int j2 end col
486
-- @see parse_range
487
function array2d.move (dest,di,dj,src,i1,j1,i2,j2)
12✔
488
    assert_arg(1,dest,'table')
6✔
489
    assert_arg(4,src,'table')
6✔
490
    i1,j1,i2,j2 = default_range(src,i1,j1,i2,j2)
8✔
491
    local nr,nc = array2d.size(dest)
6✔
492
    i2, j2 = min(nr,i2), min(nc,j2)
6✔
493
    --i1, j1 = max(1,i1), max(1,j1)
494
    dj = dj - 1
6✔
495
    for i = i1,i2 do
24✔
496
        local drow, srow = dest[i+di-1], src[i]
18✔
497
        for j = j1,j2 do
72✔
498
            drow[j+dj] = srow[j]
54✔
499
        end
500
    end
501
end
502

503
--- iterate over all elements in a 2D array, with optional indices.
504
-- @array2d a 2D array
505
-- @bool indices with indices (default false)
506
-- @tparam[opt=1] int|string i1 start row or spreadsheet range passed to `parse_range`
507
-- @tparam[opt=1] int j1 start col
508
-- @tparam[opt=N] int i2 end row
509
-- @tparam[opt=M] int j2 end col
510
-- @see parse_range
511
-- @return either `value` or `i,j,value` depending on the value of `indices`
512
function array2d.iter(a,indices,i1,j1,i2,j2)
12✔
513
    assert_arg(1,a,'table')
24✔
514
    i1,j1,i2,j2 = default_range(a,i1,j1,i2,j2)
32✔
515
    local i,j = i1,j1-1
24✔
516
    local row = a[i]
24✔
517
    return function()
518
        j = j + 1
240✔
519
        if j > j2 then
240✔
520
            j = j1
84✔
521
            i = i + 1
84✔
522
            row = a[i]
84✔
523
            if i > i2 then
84✔
524
                return nil
24✔
525
            end
526
        end
527
        if indices then
216✔
528
            return i,j,row[j]
108✔
529
        else
530
            return row[j]
108✔
531
        end
532
    end
533
end
534

535
--- iterate over all columns.
536
-- @array2d a a 2D array
537
-- @return column, column-index
538
function array2d.columns(a)
12✔
539
  assert_arg(1,a,'table')
6✔
540
  local n = #a[1]
6✔
541
  local i = 0
6✔
542
  return function()
543
      i = i + 1
24✔
544
      if i > n then return nil end
24✔
545
      return column(a,i), i
18✔
546
  end
547
end
548

549
--- iterate over all rows.
550
-- Returns a copy of the row, for read-only purposes directly iterating
551
-- is more performant; `ipairs(a)`
552
-- @array2d a a 2D array
553
-- @return row, row-index
554
function array2d.rows(a)
12✔
555
  assert_arg(1,a,'table')
6✔
556
  local n = #a
6✔
557
  local i = 0
6✔
558
  return function()
559
      i = i + 1
30✔
560
      if i > n then return nil end
30✔
561
      return array2d.row(a,i), i
32✔
562
  end
563
end
564

565
--- new array of specified dimensions
566
-- @int rows number of rows
567
-- @int cols number of cols
568
-- @param val initial value; if it's a function then use `val(i,j)`
569
-- @return new 2d array
570
function array2d.new(rows,cols,val)
12✔
571
    local res = {}
66✔
572
    local fun = types.is_callable(val)
66✔
573
    for i = 1,rows do
282✔
574
        local row = {}
216✔
575
        if fun then
216✔
576
            for j = 1,cols do row[j] = val(i,j) end
135✔
577
        else
578
            for j = 1,cols do row[j] = val end
963✔
579
        end
580
        res[i] = row
216✔
581
    end
582
    return res
66✔
583
end
584

585
return array2d
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc