mirror of https://gitlab.com/pamhyr/pamhyr2
geometry: Guidelines computation.
parent
8782056b1c
commit
cd69d56c74
|
|
@ -91,3 +91,13 @@ class Profile(object):
|
||||||
@profile_type.setter
|
@profile_type.setter
|
||||||
def profile_type(self, value: str):
|
def profile_type(self, value: str):
|
||||||
self._profile_type = value
|
self._profile_type = value
|
||||||
|
|
||||||
|
|
||||||
|
def named_points(self):
|
||||||
|
"""List of named point
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The list of named point
|
||||||
|
"""
|
||||||
|
return [point for point in self._points
|
||||||
|
if point.point_is_named()]
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ from typing import List
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
|
from tools import flatten
|
||||||
|
|
||||||
from Model.Geometry.Profile import Profile
|
from Model.Geometry.Profile import Profile
|
||||||
from Model.Geometry.ProfileXYZ import ProfileXYZ
|
from Model.Geometry.ProfileXYZ import ProfileXYZ
|
||||||
|
|
||||||
|
|
@ -17,6 +19,9 @@ class Reach:
|
||||||
self._parent = parent
|
self._parent = parent
|
||||||
self._profiles: List[Profile] = []
|
self._profiles: List[Profile] = []
|
||||||
|
|
||||||
|
self._guidelines_is_valid = False
|
||||||
|
self._guidelines = {}
|
||||||
|
|
||||||
# Copy/Paste
|
# Copy/Paste
|
||||||
self.__list_copied_profiles = []
|
self.__list_copied_profiles = []
|
||||||
|
|
||||||
|
|
@ -138,7 +143,16 @@ class Reach:
|
||||||
reverse=is_reversed
|
reverse=is_reversed
|
||||||
)
|
)
|
||||||
|
|
||||||
def z_min(self):
|
def get_x(self):
|
||||||
|
return [profile.x() for profile in self.profiles]
|
||||||
|
|
||||||
|
def get_y(self):
|
||||||
|
return [profile.y() for profile in self.profiles]
|
||||||
|
|
||||||
|
def get_z(self):
|
||||||
|
return [profile.z() for profile in self.profiles]
|
||||||
|
|
||||||
|
def get_z_min(self):
|
||||||
"""List of z min for each profile
|
"""List of z min for each profile
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -146,7 +160,7 @@ class Reach:
|
||||||
"""
|
"""
|
||||||
return [profile.z_min() for profile in self._data.profiles]
|
return [profile.z_min() for profile in self._data.profiles]
|
||||||
|
|
||||||
def z_max(self):
|
def get_z_max(self):
|
||||||
"""List of z max for each profile
|
"""List of z max for each profile
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -154,7 +168,7 @@ class Reach:
|
||||||
"""
|
"""
|
||||||
return [profile.z_max() for profile in self._data.profiles]
|
return [profile.z_max() for profile in self._data.profiles]
|
||||||
|
|
||||||
def kp(self):
|
def get_kp(self):
|
||||||
"""List of profiles kp
|
"""List of profiles kp
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -162,6 +176,92 @@ class Reach:
|
||||||
"""
|
"""
|
||||||
return [profile.kp for profile in self._data.profiles]
|
return [profile.kp for profile in self._data.profiles]
|
||||||
|
|
||||||
|
##############
|
||||||
|
# GUIDELINES #
|
||||||
|
##############
|
||||||
|
|
||||||
|
def _compute_guidelines_cache(self, guide_set, named_points):
|
||||||
|
# Reset guide lines cache
|
||||||
|
self._guidelines = {}
|
||||||
|
|
||||||
|
# Make a list of point for each guideline
|
||||||
|
for guide in guide_set:
|
||||||
|
self._guidelines[guide] = flatten(
|
||||||
|
map(
|
||||||
|
lambda l: list(
|
||||||
|
# Filter point with name (we assume we have
|
||||||
|
# only one point by profile)
|
||||||
|
filter(
|
||||||
|
lambda p: p.name == guide,
|
||||||
|
l
|
||||||
|
)
|
||||||
|
),
|
||||||
|
named_points
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def compute_guidelines(self):
|
||||||
|
"""Compute reach guideline and check if is valid for all profiles
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if all guide line is valid
|
||||||
|
"""
|
||||||
|
# Get all point contains into a guideline
|
||||||
|
named_points = [profile.named_points() for profile in self._profiles]
|
||||||
|
points_name = list(
|
||||||
|
map(
|
||||||
|
lambda lst: list(map(lambda p: p.name, lst)),
|
||||||
|
named_points
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get all guide line name
|
||||||
|
guide_set = reduce(
|
||||||
|
lambda acc, x: set(x).union(acc),
|
||||||
|
points_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# All guide line is valid
|
||||||
|
is_ok = reduce(
|
||||||
|
bool.__and__,
|
||||||
|
map(
|
||||||
|
lambda l: len(set(l).symmetric_difference(guide_set)) == 0,
|
||||||
|
points_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self._guidelines_is_valid = is_ok
|
||||||
|
|
||||||
|
# Compute guideline and put data in cache
|
||||||
|
self._compute_guidelines_cache(guide_set, named_points)
|
||||||
|
|
||||||
|
return is_ok
|
||||||
|
|
||||||
|
def _map_guidelines_points(self, func):
|
||||||
|
return list(
|
||||||
|
# Map for each guideline
|
||||||
|
map(
|
||||||
|
lambda k: list(
|
||||||
|
# Apply function FUNC on each point of guideline
|
||||||
|
map(
|
||||||
|
func,
|
||||||
|
self._guidelines[k],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
self._guidelines
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_guidelines_x(self):
|
||||||
|
return self._map_guidelines_points(lambda p: p.x)
|
||||||
|
|
||||||
|
def get_guidelines_y(self):
|
||||||
|
return self._map_guidelines_points(lambda p: p.y)
|
||||||
|
|
||||||
|
def get_guidelines_z(self):
|
||||||
|
return self._map_guidelines_points(lambda p: p.z)
|
||||||
|
|
||||||
|
# Sort
|
||||||
|
|
||||||
def sort_ascending(self):
|
def sort_ascending(self):
|
||||||
"""Sort profiles by increasing KP
|
"""Sort profiles by increasing KP
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,17 +149,17 @@ class GeometryWindow(QMainWindow, WindowToolKit):
|
||||||
for selected_row in list_selected_row[:5]:
|
for selected_row in list_selected_row[:5]:
|
||||||
selected_row = int(selected_row)
|
selected_row = int(selected_row)
|
||||||
profile_identifier = self._reach.get_profile_selected_identifier(selected_row)
|
profile_identifier = self._reach.get_profile_selected_identifier(selected_row)
|
||||||
Pk = self._reach.get_pk_i(selected_row)
|
Kp = self._reach.get_kp_i(selected_row)
|
||||||
profile_name = self._reach.get_profile_name(selected_row)
|
profile_name = self._reach.get_profile_name(selected_row)
|
||||||
|
|
||||||
if len(self.list_second_window) == 0:
|
if len(self.list_second_window) == 0:
|
||||||
self.second_window = window_profileXYZ.View(
|
self.second_window = window_profileXYZ.View(
|
||||||
selected_row + 1,
|
selected_row + 1,
|
||||||
self._reach.get_profile_via_identifier(profile_identifier),
|
self._reach.get_profile_via_identifier(profile_identifier),
|
||||||
pk=Pk, profile_name="", parent=self
|
kp=Kp, profile_name="", parent=self
|
||||||
)
|
)
|
||||||
self.second_window.window_title(
|
self.second_window.window_title(
|
||||||
pk=Pk,
|
kp=Kp,
|
||||||
profile_name=profile_name,
|
profile_name=profile_name,
|
||||||
profile_selected_num=selected_row
|
profile_selected_num=selected_row
|
||||||
)
|
)
|
||||||
|
|
@ -172,7 +172,7 @@ class GeometryWindow(QMainWindow, WindowToolKit):
|
||||||
if profile_identifier in self.list_row:
|
if profile_identifier in self.list_row:
|
||||||
self.list_second_window[self.list_row.index(profile_identifier)]\
|
self.list_second_window[self.list_row.index(profile_identifier)]\
|
||||||
.window_title(
|
.window_title(
|
||||||
pk=Pk, profile_name=profile_name,
|
kp=Kp, profile_name=profile_name,
|
||||||
profile_selected_num=selected_row
|
profile_selected_num=selected_row
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -187,10 +187,10 @@ class GeometryWindow(QMainWindow, WindowToolKit):
|
||||||
second_window1 = window_profileXYZ.View(
|
second_window1 = window_profileXYZ.View(
|
||||||
selected_row + 1,
|
selected_row + 1,
|
||||||
self._reach.get_profile_via_identifier(profile_identifier),
|
self._reach.get_profile_via_identifier(profile_identifier),
|
||||||
pk=Pk, profile_name="", parent=self
|
kp=Kp, profile_name="", parent=self
|
||||||
)
|
)
|
||||||
second_window1.window_title(
|
second_window1.window_title(
|
||||||
pk=Pk, profile_name=profile_name,
|
kp=Kp, profile_name=profile_name,
|
||||||
profile_selected_num=selected_row
|
profile_selected_num=selected_row
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -253,14 +253,16 @@ class GeometryWindow(QMainWindow, WindowToolKit):
|
||||||
|
|
||||||
self.plot_selected_1, = self.ui.canvas_1.axes.plot(
|
self.plot_selected_1, = self.ui.canvas_1.axes.plot(
|
||||||
self._reach.get_x_profile_i(0),
|
self._reach.get_x_profile_i(0),
|
||||||
self._reach.get_y_profile_i(0), lw=1., markersize=3,
|
self._reach.get_y_profile_i(0),
|
||||||
|
lw=1., markersize=3,
|
||||||
marker='+', color="b"
|
marker='+', color="b"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.plot_selected_1.set_visible(False)
|
self.plot_selected_1.set_visible(False)
|
||||||
self.before_plot_selected_1, = self.ui.canvas_1.axes.plot(
|
self.before_plot_selected_1, = self.ui.canvas_1.axes.plot(
|
||||||
self._reach.get_x_profile_i(0),
|
self._reach.get_x_profile_i(0),
|
||||||
self._reach.get_y_profile_i(0), lw=1., markersize=3,
|
self._reach.get_y_profile_i(0),
|
||||||
|
lw=1., markersize=3,
|
||||||
marker='+', color="k", linestyle="--"
|
marker='+', color="k", linestyle="--"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -268,7 +270,8 @@ class GeometryWindow(QMainWindow, WindowToolKit):
|
||||||
|
|
||||||
self.after_plot_selected_1, = self.ui.canvas_1.axes.plot(
|
self.after_plot_selected_1, = self.ui.canvas_1.axes.plot(
|
||||||
self._reach.get_x_profile_i(0),
|
self._reach.get_x_profile_i(0),
|
||||||
self._reach.get_y_profile_i(0), lw=1., markersize=3,
|
self._reach.get_y_profile_i(0),
|
||||||
|
lw=1., markersize=3,
|
||||||
marker='+', color="m", linestyle='--'
|
marker='+', color="m", linestyle='--'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -284,54 +287,54 @@ class GeometryWindow(QMainWindow, WindowToolKit):
|
||||||
self.ui.canvas_2.axes.cla()
|
self.ui.canvas_2.axes.cla()
|
||||||
self.ui.canvas_2.axes.grid(color='grey', linestyle='--', linewidth=0.5)
|
self.ui.canvas_2.axes.grid(color='grey', linestyle='--', linewidth=0.5)
|
||||||
self.ui.canvas_2.axes.set_xlabel(
|
self.ui.canvas_2.axes.set_xlabel(
|
||||||
_translate("MainWindow_reach", "Pk (m)"), color='green', fontsize=12
|
_translate("MainWindow_reach", "Kp (m)"), color='green', fontsize=12
|
||||||
)
|
)
|
||||||
self.ui.canvas_2.axes.set_ylabel(
|
self.ui.canvas_2.axes.set_ylabel(
|
||||||
_translate("MainWindow_reach", "Cote (m)"), color='green', fontsize=12
|
_translate("MainWindow_reach", "Cote (m)"), color='green', fontsize=12
|
||||||
)
|
)
|
||||||
|
|
||||||
self.get_pk = self._reach.get_pk()
|
self.get_kp = self._reach.get_kp()
|
||||||
self.get_z_min = self._reach.get_z_min()
|
self.get_z_min = self._reach.get_z_min()
|
||||||
self.get_z_max = self._reach.get_z_max()
|
self.get_z_max = self._reach.get_z_max()
|
||||||
|
|
||||||
self.line_pk_zmin_zmax = self.ui.canvas_2.axes.vlines(
|
self.line_kp_zmin_zmax = self.ui.canvas_2.axes.vlines(
|
||||||
x=self.get_pk,
|
x=self.get_kp,
|
||||||
ymin=self.get_z_min, ymax=self.get_z_max,
|
ymin=self.get_z_min, ymax=self.get_z_max,
|
||||||
color='r', lw=1.
|
color='r', lw=1.
|
||||||
)
|
)
|
||||||
|
|
||||||
self.plot_selected_2, = self.ui.canvas_2.axes.plot(
|
self.plot_selected_2, = self.ui.canvas_2.axes.plot(
|
||||||
(self.get_pk[0], self.get_pk[0]),
|
(self.get_kp[0], self.get_kp[0]),
|
||||||
(self.get_z_min[0], self.get_z_max[0]),
|
(self.get_z_min[0], self.get_z_max[0]),
|
||||||
color='b', lw=1.8
|
color='b', lw=1.8
|
||||||
)
|
)
|
||||||
self.plot_selected_2.set_visible(False)
|
self.plot_selected_2.set_visible(False)
|
||||||
|
|
||||||
self.before_plot_selected_2, = self.ui.canvas_2.axes.plot(
|
self.before_plot_selected_2, = self.ui.canvas_2.axes.plot(
|
||||||
(self.get_pk[0], self.get_pk[0]),
|
(self.get_kp[0], self.get_kp[0]),
|
||||||
(self.get_z_min[0], self.get_z_max[0]),
|
(self.get_z_min[0], self.get_z_max[0]),
|
||||||
color='k', lw=1.6, linestyle='--'
|
color='k', lw=1.6, linestyle='--'
|
||||||
)
|
)
|
||||||
self.before_plot_selected_2.set_visible(False)
|
self.before_plot_selected_2.set_visible(False)
|
||||||
|
|
||||||
self.after_plot_selected_2, = self.ui.canvas_2.axes.plot(
|
self.after_plot_selected_2, = self.ui.canvas_2.axes.plot(
|
||||||
(self.get_pk[0], self.get_pk[0]),
|
(self.get_kp[0], self.get_kp[0]),
|
||||||
(self.get_z_min[0], self.get_z_max[0]),
|
(self.get_z_min[0], self.get_z_max[0]),
|
||||||
color='m', lw=1.6, linestyle='--'
|
color='m', lw=1.6, linestyle='--'
|
||||||
)
|
)
|
||||||
self.after_plot_selected_2.set_visible(False)
|
self.after_plot_selected_2.set_visible(False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.line_pk_zld = [
|
self.line_kp_zld = [
|
||||||
self.ui.canvas_2.axes.plot(
|
self.ui.canvas_2.axes.plot(
|
||||||
self._reach.get_pk(), i, lw=1.
|
self._reach.get_kp(), i, lw=1.
|
||||||
) for i in self.model.z_complete_guideline()
|
) for i in self.model.z_complete_guideline()
|
||||||
]
|
]
|
||||||
except:
|
except:
|
||||||
print("TODO")
|
print("TODO")
|
||||||
|
|
||||||
self.line_pk_zmin, = self.ui.canvas_2.axes.plot(
|
self.line_kp_zmin, = self.ui.canvas_2.axes.plot(
|
||||||
self.get_pk, self.get_z_min,
|
self.get_kp, self.get_z_min,
|
||||||
linestyle=":", lw=1.8,
|
linestyle=":", lw=1.8,
|
||||||
color='lightgrey'
|
color='lightgrey'
|
||||||
)
|
)
|
||||||
|
|
@ -363,23 +366,23 @@ class GeometryWindow(QMainWindow, WindowToolKit):
|
||||||
def update_graphic_2(self):
|
def update_graphic_2(self):
|
||||||
self.tableView.model().blockSignals(True)
|
self.tableView.model().blockSignals(True)
|
||||||
|
|
||||||
get_pk = self._reach.get_pk()
|
get_kp = self._reach.get_kp()
|
||||||
get_z_min = self._reach.get_z_min()
|
get_z_min = self._reach.get_z_min()
|
||||||
get_z_max = self._reach.get_z_max()
|
get_z_max = self._reach.get_z_max()
|
||||||
|
|
||||||
self.line_pk_zmin.set_data(get_pk, get_z_min)
|
self.line_kp_zmin.set_data(get_kp, get_z_min)
|
||||||
|
|
||||||
self.line_pk_zmin_zmax.remove()
|
self.line_kp_zmin_zmax.remove()
|
||||||
self.line_pk_zmin_zmax = self.ui.canvas_2.axes.vlines(
|
self.line_kp_zmin_zmax = self.ui.canvas_2.axes.vlines(
|
||||||
x=get_pk,
|
x=get_kp,
|
||||||
ymin=get_z_min, ymax=get_z_max,
|
ymin=get_z_min, ymax=get_z_max,
|
||||||
color='r', lw=1.
|
color='r', lw=1.
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for i in range(len(self.line_pk_zld)):
|
for i in range(len(self.line_kp_zld)):
|
||||||
self.line_pk_zld[i][0].set_data(
|
self.line_kp_zld[i][0].set_data(
|
||||||
get_pk, self.model.z_complete_guideline()[i]
|
get_kp, self.model.z_complete_guideline()[i]
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
print("TODO")
|
print("TODO")
|
||||||
|
|
@ -618,12 +621,12 @@ class GeometryWindow(QMainWindow, WindowToolKit):
|
||||||
self.tableView.model().blockSignals(False)
|
self.tableView.model().blockSignals(False)
|
||||||
|
|
||||||
def select_plot_graphic_2(self, ind: int):
|
def select_plot_graphic_2(self, ind: int):
|
||||||
get_pk_i = self.get_pk_i(ind)
|
get_kp_i = self.get_kp_i(ind)
|
||||||
get_z_min_i = self.get_z_min_i(ind)
|
get_z_min_i = self.get_z_min_i(ind)
|
||||||
get_z_max_i = self.get_z_max_i(ind)
|
get_z_max_i = self.get_z_max_i(ind)
|
||||||
|
|
||||||
if 0 <= ind < self.model.rowCount():
|
if 0 <= ind < self.model.rowCount():
|
||||||
self.plot_selected_2.set_data((get_pk_i, get_pk_i),
|
self.plot_selected_2.set_data((get_kp_i, get_kp_i),
|
||||||
(get_z_min_i, get_z_max_i))
|
(get_z_min_i, get_z_max_i))
|
||||||
self.plot_selected_2.set_visible(True)
|
self.plot_selected_2.set_visible(True)
|
||||||
|
|
||||||
|
|
@ -651,12 +654,12 @@ class GeometryWindow(QMainWindow, WindowToolKit):
|
||||||
def select_before_plot_selected_2(self, ind: int):
|
def select_before_plot_selected_2(self, ind: int):
|
||||||
if 0 <= ind < self.model.rowCount():
|
if 0 <= ind < self.model.rowCount():
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
get_pk_i = self.get_pk_i(ind)
|
get_kp_i = self.get_kp_i(ind)
|
||||||
get_z_min_i = self.get_z_min_i(ind)
|
get_z_min_i = self.get_z_min_i(ind)
|
||||||
get_z_max_i = self.get_z_max_i(ind)
|
get_z_max_i = self.get_z_max_i(ind)
|
||||||
|
|
||||||
self.before_plot_selected_2.set_data(
|
self.before_plot_selected_2.set_data(
|
||||||
(get_pk_i, get_pk_i),
|
(get_kp_i, get_kp_i),
|
||||||
(get_z_min_i, get_z_max_i)
|
(get_z_min_i, get_z_max_i)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -666,12 +669,12 @@ class GeometryWindow(QMainWindow, WindowToolKit):
|
||||||
def select_after_plot_selected_2(self, ind: int):
|
def select_after_plot_selected_2(self, ind: int):
|
||||||
if 0 <= ind < self.model.rowCount():
|
if 0 <= ind < self.model.rowCount():
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
get_pk_i = self.get_pk_i(ind)
|
get_kp_i = self.get_kp_i(ind)
|
||||||
get_z_min_i = self.get_z_min_i(ind)
|
get_z_min_i = self.get_z_min_i(ind)
|
||||||
get_z_max_i = self.get_z_max_i(ind)
|
get_z_max_i = self.get_z_max_i(ind)
|
||||||
|
|
||||||
self.after_plot_selected_2.set_data(
|
self.after_plot_selected_2.set_data(
|
||||||
(get_pk_i, get_pk_i),
|
(get_kp_i, get_kp_i),
|
||||||
(get_z_min_i, get_z_max_i)
|
(get_z_min_i, get_z_max_i)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -730,11 +733,11 @@ class GeometryWindow(QMainWindow, WindowToolKit):
|
||||||
self.ui.verticalSlider.setMaximum(self.model.rowCount() - 1)
|
self.ui.verticalSlider.setMaximum(self.model.rowCount() - 1)
|
||||||
|
|
||||||
slider_value = self.ui.verticalSlider.value()
|
slider_value = self.ui.verticalSlider.value()
|
||||||
pk = self._reach.get_pk_profile_i(slider_value)
|
kp = self._reach.get_kp_profile_i(slider_value)
|
||||||
|
|
||||||
self.ui.vertical_slider_label.setText(
|
self.ui.vertical_slider_label.setText(
|
||||||
_translate("MainWindow_reach", "Pk : ") +
|
_translate("MainWindow_reach", "Kp : ") +
|
||||||
f"{pk}" + "\n" +
|
f"{kp}" + "\n" +
|
||||||
_translate("MainWindow_reach",
|
_translate("MainWindow_reach",
|
||||||
"Profil N° : ") +
|
"Profil N° : ") +
|
||||||
f"{slider_value + 1}"
|
f"{slider_value + 1}"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from functools import (
|
||||||
|
reduce, partial
|
||||||
|
)
|
||||||
|
|
||||||
|
def flatten(lst):
|
||||||
|
"""Flatten list of list
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lst: A list of list
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
returns a list of element
|
||||||
|
"""
|
||||||
|
if not lst:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return reduce(list.__add__, lst)
|
||||||
Loading…
Reference in New Issue