memory-graph 0.3.30__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.
@@ -0,0 +1,100 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ from memory_graph.node_base import Node_Base
6
+ from memory_graph.sequence import Sequence1D
7
+
8
+ import memory_graph.config_helpers as config_helpers
9
+
10
+ class Node_Linear(Node_Base):
11
+ """
12
+ Node_Linear (subclass of Node_Base) is a node that represents a linear sequence
13
+ of data used for most iterable type like list, tuple, set, etc.
14
+ """
15
+
16
+ def __init__(self, data, children=None):
17
+ """
18
+ Create a Node_Linear object. Use a Slicer to slice the children so the
19
+ Node_Base will not get to big or have too many childeren in the graph.
20
+ """
21
+ super().__init__(data, Sequence1D(children))
22
+
23
+ def has_references(self, nodes, slices):
24
+ """
25
+ Return if the node has references to other nodes.
26
+ """
27
+ for index in slices:
28
+ child_id = id(self.children[index])
29
+ if child_id in nodes:
30
+ child = nodes[child_id]
31
+ if not child.is_hidden_node():
32
+ return True
33
+ return False
34
+
35
+ def is_vertical(self, nodes, slices, id_to_slices):
36
+ """
37
+ Return if the node is vertical or horizontal based on the orientation of the children.
38
+ """
39
+ vertical = config_helpers.get_vertical_orientation(self, None)
40
+ if vertical is None:
41
+ vertical = not self.has_references(nodes, slices)
42
+ return vertical
43
+
44
+ def fill_html_table(self, nodes, html_table, slices, id_to_slices):
45
+ """
46
+ Fill the html_table with the children of the Node_Base.
47
+ """
48
+ if slices is None:
49
+ return
50
+ if self.is_vertical(nodes, slices, id_to_slices):
51
+ self.fill_html_table_vertical(html_table, nodes, slices, id_to_slices)
52
+ else:
53
+ self.fill_html_table_horizontal(html_table, nodes, slices, id_to_slices)
54
+
55
+ def fill_html_table_vertical(self, html_table, nodes, slices, id_to_slices):
56
+ """
57
+ Helper function to fill the html_table with the children of the Node_Base in vertical orientation.
58
+ """
59
+ for index in slices.table_iter(self.children.size()):
60
+ if index>=0:
61
+ html_table.add_index(index)
62
+ child = self.children[index]
63
+ html_table.add_entry(self, nodes, child, id_to_slices, dashed=slices.is_dashed(index))
64
+ html_table.add_new_line()
65
+ else:
66
+ html_table.add_value('', border=0)
67
+ html_table.add_dots()
68
+ html_table.add_new_line()
69
+
70
+ def fill_html_table_horizontal(self, html_table, nodes, slices, id_to_slices):
71
+ """
72
+ Helper function to fill the html_table with the children of the Node_Base in horizontal orientation.
73
+ """
74
+ for index in slices.table_iter(self.children.size()):
75
+ if index>=0:
76
+ html_table.add_index(index)
77
+ else:
78
+ html_table.add_value('', border=0)
79
+ html_table.add_new_line()
80
+ for index in slices.table_iter(self.children.size()):
81
+ if index>=0:
82
+ child = self.children[index]
83
+ html_table.add_entry(self, nodes, child, id_to_slices, dashed=slices.is_dashed(index))
84
+ else:
85
+ html_table.add_dots()
86
+
87
+ def get_label(self, slices):
88
+ """
89
+ Return a label for the node to be shown in the graph next to the HTML table.
90
+ """
91
+ type_name = self.get_type_name()
92
+ if slices is None:
93
+ return f'{type_name}'
94
+ size = self.get_children().size()
95
+ s = slices.get_slices()
96
+ if len(s) == 1:
97
+ if s[0][1] - s[0][0] == size:
98
+ return f'{type_name}'
99
+ return f'{type_name} {size}'
100
+
@@ -0,0 +1,83 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ from memory_graph.node_base import Node_Base
6
+ from memory_graph.sequence import Sequence2D
7
+ from memory_graph.list_view import List_View
8
+
9
+ class Node_Table(Node_Base):
10
+ """
11
+ Node_Table (subclass of Node_Base) is a node that represents a 2D table of data used for
12
+ example for Numpy arrays and Pandas DataFrames.
13
+ """
14
+
15
+ def __init__(self, data, children, data_width=None, row_names=None, col_names=None):
16
+ """
17
+ Create a Node_Table object. Use a Slicer to slice the children so the Node_Base
18
+ will not get to big or have too many childeren in the graph.
19
+ """
20
+ self.row_names = row_names
21
+ self.col_names = col_names
22
+ if data_width is None:
23
+ super().__init__(data, Sequence2D(children))
24
+ else:
25
+ list_views = [List_View(children, i, i+data_width) for i in range(0,len(children),data_width)]
26
+ super().__init__(data, Sequence2D(list_views))
27
+
28
+ def add_index_or_name(self, html_table, index, names):
29
+ if not names is None and index < len(names):
30
+ html_table.add_value(names[index], rounded=1, border=1)
31
+ else:
32
+ html_table.add_index(index)
33
+
34
+ def fill_html_table(self, nodes, html_table, slices, id_to_slices):
35
+ """
36
+ Fill the html_table with the children of the Node_Base.
37
+ """
38
+ if slices is None or slices.is_empty():
39
+ return
40
+ children = self.children
41
+ children_size = children.size()
42
+ children_width = children_size[1]
43
+ col_slices = slices.get_col_slices()
44
+
45
+ # use column indices for header row
46
+ html_table.add_value('', border=0)
47
+ for coli in col_slices.table_iter(children_width):
48
+ if coli == -1:
49
+ html_table.add_value('', border=0)
50
+ else:
51
+ self.add_index_or_name(html_table, coli, self.col_names)
52
+ html_table.add_new_line()
53
+
54
+ # add remaing rows
55
+ first_col = True
56
+ for index in slices.table_iter(children_size):
57
+ rowi, coli = index
58
+ if first_col and not coli==-3:
59
+ first_col = False
60
+ self.add_index_or_name(html_table, rowi, self.row_names)
61
+ if coli == -1:
62
+ html_table.add_dots()
63
+ elif coli == -2:
64
+ html_table.add_new_line()
65
+ first_col = True
66
+ elif coli == -3:
67
+ html_table.add_new_line()
68
+ html_table.add_value('', border=0)
69
+ for _ in range (html_table.get_max_column()-1):
70
+ html_table.add_dots()
71
+ html_table.add_new_line()
72
+ first_col = True
73
+ else:
74
+ child = children[index]
75
+ html_table.add_entry(self, nodes, child, id_to_slices, dashed=slices.is_dashed(index))
76
+
77
+ def get_label(self, slices):
78
+ """
79
+ Return a label for the node to be shown in the graph next to the HTML table.
80
+ """
81
+ size = self.get_children().size()
82
+ return f'{self.get_type_name()} {size[0]}⨯{size[1]}'
83
+
@@ -0,0 +1,111 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ from memory_graph.slices import Slices1D, Slices2D
8
+ import memory_graph.utils as utils
9
+
10
+ class Sequence(ABC):
11
+
12
+ @abstractmethod
13
+ def is_empty(self):
14
+ pass
15
+
16
+ @abstractmethod
17
+ def size(self):
18
+ pass
19
+
20
+ @abstractmethod
21
+ def empty_slices(self):
22
+ pass
23
+
24
+ @abstractmethod
25
+ def slice(self, slicer):
26
+ pass
27
+
28
+ @abstractmethod
29
+ def indices_all(self):
30
+ pass
31
+
32
+ @abstractmethod
33
+ def __getitem__(self, index):
34
+ pass
35
+
36
+ @abstractmethod
37
+ def __setitem__(self, index, value):
38
+ pass
39
+
40
+ class Sequence1D(Sequence):
41
+
42
+ def __init__(self, data):
43
+ self.data = utils.make_sliceable(data)
44
+
45
+ def __repr__(self):
46
+ return f'Sequence1D: {self.data}'
47
+
48
+ def is_empty(self):
49
+ return len(self.data) == 0
50
+
51
+ def size(self):
52
+ return len(self.data)
53
+
54
+ def empty_slices(self):
55
+ return Slices1D()
56
+
57
+ def slice(self, slicer):
58
+ return slicer.get_slices( len(self.data) )
59
+
60
+ def indices_all(self):
61
+ for i in range(len(self.data)):
62
+ yield i
63
+
64
+ def __getitem__(self, index):
65
+ return self.data[index]
66
+
67
+ def __setitem__(self, index, value):
68
+ self.data[index] = value
69
+
70
+ class Sequence2D(Sequence):
71
+
72
+ def __init__(self, data):
73
+ self.data = utils.make_sliceable(data)
74
+
75
+ def __repr__(self):
76
+ return f'Sequence2D: {self.data}'
77
+
78
+ def is_empty(self):
79
+ return len(self.data) == 0
80
+
81
+ def size(self):
82
+ l1, l2 = len(self.data), 0
83
+ if l1 > 0:
84
+ l2 = len(self.data[0])
85
+ return l1, l2
86
+
87
+ def empty_slices(self):
88
+ return Slices2D()
89
+
90
+ def slice(self, slicer0):
91
+ if type(slicer0) is tuple:
92
+ slicer0, slicer1 = slicer0
93
+ else:
94
+ slicer1 = slicer0
95
+ slices0 = slicer0.get_slices( len(self.data) )
96
+ slices1 = slicer1.get_slices( len(self.data[0]) )
97
+ return Slices2D(slices0, slices1)
98
+
99
+ def indices_all(self):
100
+ len0 = len(self.data)
101
+ if len0 > 0:
102
+ len1 = len(self.data[0])
103
+ for y in range(len0):
104
+ for x in range(len1):
105
+ yield (y,x)
106
+
107
+ def __getitem__(self, index):
108
+ return self.data[index[0]][index[1]]
109
+
110
+ def __setitem__(self, index, value):
111
+ self.data[index[0]][index[1]] = value
memory_graph/slicer.py ADDED
@@ -0,0 +1,46 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ from memory_graph.slices import Slices1D
6
+ import memory_graph.utils as utils
7
+
8
+ class Slicer:
9
+
10
+ def __init__(self, begin=None, end=None, middle=None, /) -> None:
11
+ self.begin = begin
12
+ self.end = end
13
+ self.middle = middle
14
+ if not self.middle is None:
15
+ self.end, self.middle = self.middle, self.end
16
+
17
+ def __repr__(self) -> str:
18
+ return f"Slicer({self.begin},{self.middle},{self.end})"
19
+
20
+ def get_slices(self, length):
21
+ slices1d = Slices1D()
22
+ if self.begin is None:
23
+ slices1d.add_slice([0, length])
24
+ else:
25
+ if isinstance(self.begin, float):
26
+ slices1d.add_slice([0,
27
+ min(length,utils.my_round(length*self.begin))])
28
+ else:
29
+ slices1d.add_slice([0,
30
+ min(length,self.begin)])
31
+ if not self.middle is None:
32
+ mid = length/2
33
+ if isinstance(self.middle, float):
34
+ half = length*self.middle/2
35
+ else:
36
+ half = self.middle/2
37
+ slices1d.add_slice([max(0,utils.my_round(mid-half)),
38
+ min(length,utils.my_round(mid+half))])
39
+ if not self.end is None:
40
+ if isinstance(self.end, float):
41
+ slices1d.add_slice([max(0,utils.my_round(length-length*self.end)),
42
+ length])
43
+ else:
44
+ slices1d.add_slice([max(0,length-self.end),
45
+ length])
46
+ return slices1d
memory_graph/slices.py ADDED
@@ -0,0 +1,163 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ import bisect
8
+ import copy
9
+
10
+ from memory_graph.slices_iterator import Slices_Iterator1D, Slices_Iterator2D
11
+ from memory_graph.slices_table_iterator import Slices_Table_Iterator1D, Slices_Table_Iterator2D
12
+
13
+ class Slices(ABC):
14
+
15
+ def __init__(self):
16
+ self.dashed = set()
17
+
18
+ def __repr__(self) -> str:
19
+ return f"dashed: {self.dashed}"
20
+
21
+ def is_dashed(self, index):
22
+ return index in self.dashed
23
+
24
+ @abstractmethod
25
+ def __iter__(self):
26
+ pass
27
+
28
+ @abstractmethod
29
+ def has_index(self, index):
30
+ pass
31
+
32
+ @abstractmethod
33
+ def add_index(self, index):
34
+ pass
35
+
36
+ @abstractmethod
37
+ def table_iter(self, size):
38
+ pass
39
+
40
+ @abstractmethod
41
+ def is_empty(self):
42
+ pass
43
+
44
+ class Slices1D(Slices):
45
+
46
+ def __init__(self, slices=None) -> None:
47
+ super().__init__()
48
+ self.slices = []
49
+ if not slices is None:
50
+ for i in slices:
51
+ self.add_slice(i)
52
+
53
+ def __repr__(self) -> str:
54
+ return f"Slices1D({self.slices}) "+super().__repr__()
55
+
56
+ def get_iter(self,length):
57
+ return Slices_Iterator(self.slices,length)
58
+
59
+ def copy(self):
60
+ s = Slices1D()
61
+ s.slices = copy.deepcopy(self.slices)
62
+ s.dashed = copy.deepcopy(self.dashed)
63
+ return s
64
+
65
+ def get_slices(self):
66
+ return self.slices
67
+
68
+ def has_index(self, index):
69
+ for i in self.slices:
70
+ if i[0] <= index and index < i[1]:
71
+ return True
72
+ return False
73
+
74
+ def add_slice(self, begin_end, remove_interposed_dots=1):
75
+ i0, i1 = begin_end
76
+ if i1 <= i0:
77
+ return False
78
+ insert0 = bisect.bisect_right(self.slices, i0, key=lambda x: x[0])
79
+ insert1 = bisect.bisect_left (self.slices, i1, key=lambda x: x[1])
80
+ merge_begin, merge_end = False, False
81
+ if insert0 > 0:
82
+ if self.slices[insert0-1][1] >= (i0 - remove_interposed_dots):
83
+ merge_begin = True
84
+ if insert1 < len(self.slices):
85
+ if self.slices[insert1][0] <= (i1 + remove_interposed_dots):
86
+ merge_end = True
87
+ if merge_begin and merge_end:
88
+ if insert0 - insert1 == 1: # no slices changed
89
+ return False
90
+ self.slices[insert0-1][1] = self.slices[insert1][1]
91
+ del self.slices[insert0:insert1+1]
92
+ elif merge_begin:
93
+ self.slices[insert0-1][1] = max(self.slices[insert1-1][1], begin_end[1])
94
+ del self.slices[insert0:insert1]
95
+ elif merge_end:
96
+ self.slices[insert1][0] = min(self.slices[insert0][0], begin_end[0])
97
+ del self.slices[insert0:insert1]
98
+ else:
99
+ del self.slices[insert0:insert1]
100
+ self.slices.insert(insert0, begin_end)
101
+ return True
102
+
103
+ def __iter__(self):
104
+ return Slices_Iterator1D(self)
105
+
106
+ def has_index(self, index):
107
+ insert = bisect.bisect_right(self.slices, index, key=lambda x: x[0])
108
+ #print('insert:',insert,'index:',index,'slices:',self.slices)
109
+ if insert==0:
110
+ return False
111
+ i0, i1 = self.slices[insert-1]
112
+ return i0 <= index and index < i1
113
+
114
+ def add_index(self, index, dashed=False):
115
+ self.add_slice([index,index+1], 0)
116
+ if dashed:
117
+ self.dashed.add(index)
118
+
119
+ def table_iter(self, size):
120
+ return Slices_Table_Iterator1D(self, size)
121
+
122
+ def is_empty(self):
123
+ return len(self.slices) == 0
124
+
125
+ class Slices2D(Slices):
126
+
127
+ def __init__(self, row_slices = None, col_slices= None) -> None:
128
+ super().__init__()
129
+ self.row_slices = Slices1D() if row_slices is None else row_slices
130
+ self.col_slices = Slices1D() if col_slices is None else col_slices
131
+
132
+ def __repr__(self):
133
+ s='Sices2D:\n'
134
+ s += 'row_slices:' + str(self.row_slices) + '\n'
135
+ s += 'col_slices:' + str(self.col_slices) + '\n'
136
+ s += super().__repr__() + '\n'
137
+ return s
138
+
139
+ def get_row_slices(self):
140
+ return self.row_slices
141
+
142
+ def get_col_slices(self):
143
+ return self.col_slices
144
+
145
+ def __iter__(self):
146
+ return Slices_Iterator2D(self)
147
+
148
+ def has_index(self, index):
149
+ i0,i1 = index
150
+ return self.row_slices.has_index(i0) and self.col_slices.has_index(i1)
151
+
152
+ def add_index(self, index, dashed=False):
153
+ i0,i1 = index
154
+ self.row_slices.add_slice([i0,i0+1], 0)
155
+ self.col_slices.add_slice([i1,i1+1], 0)
156
+ if dashed:
157
+ self.dashed.add((i0,i1))
158
+
159
+ def table_iter(self, size):
160
+ return Slices_Table_Iterator2D(self, size)
161
+
162
+ def is_empty(self):
163
+ return len(self.row_slices.slices) == 0
@@ -0,0 +1,55 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+
6
+ from abc import ABC, abstractmethod
7
+
8
+ class Slices_Iterator(ABC):
9
+
10
+ @abstractmethod
11
+ def __iter__(self):
12
+ pass
13
+
14
+ @abstractmethod
15
+ def __next__(self):
16
+ pass
17
+
18
+ class Slices_Iterator1D(Slices_Iterator):
19
+
20
+ def __init__(self, slices1d):
21
+ self.slices = slices1d
22
+ self.gen = self.generate()
23
+
24
+ def __iter__(self):
25
+ return self
26
+
27
+ def generate(self):
28
+ slices = self.slices.get_slices()
29
+ for si in range(len(slices)):
30
+ for i in range(slices[si][0], slices[si][1]):
31
+ yield i
32
+
33
+ def __next__(self):
34
+ return next(self.gen)
35
+
36
+ class Slices_Iterator2D(Slices_Iterator):
37
+
38
+ def __init__(self, slices2d):
39
+ self.slices = slices2d
40
+ self.gen = self.generate()
41
+
42
+ def __iter__(self):
43
+ return self
44
+
45
+ def generate(self):
46
+ row_slices = self.slices.get_row_slices().get_slices()
47
+ col_slices = self.slices.get_col_slices().get_slices()
48
+ for row_slice in row_slices:
49
+ for row_i in range(row_slice[0], row_slice[1]):
50
+ for col_slice in col_slices:
51
+ for col_i in range(col_slice[0], col_slice[1]):
52
+ yield (row_i, col_i)
53
+
54
+ def __next__(self):
55
+ return next(self.gen)
@@ -0,0 +1,79 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ class Slices_Table_Iterator(ABC):
8
+
9
+ @abstractmethod
10
+ def __iter__(self):
11
+ pass
12
+
13
+ @abstractmethod
14
+ def __next__(self):
15
+ pass
16
+
17
+ class Slices_Table_Iterator1D(Slices_Table_Iterator):
18
+
19
+ def __init__(self, slices1d, size):
20
+ self.slices = slices1d
21
+ self.size = size
22
+ self.gen = self.generate()
23
+
24
+ def __iter__(self):
25
+ return self
26
+
27
+ def generate(self):
28
+ slices = self.slices.get_slices()
29
+ if len(slices) > 0 and slices[0][0] > 0:
30
+ yield -1
31
+ for slice in slices:
32
+ for i in range(slice[0], slice[1]):
33
+ yield i
34
+ if i < self.size-1:
35
+ yield -1
36
+
37
+ def __next__(self):
38
+ return next(self.gen)
39
+
40
+ class Slices_Table_Iterator2D(Slices_Table_Iterator):
41
+
42
+ def __init__(self, slices2d, size):
43
+ self.slices = slices2d
44
+ self.size = size
45
+ self.gen = self.generate()
46
+
47
+ def __iter__(self):
48
+ return self
49
+
50
+ def generate(self):
51
+ row_slices = self.slices.get_row_slices().get_slices()
52
+ col_slices = self.slices.get_col_slices().get_slices()
53
+ first_row_slice = True
54
+ for row_slice in row_slices:
55
+ if first_row_slice:
56
+ if len(row_slices) > 0 and row_slice[0] > 0:
57
+ yield (-3, -3)
58
+ first_col_slice = False
59
+ else:
60
+ yield (-3, -3)
61
+ for row_i in range(row_slice[0], row_slice[1]):
62
+ first_col_slice = True
63
+ for col_slice in col_slices:
64
+ if first_col_slice:
65
+ if len(col_slices) > 0 and col_slice[0] > 0:
66
+ yield (row_i, -1)
67
+ first_col_slice = False
68
+ else:
69
+ yield (row_i, -1)
70
+ for col_i in range(col_slice[0], col_slice[1]):
71
+ yield (row_i, col_i)
72
+ if col_i < self.size[1]-1:
73
+ yield (row_i, -1)
74
+ yield (row_i, -2)
75
+ if len(row_slices)>0 and row_i < self.size[0]-1:
76
+ yield (row_i, -3)
77
+
78
+ def __next__(self):
79
+ return next(self.gen)