Browse Source

chore: basic tree buffer implementation

pull/451/head
Parodi, Eugenio 🌶 7 months ago
parent
commit
8ea77652aa
  1. 75
      libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py
  2. 251
      libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py
  3. 231
      tests/pytest/test_005_tree.py
  4. 142
      tests/t.ui/test.ui.011.tree.05.paging.py
  5. 90
      tests/timeit/02.array.10.List.creation.py
  6. 219
      tests/timeit/33.tree.01.iterate.py
  7. 159
      tests/timeit/33.tree.02.iterate.py
  8. 221
      tests/timeit/33.tree.03.iterate.py
  9. 150
      tests/timeit/33.tree.04.listify.py

75
libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py

@ -25,6 +25,7 @@ __all__ = ['TTkTreeWidget']
from typing import List
from TermTk.TTkCore.cfg import TTkCfg
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.color import TTkColor
from TermTk.TTkCore.string import TTkString
@ -153,12 +154,14 @@ class TTkTreeWidget(TTkAbstractScrollView):
'default': {
'color': TTkColor.RST,
'lineColor': TTkColor.fg("#444444"),
'lineHeightColor': TTkColor.fg("#666666"),
'headerColor': TTkColor.fg("#ffffff")+TTkColor.bg("#444444")+TTkColor.BOLD,
'selectedColor': TTkColor.fg("#ffff88")+TTkColor.bg("#000066")+TTkColor.BOLD,
'separatorColor': TTkColor.fg("#444444")},
'disabled': {
'color': TTkColor.fg("#888888"),
'lineColor': TTkColor.fg("#888888"),
'lineHeightColor': TTkColor.fg("#666666"),
'headerColor': TTkColor.fg("#888888"),
'selectedColor': TTkColor.fg("#888888"),
'separatorColor': TTkColor.fg("#888888")},
@ -174,15 +177,6 @@ class TTkTreeWidget(TTkAbstractScrollView):
'_itemChanged', '_itemClicked', '_itemDoubleClicked', '_itemExpanded', '_itemCollapsed', '_itemActivated'
)
@dataclass(frozen=True)
class _Cache:
item: TTkTreeWidgetItem
level: int
data: list
widgets: list
firstLine: bool
_cache:List[_Cache]
_selected:List[TTkTreeWidgetItem]
@dataclass(frozen=True)
@ -214,6 +208,8 @@ class TTkTreeWidget(TTkAbstractScrollView):
self._itemExpanded = pyTTkSignal(TTkTreeWidgetItem)
self._itemCollapsed = pyTTkSignal(TTkTreeWidgetItem)
self._cache = []
self._selectionMode = selectionMode
self._dndMode = dragDropMode
self._selected = []
@ -221,7 +217,6 @@ class TTkTreeWidget(TTkAbstractScrollView):
self._separatorSelected = None
self._header = header if header else []
self._columnsPos = []
self._cache = []
self._sortingEnabled=sortingEnabled
self._sortColumn = -1
self._sortOrder = TTkK.AscendingOrder
@ -363,8 +358,8 @@ class TTkTreeWidget(TTkAbstractScrollView):
def resizeColumnToContents(self, column:int) -> None:
'''resizeColumnToContents'''
if not self._cache:
return
TTkLog.critical('resizeColumnToContents Method Unimplemented')
return
contentSize = max(row.data[column].termWidth() for row in self._cache)
self.setColumnWidth(column, contentSize)
@ -403,8 +398,9 @@ class TTkTreeWidget(TTkAbstractScrollView):
return True
y += oy-1
if 0 <= y < len(self._cache):
item = self._cache[y].item
if _item_at := self._rootItem._item_at(y+1):
_,_,_i = _item_at
item = _i
if item.childIndicatorPolicy() == TTkK.DontShowIndicatorWhenChildless and item.children() or \
item.childIndicatorPolicy() == TTkK.ShowIndicator:
item.setExpanded(not item.isExpanded())
@ -453,13 +449,14 @@ class TTkTreeWidget(TTkAbstractScrollView):
return True
# Handle Tree/Table Events
y += oy-1
if 0 <= y < len(self._cache):
item = self._cache[y].item
level = self._cache[y].level
if _item_at := self._rootItem._item_at(y+1):
_l, _yi, _i = _item_at
item = _i
level = _l
# check if the expand button is pressed with +-1 tollerance
if level*2 <= x < level*2+3 and \
( item.childIndicatorPolicy() == TTkK.DontShowIndicatorWhenChildless and item.children() or
item.childIndicatorPolicy() == TTkK.ShowIndicator ):
if ( _yi==0 and level*2 <= x < level*2+3 and \
( item.childIndicatorPolicy() == TTkK.DontShowIndicatorWhenChildless and item.children() or
item.childIndicatorPolicy() == TTkK.ShowIndicator )):
item.setExpanded(not item.isExpanded())
if item.isExpanded():
self.itemExpanded.emit(item)
@ -548,6 +545,10 @@ class TTkTreeWidget(TTkAbstractScrollView):
@pyTTkSlot()
def _refreshCache(self) -> None:
self._alignWidgets()
self.update()
self.viewChanged.emit()
return
# I save a representation of the displayed tree in a cache array
# to avoid eccessve recursion over the items and
# identify quickly the nth displayed line to improve the interaction
@ -606,6 +607,7 @@ class TTkTreeWidget(TTkAbstractScrollView):
color= style['color']
lineColor= style['lineColor']
lineHeightColor= style['lineHeightColor']
headerColor= style['headerColor']
selectedColor= style['selectedColor']
separatorColor= style['separatorColor']
@ -628,16 +630,25 @@ class TTkTreeWidget(TTkAbstractScrollView):
for sy in range(1,h):
canvas.drawChar(pos=(sx-x,sy), char=tt[4], color=lineColor)
# Draw cache
for i, c in enumerate(self._cache):
if i-y<0: continue
item = c.item
col_slices = list(zip([0]+[_p+1 for _p in self._columnsPos], self._columnsPos))
for _y, (_l, _yi, _i) in enumerate(self._rootItem._get_page(-1,1+y,h+1)):
for il in range(len(self._header)):
lx = 0 if il==0 else self._columnsPos[il-1]+1
lx1 = self._columnsPos[il]
text = c.data[il]
if item.isSelected():
canvas.drawText(pos=(lx-x,i-y+1), text=text.completeColor(selectedColor), width=lx1-lx, alignment=item.textAlignment(il), color=selectedColor)
else:
canvas.drawText(pos=(lx-x,i-y+1), text=text, width=lx1-lx, alignment=item.textAlignment(il))
_lx,_lx1 = col_slices[il]
_width = _lx1-_lx
_ih = _i.height()
_data = _i.data(il).split('\n') + [TTkString()]*_ih
if il==0: # First Column
if _yi == 0:
_icon = f"{' '*_l} "+_i.icon(il)+" "
elif _yi == _ih-1:
_icon = TTkString(f"{' '*_l}", lineHeightColor)
elif _yi == 1:
_icon = TTkString(f"{' '*_l}", lineHeightColor)
else:
_icon = TTkString(f"{' '*_l}", lineHeightColor)
_text=_icon+_data[_yi]
else: # Other columns
_text=_data[_yi]
if _i.isSelected():
_text = (_text + ' '*_width).completeColor(selectedColor)
canvas.drawTTkString(text=_text,pos=(_lx-x,_y+1),width=_width)

251
libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py

@ -20,12 +20,12 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
__all__ = ['TTkTreeWidgetItem']
try:
from typing import Self
except:
class Self(): pass
from dataclasses import dataclass
from typing import List, Tuple, Iterator, Generator, Optional, Callable, Any
from TermTk.TTkCore.cfg import TTkCfg
from TermTk.TTkCore.constant import TTkK
@ -34,6 +34,89 @@ from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal
from TermTk.TTkWidgets import TTkWidget
from TermTk.TTkAbstract.abstractitemmodel import TTkAbstractItemModel
@dataclass
class _TTkTreePageItem():
y:int
level:int
item:TTkTreeWidgetItem
@dataclass
class _TTkTreeBuffer():
level:int
total_size:int
buffered_size:int
buffer:List[Tuple[int,int,TTkTreeWidgetItem]]
buffer_link:List[Tuple[int,int]]
def __init__(self):
self.level = 0
self.total_size = 0
self.buffer = []
self.buffer_link = []
self._gen:Optional[Iterator] = None
def get_link(self, index:int) -> int:
if index<0:
return 0
if index >= len(self.buffer_link):
return len(self.buffer)
return self.buffer_link[index][0]
def clearBuffer(self):
self.buffer = []
self.buffer_link = []
def clearBufferFromIndex(self, index:int) -> None:
if index<0:
self.clearBuffer()
elif index >= len(self.buffer_link):
pass
else:
link = self.buffer_link[index][0]
self.buffer[link:] = []
self.buffer_link[index+1:] = []
link = self.buffer_link[index] = (link,0)
def get_page(self, item:TTkTreeWidgetItem, level:int, index:int, size:int) -> List[Tuple[int,int,TTkTreeWidgetItem]]:
# Add the item to the buffer
if self.level != level:
self.clearBuffer()
if not self.buffer:
self.buffer = [(level, _y, item) for _y in range(item._height)]
self.buffer_link = [(item._height, 0)]
final_index = index+size
buffered_size = len(self.buffer)
if item._expanded:
while buffered_size < final_index:
last_index = len(self.buffer_link)-1
if len(item._children) <= last_index:
break
# | last_index
# | ch_last_index = ch_h
# | | => left to fetch = (ch_h, final_index - buffered_size)
# Children * <---> * <--|xxxx| > * < >
# /
# item *<-------------|xxxx| buffer
# | final_index
# buffered_size
#
ch_buffer_index, ch_h = self.buffer_link[last_index]
child = item._children[last_index]
ch_s = child.size()
if ch_h != ch_s:
ch_index = ch_h
ch_size = final_index - ch_buffer_index - ch_h
child_page = child._get_page(level+1, ch_index, ch_size)
self.buffer.extend(child_page)
ch_h += len(child_page)
self.buffer_link[last_index] = (ch_buffer_index, ch_h)
buffered_size = len(self.buffer)
if ch_h == ch_s:
self.buffer_link.append((buffered_size, 0))
return self.buffer[index:final_index]
class TTkTreeWidgetItem(TTkAbstractItemModel):
'''
The :py:class:`TTkTreeWidgetItem` class provides an item for use with the :py:class:'TTkTree' convenience class.
@ -63,13 +146,22 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
__slots__ = ('_parent', '_data', '_widgets', '_height', '_alignment', '_children', '_expanded', '_selected', '_hidden',
'_childIndicatorPolicy', '_icon', '_defaultIcon',
'_sortColumn', '_sortOrder', '_hasWidgets', '_parentWidget',
'_list_bk', '_list_h_bk',
'_buffer',
# Signals
# 'refreshData'
'heightChanged'
'heightChanged', '_invalidateListBuffer', '_sizeChanged',
# Slot that accept itself
'_sizeChangedHandler'
)
_children:List[TTkTreeWidgetItem]
_buffer:_TTkTreeBuffer
_sizeChangedHandler: Callable[[TTkTreeWidgetItem,int,int], None]
def __init__(self, *args,
parent:Self=None,
parent:TTkTreeWidgetItem=None,
expanded:bool=False,
selected:bool=False,
hidden:bool=False,
@ -79,8 +171,13 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
# Signals
# self.refreshData = pyTTkSignal(TTkTreeWidgetItem)
self.heightChanged = pyTTkSignal(int)
self._invalidateListBuffer = pyTTkSignal(TTkTreeWidgetItem)
self._sizeChanged = pyTTkSignal(TTkTreeWidgetItem,int)
self._hasWidgets = False
self._children = []
self._list_bk = []
self._list_h_bk = []
self._buffer = _TTkTreeBuffer()
self._parentWidget = None
self._height = 1
data = args[0] if len(args)>0 and type(args[0])==list else [TTkString()]
@ -94,6 +191,18 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
self._sortColumn = -1
self._sortOrder = TTkK.AscendingOrder
# I need this hack because I cannot define the class itself in the slot
@pyTTkSlot(TTkTreeWidgetItem, int)
def _sch(item:TTkTreeWidgetItem, diffSize:int) -> None:
if item == self or self._expanded:
if item==self:
self._buffer.clearBuffer()
else:
self._buffer.clearBufferFromIndex(self._children.index(item))
self._buffer.total_size += diffSize
self._sizeChanged.emit(self, diffSize)
self._sizeChangedHandler = _sch
super().__init__(**kwargs)
self._data, self._widgets = self._processDataInput(data)
self._alignment = [TTkK.LEFT_ALIGN]*len(self._data)
@ -141,6 +250,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
retData.append(TTkString())
retWidgets.append(None)
self._height = max(self._height,len(retData[-1].split('\n')))
self._buffer.total_size = self._height
return retData, retWidgets
def _setDefaultIcon(self):
@ -158,8 +268,10 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
if h != self._height:
h = max(max([len(s.split("\n")) for s in self._data]), max(w.height() for w in self._widgets if w))
if h != self._height:
diffSize = h - self._height
self._height = h
self.heightChanged.emit(h)
self._sizeChangedHandler(self,diffSize)
if self._parentWidget:
self._parentWidget._refreshCache()
@ -190,6 +302,49 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
widgets += c._setTreeItemParent(parent)
return widgets
def _item_at(self, pos:int) -> Optional[Tuple[int,int,TTkTreeWidgetItem]]:
if pos < 0:
return None
if page := self._get_page(self._buffer.level, pos, 1):
return page[0]
else:
return None
def _get_page(self, level:int, index:int, size:int) -> List[Tuple[int,int,TTkTreeWidgetItem]]:
return self._buffer.get_page(self,level,index,size)
def _iterate(self, level:int=0, skip:int=0) -> Generator[Tuple[TTkTreeWidgetItem, int], None, int]:
for _c in self._children:
if skip>0:
skip -= 1
else:
yield _c, level
if _c._expanded:
skip = yield from _c._iterate(level+1, skip)
return skip
def _iterate_h(self, level:int=0, skip:int=0) -> Generator[Tuple[TTkTreeWidgetItem, int, int], None, int]:
for _c in self._children:
for _y in range(_c._height):
if skip>0:
skip -= 1
else:
yield _c, level, _y
if _c._expanded:
skip = yield from _c._iterate_h(level+1, skip)
return skip
def _listify(self, level:int):
self._list_h_bk = [(self,level,_y) for _y in range(self._height)]
if self._expanded:
for _c in self._children:
self._list_h_bk.extend(_c.listify(level+1))
def listify(self,level:int=0) -> List[TTkTreeWidgetItem, int, int]:
if not self._list_h_bk:
self._listify(level=level)
return self._list_h_bk
def setTreeItemParent(self, parent):
if parent:
widgets = self._setTreeItemParent(parent)
@ -199,8 +354,6 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
widgets = self._clearTreeItemParent()
# pw.rootLayout().removeWidgets(widgets)
def hasWidgets(self):
return self._hasWidgets
@ -210,7 +363,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
def setHidden(self, hide):
if hide == self._hidden: return
self._hidden = hide
self.dataChanged.emit()
self.emitDataChanged()
def childIndicatorPolicy(self):
return self._childIndicatorPolicy
@ -219,7 +372,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
self._childIndicatorPolicy = policy
self._setDefaultIcon()
def _addChild(self, child):
def _addChild(self, child:TTkTreeWidgetItem):
self._children.append(child)
child._parent = self
child._sortOrder = self._sortOrder
@ -229,17 +382,28 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
if self._parentWidget:
child.setTreeItemParent(self._parentWidget)
child.dataChanged.connect(self.emitDataChanged)
child._sizeChanged.connect(self._sizeChangedHandler)
child._invalidateListBuffer.connect(self._invalidateListBufferHandler)
def addChild(self, child):
def addChild(self, child:TTkTreeWidgetItem):
self._addChild(child)
self.dataChanged.emit()
self._list_h_bk = []
self._invalidateListBuffer.emit(self)
if self._expanded:
self._sizeChangedHandler(self, child.size())
self.emitDataChanged()
def addChildren(self, children):
def addChildren(self, children:List[TTkTreeWidgetItem]):
for child in children:
self._addChild(child)
self.dataChanged.emit()
self._list_h_bk = []
self._invalidateListBuffer.emit(self)
if self._expanded:
sizes = sum(_c.size() for _c in children)
self._sizeChangedHandler(self, sizes)
self.emitDataChanged()
def removeChild(self, child):
def removeChild(self, child:TTkTreeWidgetItem):
if child in self._children:
self.takeChild(self._children.index(child))
@ -248,29 +412,38 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
return None
child = self._children.pop(index)
child.dataChanged.disconnect(self.emitDataChanged)
child._sizeChanged.disconnect(self._sizeChangedHandler)
child._invalidateListBuffer.disconnect(self._invalidateListBufferHandler)
child.setTreeItemParent(None)
self.dataChanged.emit()
self._sizeChangedHandler(self, -child.size())
self._list_h_bk = []
self._invalidateListBuffer.emit(self)
self.emitDataChanged()
return child
def takeChildren(self):
children = self._children
for child in children:
child.dataChanged.disconnect(self.emitDataChanged)
child._sizeChanged.disconnect(self._sizeChangedHandler)
child._invalidateListBuffer.disconnect(self._invalidateListBufferHandler)
child.setTreeItemParent(None)
self._sizeChangedHandler(self, self._height-self._buffer.total_size)
self._children = []
self.dataChanged.emit()
self._list_h_bk = []
self._invalidateListBuffer.emit(self)
self.emitDataChanged()
return children
def child(self, index):
def child(self, index:int) -> TTkTreeWidgetItem:
if 0 <= index < len(self._children):
return self._children[index]
return None
def children(self):
def children(self) -> List[TTkTreeWidgetItem]:
return [x for x in self._children if not x.isHidden()]
def indexOfChild(self, child):
def indexOfChild(self, child:TTkTreeWidgetItem) -> Optional[int]:
if child in self._children:
return self._children.index(child)
return None
@ -328,7 +501,11 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
if children:
for c in self._children:
c.dataChanged.disconnect(self.emitDataChanged)
c._sizeChanged.disconnect(self._sizeChangedHandler)
c._invalidateListBuffer.disconnect(self._invalidateListBufferHandler)
c.sortChildren(self._sortColumn, self._sortOrder)
c._invalidateListBuffer.connect(self._invalidateListBufferHandler)
c._sizeChanged.connect(self._sizeChangedHandler)
c.dataChanged.connect(self.emitDataChanged)
def sortChildren(self, col, order):
@ -336,16 +513,24 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
self._sortOrder = order
if not self._children: return
self._sort(children=True)
self._list_h_bk = []
self._invalidateListBuffer.emit(self)
self.dataChanged.emit()
@pyTTkSlot()
def emitDataChanged(self):
self.dataChanged.emit()
@pyTTkSlot()
def _invalidateListBufferHandler(self):
self._list_h_bk = []
if self._expanded:
self._invalidateListBuffer.emit(self)
# def setDisabled(disabled):
# pass
def setExpanded(self, expand):
def setExpanded(self, expand:bool):
# hide all the widgets if this item is not expanded
if not expand:
def _recurseHide(item):
@ -356,9 +541,16 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
if c._expanded:
_recurseHide(c)
_recurseHide(self)
if self._expanded != expand and self._children:
self._list_h_bk = []
if expand:
self._sizeChangedHandler(self, sum(_c.size() for _c in self._children))
else:
self._sizeChangedHandler(self, self._height-self._buffer.total_size)
self._invalidateListBuffer.emit(self)
self._expanded = expand
self._setDefaultIcon()
self.emitDataChanged()
self.dataChanged.emit()
def setSelected(self, select):
self._selected = select
@ -373,7 +565,14 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
return self._selected
def size(self):
if self._expanded:
return self._height + sum(c.size() for c in self.children())
else:
if not self._children:
return self._height
if not self._buffer.total_size:
if self._expanded:
ret = self._height
for _c in self._children:
ret += _c.size()
self._buffer.total_size = ret
else:
self._buffer.total_size = self._height
return self._buffer.total_size

231
tests/pytest/test_005_tree.py

@ -0,0 +1,231 @@
# MIT License
#
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys, os
import pytest
from typing import Union, Optional, List, Tuple
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import TermTk as ttk
def _gen_childs(num:int,prefix:str,nesting=2) -> List[ttk.TTkTreeWidgetItem]:
ret = []
for i in range(num):
_c = ttk.TTkTreeWidgetItem([f"{prefix} A {i}", f"{prefix} B {i}", f"{prefix} C {i}"])
_c._height = i+1
ret.append(_c)
if i%2:
_c.setExpanded(True)
if nesting:
_c.addChildren(_gen_childs(num,f"{prefix}_X",nesting-1))
return ret
def _create_tree() -> Tuple[ttk.TTkTreeWidgetItem,ttk.TTkTreeWidgetItem,ttk.TTkTreeWidgetItem]:
l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"])
l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"])
l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"])
l3 = ttk.TTkTreeWidgetItem(["String AAA", "String BBB", "String CCC"])
l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"])
l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l2.addChild(l5)
l1.addChildren(_gen_childs(2,'l1',1))
l2.addChildren(_gen_childs(4,'l2',2))
l3.addChildren(_gen_childs(3,'l3',1))
l4.addChildren(_gen_childs(2,'l4',1))
l5.addChildren(_gen_childs(2,'l5',1))
l0.addChild(l1)
l0.addChild(l2)
l0.addChild(l3)
l0.addChild(l4)
l0.setExpanded(True)
l2.setExpanded(True)
l5.setExpanded(True)
l4.setExpanded(True)
return l0, l2, l5
def _format_item(item:ttk.TTkTreeWidgetItem) -> str:
return f"{item.data(0)} {item.data(1)} {item.data(2)}"
def _print_tree(child:ttk.TTkTreeWidgetItem, level:int=0):
if child.isExpanded() and child.children():
print(' '*level, ' v ', _format_item(child))
for i,c in enumerate(child.children()):
_print_tree(c,level+1)
elif child.children():
print(' '*level, ' > ', _format_item(child))
else:
print(' '*level, ' - ', _format_item(child))
def test_tree_item_iterate_skip():
tree,_,_ = _create_tree()
print('\nTree:')
_print_tree(tree)
# for i,(a,b) in enumerate(tree.iterate()):
# print(f"{i:03} - ", b, ' '*b, _format_item(a))
# print('\nSkip 3')
# for i,(a,b) in enumerate(tree.iterate(skip=3),3):
# print(f"{i:03} - ", b, ' '*b, _format_item(a))
# print('\nSkip 7')
# for i,(a,b) in enumerate(tree.iterate(skip=7),7):
# print(f"{i:03} - ", b, ' '*b, _format_item(a))
full = [(a,b) for a,b in tree._iterate()]
assert full == [(a,b) for a,b in tree._iterate()]
assert full == [(a,b) for a,b in tree._iterate(skip= 0)]
assert full[ 3:] == [(a,b) for a,b in tree._iterate(skip= 3)]
assert full[ 5:] == [(a,b) for a,b in tree._iterate(skip= 5)]
assert full[ 6:] == [(a,b) for a,b in tree._iterate(skip= 6)]
assert full[10:] == [(a,b) for a,b in tree._iterate(skip=10)]
assert full[15:] == [(a,b) for a,b in tree._iterate(skip=15)]
assert full[20:] == [(a,b) for a,b in tree._iterate(skip=20)]
assert full[30:] == [(a,b) for a,b in tree._iterate(skip=30)]
assert full[80:] == [(a,b) for a,b in tree._iterate(skip=80)]
assert [] == [(a,b) for a,b in tree._iterate(skip=80)]
def test_tree_item_iterate_skip_2():
def my_gen():
for i in range(10):
yield i
# Save progress
gen = my_gen()
# Resume from saved_index
for i,item in enumerate(gen):
print(i, item)
if i==5:
break
for i,item in enumerate(gen):
print(i, item)
def test_tree_item_listify():
tree,c1,c2 = _create_tree()
print('\nTree:')
_print_tree(tree)
for i,(a,b,c) in enumerate(tree.listify()):
print(f"{i:03} - ", b, c, ' '*b, _format_item(a))
full = [(a,b) for a,b in tree._iterate()]
print('expand')
c1.setExpanded(False)
for i,(a,b,c) in enumerate(tree.listify()):
print(f"{i:03} - ", b, c, ' '*b, _format_item(a))
print('expand')
c1.setExpanded(True)
for i,(a,b,c) in enumerate(tree.listify()):
print(f"{i:03} - ", b, c, ' '*b, _format_item(a))
print('expand')
c2.setExpanded(False)
for i,(a,b,c) in enumerate(tree.listify()):
print(f"{i:03} - ", b, c, ' '*b, _format_item(a))
print('expand')
c2.setExpanded(True)
for i,(a,b,c) in enumerate(tree.listify()):
print(f"{i:03} - ", b, c, ' '*b, _format_item(a))
print('expand c1 False')
c1.setExpanded(False)
for i,(a,b,c) in enumerate(tree.listify()):
print(f"{i:03} - ", b, c, ' '*b, _format_item(a))
print('expand c2 False')
c2.setExpanded(False)
for i,(a,b,c) in enumerate(tree.listify()):
print(f"{i:03} - ", b, c, ' '*b, _format_item(a))
print('expand c2 True')
c2.setExpanded(True)
for i,(a,b,c) in enumerate(tree.listify()):
print(f"{i:03} - ", b, c, ' '*b, _format_item(a))
print('expand c1 True')
c1.setExpanded(True)
for i,(a,b,c) in enumerate(tree.listify()):
print(f"{i:03} - ", b, c, ' '*b, _format_item(a))
def _get_full_tree_page(item:ttk.TTkTreeWidgetItem) -> List[ttk.TTkTreeWidgetItem]:
ret = [item]*item._height
if item.isExpanded():
for ch in item.children():
ret.extend(_get_full_tree_page(ch))
return ret
def test_tree_get_page():
tree,c1,c2 = _create_tree()
full_page = _get_full_tree_page(tree)
print('\nTree:')
_print_tree(tree)
def _test_page(index,size):
page = tree._get_page(0,index,size)
# print(f"Testing: {index=} {size=} , page size={len(page)}")
assert [f"{c.isExpanded()} {c.data(0)}" for c in full_page[index:index+size]] == [f"{c.isExpanded()} {c.data(0)}" for a,b,c in page]
# _test_page(0,1)
# _test_page(0,2)
# _test_page(0,3)
# _test_page(0,4)
# _test_page(0,5)
# _test_page(0,5)
# _test_page(0,6)
# _test_page(0,7)
# _test_page(0,10)
# _test_page(0,100)
for i in range(0,100,1):
for j in range(0,100,1):
_test_page(i,j)
print("\n - 0,10")
page = tree._get_page(0,0,10)
for a,b,c in page:
print(a, b, ' '*a, _format_item(c))
print("\n - 2,5")
page = tree._get_page(0,2,5)
for a,b,c in page:
print(a, b, ' '*a, _format_item(c))

142
tests/t.ui/test.ui.011.tree.05.paging.py

@ -0,0 +1,142 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Demo inspired from:
# https://stackoverflow.com/questions/41204234/python-pyqt5-qtreewidget-sub-item
import os
import sys
from threading import Thread
sys.path.append(os.path.join(sys.path[0],'../..'))
import TermTk as ttk
ttk.TTkLog.use_default_file_logging()
root = ttk.TTk()
base_btn = ttk.TTkButton(parent=root, text="Test Base", pos=(0,0), size=(20,3), border=True)
enough_btn = ttk.TTkButton(parent=root, text="Test Enough", pos=(20,0), size=(20,3), border=True)
many_btn = ttk.TTkButton(parent=root, text="Test Many", pos=(40,0), size=(20,3), border=True)
winTree = ttk.TTkWindow(parent=root,pos = (0,3), size=(80,30), title="Test Tree 1", layout=ttk.TTkGridLayout(), border=True)
winLog = ttk.TTkWindow(parent=root,pos = (5,10), size=(100,30), title="Logs", layout=ttk.TTkGridLayout(), border=True)
ttk.TTkLogViewer(parent=winLog)
tw = ttk.TTkTree(parent=winTree)
tw.setHeaderLabels(["Column 1", "Column 2", "Column 3"])
@ttk.pyTTkSlot()
def _add_base():
tw.clear()
l1 = ttk.TTkTreeWidgetItem(["String A", "String B\nxyz\nabc\n123", "String C"])
l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"])
l3 = ttk.TTkTreeWidgetItem(["String AAA", "String BBB", "String CCC\nxyz\nabc\n123"])
l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"])
l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB\nxyz\nabc\n123", "String CCCCC"])
l2.addChild(l5)
for i in range(3):
l1_child = ttk.TTkTreeWidgetItem(["Child A" + str(i), "Child B" + str(i), "Child C" + str(i)])
l1.addChild(l1_child)
for j in range(2):
l2_child = ttk.TTkTreeWidgetItem(["Child AA\nxyz\nabc\n123" + str(j), "Child BB" + str(j), "Child CC" + str(j)])
l2.addChild(l2_child)
for j in range(2):
l3_child = ttk.TTkTreeWidgetItem(["Child AAA" + str(j), "Child BBB" + str(j), "Child CCC" + str(j)])
l3.addChild(l3_child)
for j in range(2):
l4_child = ttk.TTkTreeWidgetItem(["Child AAAA" + str(j), "Child BBBB\nxyz\nabc\n123" + str(j), "Child CCCC" + str(j)])
l4.addChild(l4_child)
for j in range(2):
l5_child = ttk.TTkTreeWidgetItem(["Child AAAAA" + str(j), "Child BBBBB\nxyz\nabc\n123" + str(j), "Child CCCCC" + str(j)])
l5.addChild(l5_child)
tw.addTopLevelItem(l1)
tw.addTopLevelItem(l2)
tw.addTopLevelItem(l3)
tw.addTopLevelItem(l4)
l1.setExpanded(True)
l3.setExpanded(True)
@ttk.pyTTkSlot()
def _add_many():
_add_base()
def _many_loop():
ttk.TTkLog.debug('Many Loop START!!!')
num = 1
for i in range(10):
ttk.TTkLog.debug(f"Loop: {i}")
_entries = []
num = num<<1
for ii in range(num):
_e = ttk.TTkTreeWidgetItem([f"({i}-{ii}) String A", "String B", "String C"])
_e.addChildren([
ttk.TTkTreeWidgetItem(["Child A" + str(ii) + (f"\nl:{ii}"*ii), "Child B" + str(ii), "Child C" + str(ii)])
for i in range(3)
])
_entries.append(_e)
if not ii%3:
_e.setExpanded(True)
tw.addTopLevelItems(_entries)
ttk.TTkLog.debug('DONE!!!')
Thread(target=_many_loop).start()
@ttk.pyTTkSlot()
def _add_enough():
_add_base()
def _many_loop():
ttk.TTkLog.debug('Many Loop START!!!')
num = 1
for i in range(4):
_entries = []
num = num<<1
for ii in range(num):
_e = ttk.TTkTreeWidgetItem([f"({i}-{ii}) String A", "String B", "String C"])
_e.addChildren([
ttk.TTkTreeWidgetItem(["Child A" + str(ii) + (f"\nl:{ii}"*ii), "Child B" + str(ii), "Child C" + str(ii)])
for i in range(3)
])
_entries.append(_e)
if not ii%3:
_e.setExpanded(True)
tw.addTopLevelItems(_entries)
ttk.TTkLog.debug('DONE!!!')
Thread(target=_many_loop).start()
base_btn.clicked.connect(_add_base)
enough_btn.clicked.connect(_add_enough)
many_btn.clicked.connect(_add_many)
root.mainloop()

90
tests/timeit/02.array.10.List.creation.py

@ -0,0 +1,90 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
import sys, os
from dataclasses import dataclass
from enum import Enum,Flag,auto
import timeit
from typing import List, Tuple, Iterator
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import TermTk as ttk
txt = "Eugenio"
l = [txt] * 1000000
l1 = [txt] * 1000000
l2 = [txt] * 1000000
l3 = [txt] * 1000000
l4 = [txt] * 1000000
def test_ti_01_append():
_ret = []
for i in l:
_ret.append(i)
return len(_ret)
def test_ti_02_copy():
_ret = []
_ret[:] = l
return len(_ret)
def test_ti_03_copy_2():
_ret = [None]*len(l)
for i,ii in enumerate(l):
_ret[i] = ii
return len(_ret)
def test_ti_03_copy_3():
_ret = []
for i in range(len(l) // 256):
_ret.extend(l[i:i+256])
return len(_ret)
def test_ti_03_copy_4():
_ret = []
_ret.extend(l)
return len(_ret)
def test_ti_03_copy_5():
_ret = l.copy()
return len(_ret)
def test_ti_04_reduce_01():
_ret = l4[:5000]
return len(_ret)
loop = 100
a:dict = {}
for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]):
result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop)
# print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}")
print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}")

219
tests/timeit/33.tree.01.iterate.py

@ -0,0 +1,219 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
import sys, os
from dataclasses import dataclass
from enum import Enum,Flag,auto
import timeit
from typing import List, Tuple, Iterator
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import TermTk as ttk
def _create_tree() -> ttk.TTkTreeWidgetItem:
l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"])
l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"])
l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"])
l3 = ttk.TTkTreeWidgetItem(["String AAA", "String BBB", "String CCC"])
l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"])
l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l51 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l511 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l5111 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l52 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l521 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l2.addChild(l5)
l5.addChild(l51)
l51.addChild(l511)
l511.addChild(l5111)
l5.addChild(l52)
l52.addChild(l521)
def _addChilds(p:ttk.TTkTreeWidgetItem,num:int,prefix:str,nesting=2):
for i in range(num):
_c = ttk.TTkTreeWidgetItem([f"{prefix} A {i}", f"{prefix} B {i}", f"{prefix} C {i}"])
_c._height = i
p.addChild(_c)
if i%2:
_c.setExpanded(True)
if nesting:
_addChilds(_c,num,f"{prefix}_X",nesting-1)
_addChilds(l1,10,'l1',3)
_addChilds(l2,10000,'l2',0)
_addChilds(l2,20,'l2',2)
_addChilds(l3,30,'l3',2)
_addChilds(l4,10,'l4',2)
_addChilds(l5,20,'l5',2)
_addChilds(l51,30,'l51',2)
_addChilds(l511,30,'l511',2)
_addChilds(l5111,40,'l5111',2)
_addChilds(l52,50,'l52',2)
_addChilds(l521,10,'l521',2)
l0.addChild(l1)
l0.addChild(l2)
l0.addChild(l3)
l0.addChild(l4)
l0.setExpanded(True)
l2.setExpanded(True)
l5.setExpanded(True)
l51.setExpanded(True)
l511.setExpanded(True)
l5111.setExpanded(True)
l52.setExpanded(True)
l521.setExpanded(True)
l4.setExpanded(True)
return l0
def _format_item(item:ttk.TTkTreeWidgetItem) -> str:
return f"{item.data(0)} {item.data(1)} {item.data(2)}"
def _full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]:
for _c in p._children:
yield _c, level
if _c._expanded:
yield from _full_iterate(_c,level+1)
def _full_full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]:
for _c in p._children:
yield _c, level
yield from _c._iterate(level+1)
def _get_size_iterate_1(p:ttk.TTkTreeWidgetItem) -> int:
return len(p._children) + sum(_get_size_iterate_1(_c) for _c in p._children if _c.isExpanded())
def _get_size_iterate_2(p:ttk.TTkTreeWidgetItem) -> int:
_ret = len(p._children)
for _c in p._children:
if _c.isExpanded():
_ret += _get_size_iterate_2(_c)
return _ret
def _get_size_iterate_3(p:ttk.TTkTreeWidgetItem) -> int:
# return p.height()
_ret = 0
for _c in p._children:
_ret += _c._height
if _c.isExpanded():
_ret += _get_size_iterate_3(_c)
return _ret
tree = _create_tree()
print('Total: ', len(list(_full_full_iterate(tree))))
def test_ti_0_00_0(): return len([(a,b) for a,b in _full_iterate(tree)])
def test_ti_0_01_0(): return len([(a,b) for a,b in tree._iterate()])
def test_ti_0_02_0(): return len([(a,b) for a,b in tree._iterate(skip=10)])
def test_ti_0_03_0(): return len([(a,b) for a,b in tree._iterate(skip=100)])
def test_ti_0_04_0(): return len([(a,b) for a,b in tree._iterate(skip=500)])
def test_ti_0_05_0(): return len([(a,b) for a,b in tree._iterate(skip=1000)])
def test_ti_0_06_0(): return len([(a,b) for a,b in tree._iterate(skip=10000)])
def test_ti_0_07_0(): return len([(a,b) for a,b in tree._iterate(skip=50000)])
def test_ti_0_08_0(): return len([(a,b) for a,b in tree._iterate(skip=100000)])
def test_ti_0_00_1(): return len([None for _ in _full_iterate(tree)])
def test_ti_0_01_1(): return len([None for _ in tree._iterate()])
def test_ti_0_02_1(): return len([None for _ in tree._iterate(skip=10)])
def test_ti_0_03_1(): return len([None for _ in tree._iterate(skip=100)])
def test_ti_0_04_1(): return len([None for _ in tree._iterate(skip=500)])
def test_ti_0_05_1(): return len([None for _ in tree._iterate(skip=1000)])
def test_ti_0_06_1(): return len([None for _ in tree._iterate(skip=10000)])
def test_ti_0_07_1(): return len([None for _ in tree._iterate(skip=50000)])
def test_ti_0_08_1(): return len([None for _ in tree._iterate(skip=100000)])
def test_ti_0_00_2(): return len(list(_full_iterate(tree)))
def test_ti_0_01_2(): return len(list(tree._iterate()))
def test_ti_0_02_2(): return len(list(tree._iterate(skip=10)))
def test_ti_0_03_2(): return len(list(tree._iterate(skip=100)))
def test_ti_0_04_2(): return len(list(tree._iterate(skip=500)))
def test_ti_0_05_2(): return len(list(tree._iterate(skip=1000)))
def test_ti_0_06_2(): return len(list(tree._iterate(skip=10000)))
def test_ti_0_07_2(): return len(list(tree._iterate(skip=50000)))
def test_ti_0_08_2(): return len(list(tree._iterate(skip=100000)))
def test_ti_1_00():
for a,b in _full_iterate(tree):
return _format_item(a)
def test_ti_1_01():
for a,b in tree._iterate():
return _format_item(a)
def test_ti_1_02():
for a,b in tree._iterate(skip=10):
return _format_item(a)
def test_ti_1_03():
for a,b in tree._iterate(skip=100):
return _format_item(a)
def test_ti_1_04():
for a,b in tree._iterate(skip=500):
return _format_item(a)
def test_ti_1_05():
for a,b in tree._iterate(skip=1000):
return _format_item(a)
def test_ti_1_06():
for a,b in tree._iterate(skip=10000):
return _format_item(a)
def test_ti_1_07():
for a,b in tree._iterate(skip=50000):
return _format_item(a)
def test_ti_1_08():
for a,b in tree._iterate(skip=100000):
return _format_item(a)
def test_ti_2_00():
for _ in _full_iterate(tree): pass
def test_ti_2_01():
for _ in tree._iterate(): pass
def test_ti_2_02():
for _ in tree._iterate(skip=10): pass
def test_ti_2_03():
for _ in tree._iterate(skip=100): pass
def test_ti_2_04():
for _ in tree._iterate(skip=500): pass
def test_ti_2_05():
for _ in tree._iterate(skip=1000): pass
def test_ti_2_06():
for _ in tree._iterate(skip=10000): pass
def test_ti_2_07():
for _ in tree._iterate(skip=50000): pass
def test_ti_2_08():
for _ in tree._iterate(skip=100000): pass
loop = 100
a:dict = {}
for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]):
result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop)
# print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}")
print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}")

159
tests/timeit/33.tree.02.iterate.py

@ -0,0 +1,159 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
import sys, os
from dataclasses import dataclass
from enum import Enum,Flag,auto
import timeit
from typing import List, Tuple, Iterator
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import TermTk as ttk
def _create_tree() -> ttk.TTkTreeWidgetItem:
l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"])
l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"])
l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"])
l3 = ttk.TTkTreeWidgetItem(["String AAA", "String BBB", "String CCC"])
l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"])
l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l51 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l511 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l5111 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l52 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l521 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l2.addChild(l5)
l5.addChild(l51)
l51.addChild(l511)
l511.addChild(l5111)
l5.addChild(l52)
l52.addChild(l521)
def _addChilds(p:ttk.TTkTreeWidgetItem,num:int,prefix:str,nesting=2):
for i in range(num):
_c = ttk.TTkTreeWidgetItem([f"{prefix} A {i}", f"{prefix} B {i}", f"{prefix} C {i}"])
_c._height = i
p.addChild(_c)
if i%2:
_c.setExpanded(True)
if nesting:
_addChilds(_c,num,f"{prefix}_X",nesting-1)
_addChilds(l1,10,'l1',3)
_addChilds(l2,10000,'l2',0)
_addChilds(l2,20,'l2',2)
_addChilds(l3,30,'l3',2)
_addChilds(l4,10,'l4',2)
_addChilds(l5,20,'l5',2)
_addChilds(l51,30,'l51',2)
_addChilds(l511,30,'l511',2)
_addChilds(l5111,40,'l5111',2)
_addChilds(l52,50,'l52',2)
_addChilds(l521,10,'l521',2)
l0.addChild(l1)
l0.addChild(l2)
l0.addChild(l3)
l0.addChild(l4)
l0.setExpanded(True)
l2.setExpanded(True)
l5.setExpanded(True)
l51.setExpanded(True)
l511.setExpanded(True)
l5111.setExpanded(True)
l52.setExpanded(True)
l521.setExpanded(True)
l4.setExpanded(True)
return l0
def _format_item(item:ttk.TTkTreeWidgetItem) -> str:
return f"{item.data(0)} {item.data(1)} {item.data(2)}"
def _full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]:
for _c in p._children:
yield _c, level
if _c._expanded:
yield from _c._iterate(level+1)
def _full_full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]:
for _c in p._children:
yield _c, level
yield from _c._iterate(level+1)
def _get_size_iterate_1(p:ttk.TTkTreeWidgetItem) -> int:
return len(p._children) + sum(_get_size_iterate_1(_c) for _c in p._children if _c.isExpanded())
def _get_size_iterate_2(p:ttk.TTkTreeWidgetItem) -> int:
_ret = len(p._children)
for _c in p._children:
if _c.isExpanded():
_ret += _get_size_iterate_2(_c)
return _ret
def _get_size_iterate_3(p:ttk.TTkTreeWidgetItem) -> int:
# return p.height()
_ret = 0
for _c in p._children:
_ret += _c._height
if _c.isExpanded():
_ret += _get_size_iterate_3(_c)
return _ret
tree = _create_tree()
print('Total: ', len(list(_full_full_iterate(tree))))
def test_ti_0_00_01(): return _get_size_iterate_1(tree)
def test_ti_0_00_02(): return _get_size_iterate_2(tree)
def test_ti_0_00_03(): return _get_size_iterate_3(tree)
def test_ti_0_00_04(): return _get_size_iterate_3(tree)
def test_ti_0_00_05(): return _get_size_iterate_2(tree)
def test_ti_0_00_06(): return _get_size_iterate_1(tree)
def test_ti_0_00_07(): return _get_size_iterate_1(tree)
def test_ti_0_00_08(): return _get_size_iterate_2(tree)
def test_ti_0_00_09(): return _get_size_iterate_3(tree)
def test_ti_0_00_10(): return _get_size_iterate_3(tree)
def test_ti_0_00_11(): return _get_size_iterate_2(tree)
def test_ti_0_00_12(): return _get_size_iterate_1(tree)
def test_ti_0_01_01(): return tree.size()
def test_ti_0_01_02(): return tree.size()
loop = 100
a:dict = {}
for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]):
result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop)
# print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}")
print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}")

221
tests/timeit/33.tree.03.iterate.py

@ -0,0 +1,221 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
import sys, os
from dataclasses import dataclass
from enum import Enum,Flag,auto
import timeit
from typing import List, Tuple, Iterator
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import TermTk as ttk
def _create_tree() -> ttk.TTkTreeWidgetItem:
l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"])
l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"])
l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"])
l3 = ttk.TTkTreeWidgetItem(["String AAA", "String BBB", "String CCC"])
l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"])
l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l51 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l511 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l5111 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l52 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l521 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l2.addChild(l5)
l5.addChild(l51)
l51.addChild(l511)
l511.addChild(l5111)
l5.addChild(l52)
l52.addChild(l521)
def _addChilds(p:ttk.TTkTreeWidgetItem,num:int,prefix:str,nesting=2):
for i in range(num):
_c = ttk.TTkTreeWidgetItem([f"{prefix} A {i}", f"{prefix} B {i}", f"{prefix} C {i}"])
_c._height = i
p.addChild(_c)
if i%2:
_c.setExpanded(True)
if nesting:
_addChilds(_c,num,f"{prefix}_X",nesting-1)
_addChilds(l1,10,'l1',3)
_addChilds(l2,100,'l2',0)
_addChilds(l2,20,'l2',2)
_addChilds(l3,30,'l3',2)
_addChilds(l4,10,'l4',2)
_addChilds(l5,20,'l5',2)
_addChilds(l51,30,'l51',2)
_addChilds(l511,30,'l511',2)
_addChilds(l5111,40,'l5111',2)
_addChilds(l52,50,'l52',2)
_addChilds(l521,10,'l521',2)
l0.addChild(l1)
l0.addChild(l2)
l0.addChild(l3)
l0.addChild(l4)
l0.setExpanded(True)
l2.setExpanded(True)
l5.setExpanded(True)
l51.setExpanded(True)
l511.setExpanded(True)
l5111.setExpanded(True)
l52.setExpanded(True)
l521.setExpanded(True)
l4.setExpanded(True)
return l0
def _format_item(item:ttk.TTkTreeWidgetItem) -> str:
return f"{item.data(0)} {item.data(1)} {item.data(2)}"
def _full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]:
for _c in p._children:
for _y in range(_c._height):
yield _c, level, _y
if _c._expanded:
yield from _full_iterate(_c,level+1)
def _full_full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]:
for _c in p._children:
yield _c, level
yield from _c._iterate(level+1)
def _get_size_iterate_1(p:ttk.TTkTreeWidgetItem) -> int:
return len(p._children) + sum(_get_size_iterate_1(_c) for _c in p._children if _c.isExpanded())
def _get_size_iterate_2(p:ttk.TTkTreeWidgetItem) -> int:
_ret = len(p._children)
for _c in p._children:
if _c.isExpanded():
_ret += _get_size_iterate_2(_c)
return _ret
def _get_size_iterate_3(p:ttk.TTkTreeWidgetItem) -> int:
# return p.height()
_ret = 0
for _c in p._children:
_ret += _c._height
if _c.isExpanded():
_ret += _get_size_iterate_3(_c)
return _ret
tree = _create_tree()
print('Total: ', len(list(_full_full_iterate(tree))))
def test_ti_0_00_0(): return len([x for x in _full_iterate(tree)])
def test_ti_0_00_1(): return len([x for x in _full_iterate(tree)])
def test_ti_0_01_0(): return len([x for x in tree.iterate_h()])
def test_ti_0_02_0(): return len([x for x in tree.iterate_h(skip=10)])
def test_ti_0_03_0(): return len([x for x in tree.iterate_h(skip=100)])
def test_ti_0_04_0(): return len([x for x in tree.iterate_h(skip=500)])
def test_ti_0_05_0(): return len([x for x in tree.iterate_h(skip=1000)])
def test_ti_0_06_0(): return len([x for x in tree.iterate_h(skip=10000)])
def test_ti_0_07_0(): return len([x for x in tree.iterate_h(skip=100000)])
def test_ti_0_08_0(): return len([x for x in tree.iterate_h(skip=1000000)])
def test_ti_0_00_1(): return len([None for _ in _full_iterate(tree)])
def test_ti_0_01_1(): return len([None for _ in tree.iterate_h()])
def test_ti_0_02_1(): return len([None for _ in tree.iterate_h(skip=10)])
def test_ti_0_03_1(): return len([None for _ in tree.iterate_h(skip=100)])
def test_ti_0_04_1(): return len([None for _ in tree.iterate_h(skip=500)])
def test_ti_0_05_1(): return len([None for _ in tree.iterate_h(skip=1000)])
def test_ti_0_06_1(): return len([None for _ in tree.iterate_h(skip=10000)])
def test_ti_0_07_1(): return len([None for _ in tree.iterate_h(skip=100000)])
def test_ti_0_08_1(): return len([None for _ in tree.iterate_h(skip=1000000)])
def test_ti_0_00_2(): return len(list(_full_iterate(tree)))
def test_ti_0_01_2(): return len(list(tree.iterate_h()))
def test_ti_0_02_2(): return len(list(tree.iterate_h(skip=10)))
def test_ti_0_03_2(): return len(list(tree.iterate_h(skip=100)))
def test_ti_0_04_2(): return len(list(tree.iterate_h(skip=500)))
def test_ti_0_05_2(): return len(list(tree.iterate_h(skip=1000)))
def test_ti_0_06_2(): return len(list(tree.iterate_h(skip=10000)))
def test_ti_0_07_2(): return len(list(tree.iterate_h(skip=100000)))
def test_ti_0_08_2(): return len(list(tree.iterate_h(skip=1000000)))
def test_ti_1_00():
for a,b,c in _full_iterate(tree):
return _format_item(a)
def test_ti_1_01():
for a,b,c in tree.iterate_h():
return _format_item(a)
def test_ti_1_02():
for a,b,c in tree.iterate_h(skip=10):
return _format_item(a)
def test_ti_1_03():
for a,b,c in tree.iterate_h(skip=100):
return _format_item(a)
def test_ti_1_04():
for a,b,c in tree.iterate_h(skip=500):
return _format_item(a)
def test_ti_1_05():
for a,b,c in tree.iterate_h(skip=1000):
return _format_item(a)
def test_ti_1_06():
for a,b,c in tree.iterate_h(skip=10000):
return _format_item(a)
def test_ti_1_07():
for a,b,c in tree.iterate_h(skip=100000):
return _format_item(a)
def test_ti_1_08():
for a,b,c in tree.iterate_h(skip=1000000):
return _format_item(a)
def test_ti_2_00():
for _ in _full_iterate(tree): pass
def test_ti_2_01():
for _ in tree.iterate_h(): pass
def test_ti_2_02():
for _ in tree.iterate_h(skip=10): pass
def test_ti_2_03():
for _ in tree.iterate_h(skip=100): pass
def test_ti_2_04():
for _ in tree.iterate_h(skip=500): pass
def test_ti_2_05():
for _ in tree.iterate_h(skip=1000): pass
def test_ti_2_06():
for _ in tree.iterate_h(skip=10000): pass
def test_ti_2_07():
for _ in tree.iterate_h(skip=100000): pass
def test_ti_2_08():
for _ in tree.iterate_h(skip=1000000): pass
loop = 10
a:dict = {}
for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]):
result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop)
# print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}")
print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}")

150
tests/timeit/33.tree.04.listify.py

@ -0,0 +1,150 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
import sys, os
from dataclasses import dataclass
from enum import Enum,Flag,auto
import timeit
from typing import List, Tuple, Iterator
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import TermTk as ttk
def _create_tree() -> ttk.TTkTreeWidgetItem:
l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"])
l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"])
l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"])
l3 = ttk.TTkTreeWidgetItem(["String AAA", "String BBB", "String CCC"])
l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"])
l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l51 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l511 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l5111 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l52 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l521 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"])
l2.addChild(l5)
l5.addChild(l51)
l51.addChild(l511)
l511.addChild(l5111)
l5.addChild(l52)
l52.addChild(l521)
def _addChilds(p:ttk.TTkTreeWidgetItem,num:int,prefix:str,nesting=2):
_children = []
for i in range(num):
_c = ttk.TTkTreeWidgetItem([f"{prefix} A {i}", f"{prefix} B {i}", f"{prefix} C {i}"])
_c._height = i
_children.append(_c)
if i%2:
_c.setExpanded(True)
if nesting:
_addChilds(_c,num,f"{prefix}_X",nesting-1)
p.addChildren(_children)
_addChilds(l1,10,'l1',3)
_addChilds(l2,100,'l2',0)
_addChilds(l2,20,'l2',2)
_addChilds(l3,30,'l3',2)
_addChilds(l4,10,'l4',2)
_addChilds(l5,20,'l5',2)
_addChilds(l51,30,'l51',2)
_addChilds(l511,30,'l511',2)
_addChilds(l5111,40,'l5111',2)
_addChilds(l52,50,'l52',2)
_addChilds(l521,10,'l521',2)
l0.addChild(l1)
l0.addChild(l2)
l0.addChild(l3)
l0.addChild(l4)
l0.setExpanded(True)
l2.setExpanded(True)
l5.setExpanded(True)
l51.setExpanded(True)
l511.setExpanded(True)
l5111.setExpanded(True)
l52.setExpanded(True)
l521.setExpanded(True)
l4.setExpanded(True)
return l0
def _format_item(item:ttk.TTkTreeWidgetItem) -> str:
return f"{item.data(0)} {item.data(1)} {item.data(2)}"
def _full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]:
for _c in p._children:
for _y in range(_c._height):
yield _c, level, _y
if _c._expanded:
yield from _full_iterate(_c,level+1)
def _full_full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]:
for _c in p._children:
yield _c, level
yield from _c._iterate(level+1)
def _get_size_iterate_1(p:ttk.TTkTreeWidgetItem) -> int:
return len(p._children) + sum(_get_size_iterate_1(_c) for _c in p._children if _c.isExpanded())
def _get_size_iterate_2(p:ttk.TTkTreeWidgetItem) -> int:
_ret = len(p._children)
for _c in p._children:
if _c.isExpanded():
_ret += _get_size_iterate_2(_c)
return _ret
def _get_size_iterate_3(p:ttk.TTkTreeWidgetItem) -> int:
# return p.height()
_ret = 0
for _c in p._children:
_ret += _c._height
if _c.isExpanded():
_ret += _get_size_iterate_3(_c)
return _ret
tree = _create_tree()
print('Total: ', len(list(_full_full_iterate(tree))))
def test_ti_0_00_0(): return len([x for x in _full_iterate(tree)])
def test_ti_0_00_1(): return len([x for x in _full_iterate(tree)])
def test_ti_0_01_0(): return len(tree.listify())
def test_ti_0_02_0(): return tree.size()
loop = 100
a:dict = {}
for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]):
result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop)
# print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}")
print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}")
Loading…
Cancel
Save