user3720101
user3720101

Reputation: 1495

Is there a more elegant or simple way to accomplish my goal?

I know the brute force way to do what I want but I am pretty sure there is a much more elegant way to accomplish my task. So I’m looking for help on an approach that’s better than the brute force way.

I have a spreadsheet like application with 21 rows and 5 columns on a grid. The first columns in the first row simply take user entered weight values (w1, w2, w3, w4). The 5th column sums the weight values. I have this working fine and don’t need much help.

The complexity comes in for rows 2 to 20. For each row, the user enters values in columns 1 : 4 and then a weighted average for the row is calculated in column 5 (using the weights from row 1). For example, for any given row, if the user entered values go into QLineEdit widgets named va1, va2, va3, va4, then va_wa= va1*w1 +va2*w2 +va3*w3 +va4*w4.

This is easy to do in code for a single row. But I’m not sure how to accomplish is for another row without copying the code over and over and changing the names for each row (the brute force way).

Here’s my code:

class MyForm(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MyForm,self).__init__(parent)
        self.ui=Ui_MainWindow()
        self.ui.setupUi(self)        

        self.ui.mdiArea.addSubWindow(self.ui.subwindow) 
        self.ui.mdiArea.addSubWindow(self.ui.subwindow_2)
        QtCore.QTimer.singleShot(10, lambda: self.ui.mdiArea.setActiveSubWindow(self.ui.mdiArea.subWindowList()[0]))

        self.ui.wt1.editingFinished.connect(self.runBoth)
        self.ui.wt2.editingFinished.connect(self.runBoth)
        self.ui.wt3.editingFinished.connect(self.runBoth)
        self.ui.wt4.editingFinished.connect(self.runBoth)

        self.ui.ca1.editingFinished.connect(self.waCalc)
        self.ui.ca2.editingFinished.connect(self.waCalc)
        self.ui.ca3.editingFinished.connect(self.waCalc)
        self.ui.ca4.editingFinished.connect(self.waCalc)  

    def runBoth(self):
            self.wtResult()
            self.waCalc()

    def wtResult(self):
        if len(self.ui.wt1.text())!=0:
            a=float(self.ui.wt1.text())
        else:
            a=0
        if len(self.ui.wt2.text())!=0:
            b=float(self.ui.wt2.text())
        else:
            b=0
        if len(self.ui.wt3.text())!=0:
            c=float(self.ui.wt3.text())
        else:
            c=0
        if len(self.ui.wt4.text())!=0:
            d=float(self.ui.wt4.text())
        else:
            d=0                      
        sum=a+b+c+d
        self.ui.wt_total.setText(str(sum)) 

    def waCalc(self):

        if len(self.ui.ca1.text())!=0:
            ca1=float(self.ui.ca1.text())
        else:
            ca1=0

        if len(self.ui.ca2.text())!=0:
            ca2=float(self.ui.ca2.text())
        else:
            ca2=0

        if len(self.ui.ca3.text())!=0:
            ca3=float(self.ui.ca3.text())
        else:
            ca3=0

        if len(self.ui.ca4.text())!=0:
            ca4=float(self.ui.ca4.text())
        else:
            ca4=0

        if len(self.ui.wt1.text())!=0:
            wt1=float(self.ui.wt1.text())
        else:
            wt1=0
        if len(self.ui.wt2.text())!=0:
            wt2=float(self.ui.wt2.text())
        else:
            wt2=0
        if len(self.ui.wt3.text())!=0:
            wt3=float(self.ui.wt3.text())
        else:
            wt3=0
        if len(self.ui.wt4.text())!=0:
            wt4=float(self.ui.wt4.text())
        else:
            wt4=0

        wa=(wt1*ca1)+(wt2*ca2)+(wt3*ca3)+(wt4*ca4)
        self.ui.ca_wa.setText(str(wa))        


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    myapp=MyForm()
    myapp.show()
    app.exec_()

So I've shown the example where the row has ca1, ca2, ca3,ca4,ca_wa. What would I do for the next 19 rows (other than copy the wa_Calc code 19 times and change the variables to nx1:4,nx_wa ab1:4,ab_wa,ba1:4,ba_wa ...etc. I know there is a more elegant approach.

Upvotes: 0

Views: 67

Answers (2)

ErikR
ErikR

Reputation: 52039

This is quite involved, so I''ll just give you an overview and some pointers on how to complete it.

The general outline is this:

  1. Create an Equation object to record the functional relationship between widgets.
  2. Write a function which takes an Equation object and recomputes the target value.
  3. Write a function which re-evaluates all of the equations which depend on a widget.
  4. Hook up all of the editingFinished callbacks to the function in #3.

Step 1. The Equation class.

Creating a new Equation object might look like:

eq1 = Equation("wt_total", computeSum, ["wt1", "wt2", "wt3", "wt4"])
eq2 = Equation("ca_wa", computeDot, ["wt1", "wt2", "wt3", "wt4", "ca1", "ca2", "ca3", "ca4"])

computeSum and computeDor might look like:

def computeSum(arr):
  return sum(arr)

def computDot(arr):
  xs = arr[0:3]
  ys = arr[4:7]
  return sum ([ x*y for (x,y) in zip(xs,ys) ])

You will need the following slots/methods for the Equation class:

  • eq.target -- name of the target widget
  • eq.argWidgets() -- return the list of widgets used in the formula
  • eq.compute(vals) -- run th computation function with a list of values
  • eq.affected(wname) -- return True if the equation depends on the widget wname

You will need a place to store all of the equations. In the code below I use self.equations where self is a MyForm object.

Step 2. - Updating a single equation.

A method to update a single equation would look like:

   # update a single equation
   def update(self, eq):
     args = []
     for wname in eq.argWidgets():
       val = ...lookup and convert value stored in wname...
       args.append(val)
     result = eq.compute(args)
     # store result in target widget eq.target

Step 3. Updated affected equations.

First we develop a method to determine all of the affected equations:

   # return the equations affected by a change in widget wname
   def affected(self, wname):
     return [ e | if e.affected(wname) for e in self.equations ]

The handleEditingFinished method will be called with a widget name:

   def handleEditingFinished(self, wname):
     eqs = self.affected(wname)
     for e in eqs:
       self.update(e)

Step 4. Hook up all of the callbacks.

This code is untested, but hopefully the intent is clear. We just route all of the editingFinished callbacks to our handleEditingFinished method with the widget named passed as the first argument.

  from functools import partial

  def callback(self, wname):
    self.handleEditingFinished(wname)

  for e in self.equations:
    for wname in e.argWidgets():
      w = ... get the widget named wname...
      w.editingFinished(partial(callback, self, wname))

Upvotes: 4

Daniel
Daniel

Reputation: 42758

Use lists:

class MyForm(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MyForm,self).__init__(parent)
        self.ui=Ui_MainWindow()
        self.ui.setupUi(self)        

        self.ui.mdiArea.addSubWindow(self.ui.subwindow) 
        self.ui.mdiArea.addSubWindow(self.ui.subwindow_2)
        QtCore.QTimer.singleShot(10, lambda: self.ui.mdiArea.setActiveSubWindow(self.ui.mdiArea.subWindowList()[0]))

        self.ui_weights = [getattr(self.ui,'wt%d'%i) for i in range(1,5)]
        self.ui_cas = [
            [getattr(self.ui,'ca%d_%d'%(row,i)) for i in range(1,5)]
            for row in range(1,21)
        ]
        self.ui_cawas = [getattr(self.ui,'ca_wa_%d'%row) for row in range(1,21)]
        for wt in self.ui_weights:
            wt.editingFinished.connect(self.runBoth)
        for row in self.ui_cas:
            for cell in row:
                cell.editingFinished.connect(self.waCalc)  

    def runBoth(self):
        self.wtResult()
        self.waCalc()

    def wtResult(self):
        result = sum(float(wt.text() or 0) for wt in self.ui_weights)
        self.ui.wt_total.setText(str(result)) 

    def waCalc(self):
        for row, wa in zip(self.ui_cas,self.ui_cawas):
            result = sum(
                float(ca.text() or 0) * float(wt.text() or 0)
                for ca,wt in zip(row, self.ui_weights)
            )
            wa.setText(str(result))        

Upvotes: 3

Related Questions