feasytools 0.0.2__tar.gz
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.
- feasytools-0.0.2/PKG-INFO +20 -0
- feasytools-0.0.2/README.md +6 -0
- feasytools-0.0.2/feasytools/__init__.py +12 -0
- feasytools-0.0.2/feasytools/argchk.py +67 -0
- feasytools-0.0.2/feasytools/pq.py +185 -0
- feasytools-0.0.2/feasytools/rangelist.py +46 -0
- feasytools-0.0.2/feasytools/table.py +490 -0
- feasytools-0.0.2/feasytools/tfunc.py +203 -0
- feasytools-0.0.2/feasytools.egg-info/PKG-INFO +20 -0
- feasytools-0.0.2/feasytools.egg-info/SOURCES.txt +14 -0
- feasytools-0.0.2/feasytools.egg-info/dependency_links.txt +1 -0
- feasytools-0.0.2/feasytools.egg-info/requires.txt +1 -0
- feasytools-0.0.2/feasytools.egg-info/top_level.txt +1 -0
- feasytools-0.0.2/setup.cfg +4 -0
- feasytools-0.0.2/setup.py +21 -0
- feasytools-0.0.2/test/test.py +61 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: feasytools
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: A set of tools for data processing, including Time Function, Table, Priority Queue, Range List, etc.
|
|
5
|
+
Home-page: UNKNOWN
|
|
6
|
+
Author: fmy_xfk
|
|
7
|
+
License: UNKNOWN
|
|
8
|
+
Description: # FEasyTools: Some useful components
|
|
9
|
+
- dprov: Data provider as time functions
|
|
10
|
+
- argchk: Argument parser with type checking
|
|
11
|
+
- table: Table helper for the CSV, SDT & SDT.GZ format, and their reader and writer.
|
|
12
|
+
- pq: Heap, priority queue & buffered priority queue
|
|
13
|
+
- rangelist: An xml reader for a time range list
|
|
14
|
+
|
|
15
|
+
Platform: UNKNOWN
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Requires-Python: >=3.6
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# FEasyTools: Some useful components
|
|
2
|
+
- dprov: Data provider as time functions
|
|
3
|
+
- argchk: Argument parser with type checking
|
|
4
|
+
- table: Table helper for the CSV, SDT & SDT.GZ format, and their reader and writer.
|
|
5
|
+
- pq: Heap, priority queue & buffered priority queue
|
|
6
|
+
- rangelist: An xml reader for a time range list
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# 具有类型注释功能的ArgParse: ArgChecker
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Any, Optional, Union
|
|
4
|
+
|
|
5
|
+
class ArgChecker:
|
|
6
|
+
@staticmethod
|
|
7
|
+
def __cast(v:str)->'Union[None,bool,int,float,str]':
|
|
8
|
+
if v=="True": return True
|
|
9
|
+
if v=="False": return False
|
|
10
|
+
if v=="None": return None
|
|
11
|
+
try:
|
|
12
|
+
return int(v)
|
|
13
|
+
except:
|
|
14
|
+
pass
|
|
15
|
+
try:
|
|
16
|
+
return float(v)
|
|
17
|
+
except:
|
|
18
|
+
return v.strip('"')
|
|
19
|
+
@staticmethod
|
|
20
|
+
def get_dict()->'dict[str,Union[str,Any]]':
|
|
21
|
+
'''将输入参数以字典的形式返回'''
|
|
22
|
+
cur_key=None
|
|
23
|
+
ret={}
|
|
24
|
+
for v in sys.argv[1:]:
|
|
25
|
+
if v.startswith('-'):
|
|
26
|
+
if cur_key!=None:
|
|
27
|
+
ret[cur_key]=True
|
|
28
|
+
cur_key=v.strip('-')
|
|
29
|
+
elif cur_key!=None:
|
|
30
|
+
ret[cur_key]=ArgChecker.__cast(v)
|
|
31
|
+
cur_key=None
|
|
32
|
+
else:
|
|
33
|
+
raise ValueError(f"无效参数'{v}'")
|
|
34
|
+
if cur_key!=None: ret[cur_key]=True
|
|
35
|
+
return ret
|
|
36
|
+
|
|
37
|
+
def __init__(self,pars: 'Optional[dict[str,Any]]'=None):
|
|
38
|
+
self.__args=self.get_dict() if pars is None else pars
|
|
39
|
+
|
|
40
|
+
def pop_bool(self, key: str) -> bool:
|
|
41
|
+
if self.__args.pop(key, False): return True
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
def pop_int(self, key: str, default: Optional[int] = None) -> int:
|
|
45
|
+
val = self.__args.pop(key, default)
|
|
46
|
+
if val is None: raise ValueError(f"必须指定'{key}'参数")
|
|
47
|
+
return int(val)
|
|
48
|
+
|
|
49
|
+
def pop_str(self, key: str, default: Optional[str]=None) -> str:
|
|
50
|
+
val = self.__args.pop(key, default)
|
|
51
|
+
if val is None: raise ValueError(f"必须指定'{key}'参数")
|
|
52
|
+
return str(val)
|
|
53
|
+
|
|
54
|
+
def pop_float(self, key: str, default: Optional[float] = None) -> float:
|
|
55
|
+
val = self.__args.pop(key, default)
|
|
56
|
+
if val is None: raise ValueError(f"必须指定'{key}'参数")
|
|
57
|
+
return float(val)
|
|
58
|
+
|
|
59
|
+
def empty(self) -> bool:
|
|
60
|
+
return len(self.__args) == 0
|
|
61
|
+
|
|
62
|
+
def keys(self): return self.__args.keys()
|
|
63
|
+
def values(self): return self.__args.values()
|
|
64
|
+
def items(self): return self.__args.items()
|
|
65
|
+
|
|
66
|
+
def __str__(self):
|
|
67
|
+
return str(self.__args)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import heapq
|
|
2
|
+
from collections import deque
|
|
3
|
+
from typing import Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
QItem=TypeVar("QItem")
|
|
6
|
+
|
|
7
|
+
class Heap(Generic[QItem]):
|
|
8
|
+
'''小根堆数据结构'''
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self._q=[]
|
|
11
|
+
|
|
12
|
+
def push(self,item:QItem):
|
|
13
|
+
'''入堆, O(log n)'''
|
|
14
|
+
heapq.heappush(self._q,item)
|
|
15
|
+
|
|
16
|
+
def pop(self)->QItem:
|
|
17
|
+
'''弹出堆顶元素, O(log n)'''
|
|
18
|
+
return heapq.heappop(self._q)
|
|
19
|
+
|
|
20
|
+
def remove(self,item)->bool:
|
|
21
|
+
'''弹出item元素, O(n)'''
|
|
22
|
+
try:
|
|
23
|
+
idx=self._q.index(item)
|
|
24
|
+
except ValueError:
|
|
25
|
+
return False
|
|
26
|
+
self._q.pop(idx)
|
|
27
|
+
heapq.heapify(self._q)
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def top(self)->QItem:
|
|
32
|
+
'''获取堆顶元素,但不弹出, O(1)'''
|
|
33
|
+
return self._q[0]
|
|
34
|
+
|
|
35
|
+
def __len__(self)->int:
|
|
36
|
+
return len(self._q)
|
|
37
|
+
|
|
38
|
+
def __contains__(self,obj)->bool:
|
|
39
|
+
'''检查元素是否存在, O(n)'''
|
|
40
|
+
return obj in self._q
|
|
41
|
+
|
|
42
|
+
def empty(self)->bool:
|
|
43
|
+
'''检测堆是否为空, O(1)'''
|
|
44
|
+
return len(self._q)==0
|
|
45
|
+
|
|
46
|
+
class PQueue(Generic[QItem]):
|
|
47
|
+
'''优先级队列(小根堆数据结构)'''
|
|
48
|
+
def __init__(self):
|
|
49
|
+
self._q:'list[tuple[int,QItem]]'=[]
|
|
50
|
+
|
|
51
|
+
def push(self,pri:int,item:QItem)->None:
|
|
52
|
+
'''
|
|
53
|
+
入队, O(log n)
|
|
54
|
+
pri: 优先级
|
|
55
|
+
item: 元素
|
|
56
|
+
'''
|
|
57
|
+
heapq.heappush(self._q,(pri,item))
|
|
58
|
+
|
|
59
|
+
def pop(self)->'tuple[int,QItem]':
|
|
60
|
+
'''获取队首元素并出队, O(log n)'''
|
|
61
|
+
return heapq.heappop(self._q)
|
|
62
|
+
|
|
63
|
+
def remove(self,item:QItem)->bool:
|
|
64
|
+
'''弹出第一个item元素, O(n), 返回是否成功'''
|
|
65
|
+
idx=-1
|
|
66
|
+
for i,(_,data) in enumerate(self._q):
|
|
67
|
+
if data == item:
|
|
68
|
+
idx = i
|
|
69
|
+
break
|
|
70
|
+
if idx==-1: return False
|
|
71
|
+
self._q.pop(idx)
|
|
72
|
+
heapq.heapify(self._q)
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def top(self)->'tuple[int,QItem]':
|
|
77
|
+
'''获取队首元素,但不出队, O(1)'''
|
|
78
|
+
return self._q[0]
|
|
79
|
+
|
|
80
|
+
def __len__(self)->int:
|
|
81
|
+
return len(self._q)
|
|
82
|
+
|
|
83
|
+
def __contains__(self,obj)->bool:
|
|
84
|
+
return obj in self._q
|
|
85
|
+
|
|
86
|
+
def empty(self)->bool:
|
|
87
|
+
'''检测队列是否为空, O(1)'''
|
|
88
|
+
return len(self._q)==0
|
|
89
|
+
|
|
90
|
+
def __str__(self)->str:
|
|
91
|
+
'''转为字符串, O(nlogn)'''
|
|
92
|
+
q2=self._q.copy()
|
|
93
|
+
q2.sort()
|
|
94
|
+
return str(q2)
|
|
95
|
+
|
|
96
|
+
class BufferedPQ(Generic[QItem]):
|
|
97
|
+
'''
|
|
98
|
+
含有缓冲的优先级队列。
|
|
99
|
+
该数据结构分为两部分,一个普通队列(记为q)和一个优先级队列(记为p)。
|
|
100
|
+
优先级队列大小恒定(设大小为n),普通队列大小可变(设大小为m)。
|
|
101
|
+
数据进入普通队列,而后进入优先级队列,最后离开本结构。
|
|
102
|
+
'''
|
|
103
|
+
def __init__(self,p_size:int):
|
|
104
|
+
self._sz=p_size
|
|
105
|
+
self._P:'PQueue[QItem]'=PQueue()
|
|
106
|
+
self._Q:'deque[tuple[int,QItem]]'=deque()
|
|
107
|
+
self._sP=set()
|
|
108
|
+
self._sQ=set()
|
|
109
|
+
|
|
110
|
+
def __len__(self)->int:
|
|
111
|
+
return len(self._P)+len(self._Q)
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def p_size(self)->int:
|
|
115
|
+
'''获取优先级区大小'''
|
|
116
|
+
return self._sz
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def p_len(self)->int:
|
|
120
|
+
'''检查优先级区长度, O(1)'''
|
|
121
|
+
return len(self._P)
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def q_len(self)->int:
|
|
125
|
+
'''检查缓冲区长度, O(1)'''
|
|
126
|
+
return len(self._Q)
|
|
127
|
+
|
|
128
|
+
def __contains__(self,obj)->bool:
|
|
129
|
+
'''检查元素是否存在, O(1)'''
|
|
130
|
+
return obj in self._sP or obj in self._sQ
|
|
131
|
+
|
|
132
|
+
def push(self,pri:int,obj:QItem)->bool:
|
|
133
|
+
'''
|
|
134
|
+
入队, O(log n)
|
|
135
|
+
pri: 优先级
|
|
136
|
+
item: 元素
|
|
137
|
+
'''
|
|
138
|
+
if self.__contains__(obj): return False
|
|
139
|
+
if len(self._P)<self._sz:
|
|
140
|
+
self._P.push(pri,obj)
|
|
141
|
+
self._sP.add(obj)
|
|
142
|
+
else:
|
|
143
|
+
self._Q.append((pri,obj))
|
|
144
|
+
self._sQ.add(obj)
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
def top(self)->'tuple[int,QItem]':
|
|
148
|
+
'''获取队首元素,但不出队, O(1)'''
|
|
149
|
+
return self._P.top
|
|
150
|
+
|
|
151
|
+
def pop(self)->'tuple[int,QItem]':
|
|
152
|
+
'''获取队首元素并出队, O(log n)'''
|
|
153
|
+
ret=self._P.pop()
|
|
154
|
+
self._sP.remove(ret[1])
|
|
155
|
+
if len(self._Q)>0:
|
|
156
|
+
pri,obj=self._Q.popleft()
|
|
157
|
+
self._sQ.remove(obj)
|
|
158
|
+
self._P.push(pri,obj)
|
|
159
|
+
self._sP.add(obj)
|
|
160
|
+
return ret
|
|
161
|
+
|
|
162
|
+
def empty(self)->bool:
|
|
163
|
+
'''检测队列是否为空, O(1)'''
|
|
164
|
+
return self._P.empty()
|
|
165
|
+
|
|
166
|
+
def remove(self,obj)->bool:
|
|
167
|
+
'''弹出第一个obj元素, O(n+m), 返回是否成功'''
|
|
168
|
+
if self._P.remove(obj): return True
|
|
169
|
+
try:
|
|
170
|
+
self._Q.remove(obj)
|
|
171
|
+
return True
|
|
172
|
+
except:
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
def p_has(self,obj)->bool:
|
|
176
|
+
'''检查元素是否存在于优先级区, O(1)'''
|
|
177
|
+
return obj in self._sP
|
|
178
|
+
|
|
179
|
+
def q_has(self,obj)->bool:
|
|
180
|
+
'''检查元素是否存在于缓冲区, O(1)'''
|
|
181
|
+
return obj in self._sQ
|
|
182
|
+
|
|
183
|
+
def __str__(self)->str:
|
|
184
|
+
'''转为字符串, O(nlogn+m)'''
|
|
185
|
+
return f"BufferedPQ[P={self._P},Q={self._Q}]"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import xml.etree.ElementTree as ET
|
|
2
|
+
from typing import Union, overload
|
|
3
|
+
|
|
4
|
+
class RangeList:
|
|
5
|
+
'''时间范围列表,用于表示一系列时间段'''
|
|
6
|
+
@staticmethod
|
|
7
|
+
def parse_time(s:str)->int:
|
|
8
|
+
'''将时间字符串转换为秒数, 支持格式为hh:mm:ss或者一个可转化成int的字符串'''
|
|
9
|
+
try:
|
|
10
|
+
return int(s)
|
|
11
|
+
except:
|
|
12
|
+
h,m,s = s.split(":")
|
|
13
|
+
return int(h)*3600+int(m)*60+int(s)
|
|
14
|
+
|
|
15
|
+
@overload
|
|
16
|
+
def __init__(self, data: ET.Element): '''从xml节点初始化'''
|
|
17
|
+
@overload
|
|
18
|
+
def __init__(self, data: 'list[tuple[int,int]]'): '''从列表初始化'''
|
|
19
|
+
|
|
20
|
+
def __init__(self, data: 'Union[list[tuple[int,int]], ET.Element]'):
|
|
21
|
+
if isinstance(data,ET.Element):
|
|
22
|
+
loop_period = int(data.attrib.get("loop_period", 0))
|
|
23
|
+
loop_times = int(data.attrib.get("loop_times", 1))
|
|
24
|
+
assert loop_times >= 1
|
|
25
|
+
data = [(self.parse_time(itm.attrib['btime']),self.parse_time(itm.attrib["etime"])) for itm in data]
|
|
26
|
+
if loop_times > 1 and len(data) > 1:
|
|
27
|
+
assert loop_period > data[-1][1]
|
|
28
|
+
n = len(data)
|
|
29
|
+
for j in range(1, loop_times):
|
|
30
|
+
for i in range(n):
|
|
31
|
+
if data[i][0]>=data[i][1]: raise ValueError(f"起始时间{data[i][0]}晚于终止时间{data[i][1]}")
|
|
32
|
+
data.append((data[i][0]+loop_period*j,data[i][1]+loop_period*j))
|
|
33
|
+
self._d:'list[tuple[int,int]]' = data
|
|
34
|
+
|
|
35
|
+
def __contains__(self, t:int):
|
|
36
|
+
for (l,r) in self._d:
|
|
37
|
+
if l<=t and t<r: return True
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
def __len__(self)->int: return self._d.__len__()
|
|
41
|
+
|
|
42
|
+
def __getitem__(self, indices): return self._d.__getitem__(indices)
|
|
43
|
+
|
|
44
|
+
def __str__(self): return str(self._d)
|
|
45
|
+
|
|
46
|
+
def __iter__(self): return iter(self._d)
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import struct, gzip
|
|
2
|
+
from typing import IO, BinaryIO, Generic, Optional, TextIO, Type, TypeVar, Union, Iterable, overload
|
|
3
|
+
import numpy as np
|
|
4
|
+
from abc import abstractmethod, ABCMeta
|
|
5
|
+
|
|
6
|
+
Table_DType = Type[Union[np.int32,np.float32]]
|
|
7
|
+
_Table_DType = TypeVar("_Table_DType",np.int32,np.float32)
|
|
8
|
+
|
|
9
|
+
def _Lchk(L):
|
|
10
|
+
assert L=='i' or L=='f'
|
|
11
|
+
|
|
12
|
+
def _dtypechk(dtype):
|
|
13
|
+
assert dtype==np.int32 or dtype==np.float32
|
|
14
|
+
|
|
15
|
+
def _L2dtype(L)->Type:
|
|
16
|
+
return np.int32 if L=='i' else np.float32
|
|
17
|
+
|
|
18
|
+
def _dtype2L(dtype)->str:
|
|
19
|
+
return 'i' if dtype==np.int32 else 'f'
|
|
20
|
+
|
|
21
|
+
class TableWriter(metaclass = ABCMeta):
|
|
22
|
+
'''数据表写入器(抽象类)'''
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def __init__(self,col_names:'list[str]',dtype:Type)->None:
|
|
25
|
+
_dtypechk(dtype)
|
|
26
|
+
self._col_names:'list[str]' = col_names
|
|
27
|
+
self._dtype:Table_DType = dtype
|
|
28
|
+
@property
|
|
29
|
+
def col_num(self)->int:
|
|
30
|
+
'''数据表的列数'''
|
|
31
|
+
return len(self._col_names)
|
|
32
|
+
@property
|
|
33
|
+
def dtype(self)->Type:
|
|
34
|
+
'''数据表的数据类型'''
|
|
35
|
+
return self._dtype
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def write(self,data:list)->None: '''写入一条数据, 数据长度必须与列数相等'''
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def write_all(self,data:np.ndarray)->None: '''写入多条数据, 数据长度(参数ndarray的列数)必须与本表的列数相等'''
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def close(self)->None: '''关闭写入器'''
|
|
42
|
+
|
|
43
|
+
class FileTableWriter(TableWriter):
|
|
44
|
+
'''文件数据表写入器(抽象类)'''
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def __init__(self,fh:IO,col_names:'list[str]',dtype:Type)->None:
|
|
47
|
+
super().__init__(col_names,dtype)
|
|
48
|
+
self._fh=fh
|
|
49
|
+
|
|
50
|
+
class MemoryTableWriter(TableWriter):
|
|
51
|
+
'''内存数据表写入器'''
|
|
52
|
+
_data:np.ndarray
|
|
53
|
+
def __init__(self,col_names:'list[str]',dtype:Type)->None:
|
|
54
|
+
'''
|
|
55
|
+
初始化
|
|
56
|
+
col_names: 列名
|
|
57
|
+
dtype: 数据类型, 可以是np.int32或np.float32
|
|
58
|
+
'''
|
|
59
|
+
super().__init__(col_names,dtype)
|
|
60
|
+
self._data=np.zeros((0,len(col_names)),dtype=dtype)
|
|
61
|
+
|
|
62
|
+
def write(self,data:list)->None:
|
|
63
|
+
self._data=np.vstack([self._data,np.array(data,dtype=self._dtype)])
|
|
64
|
+
|
|
65
|
+
def write_all(self,data:np.ndarray)->None:
|
|
66
|
+
self._data=np.vstack([self._data,data])
|
|
67
|
+
|
|
68
|
+
def close(self): pass
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def data(self)->np.ndarray:
|
|
72
|
+
'''获取写入的所有数据'''
|
|
73
|
+
return self._data
|
|
74
|
+
|
|
75
|
+
class CsvTableWriter(FileTableWriter):
|
|
76
|
+
'''CSV数据表写入器'''
|
|
77
|
+
_fh: TextIO
|
|
78
|
+
def __init__(self,fname:str,col_names:'list[str]',dtype:Type)->None:
|
|
79
|
+
'''
|
|
80
|
+
初始化
|
|
81
|
+
fname: 文件名
|
|
82
|
+
col_names: 列名
|
|
83
|
+
dtype: 数据类型, 可以是np.int32或np.float32
|
|
84
|
+
'''
|
|
85
|
+
super().__init__(open(fname,"w"),col_names,dtype)
|
|
86
|
+
self._fh.write(','.join(self._col_names)+"\n")
|
|
87
|
+
|
|
88
|
+
def write(self,data:list)->None:
|
|
89
|
+
self._fh.write(','.join(map(str,data))+"\n")
|
|
90
|
+
|
|
91
|
+
def write_all(self, data: np.ndarray)->None:
|
|
92
|
+
for ln in data: self.write(ln)
|
|
93
|
+
|
|
94
|
+
def close(self): self._fh.close()
|
|
95
|
+
|
|
96
|
+
class BinTableWriter(FileTableWriter):
|
|
97
|
+
'''二进制数据表写入器(抽象类)'''
|
|
98
|
+
_fh: IO
|
|
99
|
+
@abstractmethod
|
|
100
|
+
def __init__(self,fh,col_names:'list[str]',dtype:Type,buf_sz:int=1024):
|
|
101
|
+
super().__init__(fh,col_names,dtype)
|
|
102
|
+
header=_dtype2L(dtype)+('|'.join(self._col_names))
|
|
103
|
+
header+=" "*((4-len(header)%4)%4)
|
|
104
|
+
header=header.encode()
|
|
105
|
+
self._fh.write(struct.pack("<I",len(header)))
|
|
106
|
+
self._fh.write(header)
|
|
107
|
+
self._buf=[]
|
|
108
|
+
self._buf_sz=buf_sz
|
|
109
|
+
self._dcnt=0
|
|
110
|
+
|
|
111
|
+
def __wbuf(self):
|
|
112
|
+
self._fh.write(np.stack(self._buf,dtype=self._dtype).tobytes())
|
|
113
|
+
self._buf=[]
|
|
114
|
+
|
|
115
|
+
def write(self,data:list):
|
|
116
|
+
self._buf.append(data)
|
|
117
|
+
self._dcnt+=1
|
|
118
|
+
if len(data)>=self._buf_sz: self.__wbuf()
|
|
119
|
+
|
|
120
|
+
def write_all(self, data: np.ndarray):
|
|
121
|
+
if data.dtype!=self._dtype: data.astype(self._dtype)
|
|
122
|
+
self._fh.write(data.tobytes())
|
|
123
|
+
|
|
124
|
+
def close(self):
|
|
125
|
+
if len(self._buf)>0: self.__wbuf()
|
|
126
|
+
self._fh.close()
|
|
127
|
+
|
|
128
|
+
class SdtTableWriter(BinTableWriter):
|
|
129
|
+
'''SDT数据表写入器'''
|
|
130
|
+
def __init__(self,fname:str,col_names:'list[str]',dtype:Type,buf_sz:int=1024):
|
|
131
|
+
'''
|
|
132
|
+
初始化
|
|
133
|
+
fname: 文件名
|
|
134
|
+
col_names: 列名
|
|
135
|
+
dtype: 数据类型, 可以是np.int32或np.float32
|
|
136
|
+
buf_sz: 缓冲区大小, 默认为1024
|
|
137
|
+
'''
|
|
138
|
+
super().__init__(open(fname,"wb"),col_names,dtype,buf_sz)
|
|
139
|
+
|
|
140
|
+
class SdtGzTableWriter(BinTableWriter):
|
|
141
|
+
'''SDT.GZ数据表写入器'''
|
|
142
|
+
def __init__(self,fname:str,col_names:'list[str]',dtype:Type,buf_sz:int=1024):
|
|
143
|
+
'''
|
|
144
|
+
初始化
|
|
145
|
+
fname: 文件名
|
|
146
|
+
col_names: 列名
|
|
147
|
+
dtype: 数据类型, 可以是np.int32或np.float32
|
|
148
|
+
buf_sz: 缓冲区大小, 默认为1024
|
|
149
|
+
'''
|
|
150
|
+
super().__init__(gzip.open(fname,"wb"),col_names,dtype,buf_sz)
|
|
151
|
+
|
|
152
|
+
class TableReader(metaclass = ABCMeta):
|
|
153
|
+
'''数据表读取器(抽象类)'''
|
|
154
|
+
@abstractmethod
|
|
155
|
+
def __init__(self,col_names:'list[str]',dtype:Type)->None:
|
|
156
|
+
_dtypechk(dtype)
|
|
157
|
+
self._col_names:'list[str]'=col_names
|
|
158
|
+
self._col_cnt=len(self._col_names)
|
|
159
|
+
self._cmap = {cn:i for i,cn in enumerate(self._col_names)}
|
|
160
|
+
self._dtype:Type=dtype
|
|
161
|
+
@property
|
|
162
|
+
def head(self)->'list[str]':
|
|
163
|
+
'''表头'''
|
|
164
|
+
return self._col_names
|
|
165
|
+
@property
|
|
166
|
+
def dtype(self)->Type:
|
|
167
|
+
'''数据类型, 可以是np.int32或np.float32'''
|
|
168
|
+
return self._dtype
|
|
169
|
+
@abstractmethod
|
|
170
|
+
def read(self,cnt:int)->np.ndarray: '''从当前位置开始, 读取1行内容'''
|
|
171
|
+
@abstractmethod
|
|
172
|
+
def read_all(self)->np.ndarray: '''从头开始, 读取所有内容'''
|
|
173
|
+
@abstractmethod
|
|
174
|
+
def close(self)->None: '''关闭TableReader'''
|
|
175
|
+
|
|
176
|
+
class FileTableReader(TableReader):
|
|
177
|
+
'''文件数据表读取器(抽象类)'''
|
|
178
|
+
@abstractmethod
|
|
179
|
+
def __init__(self,f:IO,col_names:'list[str]',dtype:Type)->None:
|
|
180
|
+
super().__init__(col_names,dtype)
|
|
181
|
+
self._fh:IO=f
|
|
182
|
+
def close(self)->None:
|
|
183
|
+
self._fh.close()
|
|
184
|
+
|
|
185
|
+
class MemoryTableReader(TableReader):
|
|
186
|
+
'''内存数据表读取器'''
|
|
187
|
+
_data:np.ndarray
|
|
188
|
+
def __init__(self,col_names:'list[str]',data:np.ndarray):
|
|
189
|
+
'''
|
|
190
|
+
初始化
|
|
191
|
+
col_names: 列名
|
|
192
|
+
data: 数据
|
|
193
|
+
'''
|
|
194
|
+
super().__init__(col_names,data.dtype)
|
|
195
|
+
self._data=data
|
|
196
|
+
self.__pos = 0
|
|
197
|
+
|
|
198
|
+
def read(self,cnt:int)->np.ndarray:
|
|
199
|
+
assert cnt > 0, "读取行数必须大于0"
|
|
200
|
+
ed = cnt + self.__pos
|
|
201
|
+
if ed > self._data.shape[0]: ed = self._data.shape[0]
|
|
202
|
+
return self._data[self.__pos:ed]
|
|
203
|
+
|
|
204
|
+
def read_all(self)->np.ndarray:
|
|
205
|
+
return self._data
|
|
206
|
+
|
|
207
|
+
def close(self): pass
|
|
208
|
+
|
|
209
|
+
class BinTableReader(FileTableReader):
|
|
210
|
+
'''二进制数据表读取器(抽象类)'''
|
|
211
|
+
_fh:BinaryIO
|
|
212
|
+
@abstractmethod
|
|
213
|
+
def __init__(self,fname:str,openFunc,allowNegativeSeek:bool=False)->None:
|
|
214
|
+
self.__fn=fname
|
|
215
|
+
self._openF=openFunc
|
|
216
|
+
fh:BinaryIO=self._openF(self.__fn,"rb")
|
|
217
|
+
hlen=struct.unpack("<I",fh.read(4))[0]
|
|
218
|
+
header=fh.read(hlen).decode()
|
|
219
|
+
super().__init__(fh,header[1:].strip().split('|'),_L2dtype(header[0]))
|
|
220
|
+
self._dstart=hlen+4
|
|
221
|
+
self._itmsz=self._col_cnt*4
|
|
222
|
+
self._neg=allowNegativeSeek
|
|
223
|
+
|
|
224
|
+
def seek(self,row:int,col:int=0)->int:
|
|
225
|
+
'''定位到第row行的第col个数据, row和col均从0开始编号'''
|
|
226
|
+
dst=self._dstart+self._itmsz*row+4*col
|
|
227
|
+
if self._neg or self._fh.tell()<=dst:
|
|
228
|
+
return self._fh.seek(dst)
|
|
229
|
+
else:
|
|
230
|
+
self._fh.close()
|
|
231
|
+
self._fh=self._openF(self.__fn,"rb")
|
|
232
|
+
return self._fh.seek(dst)
|
|
233
|
+
|
|
234
|
+
def read(self,cnt:int)->Optional[np.ndarray]:
|
|
235
|
+
data=self._fh.read(self._itmsz*cnt)
|
|
236
|
+
if data==b'': return None
|
|
237
|
+
return np.frombuffer(data,self._dtype).reshape(-1,self._col_cnt)
|
|
238
|
+
|
|
239
|
+
def read_all(self)->np.ndarray:
|
|
240
|
+
self.seek(0,0)
|
|
241
|
+
return np.frombuffer(self._fh.read(),self._dtype).reshape(-1,self._col_cnt)
|
|
242
|
+
|
|
243
|
+
def read_col(self,col_id:Union[int,str])->np.ndarray:
|
|
244
|
+
'''读取col_id列. col_id可以是下标, 也可以是列名'''
|
|
245
|
+
if isinstance(col_id,str): col_id=self._cmap[col_id]
|
|
246
|
+
i=0; dat=[]
|
|
247
|
+
while True:
|
|
248
|
+
self.seek(i,col_id)
|
|
249
|
+
d=self._fh.read(4)
|
|
250
|
+
if d==b'': break
|
|
251
|
+
dat.append(d)
|
|
252
|
+
i+=1
|
|
253
|
+
return np.frombuffer(b''.join(dat),dtype=self._dtype)
|
|
254
|
+
|
|
255
|
+
def read_at(self,start:int,cnt:int)->Optional[np.ndarray]:
|
|
256
|
+
'''读取range(start,start+cnt)范围的行'''
|
|
257
|
+
self.seek(start)
|
|
258
|
+
return self.read(cnt)
|
|
259
|
+
|
|
260
|
+
class SdtTableReader(BinTableReader):
|
|
261
|
+
'''SDT表格读取器'''
|
|
262
|
+
def __init__(self,fname:str):
|
|
263
|
+
'''
|
|
264
|
+
初始化
|
|
265
|
+
fname: 文件名
|
|
266
|
+
'''
|
|
267
|
+
super().__init__(fname,open,True)
|
|
268
|
+
|
|
269
|
+
class SdtGzTableReader(BinTableReader):
|
|
270
|
+
'''SDT.GZ表格读取器'''
|
|
271
|
+
def __init__(self,fname:str):
|
|
272
|
+
'''
|
|
273
|
+
初始化
|
|
274
|
+
fname: 文件名
|
|
275
|
+
'''
|
|
276
|
+
super().__init__(fname,gzip.open,False)
|
|
277
|
+
|
|
278
|
+
class CsvTableReader(FileTableReader):
|
|
279
|
+
'''CSV表格读取器'''
|
|
280
|
+
_fh:TextIO
|
|
281
|
+
def __init__(self,fname:str,dtype:Type)->None:
|
|
282
|
+
'''
|
|
283
|
+
初始化
|
|
284
|
+
fname: 文件名
|
|
285
|
+
dtype: 数据类型, 可以是np.int32或np.float32
|
|
286
|
+
'''
|
|
287
|
+
self.__fn=fname
|
|
288
|
+
fh=open(fname,"r")
|
|
289
|
+
super().__init__(fh,fh.readline().strip().split(","),dtype)
|
|
290
|
+
|
|
291
|
+
def __parseline(self,ln:str)->np.ndarray:
|
|
292
|
+
strs=map(lambda x: x.strip(), ln.split(','))
|
|
293
|
+
if self._dtype==np.int32:
|
|
294
|
+
return np.array(list(map(int,strs)),dtype=np.int32)
|
|
295
|
+
else:
|
|
296
|
+
return np.array(list(map(float,strs)),dtype=np.float32)
|
|
297
|
+
|
|
298
|
+
def __read1(self)->Optional[np.ndarray]:
|
|
299
|
+
'''从当前位置开始, 读取1行'''
|
|
300
|
+
ln = self._fh.readline().strip()
|
|
301
|
+
if ln == "": return None
|
|
302
|
+
return self.__parseline(ln)
|
|
303
|
+
|
|
304
|
+
def read(self,cnt:int)->Optional[np.ndarray]:
|
|
305
|
+
if cnt == 1: return self.__read1()
|
|
306
|
+
ret = []
|
|
307
|
+
for _ in range(cnt):
|
|
308
|
+
dat = self.__read1()
|
|
309
|
+
if dat is None: break
|
|
310
|
+
ret.append(dat)
|
|
311
|
+
if len(ret) == 0: return None
|
|
312
|
+
return np.stack(ret)
|
|
313
|
+
|
|
314
|
+
def read_all(self)->np.ndarray:
|
|
315
|
+
self._fh.close()
|
|
316
|
+
self._fh = open(self.__fn,"r")
|
|
317
|
+
self._fh.readline()
|
|
318
|
+
return np.stack([self.__parseline(x) for x in self._fh.readlines()])
|
|
319
|
+
|
|
320
|
+
def createTableReader(fname:str,dtype:Optional[Type]=None)->TableReader:
|
|
321
|
+
'''从文件名创建TableReader, 对于CSV文件需要指定dtype'''
|
|
322
|
+
fn=fname.lower()
|
|
323
|
+
if fn.endswith(".csv"):
|
|
324
|
+
assert dtype is not None
|
|
325
|
+
return CsvTableReader(fname,dtype)
|
|
326
|
+
elif fn.endswith(".sdt"):
|
|
327
|
+
return SdtTableReader(fname)
|
|
328
|
+
elif fn.endswith(".sdt.gz"):
|
|
329
|
+
return SdtGzTableReader(fname)
|
|
330
|
+
else:
|
|
331
|
+
raise ValueError("不支持的文件类型")
|
|
332
|
+
|
|
333
|
+
def createTableWriter(fname:str,cols:'list[str]',dtype:Type)->TableWriter:
|
|
334
|
+
'''从文件名创建TableWriter'''
|
|
335
|
+
fn=fname.lower()
|
|
336
|
+
if fn.endswith(".csv"):
|
|
337
|
+
return CsvTableWriter(fname,cols,dtype)
|
|
338
|
+
elif fn.endswith(".sdt"):
|
|
339
|
+
return SdtTableWriter(fname,cols,dtype)
|
|
340
|
+
elif fn.endswith(".sdt.gz"):
|
|
341
|
+
return SdtGzTableWriter(fname,cols,dtype)
|
|
342
|
+
elif fn.endswith("<mem>"):
|
|
343
|
+
return MemoryTableWriter(cols,dtype)
|
|
344
|
+
else:
|
|
345
|
+
raise ValueError("不支持的文件类型")
|
|
346
|
+
|
|
347
|
+
def _convbinfile(r:Union[BinaryIO,gzip.GzipFile],w:Union[BinaryIO,gzip.GzipFile],bufsz:int=1024*1024*64):
|
|
348
|
+
while True:
|
|
349
|
+
data=r.read(bufsz)
|
|
350
|
+
if data==b'': break
|
|
351
|
+
w.write(data)
|
|
352
|
+
r.close()
|
|
353
|
+
w.close()
|
|
354
|
+
|
|
355
|
+
def convertTableFile(rfile:str,wfile:str,dtype:Optional[Type]=None,bufsz:int=1024)->None:
|
|
356
|
+
'''将一种文件类型的Table转化成另一文件类型的Table, 如果输入文件为CSV文件, 还需要指定dtype'''
|
|
357
|
+
if rfile.lower().endswith(".sdt.gz") and wfile.lower().endswith(".sdt"):
|
|
358
|
+
_convbinfile(gzip.open(rfile,"rb"),open(wfile,"wb"))
|
|
359
|
+
return
|
|
360
|
+
elif rfile.lower().endswith(".gz") and wfile.lower().endswith(".sdt.gz"):
|
|
361
|
+
_convbinfile(open(rfile,"rb"),gzip.open(wfile,"wb"))
|
|
362
|
+
return
|
|
363
|
+
r=createTableReader(rfile,dtype)
|
|
364
|
+
w=createTableWriter(wfile,r.head,r.dtype)
|
|
365
|
+
while True:
|
|
366
|
+
data=r.read(bufsz)
|
|
367
|
+
if data is None: break
|
|
368
|
+
w.write_all(data)
|
|
369
|
+
r.close()
|
|
370
|
+
w.close()
|
|
371
|
+
|
|
372
|
+
class ReadOnlyTable(Generic[_Table_DType]):
|
|
373
|
+
'''
|
|
374
|
+
**只读**数据表, 表中所有数据必须是同一类型, 必须是32位int或float格式.
|
|
375
|
+
某一行的内容直接用下标获取; 某一列的内容用col方法获取.
|
|
376
|
+
由于是只读列表,因此必须从文件加载. 支持的文件类型包含csv, sdt和sdt.gz.
|
|
377
|
+
'''
|
|
378
|
+
_d:'Optional[np.ndarray[tuple[int,int],np.dtype[_Table_DType]]]'
|
|
379
|
+
_btr:TableReader
|
|
380
|
+
|
|
381
|
+
@overload
|
|
382
|
+
def __init__(self,source:MemoryTableReader):
|
|
383
|
+
'''使用MemoryTableReader初始化'''
|
|
384
|
+
@overload
|
|
385
|
+
def __init__(self,source:MemoryTableWriter):
|
|
386
|
+
'''使用MemoryTableWriter初始化'''
|
|
387
|
+
@overload
|
|
388
|
+
def __init__(self,source:str,dtype:Optional[Table_DType]=None,preload:bool=False):
|
|
389
|
+
'''
|
|
390
|
+
初始化
|
|
391
|
+
fname: 数据表文件名, 仅支持csv, sdt和sdt.gz文件.
|
|
392
|
+
dtype: 表格数据类型, 只有csv文件需要提供此项.
|
|
393
|
+
preload: 是否在初始化时预加载全部数据.
|
|
394
|
+
|
|
395
|
+
注意:
|
|
396
|
+
预加载使得初始化速度大幅降低, 但大幅提升了运行时性能.
|
|
397
|
+
csv强制开启预加载功能, sdt和sdt.gz自选(推荐sdt.gz开启, sdt关闭).
|
|
398
|
+
如果数据过大导致内存不足, 请勿使用预加载功能.
|
|
399
|
+
'''
|
|
400
|
+
|
|
401
|
+
def __init__(self,source:Union[str,MemoryTableWriter,MemoryTableReader],dtype:Optional[Table_DType]=None,preload:bool=False):
|
|
402
|
+
if isinstance(source,str):
|
|
403
|
+
self._btr=createTableReader(source,dtype)
|
|
404
|
+
fn=source.lower()
|
|
405
|
+
if preload or fn.lower().endswith(".csv"):
|
|
406
|
+
self._d=self._btr.read_all()
|
|
407
|
+
else:
|
|
408
|
+
self._d=None
|
|
409
|
+
elif isinstance(source,MemoryTableReader):
|
|
410
|
+
self._btr=source
|
|
411
|
+
self._d=source._data
|
|
412
|
+
elif isinstance(source,MemoryTableWriter):
|
|
413
|
+
self._btr=MemoryTableReader(source._col_names,source._data)
|
|
414
|
+
self._d=source._data
|
|
415
|
+
|
|
416
|
+
@property
|
|
417
|
+
def head(self)->'list[str]':
|
|
418
|
+
'''表头'''
|
|
419
|
+
return self._btr._col_names
|
|
420
|
+
|
|
421
|
+
@property
|
|
422
|
+
def dtype(self)->Table_DType:
|
|
423
|
+
'''
|
|
424
|
+
表中数据类型, 可取np.int32或np.float32
|
|
425
|
+
'''
|
|
426
|
+
return self._btr._dtype
|
|
427
|
+
|
|
428
|
+
@property
|
|
429
|
+
def data(self)->'np.ndarray[(int,int),np.dtype[_Table_DType]]':
|
|
430
|
+
'''
|
|
431
|
+
表格数据. 如果没有预加载全部数据, 则调用此项时会加载所有数据.
|
|
432
|
+
'''
|
|
433
|
+
if self._d is None: self._d=self._btr.read_all()
|
|
434
|
+
return self._d
|
|
435
|
+
|
|
436
|
+
def force_load_all(self): self._d=self._btr.read_all()
|
|
437
|
+
|
|
438
|
+
@overload
|
|
439
|
+
def col(self,c:str): '''获取列名为c的列'''
|
|
440
|
+
@overload
|
|
441
|
+
def col(self,c:int): '''获取第c列'''
|
|
442
|
+
@overload
|
|
443
|
+
def col(self,c:Iterable[Union[str,int]]): '''获取多列'''
|
|
444
|
+
|
|
445
|
+
def col(self,c:Union[Iterable[Union[str,int]],str,int])->'np.ndarray[int,np.dtype[_Table_DType]]':
|
|
446
|
+
if isinstance(c,int) or isinstance(c,str):
|
|
447
|
+
if self._d is None:
|
|
448
|
+
assert isinstance(self._btr,BinTableReader)
|
|
449
|
+
return self._btr.read_col(c)
|
|
450
|
+
else:
|
|
451
|
+
if isinstance(c,str): c=self._btr._cmap[c]
|
|
452
|
+
return self._d[:,c]
|
|
453
|
+
elif isinstance(c,Iterable):
|
|
454
|
+
nc:list[int]=[self._btr._cmap[x] if isinstance(x,str) else x for x in c]
|
|
455
|
+
return self.data[:,nc]
|
|
456
|
+
else:
|
|
457
|
+
raise TypeError("不支持的索引类型")
|
|
458
|
+
|
|
459
|
+
def row(self,row_id:int)->'Optional[np.ndarray[int,np.dtype[_Table_DType]]]':
|
|
460
|
+
'''获取一行数据'''
|
|
461
|
+
if self._d is not None: return self._d[row_id]
|
|
462
|
+
assert isinstance(self._btr,BinTableReader)
|
|
463
|
+
return self._btr.read_at(row_id,1)
|
|
464
|
+
|
|
465
|
+
def at(self,col_name:str,row_id:int)->_Table_DType:
|
|
466
|
+
'''获取列名为col_name,行为row_id的数据'''
|
|
467
|
+
return self.data[self._btr._cmap[col_name],row_id]
|
|
468
|
+
|
|
469
|
+
def __getitem__(self,indices)->'Union[np.ndarray[tuple[int,...],np.dtype[_Table_DType]],_Table_DType]':
|
|
470
|
+
'''仅限数字下标'''
|
|
471
|
+
return self.data[indices]
|
|
472
|
+
|
|
473
|
+
def save(self,path:str):
|
|
474
|
+
'''
|
|
475
|
+
保存到.csv或.bin.gz文件
|
|
476
|
+
path: 文件路径
|
|
477
|
+
'''
|
|
478
|
+
if self._d is None: self._d=self._btr.read_all()
|
|
479
|
+
fn=path.lower()
|
|
480
|
+
if fn.endswith(".csv"):
|
|
481
|
+
CsvTableWriter(path,self.head,self.dtype).write_all(self._d)
|
|
482
|
+
elif fn.endswith(".sdt"):
|
|
483
|
+
SdtTableWriter(path,self.head,self.dtype).write_all(self._d)
|
|
484
|
+
elif fn.endswith(".sdt.gz"):
|
|
485
|
+
SdtGzTableWriter(path,self.head,self.dtype).write_all(self._d)
|
|
486
|
+
else:
|
|
487
|
+
raise ValueError("不支持的文件名")
|
|
488
|
+
|
|
489
|
+
def __str__(self):
|
|
490
|
+
return ','.join(self.head)+"\n"+super().__str__()
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from abc import ABCMeta, abstractmethod
|
|
2
|
+
from functools import reduce
|
|
3
|
+
from operator import add, sub, mul, truediv, floordiv
|
|
4
|
+
from typing import Callable, Iterable, Optional, Union
|
|
5
|
+
import bisect
|
|
6
|
+
|
|
7
|
+
class TimeFunc(metaclass=ABCMeta):
|
|
8
|
+
'''时变函数'''
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def __call__(self,time:int)->float: ...
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def __str__(self)->str: ...
|
|
13
|
+
def __add__(self,other:'FloatLike')->'TimeFunc':
|
|
14
|
+
return calcFunc(self,other,'+')
|
|
15
|
+
def __sub__(self,other:'TimeFunc')->'TimeFunc':
|
|
16
|
+
return calcFunc(self,other,'-')
|
|
17
|
+
def __mul__(self,other)->'TimeFunc':
|
|
18
|
+
return calcFunc(self,other,'*')
|
|
19
|
+
def __truediv__(self,other)->'TimeFunc':
|
|
20
|
+
return calcFunc(self,other,'/')
|
|
21
|
+
def __floordiv__(self,other)->'TimeFunc':
|
|
22
|
+
return calcFunc(self,other,'//')
|
|
23
|
+
|
|
24
|
+
_oper_trans = {
|
|
25
|
+
'+': add,
|
|
26
|
+
'-': sub,
|
|
27
|
+
'*': mul,
|
|
28
|
+
'/': truediv,
|
|
29
|
+
'//': floordiv
|
|
30
|
+
}
|
|
31
|
+
_Oper = Callable[[float,float],float]
|
|
32
|
+
FloatLike = Union[TimeFunc,float,int]
|
|
33
|
+
|
|
34
|
+
class OverrideFunc(TimeFunc):
|
|
35
|
+
'''重载函数. 正常情况下返回原始时间函数的值, 当设置了重载值时返回重载值'''
|
|
36
|
+
def __init__(self,default:TimeFunc,override_val:Optional[float] = None):
|
|
37
|
+
self._val:TimeFunc=default
|
|
38
|
+
self._override:Optional[float] = override_val
|
|
39
|
+
|
|
40
|
+
def __call__(self,t:int)->float:
|
|
41
|
+
if self._override is not None: return self._override
|
|
42
|
+
return self._val(t)
|
|
43
|
+
|
|
44
|
+
def setOverride(self,overval:float):
|
|
45
|
+
'''设置重载值'''
|
|
46
|
+
self._override=overval
|
|
47
|
+
|
|
48
|
+
def clearOverride(self):
|
|
49
|
+
'''清除重载值'''
|
|
50
|
+
self._override=None
|
|
51
|
+
|
|
52
|
+
def __str__(self):
|
|
53
|
+
if self._override is None: return f"OverrideF<{self._val}>"
|
|
54
|
+
return f"OverrideF<O={self._override}>"
|
|
55
|
+
|
|
56
|
+
def __repr__(self): return str(self)
|
|
57
|
+
|
|
58
|
+
class PlusFunc(TimeFunc):
|
|
59
|
+
'''和函数'''
|
|
60
|
+
def __init__(self,f1:TimeFunc,f2:TimeFunc): self._f1=f1; self._f2=f2
|
|
61
|
+
def __call__(self,_t:int)->float: return self._f1(_t)+self._f2(_t)
|
|
62
|
+
def __str__(self)->str: return f"F<{self._f1}+{self._f2}>"
|
|
63
|
+
def __repr__(self)->str: return str(self)
|
|
64
|
+
|
|
65
|
+
class QuickSumFunc(TimeFunc):
|
|
66
|
+
'''快速求和函数'''
|
|
67
|
+
def __init__(self,funcs:'list[TimeFunc]'): self._fs=funcs
|
|
68
|
+
def __call__(self,_t:int)->float: return sum(f(_t) for f in self._fs)
|
|
69
|
+
def __str__(self)->str: return f"FSum<{len(self._fs)} funcs>"
|
|
70
|
+
def __repr__(self)->str: return str(self)
|
|
71
|
+
|
|
72
|
+
class MinusFunc(TimeFunc):
|
|
73
|
+
'''差函数'''
|
|
74
|
+
def __init__(self,f1:TimeFunc,f2:TimeFunc): self._f1=f1; self._f2=f2
|
|
75
|
+
def __call__(self,_t:int)->float: return self._f1(_t)-self._f2(_t)
|
|
76
|
+
def __str__(self)->str: return f"F<{self._f1}-{self._f2}>"
|
|
77
|
+
def __repr__(self)->str: return str(self)
|
|
78
|
+
|
|
79
|
+
class MulFunc(TimeFunc):
|
|
80
|
+
'''积函数'''
|
|
81
|
+
def __init__(self,f1:TimeFunc,f2:TimeFunc): self._f1=f1; self._f2=f2
|
|
82
|
+
def __call__(self,_t:int)->float: return self._f1(_t)*self._f2(_t)
|
|
83
|
+
def __str__(self)->str: return f"F<{self._f1}*{self._f2}>"
|
|
84
|
+
def __repr__(self)->str: return str(self)
|
|
85
|
+
|
|
86
|
+
class QuickMulFunc(TimeFunc):
|
|
87
|
+
'''快速求积函数'''
|
|
88
|
+
def __init__(self,funcs:'list[TimeFunc]'): self._fs=funcs
|
|
89
|
+
def __call__(self,_t:int)->float: return reduce(mul, (f(_t) for f in self._fs))
|
|
90
|
+
def __str__(self)->str: return f"FMul<{len(self._fs)} funcs>"
|
|
91
|
+
def __repr__(self)->str: return str(self)
|
|
92
|
+
|
|
93
|
+
class TrueDivFunc(TimeFunc):
|
|
94
|
+
'''商函数'''
|
|
95
|
+
def __init__(self,f1:TimeFunc,f2:TimeFunc): self._f1=f1; self._f2=f2
|
|
96
|
+
def __call__(self,_t:int)->float: return self._f1(_t)/self._f2(_t)
|
|
97
|
+
def __str__(self)->str: return f"F<{self._f1}/{self._f2}>"
|
|
98
|
+
def __repr__(self)->str: return str(self)
|
|
99
|
+
|
|
100
|
+
class FloorDivFunc(TimeFunc):
|
|
101
|
+
'''整除函数'''
|
|
102
|
+
def __init__(self,f1:TimeFunc,f2:TimeFunc): self._f1=f1; self._f2=f2
|
|
103
|
+
def __call__(self,_t:int)->float: return self._f1(_t)//self._f2(_t)
|
|
104
|
+
def __str__(self)->str: return f"F<{self._f1}//{self._f2}>"
|
|
105
|
+
def __repr__(self)->str: return str(self)
|
|
106
|
+
|
|
107
|
+
class ConstFunc(TimeFunc):
|
|
108
|
+
'''常数函数'''
|
|
109
|
+
def __init__(self,const:float): self._val:float=const
|
|
110
|
+
def __call__(self,time:int)->float: return self._val
|
|
111
|
+
def __str__(self)->str: return f"ConstF<{self._val}>"
|
|
112
|
+
def __repr__(self)->str: return str(self)
|
|
113
|
+
|
|
114
|
+
class SegFunc(TimeFunc):
|
|
115
|
+
'''分段常数函数'''
|
|
116
|
+
def __init__(self,time_line:'list[int]',data:'list[float]'):
|
|
117
|
+
if len(time_line) != len(data): raise ValueError(f"时间线长度{len(time_line)}和数据长度{len(data)}不一致")
|
|
118
|
+
for i in range(1,len(time_line)):
|
|
119
|
+
if time_line[i]<=time_line[i-1]:
|
|
120
|
+
raise ValueError(f"时间必须严格递增: [{i}]={time_line[i]}<=[{i-1}]={time_line[i-1]}")
|
|
121
|
+
self._tl = time_line
|
|
122
|
+
self._d = data
|
|
123
|
+
|
|
124
|
+
def __call__(self,time:int)->float:
|
|
125
|
+
if time < self._tl[0]: raise ValueError(f"时间{time}必须在开始时间{self._tl[0]}之后")
|
|
126
|
+
return self._d[bisect.bisect_right(self._tl, time) - 1]
|
|
127
|
+
def __str__(self)->str: return f"SegF<{len(self._d)} segs>"
|
|
128
|
+
def __repr__(self)->str: return str(self)
|
|
129
|
+
|
|
130
|
+
class TimeImplictFunc(TimeFunc):
|
|
131
|
+
'''隐式时变函数, 即调用的时机决定了时间, 而无需在__call__时指定。__call__的参数time无效'''
|
|
132
|
+
def __init__(self,func:'Callable[[],float]'):self._f=func
|
|
133
|
+
def __call__(self,time:int)->float:return self._f()
|
|
134
|
+
def __str__(self)->str:return f"TImpF<{self._f}>"
|
|
135
|
+
def __repr__(self)->str: return str(self)
|
|
136
|
+
|
|
137
|
+
class ComFunc(TimeFunc):
|
|
138
|
+
'''将普通Python函数包装成可运算函数'''
|
|
139
|
+
def __init__(self,func:'Callable[[int],float]'):self._f=func
|
|
140
|
+
def __call__(self,time:int)->float:return self._f(time)
|
|
141
|
+
def __str__(self)->str:return f"TImpF<{self._f}>"
|
|
142
|
+
def __repr__(self)->str: return str(self)
|
|
143
|
+
|
|
144
|
+
class ManualFunc(TimeFunc):
|
|
145
|
+
'''手动指定常数函数'''
|
|
146
|
+
def __init__(self,init_val:float):self._v=init_val
|
|
147
|
+
def setManual(self,val:float):self._v=val
|
|
148
|
+
def __call__(self,time:int)->float:return self._v
|
|
149
|
+
def __str__(self)->str:return f"ManF<{self._v}>"
|
|
150
|
+
def __repr__(self)->str: return str(self)
|
|
151
|
+
|
|
152
|
+
def __calc_c0(f1:Union[ConstFunc,float],f2:float,op:_Oper)->float:
|
|
153
|
+
if isinstance(f1,float): return op(f1,f2)
|
|
154
|
+
elif isinstance(f1,ConstFunc): return op(f1._val,f2)
|
|
155
|
+
else: raise TypeError(f1)
|
|
156
|
+
|
|
157
|
+
def __calc_c1(f1:SegFunc,f2:float,op:_Oper)->SegFunc:
|
|
158
|
+
return SegFunc(f1._tl,[op(d,f2) for d in f1._d])
|
|
159
|
+
|
|
160
|
+
def __calc_c2(f1:TimeImplictFunc,f2:float,op:_Oper)->TimeImplictFunc:
|
|
161
|
+
return TimeImplictFunc(lambda: op(f1._f(),f2))
|
|
162
|
+
|
|
163
|
+
def quicksum(funcs:Iterable[TimeFunc])->TimeFunc:
|
|
164
|
+
'''快速求和'''
|
|
165
|
+
ret = list(funcs)
|
|
166
|
+
if len(ret)==0: return ConstFunc(0)
|
|
167
|
+
if len(ret)==1: return ret[0]
|
|
168
|
+
if len(ret)==2: return PlusFunc(ret[0],ret[1])
|
|
169
|
+
return QuickSumFunc(ret)
|
|
170
|
+
|
|
171
|
+
def quickmul(funcs:Iterable[TimeFunc])->TimeFunc:
|
|
172
|
+
'''快速求积'''
|
|
173
|
+
ret = list(funcs)
|
|
174
|
+
if len(ret)==0: return ConstFunc(1)
|
|
175
|
+
if len(ret)==1: return ret[0]
|
|
176
|
+
if len(ret)==2: return MulFunc(ret[0],ret[1])
|
|
177
|
+
return QuickMulFunc(ret)
|
|
178
|
+
|
|
179
|
+
def calcFunc(f1:FloatLike,f2:FloatLike,op:str)->TimeFunc:
|
|
180
|
+
_op = _oper_trans[op]
|
|
181
|
+
if isinstance(f2,ConstFunc): f2=f2._val
|
|
182
|
+
if isinstance(f2,(float,int)):
|
|
183
|
+
if isinstance(f1,(ConstFunc,float,int)): return ConstFunc(__calc_c0(f1,f2,_op))
|
|
184
|
+
elif isinstance(f1,SegFunc): return __calc_c1(f1,f2,_op)
|
|
185
|
+
elif isinstance(f1,TimeImplictFunc): return __calc_c2(f1,f2,_op)
|
|
186
|
+
else: f2 = ConstFunc(f2)
|
|
187
|
+
if isinstance(f1,(float,int)): f1 = ConstFunc(f1)
|
|
188
|
+
if isinstance(f1,ConstFunc) and op in ['+','*']:
|
|
189
|
+
if isinstance(f2,SegFunc): return __calc_c1(f2,f1._val,_op)
|
|
190
|
+
elif isinstance(f2,TimeImplictFunc): return __calc_c2(f2,f1._val,_op)
|
|
191
|
+
assert isinstance(f1,TimeFunc) and isinstance(f2,TimeFunc)
|
|
192
|
+
if op=='+': return PlusFunc(f1,f2)
|
|
193
|
+
elif op=='-': return MinusFunc(f1,f2)
|
|
194
|
+
elif op=='*': return MulFunc(f1,f2)
|
|
195
|
+
elif op=='/': return TrueDivFunc(f1,f2)
|
|
196
|
+
elif op=='//': return FloorDivFunc(f1,f2)
|
|
197
|
+
else: raise ValueError(op)
|
|
198
|
+
|
|
199
|
+
def makeFunc(time_line:'list[int]',data:list)->TimeFunc:
|
|
200
|
+
'''生成分段常数函数或常数函数'''
|
|
201
|
+
if len(time_line)!=len(data): raise ValueError(f"时间线长度{len(time_line)}和数据长度{len(data)}不一致")
|
|
202
|
+
if len(data)==1: return ConstFunc(data[0])
|
|
203
|
+
else: return SegFunc(time_line,data)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: feasytools
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: A set of tools for data processing, including Time Function, Table, Priority Queue, Range List, etc.
|
|
5
|
+
Home-page: UNKNOWN
|
|
6
|
+
Author: fmy_xfk
|
|
7
|
+
License: UNKNOWN
|
|
8
|
+
Description: # FEasyTools: Some useful components
|
|
9
|
+
- dprov: Data provider as time functions
|
|
10
|
+
- argchk: Argument parser with type checking
|
|
11
|
+
- table: Table helper for the CSV, SDT & SDT.GZ format, and their reader and writer.
|
|
12
|
+
- pq: Heap, priority queue & buffered priority queue
|
|
13
|
+
- rangelist: An xml reader for a time range list
|
|
14
|
+
|
|
15
|
+
Platform: UNKNOWN
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Requires-Python: >=3.6
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
feasytools/__init__.py
|
|
4
|
+
feasytools/argchk.py
|
|
5
|
+
feasytools/pq.py
|
|
6
|
+
feasytools/rangelist.py
|
|
7
|
+
feasytools/table.py
|
|
8
|
+
feasytools/tfunc.py
|
|
9
|
+
feasytools.egg-info/PKG-INFO
|
|
10
|
+
feasytools.egg-info/SOURCES.txt
|
|
11
|
+
feasytools.egg-info/dependency_links.txt
|
|
12
|
+
feasytools.egg-info/requires.txt
|
|
13
|
+
feasytools.egg-info/top_level.txt
|
|
14
|
+
test/test.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
numpy>=1.19.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
feasytools
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r") as f:
|
|
4
|
+
long_description = f.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="feasytools",
|
|
8
|
+
version="0.0.2",
|
|
9
|
+
author="fmy_xfk",
|
|
10
|
+
packages=find_packages(),
|
|
11
|
+
description="A set of tools for data processing, including Time Function, Table, Priority Queue, Range List, etc.",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
classifiers=[
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
],
|
|
19
|
+
python_requires=">=3.6",
|
|
20
|
+
install_requires=["numpy>=1.19.0"],
|
|
21
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
print(__file__)
|
|
4
|
+
sys.path.append(str(Path(__file__).parent.parent))
|
|
5
|
+
print(sys.path)
|
|
6
|
+
from feasytools import *
|
|
7
|
+
|
|
8
|
+
print("ArgChecker test:")
|
|
9
|
+
print(ArgChecker())
|
|
10
|
+
|
|
11
|
+
print("PQ test:")
|
|
12
|
+
hp = Heap()
|
|
13
|
+
hp.push("h")
|
|
14
|
+
hp.push("e")
|
|
15
|
+
assert hp.empty() == False
|
|
16
|
+
assert len(hp) == 2
|
|
17
|
+
assert hp.remove("h") == True
|
|
18
|
+
hp.push("f")
|
|
19
|
+
assert hp.pop() == "e"
|
|
20
|
+
assert hp.pop() == "f"
|
|
21
|
+
assert hp.empty() == True
|
|
22
|
+
|
|
23
|
+
q = PQueue()
|
|
24
|
+
q.push(3,"henks")
|
|
25
|
+
q.push(1,"shjd")
|
|
26
|
+
assert q.pop() == (1,"shjd")
|
|
27
|
+
assert q.remove("henks") == True
|
|
28
|
+
|
|
29
|
+
print("Rangelist test:")
|
|
30
|
+
rl = RangeList([(0,1),(2,3),(4,5)])
|
|
31
|
+
assert 0 in rl
|
|
32
|
+
assert not 1 in rl
|
|
33
|
+
|
|
34
|
+
# More general tests will be added in the future
|
|
35
|
+
|
|
36
|
+
print("TimeFunc test:")
|
|
37
|
+
f1 = ConstFunc(1)
|
|
38
|
+
f2 = TimeImplictFunc(lambda: 1+2+3)
|
|
39
|
+
f3 = ComFunc(lambda t: t)
|
|
40
|
+
f4 = ManualFunc(1)
|
|
41
|
+
f4.setManual(2)
|
|
42
|
+
print(f1(0))
|
|
43
|
+
print(f2(0))
|
|
44
|
+
print(f3(0))
|
|
45
|
+
print(f4(0))
|
|
46
|
+
print(f1)
|
|
47
|
+
print(f2)
|
|
48
|
+
print(f3)
|
|
49
|
+
print(f4)
|
|
50
|
+
f5 = quicksum([f1,f2,f3,f4])
|
|
51
|
+
print(f5)
|
|
52
|
+
f6 = quickmul([f1,f2,f3,f4])
|
|
53
|
+
print(f6)
|
|
54
|
+
f7 = f5 + 1
|
|
55
|
+
print(f7)
|
|
56
|
+
f8 = f6 * 2
|
|
57
|
+
print(f8)
|
|
58
|
+
f9 = f5 + f6
|
|
59
|
+
print(f9)
|
|
60
|
+
f10 = f5 * f6
|
|
61
|
+
print(f10)
|