topologicpy 0.5.8__py3-none-any.whl → 6.0.0__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.
- topologicpy/Aperture.py +72 -72
- topologicpy/Cell.py +2169 -2169
- topologicpy/CellComplex.py +1137 -1137
- topologicpy/Cluster.py +1288 -1280
- topologicpy/Color.py +423 -393
- topologicpy/Context.py +79 -79
- topologicpy/DGL.py +3213 -3136
- topologicpy/Dictionary.py +698 -695
- topologicpy/Edge.py +1187 -1187
- topologicpy/EnergyModel.py +1180 -1171
- topologicpy/Face.py +2141 -2141
- topologicpy/Graph.py +7768 -7700
- topologicpy/Grid.py +353 -353
- topologicpy/Helper.py +507 -507
- topologicpy/Honeybee.py +461 -461
- topologicpy/Matrix.py +271 -271
- topologicpy/Neo4j.py +521 -521
- topologicpy/Plotly.py +2 -2
- topologicpy/Polyskel.py +541 -541
- topologicpy/Shell.py +1768 -1768
- topologicpy/Speckle.py +508 -508
- topologicpy/Topology.py +7060 -6988
- topologicpy/Vector.py +905 -905
- topologicpy/Vertex.py +1585 -1585
- topologicpy/Wire.py +3050 -3050
- topologicpy/__init__.py +22 -38
- topologicpy/version.py +1 -0
- {topologicpy-0.5.8.dist-info → topologicpy-6.0.0.dist-info}/LICENSE +661 -704
- topologicpy-6.0.0.dist-info/METADATA +751 -0
- topologicpy-6.0.0.dist-info/RECORD +32 -0
- topologicpy/bin/linux/topologic/__init__.py +0 -2
- topologicpy/bin/linux/topologic/libTKBO-6bdf205d.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKBRep-2960a069.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKBool-c44b74bd.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKFillet-9a670ba0.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKG2d-8f31849e.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKG3d-4c6bce57.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKMath-72572fa8.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKMesh-2a060427.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKOffset-6cab68ff.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKPrim-eb1262b3.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libgcc_s-32c1665e.so.1 +0 -0
- topologicpy/bin/linux/topologic/libstdc++-672d7b41.so.6.0.30 +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-310-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-311-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-38-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-39-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBO-6bdf205d.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBRep-2960a069.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBool-c44b74bd.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKFillet-9a670ba0.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKG2d-8f31849e.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKG3d-4c6bce57.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKMath-72572fa8.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKMesh-2a060427.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKOffset-6cab68ff.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKPrim-eb1262b3.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libgcc_s-32c1665e.so.1 +0 -0
- topologicpy/bin/linux/topologic.libs/libstdc++-672d7b41.so.6.0.30 +0 -0
- topologicpy/bin/macos/topologic/__init__.py +0 -2
- topologicpy/bin/windows/topologic/TKBO-f6b191de.dll +0 -0
- topologicpy/bin/windows/topologic/TKBRep-e56a600e.dll +0 -0
- topologicpy/bin/windows/topologic/TKBool-7b8d47ae.dll +0 -0
- topologicpy/bin/windows/topologic/TKFillet-0ddbf0a8.dll +0 -0
- topologicpy/bin/windows/topologic/TKG2d-2e2dee3d.dll +0 -0
- topologicpy/bin/windows/topologic/TKG3d-6674513d.dll +0 -0
- topologicpy/bin/windows/topologic/TKGeomAlgo-d240e370.dll +0 -0
- topologicpy/bin/windows/topologic/TKGeomBase-df87aba5.dll +0 -0
- topologicpy/bin/windows/topologic/TKMath-45bd625a.dll +0 -0
- topologicpy/bin/windows/topologic/TKMesh-d6e826b1.dll +0 -0
- topologicpy/bin/windows/topologic/TKOffset-79b9cc94.dll +0 -0
- topologicpy/bin/windows/topologic/TKPrim-aa430a86.dll +0 -0
- topologicpy/bin/windows/topologic/TKShHealing-bb48be89.dll +0 -0
- topologicpy/bin/windows/topologic/TKTopAlgo-7d0d1e22.dll +0 -0
- topologicpy/bin/windows/topologic/TKernel-08c8cfbb.dll +0 -0
- topologicpy/bin/windows/topologic/__init__.py +0 -2
- topologicpy/bin/windows/topologic/topologic.cp310-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp311-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp38-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp39-win_amd64.pyd +0 -0
- topologicpy-0.5.8.dist-info/METADATA +0 -96
- topologicpy-0.5.8.dist-info/RECORD +0 -91
- {topologicpy-0.5.8.dist-info → topologicpy-6.0.0.dist-info}/WHEEL +0 -0
- {topologicpy-0.5.8.dist-info → topologicpy-6.0.0.dist-info}/top_level.txt +0 -0
topologicpy/Helper.py
CHANGED
@@ -1,507 +1,507 @@
|
|
1
|
-
# Copyright (C) 2024
|
2
|
-
# Wassim Jabi <wassim.jabi@gmail.com>
|
3
|
-
#
|
4
|
-
# This program is free software: you can redistribute it and/or modify it under
|
5
|
-
# the terms of the GNU Affero General Public License as published by the Free Software
|
6
|
-
# Foundation, either version 3 of the License, or (at your option) any later
|
7
|
-
# version.
|
8
|
-
#
|
9
|
-
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10
|
-
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11
|
-
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
12
|
-
# details.
|
13
|
-
#
|
14
|
-
# You should have received a copy of the GNU Affero General Public License along with
|
15
|
-
# this program. If not, see <https://www.gnu.org/licenses/>.
|
16
|
-
|
17
|
-
import topologicpy
|
18
|
-
import os
|
19
|
-
import warnings
|
20
|
-
|
21
|
-
try:
|
22
|
-
import numpy as np
|
23
|
-
import numpy.linalg as la
|
24
|
-
except:
|
25
|
-
print("Helper - Installing required numpy library.")
|
26
|
-
try:
|
27
|
-
os.system("pip install numpy")
|
28
|
-
except:
|
29
|
-
os.system("pip install numpy --user")
|
30
|
-
try:
|
31
|
-
import numpy as np
|
32
|
-
import numpy.linalg as la
|
33
|
-
print("Helper - numpy library installed correctly.")
|
34
|
-
except:
|
35
|
-
warnings.warn("Helper - Error: Could not import numpy.")
|
36
|
-
|
37
|
-
class Helper:
|
38
|
-
@staticmethod
|
39
|
-
def ClosestMatch(item, listA):
|
40
|
-
"""
|
41
|
-
Returns the index of the closest match in the input list to the input item.
|
42
|
-
This works for lists made out of numeric or string values.
|
43
|
-
|
44
|
-
Parameters
|
45
|
-
----------
|
46
|
-
item : int, float, or str
|
47
|
-
The input item.
|
48
|
-
listA : list
|
49
|
-
The input list.
|
50
|
-
|
51
|
-
Returns
|
52
|
-
-------
|
53
|
-
int
|
54
|
-
The index of the best match in listA for the input item.
|
55
|
-
|
56
|
-
"""
|
57
|
-
import numbers
|
58
|
-
import random
|
59
|
-
import string
|
60
|
-
def levenshtein_distance(s1, s2):
|
61
|
-
if len(s1) < len(s2):
|
62
|
-
return levenshtein_distance(s2, s1)
|
63
|
-
|
64
|
-
if len(s2) == 0:
|
65
|
-
return len(s1)
|
66
|
-
|
67
|
-
previous_row = range(len(s2) + 1)
|
68
|
-
for i, c1 in enumerate(s1):
|
69
|
-
current_row = [i + 1]
|
70
|
-
for j, c2 in enumerate(s2):
|
71
|
-
insertions = previous_row[j + 1] + 1
|
72
|
-
deletions = current_row[j] + 1
|
73
|
-
substitutions = previous_row[j] + (c1 != c2)
|
74
|
-
current_row.append(min(insertions, deletions, substitutions))
|
75
|
-
previous_row = current_row
|
76
|
-
|
77
|
-
return previous_row[-1]
|
78
|
-
|
79
|
-
def generate_unlikely_string(length=16):
|
80
|
-
characters = string.ascii_letters + string.digits + string.punctuation
|
81
|
-
return ''.join(random.choice(characters) for _ in range(length))
|
82
|
-
|
83
|
-
if not listA:
|
84
|
-
print("Helper.ClosestMatch - Error: THe input listA parameter is not a valid list. Returning None.")
|
85
|
-
return None # Handle empty list case
|
86
|
-
|
87
|
-
if isinstance(item, str):
|
88
|
-
listA = [generate_unlikely_string(length=32) if not isinstance(x, str) else x for x in listA]
|
89
|
-
# For string inputs, find the closest match using Levenshtein distance
|
90
|
-
closest_index = min(range(len(listA)), key=lambda i: levenshtein_distance(item, listA[i]))
|
91
|
-
else:
|
92
|
-
listA = [float('-inf') if not isinstance(x, numbers.Real) else x for x in listA]
|
93
|
-
# For numeric or boolean inputs, find the closest match based on absolute difference
|
94
|
-
closest_index = min(range(len(listA)), key=lambda i: abs(listA[i] - item))
|
95
|
-
|
96
|
-
return closest_index
|
97
|
-
|
98
|
-
@staticmethod
|
99
|
-
def Flatten(listA):
|
100
|
-
"""
|
101
|
-
Flattens the input nested list.
|
102
|
-
|
103
|
-
Parameters
|
104
|
-
----------
|
105
|
-
listA : list
|
106
|
-
The input nested list.
|
107
|
-
|
108
|
-
Returns
|
109
|
-
-------
|
110
|
-
list
|
111
|
-
The flattened list.
|
112
|
-
|
113
|
-
"""
|
114
|
-
|
115
|
-
if not isinstance(listA, list):
|
116
|
-
return [listA]
|
117
|
-
flat_list = []
|
118
|
-
for item in listA:
|
119
|
-
flat_list = flat_list + Helper.Flatten(item)
|
120
|
-
return flat_list
|
121
|
-
|
122
|
-
@staticmethod
|
123
|
-
def Iterate(listA):
|
124
|
-
"""
|
125
|
-
Iterates the input nested list so that each sublist has the same number of members. To fill extra members, the shorter lists are iterated from their first member.
|
126
|
-
For example Iterate([[1,2,3],['m','n','o','p'],['a','b','c','d','e']]) yields [[1, 2, 3, 1, 2], ['m', 'n', 'o', 'p', 'm'], ['a', 'b', 'c', 'd', 'e']]
|
127
|
-
|
128
|
-
Parameters
|
129
|
-
----------
|
130
|
-
listA : list
|
131
|
-
The input nested list.
|
132
|
-
|
133
|
-
Returns
|
134
|
-
-------
|
135
|
-
list
|
136
|
-
The iterated list.
|
137
|
-
|
138
|
-
"""
|
139
|
-
# From https://stackoverflow.com/questions/34432056/repeat-elements-of-list-between-each-other-until-we-reach-a-certain-length
|
140
|
-
def onestep(cur,y,base):
|
141
|
-
# one step of the iteration
|
142
|
-
if cur is not None:
|
143
|
-
y.append(cur)
|
144
|
-
base.append(cur)
|
145
|
-
else:
|
146
|
-
y.append(base[0]) # append is simplest, for now
|
147
|
-
base = base[1:]+[base[0]] # rotate
|
148
|
-
return base
|
149
|
-
|
150
|
-
maxLength = len(listA[0])
|
151
|
-
iterated_list = []
|
152
|
-
for aSubList in listA:
|
153
|
-
newLength = len(aSubList)
|
154
|
-
if newLength > maxLength:
|
155
|
-
maxLength = newLength
|
156
|
-
for anItem in listA:
|
157
|
-
for i in range(len(anItem), maxLength):
|
158
|
-
anItem.append(None)
|
159
|
-
y=[]
|
160
|
-
base=[]
|
161
|
-
for cur in anItem:
|
162
|
-
base = onestep(cur,y,base)
|
163
|
-
iterated_list.append(y)
|
164
|
-
return iterated_list
|
165
|
-
|
166
|
-
@staticmethod
|
167
|
-
def K_Means(data, k=4, maxIterations=100):
|
168
|
-
import random
|
169
|
-
def euclidean_distance(p, q):
|
170
|
-
return sum((pi - qi) ** 2 for pi, qi in zip(p, q)) ** 0.5
|
171
|
-
|
172
|
-
# Initialize k centroids randomly
|
173
|
-
centroids = random.sample(data, k)
|
174
|
-
|
175
|
-
for _ in range(maxIterations):
|
176
|
-
# Assign each data point to the nearest centroid
|
177
|
-
clusters = [[] for _ in range(k)]
|
178
|
-
for point in data:
|
179
|
-
distances = [euclidean_distance(point, centroid) for centroid in centroids]
|
180
|
-
nearest_centroid_index = distances.index(min(distances))
|
181
|
-
clusters[nearest_centroid_index].append(point)
|
182
|
-
|
183
|
-
# Compute the new centroids as the mean of the points in each cluster
|
184
|
-
new_centroids = []
|
185
|
-
for cluster in clusters:
|
186
|
-
if not cluster:
|
187
|
-
# If a cluster is empty, keep the previous centroid
|
188
|
-
new_centroids.append(centroids[clusters.index(cluster)])
|
189
|
-
else:
|
190
|
-
new_centroids.append([sum(dim) / len(cluster) for dim in zip(*cluster)])
|
191
|
-
|
192
|
-
# Check if the centroids have converged
|
193
|
-
if new_centroids == centroids:
|
194
|
-
break
|
195
|
-
|
196
|
-
centroids = new_centroids
|
197
|
-
|
198
|
-
return {'clusters': clusters, 'centroids': centroids}
|
199
|
-
|
200
|
-
@staticmethod
|
201
|
-
def MergeByThreshold(listA, threshold=0.0001):
|
202
|
-
"""
|
203
|
-
Merges the numbers in the input list so that numbers within the input threshold are averaged into one number.
|
204
|
-
|
205
|
-
Parameters
|
206
|
-
----------
|
207
|
-
listA : list
|
208
|
-
The input nested list.
|
209
|
-
threshold : float , optional
|
210
|
-
The desired merge threshold value. The default is 0.0001.
|
211
|
-
|
212
|
-
Returns
|
213
|
-
-------
|
214
|
-
list
|
215
|
-
The merged list. The list is sorted in ascending numeric order.
|
216
|
-
|
217
|
-
"""
|
218
|
-
# Sort the list in ascending order
|
219
|
-
listA.sort()
|
220
|
-
merged_list = []
|
221
|
-
|
222
|
-
# Initialize the first element in the merged list
|
223
|
-
merged_list.append(listA[0])
|
224
|
-
|
225
|
-
# Merge numbers within the threshold
|
226
|
-
for i in range(1, len(listA)):
|
227
|
-
if listA[i] - merged_list[-1] <= threshold:
|
228
|
-
# Merge the current number with the last element in the merged list
|
229
|
-
merged_list[-1] = (merged_list[-1] + listA[i]) / 2
|
230
|
-
else:
|
231
|
-
# If the current number is beyond the threshold, add it as a new element
|
232
|
-
merged_list.append(listA[i])
|
233
|
-
|
234
|
-
return merged_list
|
235
|
-
|
236
|
-
@staticmethod
|
237
|
-
def MakeUnique(listA):
|
238
|
-
"""
|
239
|
-
Forces the strings in the input list to be unique if they have duplicates.
|
240
|
-
|
241
|
-
Parameters
|
242
|
-
----------
|
243
|
-
listA : list
|
244
|
-
The input list of strings.
|
245
|
-
|
246
|
-
Returns
|
247
|
-
-------
|
248
|
-
list
|
249
|
-
The input list, but with each item ensured to be unique if they have duplicates.
|
250
|
-
|
251
|
-
"""
|
252
|
-
# Create a dictionary to store counts of each string
|
253
|
-
counts = {}
|
254
|
-
# Create a list to store modified strings
|
255
|
-
unique_strings = []
|
256
|
-
|
257
|
-
for string in listA:
|
258
|
-
# If the string already exists in the counts dictionary
|
259
|
-
if string in counts:
|
260
|
-
# Increment the count
|
261
|
-
counts[string] += 1
|
262
|
-
# Append the modified string with underscore and count
|
263
|
-
unique_strings.append(f"{string}_{counts[string]}")
|
264
|
-
else:
|
265
|
-
# If it's the first occurrence of the string, add it to the counts dictionary
|
266
|
-
counts[string] = 0
|
267
|
-
unique_strings.append(string)
|
268
|
-
|
269
|
-
return unique_strings
|
270
|
-
|
271
|
-
@staticmethod
|
272
|
-
def Normalize(listA, mantissa: int = 6):
|
273
|
-
"""
|
274
|
-
Normalizes the input list so that it is in the range 0 to 1
|
275
|
-
|
276
|
-
Parameters
|
277
|
-
----------
|
278
|
-
listA : list
|
279
|
-
The input nested list.
|
280
|
-
mantissa : int , optional
|
281
|
-
The desired mantissa value. The default is
|
282
|
-
|
283
|
-
Returns
|
284
|
-
-------
|
285
|
-
list
|
286
|
-
The normalized list.
|
287
|
-
|
288
|
-
"""
|
289
|
-
if not isinstance(listA, list):
|
290
|
-
print("Helper.Normalize - Error: The input list is not valid. Returning None.")
|
291
|
-
return None
|
292
|
-
|
293
|
-
# Make sure the list is numeric
|
294
|
-
l = [x for x in listA if type(x) == int or type(x) == float]
|
295
|
-
if len(l) < 1:
|
296
|
-
print("Helper.Normalize - Error: The input list does not contain numeric values. Returning None.")
|
297
|
-
return None
|
298
|
-
min_val = min(l)
|
299
|
-
max_val = max(l)
|
300
|
-
if min_val == max_val:
|
301
|
-
normalized_list = [0 for x in l]
|
302
|
-
else:
|
303
|
-
normalized_list = [round((x - min_val) / (max_val - min_val), mantissa) for x in l]
|
304
|
-
return normalized_list
|
305
|
-
|
306
|
-
@staticmethod
|
307
|
-
def Position(item, listA):
|
308
|
-
"""
|
309
|
-
Returns the position of the item in the list or the position it would have been inserts.
|
310
|
-
item is assumed to be numeric. listA is assumed to contain only numeric values and sorted from lowest to highest value.
|
311
|
-
|
312
|
-
Parameters
|
313
|
-
----------
|
314
|
-
item : int or float
|
315
|
-
The input number to be positioned.
|
316
|
-
listA : list
|
317
|
-
The input sorted list.
|
318
|
-
|
319
|
-
Returns
|
320
|
-
-------
|
321
|
-
int
|
322
|
-
The position of the item within the list.
|
323
|
-
|
324
|
-
"""
|
325
|
-
left = 0
|
326
|
-
right = len(listA) - 1
|
327
|
-
|
328
|
-
while left <= right:
|
329
|
-
mid = (left + right) // 2
|
330
|
-
if listA[mid] == item:
|
331
|
-
return mid
|
332
|
-
elif listA[mid] < item:
|
333
|
-
left = mid + 1
|
334
|
-
else:
|
335
|
-
right = mid - 1
|
336
|
-
|
337
|
-
# If the target is not found, return the position where it would be inserted
|
338
|
-
return left
|
339
|
-
|
340
|
-
@staticmethod
|
341
|
-
def Repeat(listA):
|
342
|
-
"""
|
343
|
-
Repeats the input nested list so that each sublist has the same number of members. To fill extra members, the last item in the shorter lists are repeated and appended.
|
344
|
-
For example Iterate([[1,2,3],['m','n','o','p'],['a','b','c','d','e']]) yields [[1, 2, 3, 3, 3], ['m', 'n', 'o', 'p', 'p'], ['a', 'b', 'c', 'd', 'e']]
|
345
|
-
|
346
|
-
Parameters
|
347
|
-
----------
|
348
|
-
listA : list
|
349
|
-
The input nested list.
|
350
|
-
|
351
|
-
Returns
|
352
|
-
-------
|
353
|
-
list
|
354
|
-
The repeated list.
|
355
|
-
|
356
|
-
"""
|
357
|
-
if not isinstance(listA, list):
|
358
|
-
return None
|
359
|
-
repeated_list = [x for x in listA if isinstance(x, list)]
|
360
|
-
if len(repeated_list) < 1:
|
361
|
-
return None
|
362
|
-
maxLength = len(repeated_list[0])
|
363
|
-
for aSubList in repeated_list:
|
364
|
-
newLength = len(aSubList)
|
365
|
-
if newLength > maxLength:
|
366
|
-
maxLength = newLength
|
367
|
-
for anItem in repeated_list:
|
368
|
-
if (len(anItem) > 0):
|
369
|
-
itemToAppend = anItem[-1]
|
370
|
-
else:
|
371
|
-
itemToAppend = None
|
372
|
-
for i in range(len(anItem), maxLength):
|
373
|
-
anItem.append(itemToAppend)
|
374
|
-
return repeated_list
|
375
|
-
|
376
|
-
@staticmethod
|
377
|
-
def Sort(listA, *otherLists, reverseFlags=None):
|
378
|
-
"""
|
379
|
-
Sorts the first input list according to the values in the subsequent input lists in order. For example,
|
380
|
-
your first list can be a list of topologies and the next set of lists can be their volume, surface area, and z level.
|
381
|
-
The list of topologies will then be sorted first by volume, then by surface, and lastly by z level. You can choose
|
382
|
-
to reverse the order of sorting by including a list of TRUE/FALSE values in the reverseFlags input parameter.
|
383
|
-
For example, if you wish to sort the volume in reverse order (from large to small), but sort the other parameters
|
384
|
-
normally, you would include the following list for reverseFlag: [True, False, False].
|
385
|
-
|
386
|
-
Parameters
|
387
|
-
----------
|
388
|
-
listA : list
|
389
|
-
The first input list to be sorts
|
390
|
-
*otherLists : any number of lists to use for sorting listA, optional.
|
391
|
-
Any number of lists that are used to sort the listA input parameter. The order of these input
|
392
|
-
parameters determines the order of sorting (from left to right). If no lists are included, the input list will be sorted as is.
|
393
|
-
reverseFlags : list, optional.
|
394
|
-
The list of booleans (TRUE/FALSE) to indicated if sorting based on a particular list should be conducted in reverse order.
|
395
|
-
The length of the reverseFlags list should match the number of the lists in the input otherLists parameter. If set to None,
|
396
|
-
a default list of FALSE values is created to match the number of the lists in the input otherLists parameter. The default
|
397
|
-
is None.
|
398
|
-
|
399
|
-
Returns
|
400
|
-
-------
|
401
|
-
list
|
402
|
-
The sorted list.
|
403
|
-
|
404
|
-
"""
|
405
|
-
|
406
|
-
# If reverseFlags is not provided, assume all lists should be sorted in ascending order
|
407
|
-
if reverseFlags is None:
|
408
|
-
reverseFlags = [False] * len(otherLists)
|
409
|
-
if not isinstance(otherLists, tuple):
|
410
|
-
print("Helper.Sort - Error: No other lists to use for sorting have been provided. Returning None.")
|
411
|
-
return None
|
412
|
-
if len(otherLists) < 1:
|
413
|
-
print("Helper.Sort - Error: The otherLists input parameter does not contain any valid lists. Returning None.")
|
414
|
-
return None
|
415
|
-
if not len(reverseFlags) == len(otherLists):
|
416
|
-
print("Helper.Sort - Error: The length of the reverseFlags input parameter is not equal to the number of input lists. Returning None.")
|
417
|
-
return None
|
418
|
-
# Convert other_lists to numeric and reverse if needed.
|
419
|
-
sorting_lists = []
|
420
|
-
for i, a_list in enumerate(otherLists):
|
421
|
-
temp_list = []
|
422
|
-
temp_set = list(set(a_list))
|
423
|
-
temp_set = sorted(temp_set)
|
424
|
-
if reverseFlags[i] == True:
|
425
|
-
temp_set.reverse()
|
426
|
-
for item in a_list:
|
427
|
-
temp_list.append(temp_set.index(item))
|
428
|
-
sorting_lists.append(temp_list)
|
429
|
-
|
430
|
-
combined_lists = list(zip(listA, *sorting_lists))
|
431
|
-
# Sort the combined list based on all the elements and reverse the lists as needed
|
432
|
-
combined_lists.sort(key=lambda x: tuple((-val) if reverse else val for val, reverse in zip(x[1:], reverseFlags)))
|
433
|
-
sorted_listA = [item[0] for item in combined_lists]
|
434
|
-
return sorted_listA
|
435
|
-
|
436
|
-
@staticmethod
|
437
|
-
def Transpose(listA):
|
438
|
-
"""
|
439
|
-
Transposes the input list (swaps rows and columns).
|
440
|
-
|
441
|
-
Parameters
|
442
|
-
----------
|
443
|
-
listA : list
|
444
|
-
The input list.
|
445
|
-
|
446
|
-
Returns
|
447
|
-
-------
|
448
|
-
list
|
449
|
-
The transposed list.
|
450
|
-
|
451
|
-
"""
|
452
|
-
if not isinstance(listA, list):
|
453
|
-
return None
|
454
|
-
length = len(listA[0])
|
455
|
-
transposed_list = []
|
456
|
-
for i in range(length):
|
457
|
-
tempRow = []
|
458
|
-
for j in range(len(listA)):
|
459
|
-
tempRow.append(listA[j][i])
|
460
|
-
transposed_list.append(tempRow)
|
461
|
-
return transposed_list
|
462
|
-
|
463
|
-
@staticmethod
|
464
|
-
def Trim(listA):
|
465
|
-
"""
|
466
|
-
Trims the input nested list so that each sublist has the same number of members. All lists are trimmed to match the length of the shortest list.
|
467
|
-
For example Trim([[1,2,3],['m','n','o','p'],['a','b','c','d','e']]) yields [[1, 2, 3], ['m', 'n', 'o'], ['a', 'b', 'c']]
|
468
|
-
|
469
|
-
Parameters
|
470
|
-
----------
|
471
|
-
listA : list
|
472
|
-
The input nested list.
|
473
|
-
|
474
|
-
Returns
|
475
|
-
-------
|
476
|
-
list
|
477
|
-
The repeated list.
|
478
|
-
|
479
|
-
"""
|
480
|
-
minLength = len(listA[0])
|
481
|
-
returnList = []
|
482
|
-
for aSubList in listA:
|
483
|
-
newLength = len(aSubList)
|
484
|
-
if newLength < minLength:
|
485
|
-
minLength = newLength
|
486
|
-
for anItem in listA:
|
487
|
-
anItem = anItem[:minLength]
|
488
|
-
returnList.append(anItem)
|
489
|
-
return returnList
|
490
|
-
|
491
|
-
@staticmethod
|
492
|
-
def Version():
|
493
|
-
"""
|
494
|
-
Returns the current version of the software.
|
495
|
-
|
496
|
-
Parameters
|
497
|
-
----------
|
498
|
-
None
|
499
|
-
|
500
|
-
|
501
|
-
Returns
|
502
|
-
-------
|
503
|
-
str
|
504
|
-
The current version of the software.
|
505
|
-
|
506
|
-
"""
|
507
|
-
return topologicpy.__version__
|
1
|
+
# Copyright (C) 2024
|
2
|
+
# Wassim Jabi <wassim.jabi@gmail.com>
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it under
|
5
|
+
# the terms of the GNU Affero General Public License as published by the Free Software
|
6
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
7
|
+
# version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
12
|
+
# details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Affero General Public License along with
|
15
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
import topologicpy
|
18
|
+
import os
|
19
|
+
import warnings
|
20
|
+
|
21
|
+
try:
|
22
|
+
import numpy as np
|
23
|
+
import numpy.linalg as la
|
24
|
+
except:
|
25
|
+
print("Helper - Installing required numpy library.")
|
26
|
+
try:
|
27
|
+
os.system("pip install numpy")
|
28
|
+
except:
|
29
|
+
os.system("pip install numpy --user")
|
30
|
+
try:
|
31
|
+
import numpy as np
|
32
|
+
import numpy.linalg as la
|
33
|
+
print("Helper - numpy library installed correctly.")
|
34
|
+
except:
|
35
|
+
warnings.warn("Helper - Error: Could not import numpy.")
|
36
|
+
|
37
|
+
class Helper:
|
38
|
+
@staticmethod
|
39
|
+
def ClosestMatch(item, listA):
|
40
|
+
"""
|
41
|
+
Returns the index of the closest match in the input list to the input item.
|
42
|
+
This works for lists made out of numeric or string values.
|
43
|
+
|
44
|
+
Parameters
|
45
|
+
----------
|
46
|
+
item : int, float, or str
|
47
|
+
The input item.
|
48
|
+
listA : list
|
49
|
+
The input list.
|
50
|
+
|
51
|
+
Returns
|
52
|
+
-------
|
53
|
+
int
|
54
|
+
The index of the best match in listA for the input item.
|
55
|
+
|
56
|
+
"""
|
57
|
+
import numbers
|
58
|
+
import random
|
59
|
+
import string
|
60
|
+
def levenshtein_distance(s1, s2):
|
61
|
+
if len(s1) < len(s2):
|
62
|
+
return levenshtein_distance(s2, s1)
|
63
|
+
|
64
|
+
if len(s2) == 0:
|
65
|
+
return len(s1)
|
66
|
+
|
67
|
+
previous_row = range(len(s2) + 1)
|
68
|
+
for i, c1 in enumerate(s1):
|
69
|
+
current_row = [i + 1]
|
70
|
+
for j, c2 in enumerate(s2):
|
71
|
+
insertions = previous_row[j + 1] + 1
|
72
|
+
deletions = current_row[j] + 1
|
73
|
+
substitutions = previous_row[j] + (c1 != c2)
|
74
|
+
current_row.append(min(insertions, deletions, substitutions))
|
75
|
+
previous_row = current_row
|
76
|
+
|
77
|
+
return previous_row[-1]
|
78
|
+
|
79
|
+
def generate_unlikely_string(length=16):
|
80
|
+
characters = string.ascii_letters + string.digits + string.punctuation
|
81
|
+
return ''.join(random.choice(characters) for _ in range(length))
|
82
|
+
|
83
|
+
if not listA:
|
84
|
+
print("Helper.ClosestMatch - Error: THe input listA parameter is not a valid list. Returning None.")
|
85
|
+
return None # Handle empty list case
|
86
|
+
|
87
|
+
if isinstance(item, str):
|
88
|
+
listA = [generate_unlikely_string(length=32) if not isinstance(x, str) else x for x in listA]
|
89
|
+
# For string inputs, find the closest match using Levenshtein distance
|
90
|
+
closest_index = min(range(len(listA)), key=lambda i: levenshtein_distance(item, listA[i]))
|
91
|
+
else:
|
92
|
+
listA = [float('-inf') if not isinstance(x, numbers.Real) else x for x in listA]
|
93
|
+
# For numeric or boolean inputs, find the closest match based on absolute difference
|
94
|
+
closest_index = min(range(len(listA)), key=lambda i: abs(listA[i] - item))
|
95
|
+
|
96
|
+
return closest_index
|
97
|
+
|
98
|
+
@staticmethod
|
99
|
+
def Flatten(listA):
|
100
|
+
"""
|
101
|
+
Flattens the input nested list.
|
102
|
+
|
103
|
+
Parameters
|
104
|
+
----------
|
105
|
+
listA : list
|
106
|
+
The input nested list.
|
107
|
+
|
108
|
+
Returns
|
109
|
+
-------
|
110
|
+
list
|
111
|
+
The flattened list.
|
112
|
+
|
113
|
+
"""
|
114
|
+
|
115
|
+
if not isinstance(listA, list):
|
116
|
+
return [listA]
|
117
|
+
flat_list = []
|
118
|
+
for item in listA:
|
119
|
+
flat_list = flat_list + Helper.Flatten(item)
|
120
|
+
return flat_list
|
121
|
+
|
122
|
+
@staticmethod
|
123
|
+
def Iterate(listA):
|
124
|
+
"""
|
125
|
+
Iterates the input nested list so that each sublist has the same number of members. To fill extra members, the shorter lists are iterated from their first member.
|
126
|
+
For example Iterate([[1,2,3],['m','n','o','p'],['a','b','c','d','e']]) yields [[1, 2, 3, 1, 2], ['m', 'n', 'o', 'p', 'm'], ['a', 'b', 'c', 'd', 'e']]
|
127
|
+
|
128
|
+
Parameters
|
129
|
+
----------
|
130
|
+
listA : list
|
131
|
+
The input nested list.
|
132
|
+
|
133
|
+
Returns
|
134
|
+
-------
|
135
|
+
list
|
136
|
+
The iterated list.
|
137
|
+
|
138
|
+
"""
|
139
|
+
# From https://stackoverflow.com/questions/34432056/repeat-elements-of-list-between-each-other-until-we-reach-a-certain-length
|
140
|
+
def onestep(cur,y,base):
|
141
|
+
# one step of the iteration
|
142
|
+
if cur is not None:
|
143
|
+
y.append(cur)
|
144
|
+
base.append(cur)
|
145
|
+
else:
|
146
|
+
y.append(base[0]) # append is simplest, for now
|
147
|
+
base = base[1:]+[base[0]] # rotate
|
148
|
+
return base
|
149
|
+
|
150
|
+
maxLength = len(listA[0])
|
151
|
+
iterated_list = []
|
152
|
+
for aSubList in listA:
|
153
|
+
newLength = len(aSubList)
|
154
|
+
if newLength > maxLength:
|
155
|
+
maxLength = newLength
|
156
|
+
for anItem in listA:
|
157
|
+
for i in range(len(anItem), maxLength):
|
158
|
+
anItem.append(None)
|
159
|
+
y=[]
|
160
|
+
base=[]
|
161
|
+
for cur in anItem:
|
162
|
+
base = onestep(cur,y,base)
|
163
|
+
iterated_list.append(y)
|
164
|
+
return iterated_list
|
165
|
+
|
166
|
+
@staticmethod
|
167
|
+
def K_Means(data, k=4, maxIterations=100):
|
168
|
+
import random
|
169
|
+
def euclidean_distance(p, q):
|
170
|
+
return sum((pi - qi) ** 2 for pi, qi in zip(p, q)) ** 0.5
|
171
|
+
|
172
|
+
# Initialize k centroids randomly
|
173
|
+
centroids = random.sample(data, k)
|
174
|
+
|
175
|
+
for _ in range(maxIterations):
|
176
|
+
# Assign each data point to the nearest centroid
|
177
|
+
clusters = [[] for _ in range(k)]
|
178
|
+
for point in data:
|
179
|
+
distances = [euclidean_distance(point, centroid) for centroid in centroids]
|
180
|
+
nearest_centroid_index = distances.index(min(distances))
|
181
|
+
clusters[nearest_centroid_index].append(point)
|
182
|
+
|
183
|
+
# Compute the new centroids as the mean of the points in each cluster
|
184
|
+
new_centroids = []
|
185
|
+
for cluster in clusters:
|
186
|
+
if not cluster:
|
187
|
+
# If a cluster is empty, keep the previous centroid
|
188
|
+
new_centroids.append(centroids[clusters.index(cluster)])
|
189
|
+
else:
|
190
|
+
new_centroids.append([sum(dim) / len(cluster) for dim in zip(*cluster)])
|
191
|
+
|
192
|
+
# Check if the centroids have converged
|
193
|
+
if new_centroids == centroids:
|
194
|
+
break
|
195
|
+
|
196
|
+
centroids = new_centroids
|
197
|
+
|
198
|
+
return {'clusters': clusters, 'centroids': centroids}
|
199
|
+
|
200
|
+
@staticmethod
|
201
|
+
def MergeByThreshold(listA, threshold=0.0001):
|
202
|
+
"""
|
203
|
+
Merges the numbers in the input list so that numbers within the input threshold are averaged into one number.
|
204
|
+
|
205
|
+
Parameters
|
206
|
+
----------
|
207
|
+
listA : list
|
208
|
+
The input nested list.
|
209
|
+
threshold : float , optional
|
210
|
+
The desired merge threshold value. The default is 0.0001.
|
211
|
+
|
212
|
+
Returns
|
213
|
+
-------
|
214
|
+
list
|
215
|
+
The merged list. The list is sorted in ascending numeric order.
|
216
|
+
|
217
|
+
"""
|
218
|
+
# Sort the list in ascending order
|
219
|
+
listA.sort()
|
220
|
+
merged_list = []
|
221
|
+
|
222
|
+
# Initialize the first element in the merged list
|
223
|
+
merged_list.append(listA[0])
|
224
|
+
|
225
|
+
# Merge numbers within the threshold
|
226
|
+
for i in range(1, len(listA)):
|
227
|
+
if listA[i] - merged_list[-1] <= threshold:
|
228
|
+
# Merge the current number with the last element in the merged list
|
229
|
+
merged_list[-1] = (merged_list[-1] + listA[i]) / 2
|
230
|
+
else:
|
231
|
+
# If the current number is beyond the threshold, add it as a new element
|
232
|
+
merged_list.append(listA[i])
|
233
|
+
|
234
|
+
return merged_list
|
235
|
+
|
236
|
+
@staticmethod
|
237
|
+
def MakeUnique(listA):
|
238
|
+
"""
|
239
|
+
Forces the strings in the input list to be unique if they have duplicates.
|
240
|
+
|
241
|
+
Parameters
|
242
|
+
----------
|
243
|
+
listA : list
|
244
|
+
The input list of strings.
|
245
|
+
|
246
|
+
Returns
|
247
|
+
-------
|
248
|
+
list
|
249
|
+
The input list, but with each item ensured to be unique if they have duplicates.
|
250
|
+
|
251
|
+
"""
|
252
|
+
# Create a dictionary to store counts of each string
|
253
|
+
counts = {}
|
254
|
+
# Create a list to store modified strings
|
255
|
+
unique_strings = []
|
256
|
+
|
257
|
+
for string in listA:
|
258
|
+
# If the string already exists in the counts dictionary
|
259
|
+
if string in counts:
|
260
|
+
# Increment the count
|
261
|
+
counts[string] += 1
|
262
|
+
# Append the modified string with underscore and count
|
263
|
+
unique_strings.append(f"{string}_{counts[string]}")
|
264
|
+
else:
|
265
|
+
# If it's the first occurrence of the string, add it to the counts dictionary
|
266
|
+
counts[string] = 0
|
267
|
+
unique_strings.append(string)
|
268
|
+
|
269
|
+
return unique_strings
|
270
|
+
|
271
|
+
@staticmethod
|
272
|
+
def Normalize(listA, mantissa: int = 6):
|
273
|
+
"""
|
274
|
+
Normalizes the input list so that it is in the range 0 to 1
|
275
|
+
|
276
|
+
Parameters
|
277
|
+
----------
|
278
|
+
listA : list
|
279
|
+
The input nested list.
|
280
|
+
mantissa : int , optional
|
281
|
+
The desired mantissa value. The default is 6.
|
282
|
+
|
283
|
+
Returns
|
284
|
+
-------
|
285
|
+
list
|
286
|
+
The normalized list.
|
287
|
+
|
288
|
+
"""
|
289
|
+
if not isinstance(listA, list):
|
290
|
+
print("Helper.Normalize - Error: The input list is not valid. Returning None.")
|
291
|
+
return None
|
292
|
+
|
293
|
+
# Make sure the list is numeric
|
294
|
+
l = [x for x in listA if type(x) == int or type(x) == float]
|
295
|
+
if len(l) < 1:
|
296
|
+
print("Helper.Normalize - Error: The input list does not contain numeric values. Returning None.")
|
297
|
+
return None
|
298
|
+
min_val = min(l)
|
299
|
+
max_val = max(l)
|
300
|
+
if min_val == max_val:
|
301
|
+
normalized_list = [0 for x in l]
|
302
|
+
else:
|
303
|
+
normalized_list = [round((x - min_val) / (max_val - min_val), mantissa) for x in l]
|
304
|
+
return normalized_list
|
305
|
+
|
306
|
+
@staticmethod
|
307
|
+
def Position(item, listA):
|
308
|
+
"""
|
309
|
+
Returns the position of the item in the list or the position it would have been inserts.
|
310
|
+
item is assumed to be numeric. listA is assumed to contain only numeric values and sorted from lowest to highest value.
|
311
|
+
|
312
|
+
Parameters
|
313
|
+
----------
|
314
|
+
item : int or float
|
315
|
+
The input number to be positioned.
|
316
|
+
listA : list
|
317
|
+
The input sorted list.
|
318
|
+
|
319
|
+
Returns
|
320
|
+
-------
|
321
|
+
int
|
322
|
+
The position of the item within the list.
|
323
|
+
|
324
|
+
"""
|
325
|
+
left = 0
|
326
|
+
right = len(listA) - 1
|
327
|
+
|
328
|
+
while left <= right:
|
329
|
+
mid = (left + right) // 2
|
330
|
+
if listA[mid] == item:
|
331
|
+
return mid
|
332
|
+
elif listA[mid] < item:
|
333
|
+
left = mid + 1
|
334
|
+
else:
|
335
|
+
right = mid - 1
|
336
|
+
|
337
|
+
# If the target is not found, return the position where it would be inserted
|
338
|
+
return left
|
339
|
+
|
340
|
+
@staticmethod
|
341
|
+
def Repeat(listA):
|
342
|
+
"""
|
343
|
+
Repeats the input nested list so that each sublist has the same number of members. To fill extra members, the last item in the shorter lists are repeated and appended.
|
344
|
+
For example Iterate([[1,2,3],['m','n','o','p'],['a','b','c','d','e']]) yields [[1, 2, 3, 3, 3], ['m', 'n', 'o', 'p', 'p'], ['a', 'b', 'c', 'd', 'e']]
|
345
|
+
|
346
|
+
Parameters
|
347
|
+
----------
|
348
|
+
listA : list
|
349
|
+
The input nested list.
|
350
|
+
|
351
|
+
Returns
|
352
|
+
-------
|
353
|
+
list
|
354
|
+
The repeated list.
|
355
|
+
|
356
|
+
"""
|
357
|
+
if not isinstance(listA, list):
|
358
|
+
return None
|
359
|
+
repeated_list = [x for x in listA if isinstance(x, list)]
|
360
|
+
if len(repeated_list) < 1:
|
361
|
+
return None
|
362
|
+
maxLength = len(repeated_list[0])
|
363
|
+
for aSubList in repeated_list:
|
364
|
+
newLength = len(aSubList)
|
365
|
+
if newLength > maxLength:
|
366
|
+
maxLength = newLength
|
367
|
+
for anItem in repeated_list:
|
368
|
+
if (len(anItem) > 0):
|
369
|
+
itemToAppend = anItem[-1]
|
370
|
+
else:
|
371
|
+
itemToAppend = None
|
372
|
+
for i in range(len(anItem), maxLength):
|
373
|
+
anItem.append(itemToAppend)
|
374
|
+
return repeated_list
|
375
|
+
|
376
|
+
@staticmethod
|
377
|
+
def Sort(listA, *otherLists, reverseFlags=None):
|
378
|
+
"""
|
379
|
+
Sorts the first input list according to the values in the subsequent input lists in order. For example,
|
380
|
+
your first list can be a list of topologies and the next set of lists can be their volume, surface area, and z level.
|
381
|
+
The list of topologies will then be sorted first by volume, then by surface, and lastly by z level. You can choose
|
382
|
+
to reverse the order of sorting by including a list of TRUE/FALSE values in the reverseFlags input parameter.
|
383
|
+
For example, if you wish to sort the volume in reverse order (from large to small), but sort the other parameters
|
384
|
+
normally, you would include the following list for reverseFlag: [True, False, False].
|
385
|
+
|
386
|
+
Parameters
|
387
|
+
----------
|
388
|
+
listA : list
|
389
|
+
The first input list to be sorts
|
390
|
+
*otherLists : any number of lists to use for sorting listA, optional.
|
391
|
+
Any number of lists that are used to sort the listA input parameter. The order of these input
|
392
|
+
parameters determines the order of sorting (from left to right). If no lists are included, the input list will be sorted as is.
|
393
|
+
reverseFlags : list, optional.
|
394
|
+
The list of booleans (TRUE/FALSE) to indicated if sorting based on a particular list should be conducted in reverse order.
|
395
|
+
The length of the reverseFlags list should match the number of the lists in the input otherLists parameter. If set to None,
|
396
|
+
a default list of FALSE values is created to match the number of the lists in the input otherLists parameter. The default
|
397
|
+
is None.
|
398
|
+
|
399
|
+
Returns
|
400
|
+
-------
|
401
|
+
list
|
402
|
+
The sorted list.
|
403
|
+
|
404
|
+
"""
|
405
|
+
|
406
|
+
# If reverseFlags is not provided, assume all lists should be sorted in ascending order
|
407
|
+
if reverseFlags is None:
|
408
|
+
reverseFlags = [False] * len(otherLists)
|
409
|
+
if not isinstance(otherLists, tuple):
|
410
|
+
print("Helper.Sort - Error: No other lists to use for sorting have been provided. Returning None.")
|
411
|
+
return None
|
412
|
+
if len(otherLists) < 1:
|
413
|
+
print("Helper.Sort - Error: The otherLists input parameter does not contain any valid lists. Returning None.")
|
414
|
+
return None
|
415
|
+
if not len(reverseFlags) == len(otherLists):
|
416
|
+
print("Helper.Sort - Error: The length of the reverseFlags input parameter is not equal to the number of input lists. Returning None.")
|
417
|
+
return None
|
418
|
+
# Convert other_lists to numeric and reverse if needed.
|
419
|
+
sorting_lists = []
|
420
|
+
for i, a_list in enumerate(otherLists):
|
421
|
+
temp_list = []
|
422
|
+
temp_set = list(set(a_list))
|
423
|
+
temp_set = sorted(temp_set)
|
424
|
+
if reverseFlags[i] == True:
|
425
|
+
temp_set.reverse()
|
426
|
+
for item in a_list:
|
427
|
+
temp_list.append(temp_set.index(item))
|
428
|
+
sorting_lists.append(temp_list)
|
429
|
+
|
430
|
+
combined_lists = list(zip(listA, *sorting_lists))
|
431
|
+
# Sort the combined list based on all the elements and reverse the lists as needed
|
432
|
+
combined_lists.sort(key=lambda x: tuple((-val) if reverse else val for val, reverse in zip(x[1:], reverseFlags)))
|
433
|
+
sorted_listA = [item[0] for item in combined_lists]
|
434
|
+
return sorted_listA
|
435
|
+
|
436
|
+
@staticmethod
|
437
|
+
def Transpose(listA):
|
438
|
+
"""
|
439
|
+
Transposes the input list (swaps rows and columns).
|
440
|
+
|
441
|
+
Parameters
|
442
|
+
----------
|
443
|
+
listA : list
|
444
|
+
The input list.
|
445
|
+
|
446
|
+
Returns
|
447
|
+
-------
|
448
|
+
list
|
449
|
+
The transposed list.
|
450
|
+
|
451
|
+
"""
|
452
|
+
if not isinstance(listA, list):
|
453
|
+
return None
|
454
|
+
length = len(listA[0])
|
455
|
+
transposed_list = []
|
456
|
+
for i in range(length):
|
457
|
+
tempRow = []
|
458
|
+
for j in range(len(listA)):
|
459
|
+
tempRow.append(listA[j][i])
|
460
|
+
transposed_list.append(tempRow)
|
461
|
+
return transposed_list
|
462
|
+
|
463
|
+
@staticmethod
|
464
|
+
def Trim(listA):
|
465
|
+
"""
|
466
|
+
Trims the input nested list so that each sublist has the same number of members. All lists are trimmed to match the length of the shortest list.
|
467
|
+
For example Trim([[1,2,3],['m','n','o','p'],['a','b','c','d','e']]) yields [[1, 2, 3], ['m', 'n', 'o'], ['a', 'b', 'c']]
|
468
|
+
|
469
|
+
Parameters
|
470
|
+
----------
|
471
|
+
listA : list
|
472
|
+
The input nested list.
|
473
|
+
|
474
|
+
Returns
|
475
|
+
-------
|
476
|
+
list
|
477
|
+
The repeated list.
|
478
|
+
|
479
|
+
"""
|
480
|
+
minLength = len(listA[0])
|
481
|
+
returnList = []
|
482
|
+
for aSubList in listA:
|
483
|
+
newLength = len(aSubList)
|
484
|
+
if newLength < minLength:
|
485
|
+
minLength = newLength
|
486
|
+
for anItem in listA:
|
487
|
+
anItem = anItem[:minLength]
|
488
|
+
returnList.append(anItem)
|
489
|
+
return returnList
|
490
|
+
|
491
|
+
@staticmethod
|
492
|
+
def Version():
|
493
|
+
"""
|
494
|
+
Returns the current version of the software.
|
495
|
+
|
496
|
+
Parameters
|
497
|
+
----------
|
498
|
+
None
|
499
|
+
|
500
|
+
|
501
|
+
Returns
|
502
|
+
-------
|
503
|
+
str
|
504
|
+
The current version of the software.
|
505
|
+
|
506
|
+
"""
|
507
|
+
return topologicpy.__version__
|