Advertisement
fernly

Untitled

Mar 17th, 2015
276
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.08 KB | None | 0 0
  1. import random
  2. import time
  3. from sortedcontainers import SortedDict
  4.  
  5. # These functions are used to populate the database with
  6. # values that are kinda-sorta like the ones in my app.
  7.  
  8. def xdi(B,N,M):
  9.     '''
  10.            eXponential Distribution of Integers, XDI
  11.    Return a list of length N of integers with max value M, from an exponential
  12.    distribution with beta B.
  13.    '''
  14.     M = int(M) # just in case
  15.     # pre-allocate the list rather than building it with .append()
  16.     r = [0]*N
  17.     for j in range(N) :
  18.         r[j] = int( random.expovariate(B) ) % M
  19.     return r
  20.  
  21. RWTALPH = 'aeiouåáïbcdfghjklmnpqrstvwxyz_BCDFGHJKLMNPQRSTVWZ'
  22. def rwt(N):
  23.     '''
  24.            Random Word-like Token, RWT
  25.    Return a word-like token comprising N Latin-1 letters with an
  26.    emphasis on vowels.
  27.    '''
  28.     ii = xdi( 0.1, N, len(RWTALPH)-1 )
  29.     return ''.join( [ RWTALPH[i] for i in ii ] )
  30.  
  31. def rft():
  32.     '''
  33.            Random Flag Token
  34.    Return an 8-char token composed mostly of dashes with a sprinkle of Xs,
  35.    e.g. "---X--X-" or "XX------".
  36.  
  37.    '''
  38.     ii = xdi( 2, 8, 2 )
  39.     return ''.join( ['-X'[i] for i in ii ] )
  40.  
  41. #
  42. # Define the database. All this code is out here at the module level --
  43. # instead of inside the Table Model class -- because in the real app, the
  44. # database is managed in a separate module entirely.
  45. #
  46. # The database comprises a single SortedDict, DATABASE, in which the keys are
  47. # rwt(8) "word" tokens, and the values are tuples comprising ( word, int in
  48. # 0-99, rft() flag string). (Yes this is redundant in having the word as both
  49. # key and value but it simplifies coding the data() method of the model.)
  50. #
  51. DATABASE = SortedDict()
  52. NUMBER_OF_ROWS = 10000 # how many rows to create
  53. #
  54. # The contents are accessed via a valuesView object, VALUES_BY_ROW. This can
  55. # be indexed, so VALUES_BY_ROW[J] is the values for the Jth entry in the
  56. # ascending order of the dict keys. VALUES_BY_ROW[J][2] is the column-2 data
  57. # from row J (the flag string column).
  58. #
  59. VALUES_BY_ROW = None # valuesview here
  60. #
  61. # In order to provide the indirection required for sorting, access to VALUES_BY_ROW is
  62. # by way of a sort vector of indices. As many as 6 such vectors, one for each column
  63. # for each sort order, are stored in a two lists, SORT_UP_VECTORS and SORT_DOWN_VECTORS.
  64. # When the database is initialized, only one of these is created, the sort-up for
  65. # column 0. Others are made as needed.
  66. #
  67. SORT_UP_VECTORS = [ None, None, None ]
  68. SORT_DOWN_VECTORS = [ None, None, None ]
  69. #
  70. # When the database is "refreshed" (filled with new data), all vectors are
  71. # discarded and a new SORT_UP_VECTORS[0] created as simply
  72. # range(len(DATABASE)). Thus VALUES_BY_ROW[ SORT_UP_VECTORS[0][J] ] is
  73. # exactly equivalent to VALUES_BY_ROW[J], except it takes a bit longer.
  74. #
  75.  
  76. # Clear the database, called when the model initializes and before Refresh
  77.  
  78. def clear_the_db() :
  79.     global DATABASE, VALUES_BY_ROW, SORT_UP_VECTORS, SORT_DOWN_VECTORS
  80.     DATABASE.clear()
  81.     VALUES_BY_ROW = None
  82.     SORT_UP_VECTORS = [ None, None, None ]
  83.     SORT_DOWN_VECTORS = [ None, None, None ]
  84.  
  85. # Rebuild the database by repopulating it with phony data. This emulates what
  86. # worddata.py does when it scans the book and tabulates its vocabulary.
  87.  
  88. def rebuild_the_db():
  89.     global DATABASE, VALUES_BY_ROW, NUMBER_OF_ROWS, SORT_UP_VECTORS
  90.     # Throw everything away. In the real app, worddata doesn't throw everything away,
  91.     # it only clears the counts in column 1.
  92.     clear_the_db()
  93.     # Create values for column 1, small random integers. Use the length
  94.     # of column 1 to control the loop.
  95.     column_1 = xdi( 0.05, NUMBER_OF_ROWS, 100 )
  96.     # Populate the database with rows of stuff
  97.     for k in column_1 :
  98.         w = rwt(8) # a random "word"
  99.         f = rft()  # a random "flag" string
  100.         DATABASE[ w ] = ( w, k, f )
  101.     # Create the indexable view of the data
  102.     VALUES_BY_ROW = DATABASE.values()
  103.     # Initialize the first sort vector to "idempotent" values
  104.     SORT_UP_VECTORS[0] = range( len( DATABASE ) )
  105.  
  106. #
  107. # On request from the table model, return a sort vector for a given column
  108. # (0, 1 or 2) and given sort order. If we have a vector for that column and
  109. # sort order, just return it.
  110. #
  111. # Otherwise, ask if we have a vector for ascending order on this column. If
  112. # we do not, build a vector for accessing the values of DATABASE in ascending
  113. # sequence over that column. If the request is for ascending order, return
  114. # that.
  115. #
  116. # Otherwise, make a vector for descending order by applying reversed() to the
  117. # ascending order vector and place it in SORT_DOWN_VECTORS[col]
  118. #
  119. # We build the ascending vector as follows. Make a SortedDict in which each
  120. # key is the concatenation of the column value for a row (which may have
  121. # dups) and the primary key for the same row. The combination is unique and
  122. # sorts properly.
  123. #
  124. # The value for each key is the index of that row in DATABASE. Because this
  125. # is a SortedDict, the values are a sort vector for the DATABASE rows in
  126. # column-value order.
  127. #
  128. # Place in SORT_UP_VECTORS[col] a valuesView on this sorted dict. This is
  129. # effectively a vector of DATABASE row numbers in the desired sequence.
  130.  
  131. # What we want is a valuesView on a dict, but we have to stow the dict
  132. # itself somewhere:
  133.  
  134. SORT_UP_DICTS = [ None, None, None ] # only 1 & 2 used
  135.  
  136. def get_column_vector( col, order ):
  137.     global DATABASE, VALUES_BY_ROW, SORT_UP_VECTORS, SORT_DOWN_VECTORS
  138.     vector = SORT_UP_VECTORS[ col ] if order == Qt.AscendingOrder else SORT_DOWN_VECTORS[ col ]
  139.     if vector is None : # we need to make one
  140.         if SORT_UP_VECTORS[ col ] is None : # we have no up-vector
  141.             # build a format string for col 1 int:str or col 2 str:str
  142.             key_format = '{:05}{}' if col == 1 else '{}{}'
  143.             sort_dict = SortedDict()
  144.             for j in range( len(DATABASE) ) :
  145.                 k = key_format.format( VALUES_BY_ROW[j][col], VALUES_BY_ROW[j][0] )
  146.                 sort_dict[k] = j
  147.             SORT_UP_DICTS[ col ] = sort_dict # save the dict itself
  148.             SORT_UP_VECTORS[ col ] = sort_dict.values()
  149.         vector = SORT_UP_VECTORS[ col ] # be optimistic
  150.         # We have a sort-up vector, or was it sort-down we need?
  151.         if order == Qt.DescendingOrder : # ok, make the reverse
  152.             SORT_DOWN_VECTORS[ col ] = [j for j in reversed( SORT_UP_VECTORS[ col ] ) ]
  153.             vector = SORT_DOWN_VECTORS [ col ]
  154.     return vector
  155.  
  156. # Define the Table Model, instrumented to count certain calls
  157.  
  158. from PyQt5.QtCore import Qt, QAbstractTableModel, pyqtSignal
  159.  
  160. class Model( QAbstractTableModel ):
  161.     tableSorted = pyqtSignal()
  162.  
  163.     def __init__ ( self, parent=None ) :
  164.         super().__init__( parent )
  165.         clear_the_db()
  166.         self.access_counts = [0, 0, 0]
  167.         self.sort_order = Qt.AscendingOrder
  168.         self.sort_column = 0
  169.         self.sort_vector = []
  170.         self.sort_time = 0.0 # TIMING
  171.  
  172.     def rowCount( self, index ) :
  173.         global DATABASE
  174.         if index.isValid() : return 0
  175.         return len(DATABASE) # 0 until Refresh is called
  176.  
  177.     def columnCount( self, index ) :
  178.         if index.isValid() : return 0
  179.         return 3
  180.  
  181.     def data(self, index, role ) :
  182.         global VALUES_BY_ROW
  183.         if role != Qt.DisplayRole :
  184.             return None
  185.         row = index.row()
  186.         col = index.column()
  187.         self.access_counts[col] += 1
  188.         sort_row = self.sort_vector[ row ]
  189.         return VALUES_BY_ROW[ sort_row ][ col ]
  190.  
  191.     def clear_counts( self ) :
  192.         self.access_counts = [0, 0, 0]
  193.  
  194.     def counts( self ) :
  195.         return list( self.access_counts )
  196.  
  197.     # reimplement QAbstractItemModel.sort() which receives two arguments,
  198.     # the column number to sort, and the sort order, Qt.AscendingOrder
  199.     # or Qt.DescendingOrder. Then emit layoutChanged to repaint the view.
  200.  
  201.     def sort( self, col, order ) :
  202.         t0 = time.process_time() # TIMING
  203.         self.sort_column = col
  204.         self.sort_order = order
  205.         self.sort_vector = get_column_vector( col, order )
  206.         self.layoutChanged.emit()
  207.         self.sort_time = time.process_time() - t0    # TIMING
  208.         self.tableSorted.emit()    # TIMING
  209.  
  210.     # Override endResetModel() to make sure to refresh the sort
  211.     # when there is new data.
  212.  
  213.     def endResetModel( self ) :
  214.         super().endResetModel()
  215.         self.sort( self.sort_column, self.sort_order )
  216.  
  217. # Define the Table View, which at this point is quite minimal.
  218. # The View is instantiated from MainWindow, which also connects
  219. # the model to it.
  220.  
  221. from PyQt5.QtWidgets import QTableView
  222.  
  223. class View( QTableView ):
  224.     def __init__ ( self, parent=None ) :
  225.         super().__init__( parent )
  226.         self.setSortingEnabled( True )
  227.         self.sortByColumn( 0, Qt.AscendingOrder )
  228.  
  229. # Define the main window which is the visual face of this app.
  230.  
  231. from PyQt5.QtWidgets import (
  232.     QLabel,
  233.     QMainWindow,
  234.     QPushButton,
  235.     QVBoxLayout,
  236.     QHBoxLayout,
  237.     QWidget
  238.     )
  239.  
  240. class Main( QMainWindow ) :
  241.     def __init__ ( self ) :
  242.         super().__init__( )
  243.         self.times = [0, 0, 0]
  244.         clear_the_db() # make sure to start with 0 rows
  245.         self._uic() # all the layout stuff out of line
  246.         self.refresh_button.clicked.connect( self.do_refresh )
  247.         self.table_model.tableSorted.connect( self.update_sort_time )
  248.  
  249.     # Slot called when Refresh is clicked:
  250.     # * clear the counts
  251.     # * start model reset
  252.     # * rebuild the DATABASE, timing it
  253.     # * end the model reset, timing that
  254.     # * update the labels displaying call counts and times
  255.  
  256.     def do_refresh( self ) :
  257.         self.table_model.clear_counts()
  258.         self.table_model.beginResetModel()
  259.         self.times[0] = time.process_time()
  260.         rebuild_the_db()
  261.         self.times[1] = time.process_time()
  262.         self.table_model.endResetModel()
  263.         QApplication.processEvents()
  264.         self.times[2] = time.process_time()
  265.         self.update_labels()
  266.  
  267.     # Slot called when a sort happens. Get the process time from
  268.     # the table model and put it in the sort_time_label.
  269.  
  270.     def update_sort_time( self ) :
  271.         self.sort_time_label.setText ('{:02.5f}'.format( self.table_model.sort_time  ) )
  272.  
  273.     def update_labels(self) :
  274.         [c0, c1, c2] = self.table_model.counts()
  275.         [t0, t1, t2] = self.times
  276.         self.c0_label.setText( str(c0) )
  277.         self.c1_label.setText( str(c1) )
  278.         self.c2_label.setText( str(c2) )
  279.         self.td_label.setText( '{:02.5f}'.format(t1-t0) )
  280.         self.tr_label.setText( '{:02.5f}'.format(t2-t1) )
  281.  
  282.     def _make_label( self, text='0' ) :
  283.         # just make a right-aligned label out of line
  284.         L = QLabel(text)
  285.         L.setAlignment( Qt.AlignRight | Qt.AlignVCenter )
  286.         return L
  287.  
  288.     def _uic( self ) :
  289.         # create the table
  290.         self.table_view = View( parent=self )
  291.         self.table_model = Model( parent=self )
  292.         self.table_view.setModel( self.table_model )
  293.         # create the refresh button, put it in an hbox by with the sort time
  294.         self.refresh_button = QPushButton( "Refresh" )
  295.         hb0 = QHBoxLayout()
  296.         hb0.addWidget(self.refresh_button, 0)
  297.         hb0.addStretch(1)
  298.         hb0.addWidget( self._make_label( 'sort time:'), 0  )
  299.         self.sort_time_label = self._make_label()
  300.         hb0.addWidget( self.sort_time_label )
  301.         # create a set of labels to display counts of
  302.         # entry to the model.data() method and times
  303.         self.c0_label = self._make_label() # display role calls to column 0
  304.         self.c1_label = self._make_label() # 1
  305.         self.c2_label = self._make_label() # 2
  306.         self.tr_label = self._make_label() # time to reset the model
  307.         self.td_label = self._make_label() # time to rebuild the db
  308.         # build the row of call numbers
  309.         hb1 = QHBoxLayout()
  310.         hb1.addStretch(1) # push this row to the right
  311.         hb1.addWidget( self._make_label( 'Display role calls col 0:' ) )
  312.         hb1.addWidget( self.c0_label )
  313.         hb1.addStretch(0)
  314.         hb1.addWidget( self._make_label( 'col 1:' ) )
  315.         hb1.addWidget( self.c1_label )
  316.         hb1.addStretch(0)
  317.         hb1.addWidget( self._make_label( 'col 2:' ) )
  318.         hb1.addWidget( self.c2_label )
  319.         # build the row of times
  320.         hb2 = QHBoxLayout()
  321.         hb2.addStretch(1)
  322.         hb2.addWidget( self._make_label( 'Seconds to build DB:' ) )
  323.         hb2.addWidget( self.td_label )
  324.         hb2.addStretch(0)
  325.         hb2.addWidget( self._make_label( 'to reset model:' ) )
  326.         hb2.addWidget( self.tr_label )
  327.         # stack up the central layout
  328.         vb = QVBoxLayout()
  329.         vb.addLayout( hb0, 0 )
  330.         vb.addWidget( self.table_view, 1 )
  331.         vb.addLayout( hb1, 0 )
  332.         vb.addLayout( hb2, 0 )
  333.         # put all that in a widget and make the widget our central layout
  334.         wij = QWidget()
  335.         wij.setLayout( vb )
  336.         wij.setMinimumSize( 500, 500 )
  337.         self.setCentralWidget( wij )
  338.  
  339.  
  340. if __name__ == '__main__' :
  341.     from PyQt5.QtWidgets import QApplication
  342.     the_app = QApplication([])
  343.     main_window = Main()
  344.     main_window.show()
  345.     the_app.exec_()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement