py3toolset 1.2.18__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py3toolset/LICENSE.md +11 -0
- py3toolset/__init__.py +1 -0
- py3toolset/bash_autocomp.py +70 -0
- py3toolset/cmd_interact.py +153 -0
- py3toolset/dep.py +144 -0
- py3toolset/file_backup.py +113 -0
- py3toolset/fs.py +543 -0
- py3toolset/nmath.py +419 -0
- py3toolset/setup.py +32 -0
- py3toolset/tuple_file.py +104 -0
- py3toolset/txt_color.py +168 -0
- py3toolset-1.2.18.dist-info/METADATA +24 -0
- py3toolset-1.2.18.dist-info/RECORD +16 -0
- py3toolset-1.2.18.dist-info/WHEEL +5 -0
- py3toolset-1.2.18.dist-info/licenses/LICENSE.md +11 -0
- py3toolset-1.2.18.dist-info/top_level.txt +1 -0
py3toolset/nmath.py
ADDED
@@ -0,0 +1,419 @@
|
|
1
|
+
"""
|
2
|
+
Math utility functions.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import re
|
6
|
+
import numpy as np
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
def str_isint(s, abs=True):
|
11
|
+
"""
|
12
|
+
Tests if a ``str`` can be converted to a ``int``.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
s: ``str``
|
16
|
+
character string to test.
|
17
|
+
abs: ``bool``
|
18
|
+
``True`` (default) to match only positive/absolute value,
|
19
|
+
``False`` otherwise (to match also a negative value).
|
20
|
+
|
21
|
+
Return:
|
22
|
+
``True`` if ``str`` ``s`` matches a ``int`` ``False`` otherwise.
|
23
|
+
|
24
|
+
Examples:
|
25
|
+
>>> str_isint('10')
|
26
|
+
True
|
27
|
+
>>> str_isint('-5')
|
28
|
+
False
|
29
|
+
>>> str_isint('-5', abs=True)
|
30
|
+
False
|
31
|
+
>>> str_isint('-5', abs=False)
|
32
|
+
True
|
33
|
+
>>> str_isint('25', abs=True)
|
34
|
+
True
|
35
|
+
>>> str_isint('not_an_int')
|
36
|
+
False
|
37
|
+
|
38
|
+
.. seealso::
|
39
|
+
str_isfloat
|
40
|
+
"""
|
41
|
+
if abs:
|
42
|
+
int_re = r"^[0-9]+$"
|
43
|
+
else:
|
44
|
+
int_re = r"^-?[0-9]+$"
|
45
|
+
return re.fullmatch(int_re, s) is not None
|
46
|
+
|
47
|
+
|
48
|
+
def str_isfloat(s, abs=True):
|
49
|
+
"""
|
50
|
+
Tests if a ``str`` can be converted to a ``float``.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
s: ``str``
|
54
|
+
character string to test.
|
55
|
+
abs: ``bool``
|
56
|
+
``True`` (default) to match only positive/absolute value,
|
57
|
+
``False`` otherwise (to match also a negative value).
|
58
|
+
|
59
|
+
Return:
|
60
|
+
``True`` if ``str`` ``s`` matches a ``float`` ``False`` otherwise.
|
61
|
+
If ``str_isint(s) == True`` then ``str_isfloat(s) == True``.
|
62
|
+
|
63
|
+
Examples:
|
64
|
+
>>> str_isfloat('10.2')
|
65
|
+
True
|
66
|
+
>>> str_isfloat('10')
|
67
|
+
True
|
68
|
+
>>> str_isfloat('-5')
|
69
|
+
False
|
70
|
+
>>> str_isfloat('-5', abs=True)
|
71
|
+
False
|
72
|
+
>>> str_isfloat('-5', abs=False)
|
73
|
+
True
|
74
|
+
>>> str_isfloat('25', abs=True)
|
75
|
+
True
|
76
|
+
>>> str_isfloat('not_a_float')
|
77
|
+
False
|
78
|
+
|
79
|
+
|
80
|
+
.. seealso::
|
81
|
+
str_isint
|
82
|
+
"""
|
83
|
+
# exclude first space case (because we don't use fullmatch below)
|
84
|
+
if re.match(r'.*\s.*', s):
|
85
|
+
return None
|
86
|
+
# the reason fullmatch is not used is negative numbers
|
87
|
+
if abs:
|
88
|
+
float_re = r"^([0-9]+)|([0-9]*\.[0-9]+)$"
|
89
|
+
else:
|
90
|
+
float_re = r"^-?([0-9]+)|([0-9]*\.[0-9]+)$"
|
91
|
+
return re.match(float_re, s) is not None
|
92
|
+
|
93
|
+
|
94
|
+
def get_locmaxs(xy_tuples):
|
95
|
+
"""
|
96
|
+
Finds local maximums in ``xy_tuples``.
|
97
|
+
|
98
|
+
The first and last points cannot be local maximum.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
xy_tuples: ``Sequence[tuple]``, numpy 2d array
|
102
|
+
Sequence of pairs ``(x, y)`` (the points).
|
103
|
+
If a numpy array, it must have two columns (``x``, ``y``), one row
|
104
|
+
per point.
|
105
|
+
The max points are searched on ``y`` dimension along ``x``
|
106
|
+
dimension.
|
107
|
+
|
108
|
+
Return:
|
109
|
+
The list of local max found.
|
110
|
+
If no max found, returns an empty list.
|
111
|
+
|
112
|
+
Examples:
|
113
|
+
>>> x1 = np.arange(10)
|
114
|
+
>>> pts = list(zip(x1, np.cos(x1)))
|
115
|
+
>>> get_locmaxs(pts)
|
116
|
+
[(np.int64(6), np.float64(0.960170286650366))]
|
117
|
+
>>> x2 = np.arange(100)
|
118
|
+
>>> more_pts = list(zip(x2, np.cos(x2)))
|
119
|
+
>>> np.array(get_locmaxs(more_pts))
|
120
|
+
array([[ 6. , 0.96017029],
|
121
|
+
[13. , 0.90744678],
|
122
|
+
[19. , 0.98870462],
|
123
|
+
[25. , 0.99120281],
|
124
|
+
[31. , 0.91474236],
|
125
|
+
[38. , 0.95507364],
|
126
|
+
[44. , 0.99984331],
|
127
|
+
[50. , 0.96496603],
|
128
|
+
[57. , 0.89986683],
|
129
|
+
[63. , 0.98589658],
|
130
|
+
[69. , 0.99339038],
|
131
|
+
[75. , 0.92175127],
|
132
|
+
[82. , 0.9496777 ],
|
133
|
+
[88. , 0.99937328],
|
134
|
+
[94. , 0.96945937]])
|
135
|
+
>>> # passing more points as a numpy array works the same
|
136
|
+
>>> np.array(get_locmaxs(np.array(more_pts)))
|
137
|
+
array([[ 6. , 0.96017029],
|
138
|
+
[13. , 0.90744678],
|
139
|
+
[19. , 0.98870462],
|
140
|
+
[25. , 0.99120281],
|
141
|
+
[31. , 0.91474236],
|
142
|
+
[38. , 0.95507364],
|
143
|
+
[44. , 0.99984331],
|
144
|
+
[50. , 0.96496603],
|
145
|
+
[57. , 0.89986683],
|
146
|
+
[63. , 0.98589658],
|
147
|
+
[69. , 0.99339038],
|
148
|
+
[75. , 0.92175127],
|
149
|
+
[82. , 0.9496777 ],
|
150
|
+
[88. , 0.99937328],
|
151
|
+
[94. , 0.96945937]])
|
152
|
+
>>> # to render as a figure
|
153
|
+
>>> import matplotlib.pyplot as plt
|
154
|
+
>>> more_pts = np.array(more_pts)
|
155
|
+
>>> max_pts = np.array(get_locmaxs(more_pts))
|
156
|
+
>>> plt.scatter(max_pts[:, 0], max_pts[:, 1]) # doctest: +ELLIPSIS
|
157
|
+
<matplotlib.collections.PathCollection object at ...>
|
158
|
+
>>> plt.plot(more_pts[:, 0], more_pts[:, 1]) # doctest: +ELLIPSIS
|
159
|
+
[<matplotlib.lines.Line2D object at ...]
|
160
|
+
>>> # plt.show() # uncomment to display
|
161
|
+
|
162
|
+
.. seealso::
|
163
|
+
:func:`.get_globmax`
|
164
|
+
"""
|
165
|
+
locmaxs = []
|
166
|
+
# ignore first point (as a local max)
|
167
|
+
prev_y = xy_tuples[0][1] + 1
|
168
|
+
# the last point is also ignored
|
169
|
+
prev_x = -1
|
170
|
+
ascending = False
|
171
|
+
for x, y in xy_tuples:
|
172
|
+
if y > prev_y:
|
173
|
+
ascending = True
|
174
|
+
elif ascending:
|
175
|
+
locmaxs.append((prev_x, prev_y))
|
176
|
+
ascending = False
|
177
|
+
prev_x, prev_y = x, y
|
178
|
+
return locmaxs
|
179
|
+
|
180
|
+
|
181
|
+
_glob_max_meths = ['greatest_local', 'max']
|
182
|
+
|
183
|
+
|
184
|
+
def get_globmax(xy_tuples, meth='greatest_local'):
|
185
|
+
"""
|
186
|
+
Finds the global maximum in ``xy_tuples``.
|
187
|
+
|
188
|
+
Args:
|
189
|
+
|
190
|
+
xy_tuples: ``Sequence[tuple]``, numpy 2d array
|
191
|
+
Sequence of pairs $(x, y)$ (the points).
|
192
|
+
If a numpy array, it must have two columns $(x, y)$, one row
|
193
|
+
per point.
|
194
|
+
The max is searched on $y$ dimension along $x$ dimension.
|
195
|
+
|
196
|
+
meth: ``str``
|
197
|
+
|
198
|
+
- ``'greatest_local'``: (default) the global maximum is the
|
199
|
+
greatest of local maximums (see :func:`.get_locmaxs`).
|
200
|
+
Hence, if no local maximum is found, it is considered that no
|
201
|
+
global maximum exists (the function returns None).
|
202
|
+
|
203
|
+
- ``'max'``: the function simply returns the max of
|
204
|
+
``xy_tuples.``.
|
205
|
+
|
206
|
+
Return:
|
207
|
+
The global maximum or ``None`` if not found.
|
208
|
+
|
209
|
+
Example:
|
210
|
+
>>> x = np.arange(100)
|
211
|
+
>>> pts = list(zip(x, np.cos(x)))
|
212
|
+
>>> get_globmax(pts)
|
213
|
+
(np.int64(44), np.float64(0.9998433086476912))
|
214
|
+
>>> # this is the greatest local maximum
|
215
|
+
>>> loc_maxs = np.array(get_locmaxs(pts))
|
216
|
+
>>> loc_maxs[np.argmax(loc_maxs[:, 1])]
|
217
|
+
array([44. , 0.99984331])
|
218
|
+
>>> # situation with no local max (see get_locmaxs for a better
|
219
|
+
>>> # understanding)
|
220
|
+
>>> pts2 = list(zip(x.tolist(), np.linspace(1, 50, 100).tolist()))
|
221
|
+
>>> get_globmax(pts2) is None
|
222
|
+
True
|
223
|
+
>>> # but a max point exists necessarily
|
224
|
+
>>> get_globmax(pts2, meth='max')
|
225
|
+
(99, 50.0)
|
226
|
+
|
227
|
+
|
228
|
+
.. seealso::
|
229
|
+
:func:`.get_locmaxs`
|
230
|
+
|
231
|
+
"""
|
232
|
+
if meth not in _glob_max_meths:
|
233
|
+
raise ValueError(str(meth)+' is not known. Valid meth values:' +
|
234
|
+
_glob_max_meths)
|
235
|
+
# method 'greatest_local' (not optimal but for historical needs)
|
236
|
+
loc_maxs = get_locmaxs(xy_tuples)
|
237
|
+
loc_maxs.sort(key=lambda t: t[1], reverse=True)
|
238
|
+
if len(loc_maxs) > 0:
|
239
|
+
return loc_maxs[0]
|
240
|
+
if meth == 'max':
|
241
|
+
# return max(xy_tuples, key=lambda t: t[1])
|
242
|
+
return xy_tuples[np.argmax(np.array(xy_tuples)[:, 1])]
|
243
|
+
# tmp = list(xy_tuples)
|
244
|
+
# tmp.sort(key=lambda t:t[1], reverse=True)
|
245
|
+
# return tmp[0]
|
246
|
+
return None
|
247
|
+
|
248
|
+
|
249
|
+
_interpoline_meths = ['slope', 'barycenter']
|
250
|
+
|
251
|
+
|
252
|
+
def interpoline(x1, y1, x2, y2, x, meth='slope'):
|
253
|
+
"""
|
254
|
+
Interpolates linearly (``x1``, ``y1``), (``x2``, ``y2``) at value ``x``.
|
255
|
+
|
256
|
+
Return:
|
257
|
+
$f(x) = ax + b$ such that $f(x_1) = y_1$, $f(x_2) = y_2$.
|
258
|
+
|
259
|
+
Example:
|
260
|
+
>>> x1 = 10
|
261
|
+
>>> x2 = 20
|
262
|
+
>>> y1 = 18
|
263
|
+
>>> y2 = 5
|
264
|
+
>>> x = 15
|
265
|
+
>>> y = interpoline(x1, y1, x2, y2, x, meth='slope')
|
266
|
+
>>> y
|
267
|
+
11.5
|
268
|
+
>>> interpoline(x1, y1, x2, y2, x, meth='barycenter')
|
269
|
+
11.5
|
270
|
+
>>> # plot the line and point (x, f(x))
|
271
|
+
>>> import matplotlib.pyplot as plt
|
272
|
+
>>> plt.plot([x1, x2], [y1, y2], marker='+') # doctest: +ELLIPSIS
|
273
|
+
[...
|
274
|
+
>>> plt.scatter([x], [y]) # doctest: +ELLIPSIS
|
275
|
+
<...
|
276
|
+
>>> # plt.show() # uncomment to display
|
277
|
+
|
278
|
+
"""
|
279
|
+
if meth == 'slope':
|
280
|
+
a = (y2 - y1) / (x2 - x1)
|
281
|
+
b = y1 - a * x1
|
282
|
+
return a * x + b
|
283
|
+
elif meth == 'barycenter':
|
284
|
+
t = (x - x1) / abs(x2 - x1)
|
285
|
+
return (1 - t) * y1 + t * y2
|
286
|
+
else:
|
287
|
+
raise ValueError(str(meth) + ' is not a valid method: ' +
|
288
|
+
_interpoline_meths)
|
289
|
+
|
290
|
+
|
291
|
+
_mv_avg_meths = ['basic', 'cumsum', 'convolve']
|
292
|
+
|
293
|
+
|
294
|
+
def calc_moving_average_list(xy_tuples, window_sz, meth='basic', x_start=None):
|
295
|
+
"""
|
296
|
+
Computes the moving average of ``xy_tuples``.
|
297
|
+
|
298
|
+
The average is computed for the y-dimension, 2nd column.
|
299
|
+
|
300
|
+
Args:
|
301
|
+
xy_tuples: ``Sequence[tuple]``, numpy 2d array
|
302
|
+
Sequence of pairs ``(x, y)`` (the points).
|
303
|
+
If a numpy array, it must have two columns (``x``, ``y``), one row
|
304
|
+
per point.
|
305
|
+
window_sz: ``int``
|
306
|
+
The window size for average.
|
307
|
+
meth: ``str``
|
308
|
+
- 'basic': manual default method.
|
309
|
+
- 'cumsum': use numpy.cumsum.
|
310
|
+
- 'convolve': use the convolution trick.
|
311
|
+
x_start: ``int``
|
312
|
+
xy_tuples[x_start][0] is the first element of the x-dimension
|
313
|
+
average/output.
|
314
|
+
Default is ``None`` for ``x_start = int(window_sz) // 2``.
|
315
|
+
In other words, the mean of ``xy_tuples[:window_sz]`` is the first
|
316
|
+
element of the moving average and its x coordinate is
|
317
|
+
``xy_tuples[x_start][0]``. The next x-coordinates of the moving
|
318
|
+
average are:
|
319
|
+
``xy_tuples[x_start+1][0], xy_tuples[x_start+2][0],`` ...
|
320
|
+
|
321
|
+
Return:
|
322
|
+
Moving average as a list of tuples (x, y).
|
323
|
+
|
324
|
+
Examples:
|
325
|
+
>>> import numpy as np
|
326
|
+
>>> xy = list(zip(np.arange(8).tolist(), np.arange(10, 17).tolist()))
|
327
|
+
>>> xy = np.round(xy, decimals=3).tolist()
|
328
|
+
>>> xy
|
329
|
+
[[0, 10], [1, 11], [2, 12], [3, 13], [4, 14], [5, 15], [6, 16]]
|
330
|
+
>>> calc_moving_average_list(xy, 5)
|
331
|
+
[(2, np.float64(12.0)), (3, np.float64(13.0)), (4, np.float64(14.0))]
|
332
|
+
>>> calc_moving_average_list(xy, 5, meth='cumsum')
|
333
|
+
[[2.0, 12.0], [3.0, 13.0], [4.0, 14.0]]
|
334
|
+
>>> calc_moving_average_list(xy, 5, x_start=3)
|
335
|
+
[(3, np.float64(12.0)), (4, np.float64(13.0)), (5, np.float64(14.0))]
|
336
|
+
>>> calc_moving_average_list(xy, 5, meth='convolve')
|
337
|
+
[[2.0, 12.0], [3.0, 13.0], [4.0, 14.0]]
|
338
|
+
"""
|
339
|
+
if x_start is None:
|
340
|
+
x_start = int(window_sz) // 2
|
341
|
+
x_offset = x_start
|
342
|
+
if meth == 'basic':
|
343
|
+
i = 0
|
344
|
+
window_amps = np.zeros((window_sz))
|
345
|
+
mvavg = []
|
346
|
+
avg_amp = 0
|
347
|
+
for j, (t, amp) in enumerate(xy_tuples):
|
348
|
+
window_amps[i] = amp
|
349
|
+
i += 1
|
350
|
+
if j >= window_sz - 1:
|
351
|
+
avg_amp = np.mean(window_amps)
|
352
|
+
mvavg.append((xy_tuples[x_offset][0], avg_amp))
|
353
|
+
x_offset += 1
|
354
|
+
i %= window_sz
|
355
|
+
return mvavg
|
356
|
+
# below are two variants of the code found here:
|
357
|
+
# https://stackoverflow.com/questions/14313510/how-to-calculate-rolling-
|
358
|
+
# moving-average-using-python-numpy-scipy#14314054
|
359
|
+
elif meth == 'cumsum':
|
360
|
+
sig = np.array([xy[1] for xy in xy_tuples])
|
361
|
+
x = np.array([xy[0] for xy in xy_tuples])
|
362
|
+
n = window_sz
|
363
|
+
avg = np.cumsum(sig)
|
364
|
+
avg[n:] = avg[n:] - avg[:-n]
|
365
|
+
return np.hstack((
|
366
|
+
x.reshape(-1, 1)[x_offset:len(avg) - n + x_offset + 1],
|
367
|
+
(avg[n-1:] / n).reshape(-1, 1)
|
368
|
+
)).tolist()
|
369
|
+
elif meth == 'convolve':
|
370
|
+
sig = np.array([xy[1] for xy in xy_tuples])
|
371
|
+
x = np.array([xy[0] for xy in xy_tuples])
|
372
|
+
avg = np.convolve(sig, np.ones(window_sz), 'valid') / window_sz
|
373
|
+
return np.hstack((
|
374
|
+
x.reshape(-1, 1)[x_offset:len(avg) + x_offset],
|
375
|
+
avg.reshape(-1, 1)
|
376
|
+
)).tolist()
|
377
|
+
else:
|
378
|
+
raise ValueError(str(meth) + ' is not a valid method: ' +
|
379
|
+
_mv_avg_meths)
|
380
|
+
|
381
|
+
|
382
|
+
def zeropad(ref_num, nums):
|
383
|
+
"""
|
384
|
+
Pads/Prefixes nums with zeros to match the number of digits of ref_num.
|
385
|
+
|
386
|
+
Args:
|
387
|
+
ref_num: an integer.
|
388
|
+
nums: integer or str list.
|
389
|
+
|
390
|
+
Return:
|
391
|
+
- if nums is a sequence returns a list of zero-padded int-s (as str).
|
392
|
+
- if nums is a int returns a zero-padded int (as str).
|
393
|
+
|
394
|
+
Example:
|
395
|
+
>>> zeropad(96, 0)
|
396
|
+
'00'
|
397
|
+
>>> zeropad(96, [0, 1, 2, 3])
|
398
|
+
['00', '01', '02', '03']
|
399
|
+
>>> zeropad(96, list(range(4)))
|
400
|
+
['00', '01', '02', '03']
|
401
|
+
|
402
|
+
"""
|
403
|
+
zpadded_nums = []
|
404
|
+
nums_is_seq = True
|
405
|
+
if not hasattr(nums, '__len__'):
|
406
|
+
nums = [nums]
|
407
|
+
nums_is_seq = False
|
408
|
+
for n in nums:
|
409
|
+
sn = str(n)
|
410
|
+
if not str_isint(sn):
|
411
|
+
raise ValueError('nums must be int or sequence of int')
|
412
|
+
dl = len(str(ref_num)) - len(sn)
|
413
|
+
if dl < 0:
|
414
|
+
raise ValueError('The reference number is smaller than the'
|
415
|
+
' integers to pad with zeros')
|
416
|
+
zpadded_nums += [('0'*dl)+sn] # equiv. to sn.zfill(len(ref_num))
|
417
|
+
if len(zpadded_nums) == 1 and not nums_is_seq:
|
418
|
+
return zpadded_nums[0]
|
419
|
+
return zpadded_nums
|
py3toolset/setup.py
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
from setuptools import setup
|
2
|
+
from os import environ
|
3
|
+
|
4
|
+
version = '1.0.0'
|
5
|
+
if 'VERSION' in environ.keys():
|
6
|
+
version = environ['VERSION']
|
7
|
+
|
8
|
+
doc_url = 'https://neemgrp.univ-nantes.io/py3toolset/html'
|
9
|
+
api_doc_url = doc_url + '/api.html'
|
10
|
+
mods = ['bash_autocomp', 'cmd_interact', 'dep', 'file_backup',
|
11
|
+
'fs', 'nmath', 'tuple_file', 'txt_color']
|
12
|
+
nmods = len(mods)
|
13
|
+
mod_links = [f'[{mod}]({api_doc_url}#module-{imod})' for imod, mod in enumerate(mods)]
|
14
|
+
|
15
|
+
setup(
|
16
|
+
name='py3toolset',
|
17
|
+
version=version,
|
18
|
+
packages=['py3toolset'],
|
19
|
+
url='',
|
20
|
+
description='Python utility modules.',
|
21
|
+
classifiers=['License :: OSI Approved :: BSD License',
|
22
|
+
'Programming Language :: Python :: 3',
|
23
|
+
'Topic :: Software Development'],
|
24
|
+
install_requires=['numpy>=2'],
|
25
|
+
package_data={'py3toolset': ['LICENSE.md']},
|
26
|
+
license="3-clause BSD 2.0",
|
27
|
+
long_description_content_type='text/markdown',
|
28
|
+
long_description= f"""Python collection of utility modules: {", ".join(mod_links)}...
|
29
|
+
|
30
|
+
This package software engineering has been funded by the [Laboratoire de Planétologie et Géosciences](https://lpg-umr6112.fr/), Nantes (France).
|
31
|
+
"""
|
32
|
+
)
|
py3toolset/tuple_file.py
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
"""
|
2
|
+
Module for conversion of list of tuples <-> text file.
|
3
|
+
|
4
|
+
This module is mostly to use to avoid:
|
5
|
+
|
6
|
+
- use of ``numpy.savetxt``/``loadtxt``,
|
7
|
+
- and NumPy array to list-of-tuples conversion.
|
8
|
+
"""
|
9
|
+
from os.path import exists
|
10
|
+
|
11
|
+
|
12
|
+
def tuples2file(tuple_list, tfile, format_str='', overwrite=False):
|
13
|
+
"""
|
14
|
+
Converts ``tuple_list`` into a text file ``tfile``.
|
15
|
+
|
16
|
+
Each tuple occupies one line.
|
17
|
+
One column is added in file for each tuple element.
|
18
|
+
|
19
|
+
For example, the tuple list [(x1,y1,z1,w1), (x2,y2,z2,w2)]
|
20
|
+
is converted to the following file lines:
|
21
|
+
x1 y1 z1 w1
|
22
|
+
x2 y2 z2 w2
|
23
|
+
|
24
|
+
Args:
|
25
|
+
tuple_list: ``list[tuple]``
|
26
|
+
The list of tuples to write as in file.
|
27
|
+
tfile: ``str``
|
28
|
+
Output filepath.
|
29
|
+
format_str: ``str``
|
30
|
+
if specified is used as a line format string to the data in tuple.
|
31
|
+
See example below.
|
32
|
+
|
33
|
+
.. warning::
|
34
|
+
The function doesn't check the format string validity.
|
35
|
+
overwrite: ``bool``
|
36
|
+
- ``True``: a preexisting file is overwritten.
|
37
|
+
- ``False`` (default): if file preexists an exception is raised.
|
38
|
+
|
39
|
+
Example:
|
40
|
+
>>> import os
|
41
|
+
>>> from random import randint
|
42
|
+
>>> rand_suffix = str(randint(1, 2**10))
|
43
|
+
>>> output = "t2f_out-" + rand_suffix
|
44
|
+
>>> xy_tuples = [(1, 456.), (2, 789), (3, 111213)]
|
45
|
+
>>> tuples2file(xy_tuples, output, format_str="{0:1d}={1:3.1f}")
|
46
|
+
>>> # output is then:
|
47
|
+
>>> f = open(output)
|
48
|
+
>>> for line in f.readlines(): print(line, end='')
|
49
|
+
1=456.0
|
50
|
+
2=789.0
|
51
|
+
3=111213.0
|
52
|
+
>>> f.close()
|
53
|
+
>>> # delete file
|
54
|
+
>>> os.unlink(output)
|
55
|
+
|
56
|
+
"""
|
57
|
+
if exists(tfile) and not overwrite:
|
58
|
+
raise Exception("Error tuples2file(): file "+tfile+" already exists.")
|
59
|
+
f = open(tfile, "w")
|
60
|
+
for t in tuple_list:
|
61
|
+
if format_str:
|
62
|
+
if not format_str.endswith("\n"):
|
63
|
+
format_str += "\n"
|
64
|
+
f.write(str(format_str).format(*t))
|
65
|
+
else:
|
66
|
+
f.write(" ".join([str(e) for e in t])+"\n")
|
67
|
+
f.close()
|
68
|
+
|
69
|
+
|
70
|
+
def file2tuples(tfile, tuple_list=None):
|
71
|
+
"""
|
72
|
+
Converts a file ``tfile`` organized in columns to a list of tuples.
|
73
|
+
|
74
|
+
Returns a list of tuples (one tuple per line of file).
|
75
|
+
The elements in file as taken as float numbers.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
tfile: ``str``
|
79
|
+
input filepath.
|
80
|
+
tuple_list: ``list[tuple]``
|
81
|
+
The output list of tuples. Defaultly ``None``.
|
82
|
+
|
83
|
+
Return:
|
84
|
+
tuple list corresponding to ``tfile`` input.
|
85
|
+
|
86
|
+
Example:
|
87
|
+
>>> import os
|
88
|
+
>>> from random import randint
|
89
|
+
>>> rand_suffix = str(randint(1, 2**10))
|
90
|
+
>>> output = "t2f_out-" + rand_suffix
|
91
|
+
>>> in_tuples = [(1, 456.), (2, 789), (3, 111213)]
|
92
|
+
>>> tuples2file(in_tuples, output)
|
93
|
+
>>> # output is then:
|
94
|
+
>>> out_tuples = file2tuples(output)
|
95
|
+
>>> print(out_tuples)
|
96
|
+
[(1.0, 456.0), (2.0, 789.0), (3.0, 111213.0)]
|
97
|
+
"""
|
98
|
+
if tuple_list is None:
|
99
|
+
tuple_list = []
|
100
|
+
f = open(tfile)
|
101
|
+
for line in f:
|
102
|
+
tuple_list.append(tuple([float(n) for n in line.strip().split()]))
|
103
|
+
f.close()
|
104
|
+
return tuple_list
|