Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια
σε

 

Αρχική σελίδα Ιστολόγια Συζητήσεις Εκθέσεις Φωτογραφιών Αρχειοθήκες

Τι κάνεις άμα βαριέσαι να γράψεις βαρετό / βλαμμένο κώδικα στο UI ...

Îåêßíçóå áðü ôï ìÝëïò anjelinio. Τελευταία δημοσίευση από το μέλος PALLADIN στις 29-08-2007, 16:11. Υπάρχουν 11 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  06-05-2007, 21:12 31492

    Τι κάνεις άμα βαριέσαι να γράψεις βαρετό / βλαμμένο κώδικα στο UI ...

    Συνημμένα: FuncGrid.rar

    Ανάλυση Δαπανών

     

    Ιανουάριος

    Φεβρουάριος

    Μάρτιος

    Δαπάνη 1

    10.00

     

     

     

    Δαπάνη 2

    23.56

     

     

     

    Δαπάνη 3

    12.00

     

     

     

    Δαπάνη 4

    45.78

     

     

     

     

     

     

    Δαπάνη ν

     

     

     

     

    Σύνολα Κατηγορίας

    Κατηγορία 1

    33.56

     

     

     

    Κατηγορία 2

    12.00

     

     

     

    Κατηγορία 3

    45.78

     

     

     

     

     

     

     

     

     

     

    Σύνολο

    89.34

     

     

     

     

     

    Πριν κάποιο καιρό, χρειάστηκε να υλοποιήσω στη δουλειά ένα interface όπως αυτό που βλέπετε παραπάνω.

    Η δομή είναι σχετικά απλή. Υπάρχουν κάποιες δαπάνες, οι οποίες ανήκουν σε κατηγορίες δαπανών, και έλαβαν χώρα σε κάποιο συγκεκριμένο μήνα. Το πρώτο μέρος του πίνακα είναι λοιπόν πολύ απλό.

    Στο δεύτερο μέρος, έπρεπε να βγούν σύνολα ανά κατηγορία δαπάνης.

    Στο τρίτο μέρος υπάρχει γενικό σύνολο, το σύνολο για όλες τις κατηγορίες για το δεδομένο μήνα.

    Στην real-life υλοποίηση είχα βέβαια πιο σύνθετα requirements. Ακολουθούσαν κι άλλα μέρη, τα οποία βασίζονταν σε σύνολα επί συνόλων, μετά επόμενα που βασίζονταν στα προηγούμενα με κάποιες φόρμουλες κτλ κτλ Ένα μικρό χάος …

    Τώρα, αυτά τα requirements δεν ήρθαν όλα μαζί. Ξεκινήσαμε από την απλή απαρίθμηση στο grid των δαπανών. Οπότε, η αρχική υλοποίηση ήταν το bind ενός DataTable σε ένα grid.

    Σε όλα αυτά τα ωραία λοιπόν, προστέθηκαν σιγά-σιγά τα Σύνολα κατηγοριών, και τα γενικά σύνολα … μέχρι που ήρθε πλέον η ώρα να προστεθούν derived κελιά από τιμές άλλων κελιών, formulas κτλ κτλ. Ένα χάος «βρωμιάς». Τι να σου κάνει και το καημένο το DataSet.Compute(...) σε βάθος 3-4 nested υπολογισμών;;; Η ώρα ήταν ήδη αργά, το deadline πολύ πολύ κοντά, κι εγώ και μόνο στη σκέψη ότι θα έπρεπε να βγάλω αυτούς τους υπολογισμούς με το χέρι .. φ ρ ί κ α ι ρ ν α άσκημα !!! Έπρεπε να βρώ κάτι άλλο, κάτι πιο εύκολο, και σίγουρα κάτι που θα μπορούσα να χρησιμοποιήσω για να λύσω το ίδιο πρόβλημα στο μέλλον.

    Σκέφτηκα λοιπόν. Αν μπορούσα να κάνω bind αυτά τα κελιά σε κάτι το οποίο μου έδινε πίσω μια τιμή, και είχα κάπου και το όρισμα ενός composite τέτοιου πράγματος, ίσως την πάλευα με αυτό το composition να φτιάξω κάτι το οποίο θα μου επέτρεπε – στο τέλος – να γράψω πολύ λιγότερο κώδικα για αυτούς καθ’ αυτούς του υπολογισμούς, να μην ανατινάξω το μυαλό μου από τη βαρεμάρα, και κατά συνέπεια να ξυπνήσω μεν σπίτι μου το πρωί, αλλά και να ξυπνήσω λίγο πιο χαρούμενος και καλοδιάθετος, κυρίως γιατί έγραψα κάτι ενδιαφέρον που μου έλυσε τα χέρια τώρα και στο μέλλον και δεν αναγκάστηκα να γράψω χίλιες non-reusable γραμμές βαρετού και error-prone κώδικα.
     

    Αλλά τι; Πώς;

    Έπρεπε να γράψω ένα πρωτότυπο, κάτι λίγο πιο απλουστευμένο, στο οποίο θα μπορούσα να πειραματιστώ λίγο για να βρω μια βολική λύση. Το post αυτό είναι το … postmortem αυτού του πρωτότυπου, και κυρίως στόχο έχει να δείξω που με οδήγησε αυτή η ιστορία στη συνέχεια.

    Ξεκινώντας λοιπόν, κατήργησα την παράμετρο «μήνας». Θα έπαιζα με μια στήλη μόνο αρχικά.

    Επίσης, κατήργησα τα πεδία Quantity / Price στο DataSet μου, και έκανα την παραδοχή ότι θα έπαιζα με ένα μόνο cell, το Price αρχικά. Το point άλλωστε δεν ήταν αυτό, ήταν να βρω τον τρόπο να γλιτώσω το βαρετό κωδικιλίκι μες στη νυχτιά :D

    Οκ, στο πρωτότυπο λοιπόν. Αρχικά για να φτιάξω τα δεδομένα μου έγραψα 5-10 γραμμές να κάνω initialize ένα μικρό DataTable με το schema που ήθελα:

     

    static DataSet _items = null;

     

            static object[][] _dataPattern = {

                //            Pr.   Qua. Ct. ID

                new object[]{10.00, 2.00, 1, 1},

                new object[]{10.00, 2.00, 1, 2},

                new object[]{10.00, 2.00, 2, 3},

                new object[]{10.00, 2.00, 2, 4},

                new object[]{10.00, 2.00, 3, 5}

            };

     

            /// <summary>

            /// static constructor for initialization

            /// </summary>

            static Program() {

                // ok, init the DataSet, the Datatable, add the Columns etc etc

                _items = new DataSet();

                // init the columns

                DataTable expenseItems = _items.Tables.Add("ExpenseItems");

                expenseItems.Columns.Add("Price", typeof(Double));

                expenseItems.Columns.Add("Quantity", typeof(Double));

                expenseItems.Columns.Add("Category", typeof(int));

                expenseItems.Columns.Add("ID", typeof(int));

                // and now fill in the data ...

                foreach (object[] row in _dataPattern) {

                    expenseItems.Rows.Add(row);

                }

            }

     

     

    Καλά ως εδώ, αλλά τώρα αρχίζουν τα δύσκολα. Τώρα χρειαζόμουν ένα intreface, το οποίο θα όριζε τη συμπεριφορά του βασικού calculator για κάθε ένα κελί στο UI.

    Κάτι σαν αυτό:

     

        /// <summary>

        /// Base inteface definition for a Calculator.

        /// it just returns a double value.

        /// </summary>

        public interface ICalculator {

            double Value { get; }

        }

     

     Πολύ απλό. Πάρα πολύ. Τώρα έπρεπε να το κάνω να μιλάει με ένα DataSet, και να μου υπολογίζει «κάτι», βάσει των τιμών σε κάποια κελιά του πίνακα. Άρα, χρειαζόμουν ένα filter expression για να βρω με ποια rows του DataTable να παίξω, κι ένα compute expression για να του λέω τι πράξεις θα κάνει.

    Οπότε, κλασσούλα που υλοποιεί το interface, και έχει αυτά τα properties:

     

        /// <summary>

        /// Base class for all calculators regarding Datarows.

        /// </summary>

        public abstract class DataRowsCalculator : ICalculator

        {

            public DataRowsCalculator(DataTable data, string computeExpression) {

                this.Data = data;

                this.ComputeExpression = computeExpression;

            }

     

            public DataRowsCalculator(DataTable data, string computeExpression, string filterExpression) : this(data, computeExpression) {

                this.RowFilter = filterExpression;

            }

     

            #region data properties and methods

     

            private DataTable m_Data = null;

     

            /// <summary>

            /// Gets / Sets the DataTable we're operating on

            /// </summary>

            protected DataTable Data {

                get {

                    // always tell the user what happened !!!

                    if (null == m_Data)

                        throw new Exception("Data has not been initialized");

                   

                    return m_Data;

                }

                set {

                    // check !!!

                    if (null == value)

                        throw new ArgumentNullException("Data");

     

                    m_Data = value;

                }

            }

     

            private string m_RowFilter = null;

     

            /// <summary>

            /// A filter that will be applied to my DataTable, to limit the

            /// rows to the ones i really need

            /// </summary>

            protected string RowFilter {

                get {

                    return m_RowFilter;

                }

                set {

                    m_RowFilter = value;

                }

            }

     

            private string m_Expression = null;

     

            /// <summary>

            /// A filter that will be applied to my DataTable, to limit the

            /// rows to the ones i really need

            /// </summary>

            protected string ComputeExpression {

                get {

                    if(string.IsNullOrEmpty(m_BLOCKED EXPRESSION

                        throw new Exception("ComputeExpression has not been initialized");

     

                    return m_Expression;

                }

                set {

                    if(string.IsNullOrEmpty(value))

                        throw new ArgumentNullException("ComputeExpression");

     

                    m_Expression = value;

                }

            }

     

            /// <summary>

            /// Instructs the DataTable to compute the expression on the selected rows and return

            /// the result as a double

            /// </summary>

            /// <param name="expression"></param>

            /// <returns></returns>

            protected static double Compute(DataTable data, string expression, string filter) {

                return (double)data.Compute(expression, filter);

            }

     

            #endregion

     

            public double Value {

                get {

                    return DataRowsCalculator.Compute(this.Data, this.ComputeExpression, this.RowFilter);

                }

            }

        }

     

    Πέρα από τον κώδικα στον constructor και τα properties, το μόνο που μας ενδιαφέρει σε αυτό το κομμάτι κώδικα είναι η static Compute μέθοδος που υπολογίζει τελικά το αποτέλεσμα, και η υλοποίηση του Value property που το μόνο που κάνει είναι να καλεί τη static μέθοδο. Τίποτα δυσνόητο.

    Το «ζουμί» σε αυτή την κλάσση, είναι ότι είναι ορθάνοιχτη σε οποιονδήποτε υπολογισμό σε μια στήλη ενός DataTable. Μπορεί να εκτελεστεί είτε σε όλες, είτε σε «φιλτραρισμένες» γραμμές στον πίνακα. Μια πολύ καλή αρχή με άλλα λόγια.

    Θα μπορούσα να είχα αποφύγει το παραπέρα subclassing, αλλά θα μου «βρώμιζε» τον κώδικα, οπότε «σκέφτηκα και βρήκα το πιο σωστό, τον γάτο να τσακώσω, σα μούτρο αναρχικό», και αποφάσισα να βγάλω subclasses κατά βούληση, που κάνουν την εκάστοτε πράξη στα δεδομένα. Μιας και ήθελα για αρχή μόνο προσθέσεις ( αποφάσισα να βγάλω αρχικά μόνο τα σύνολα ανά κατηγορία, και το γενικό σύνολο ), η πρώτη subclass ήταν ο RowSumCalculator:
     

    /// <summary>

        /// Computes the sum of the rows found here ...

        /// </summary>

        public class RowSumCalculator : DataRowsCalculator {

     

            public RowSumCalculator(DataTable data, string columnName, string filter)

                : base(data, string.Format("sum({0})", columnName), filter)

            {

                // no work necesary here !!! :P

            }

        }

     

    Κάπου εδώ, την ψυλλιάστηκα ότι μάλλον η προηγούμενη κλασσούλα ήταν τελικά καλή ιδέα, και μάλλον ήταν πολύ βολικό τελικά που δεν έκανα virtual το Value property. Θα μπορούσα να κάνω προσθέσεις, αφαιρέσεις, πολλαπλασιασμούς κτλ κτλ με 4 γραμμές κώδικα τη φορά, και να κάνω και τον τελικό κώδικα που τα χρησιμοποιεί πολύ πιο ξεκάθαρο και κατανοητό στον «αναγνώστη».

    Ήρθε λοιπόν η στιγμή να δοκιμάσω το πρώτο μικρό τεστάκι μου, και να βγάλω ( στην κονσόλα, σαν πραγματικός άντρας :D ) τα σύνολα ανα κατηγορία δαπάνης:

     

    // ok, let's see .. what do I need ???

                // first Calculators for the categories ...

                // "buffer' them, it'll come in handy ...

                IDictionary<int, ICalculator> categoryCalcs = new Dictionary<int, ICalculator>();

                // create them ...

                for (int i = 1; i < 4; i++) {

                    categoryCalcsIdea = new RowSumCalculator(_items.Tables["ExpenseItems"], "Price", string.Format("Category = {0}", i));

                    Console.WriteLine("Category {0}: {1}", i, categoryCalcsIdea.Value);

                }

     

    Και γουάου !!! πολύ όμορφα και αναμενόμενα το αποτέλεσμα ήταν 3 γραμμούλες με το σωστό συνολάκι ανα κατηγορία. No big deal ως εδώ όμως, το μόνο που κατάφερα ήταν να γράψω περίπου 50 γραμμές κώδικα, για να αποφύγω να γράψω … 4 … αν το έκανα hard-coded. Το μυστικό σε αυτή τη φράση όμως είναι το hard-coded. Αυτός ο κώδικας θα μου μείνει, και διαβάζεται. Ο άλλος θα ήταν one-off και υποψήφιος για copy-paste inheritance και μπελάδες. Τέλος πάντων όμως.

    Ωραία ως εδώ. Αλλά τώρα, έπρεπε να βγάλω το γενικό σύνολο. Άρα, έπρεπε να βρω ένα τρόπο να κάνω «compose» αυτούς τους υπάρχοντες σε έναν τρίτο, που θα μπορούσε να προσθέσει τα αποτελέσματά τους.  

    Πάμε λοιπόν για μια ακόμα υλοποίηση του ICalculator interface, αυτή τη φορά για πιο composite περιπέτειες.

    Σε ακριβώς αυτό το σημείο, μου «έσκασε» ακόμα μια ανάμνηση – πρίν κάποιον καιρό πάλι, είχα γράψει ένα μικρό κομμάτι κώδικα σε ένα post του Δημήτρη (papadi) που στην ουσία υλοποιούσε το IteratorVisitor pattern. Το είχα κάνει σε μια κλασσούλα που έπαιρνε ως παραμέτρους ένα enumeration από controls, κι έναν multicast delegate ο οποίος περιείχε function pointers σε μεθόδους οι οποίες έκαναν visible/not visible/enabled/readonly τα controls βάσει … οποιονδήποτε συνθηκών είχαν μέσα τους.

    Δεν είχα το χρόνο να κοιτάξω το post, αλλά θυμήθηκα το principle της λειτουργίας του και σκέφτηκα ότι αν έπαιζα έτσι, θα είχα έναν aggregate calculator όσο ευέλικτος ήταν και ο DataRowsCalculator. Θα μπορούσα να βγάζω subclases κατά βούληση με ένα method definition κι ένα IΕnumerable instance. Καλή ιδέα !

     

    /// <summary>

        /// Defines a basic "aggregate" calculator, one that combines the values

        /// of it's child calculators ...

        /// </summary>

        public class AggregateCalculator : ICalculator {

     

            /// <summary>

            /// Define the footprint of a method that operates on

            /// a collection of ICalculators, and returns a double

            /// </summary>

            /// <param name="calcs"></param>

            /// <returns></returns>

            public delegate double AggregateOperation(IEnumerable<ICalculator> calcs);

     

            #region private members

     

            /// <summary>

            /// The list of calculators I'll be working with ...

            /// </summary>

            private IEnumerable<ICalculator> m_Children = null;

            /// <summary>

            /// The actual function pointer to the method that will

            /// implement my computation

            /// </summary>

            private AggregateOperation m_Operation = null;

     

            #endregion

     

     

            public AggregateCalculator(IEnumerable<ICalculator> calcs, AggregateOperation operation) {

                // ok, i can't be bothered to write properties, so I'll be doing my checks in here instead ...

                if (null == calcs)

                    throw new ArgumentNullException("calcs", "Cannot be initialized to null");

                if (null == operation)

                    throw new ArgumentNullException("operation", "Cannot be initialized to null");

                if (operation.GetInvocationList().Length > 1)

                    throw new ArgumentException("Cannot use multi-cast delegates for an AggregateOperation", "operation");

     

                m_Operation = operation;

                m_Children = calcs;

            }

     

            /// <summary>

            /// ok, implement the base interface, by just calling my

            /// delegate method to get back the result, whatever that may be

            /// </summary>

            public double Value {

                get {

                    return m_Operation(m_Children);

                }

            }   

        }
     

    Δε μου πήρε παραπάνω από 10 λεπτά η όλη διαδικασία. Το actual coding όπως βλέπετε, μάλλον λιγότερο από 5. Κι ήμουν έτοιμος να το δοκιμάσω …

    Τώρα σε αυτή τη φάση συγκρατήθηκα. Δεν έκανα subclass για την πράξη της πρόσθεσης, απλώς του έδωσα ένα anonymous delegate να φάει, κι έκανα τη δουλειά μου – κράτησα το subclassing για πιο «σοβαρές» περιπτώσεις.

     

    // hmmm ... ok, cool. Now I need to define the Totals calculator, innit ?

                // I'll need a delegate instance for that though ...

                AggregateCalculator.AggregateOperation sumOperation =

                    delegate(IEnumerable<ICalculator> calcs) {

                        double d = 0.0;

                        foreach (ICalculator calc in calcs)

                            d += calc.Value;

                        return d;

                    };

     

                AggregateCalculator totalCategories = new AggregateCalculator(categoryCalcs.Values, sumOperation);

                // way cool ???!?! :D

                Console.WriteLine("Total : {0}", totalCategories.Value);

     

    Το έτρεξα και … et voilat !!! Έπαιξε χαρούμενα και όμορφα, και με έκανε ένα πολύ πολύ ευτυχισμένο anjelinio :]

    Αν δεί κάποιος το μικρό αυτό sample prototype, ίσως σκεφτεί ότι είναι overkill. Αυτό ισχύει στο prototype μόνο όμως. Στην «πραγματική ζωή» χρειαζόταν να κάνω πιο περίπλοκες πράξεις, και είχα σίγουρα πολύ περισσότερα tables να υπολογίσω.

    Τελικά μετά το prototype αυτό, είμαι σίγουρος ότι γλίτωσα αρκετές ώρες debugging, αν μη τι άλλο γιατί με αυτό το approach επικεντρώθηκα στο process, στο τι πράξεις χρειάζεται να κάνω και στο δέσιμο των caclulators σε αυτό που τελικά γίνεται μια αλυσίδα από dependend calculators. Δεν επικεντρώθηκα σε trivial κώδικα για υπολογισμούς στο DataTable, χιλιάδες ενδιάμεσες μεταβλητές, πολλές γραμμές κώδικα και ότι άλλο μπορεί να μετατρέψει μια σελίδα κώδικα σε .. μακαρονάδα. Ήταν καθαρό, reusable, και πολύ λιγότερο error prone.

    Ε, και ήταν και πιο όμορφο ρε παιδιά … πολύ πιο όμορφο.

    Αλλά τέλος πάντων. Στο συμπέρασμα.

    Αυτό το approach πλησιάζει πολύ σε αυτό που λεν’ “functional progarmming”. Δεν το σκέφτηκα εκείνη τη στιγμή, αλλά ισχύει. Κι όταν βρήκα το χρόνο, με οδήγησε στο να ψάξω λίγο περισσότερο το functional programming, ειδικά εν όψει LINQ, το οποίο τελικά είναι απλώς μια πολύ όμορφη υλοποίηση η οποία εκμεταλλεύεται τα νέα functional features της C#. To LINQ με άλλα λόγια, το είδα σαν το “killer app” για το functional programming σε C#.

    Αποφάσισα λοιπόν να ξαναδώ τούτο το μικρό sample, και σιγά-σιγά να το περάσω πρώτα από Generics ( όπως είδατε είναι hard coded να γυρνάνε double όλα ), και μετά στην Orcas εποχή. Κι όταν κάνω κάθε βήμα, θα το post-άρω κι εδώ να το συζητάμε …

    Καλό βράδυ, και may the source be with us. To sample είναι attached στο post.


    Angel
    O:]
  •  06-05-2007, 21:46 31497 σε απάντηση της 31492

    Απ: Τι κάνεις άμα βαριέσαι να γράψεις βαρετό / βλαμμένο κώδικα στο UI ...

    Ή απλά να δεις τα data templates του Blend και πως αυτά δέχονται expressions και να το δέσεις με WF (Windows Workflow Foundation).. Smile

    Παναγιώτης Κεφαλίδης

    "Για να επιτύχεις, θα πρέπει το πάθος σου για την επιτυχία να είναι μεγαλύτερο απο τον φόβο σου για την αποτυχία"

    Οι απαντήσεις παρέχονται για συγκεκριμένες ερωτήσεις και χωρίς καμιά εγγύηση. Παρακαλώ διαβάστε τους όρους χρήσης.
  •  07-05-2007, 01:38 31504 σε απάντηση της 31497

    Απ: Τι κάνεις άμα βαριέσαι να γράψεις βαρετό / βλαμμένο κώδικα στο UI ...

    Όχι ρε Πάνο, you're missing the point. Αυτό ήταν απλώς μια νύχτα που είχα πλέον βαρεθεί να γράφω trivial web UI κώδικα, και χρειαζόμουν κάτι για να την παλέψω. Ότι ακριβώς λεεί ο τίτλος του post. Ενδιαφέρον κώδικας, που απλώς χρησιμεύει όμορφα ως παράδειγμα :)

    .. ή για να το θέσω αλλιώς. Σαν επαγγελματίες, πολύ συχνά "αναλωνόμαστε" τρόπον τινά στις ανάγκες ενός project. Ψάχνουμε τον ευκολότερο τρόπο να κάνουμε κάτι, χωρίς να γράψουμε κώδικα. Μαθαίνουμε νέα API's, κατεβάζουμε τα τελευταία open & closed source εργαλεία που βλέπουμε απο 'δω κι απο 'κεί. Αυτό θεωρώ οτι είναι μεν σωστό, αλλά ... κάποιες φορές πιστεύω ακράδαντα οτι πρέπει να βγάζουμε το καπελάκι του Software Engineer, και να κάνουμε κάτι που φέρνει λίγο περισσότερο στο "πιτσιρίκι" απο το οποίο όλοι ξεκινήσαμε. Υπάρχει ομορφιά στον κώδικα και κάποιες φορές πρέπει να το κάνεις μόνο για αυτό. Και μου 'χε λείψει πάρα πολύ αυτή η αίσθηση τελευταία.

    Γαμώτο, δεν είμαστε μόνο μηχανικοί, είμαστε και καλλιτέχνες. Καλό κι ωραίο να χτίζεις ουρανοξύστες, αλλά πάντα θα μένεις άναυδος κοιτώντας τον καθεδρικό του Gaudin.



    Angel
    O:]
  •  07-05-2007, 03:39 31505 σε απάντηση της 31504

    Απ: Τι κάνεις άμα βαριέσαι να γράψεις βαρετό / βλαμμένο κώδικα στο UI ...

    Συμφωνώ κι επαυξάνω.. Εγώ απλά μας προσγείωσα λίγο απότομα Stick out tongue Yet another solution though..
    Δεν το πήρα σαν το "feeling" της όλης υπόθεσης για να δω την ομορφιά του! Big Smile


    Παναγιώτης Κεφαλίδης

    "Για να επιτύχεις, θα πρέπει το πάθος σου για την επιτυχία να είναι μεγαλύτερο απο τον φόβο σου για την αποτυχία"

    Οι απαντήσεις παρέχονται για συγκεκριμένες ερωτήσεις και χωρίς καμιά εγγύηση. Παρακαλώ διαβάστε τους όρους χρήσης.
  •  07-05-2007, 14:21 31529 σε απάντηση της 31504

    Απ: Τι κάνεις άμα βαριέσαι να γράψεις βαρετό / βλαμμένο κώδικα στο UI ...

    Φιλε anjelinio

    anjelinio:
    Υπάρχει ομορφιά στον κώδικα και κάποιες φορές πρέπει να το κάνεις μόνο για αυτό.

    Χαίρομαι που υπάρχουν και άλλοι fellow programmers που το βλέπουν αυτό. (Αυτός άλλωστε είναι και ο ορισμός του πραγματικού hacker)

    Επηρεασμένος από τον κώδικα που έκανες post, έπαιξα λιγάκι με LINQ και C# 3.0 και ιδού το αποτέλεσμα.

    var dataPattern = new[] { 
                               new { Price = 10.00, Quantity = 2.00, Category = 1, ID = 1 },
                               new { Price = 10.00, Quantity = 2.00, Category = 1, ID = 2 },
                               new { Price = 10.00, Quantity = 2.00, Category = 2, ID = 3 },
                               new { Price = 10.00, Quantity = 2.00, Category = 2, ID = 4 },
                               new { Price = 10.00, Quantity = 2.00, Category = 3, ID = 5 }
                            };


    var groupResult = from data in dataPattern
                      group data by data.Category into g
                      select new { Category = g.Key, Sum = g.Sum(s => s.Price) };

    foreach (var group in groupResult)
    {
       Console.WriteLine("Category {0}: {1}", group.Category, group.Sum);
    }

    Console.WriteLine("Total: {0}", groupResult.Sum(s => s.Sum));

     


    Palladinos Nick
    Software Engineer
    -----------------------
    The limits of my language mean the limits of my world. (Ludwig Wittgenstein)
  •  07-05-2007, 16:22 31545 σε απάντηση της 31529

    Απ: Τι κάνεις άμα βαριέσαι να γράψεις βαρετό / βλαμμένο κώδικα στο UI ...

    PALLADIN:

    Φιλε anjelinio

    anjelinio:
    Υπάρχει ομορφιά στον κώδικα και κάποιες φορές πρέπει να το κάνεις μόνο για αυτό.
     

    Χαίρομαι που υπάρχουν και άλλοι fellow programmers που το βλέπουν αυτό. (Αυτός άλλωστε είναι και ο ορισμός του πραγματικού hacker)



    ... αυτό είναι μέγιστο κομπλιμέντο Νίκο, thanx.


    Angel
    O:]
  •  07-05-2007, 16:35 31550 σε απάντηση της 31545

    Απ: Τι κάνεις άμα βαριέσαι να γράψεις βαρετό / βλαμμένο κώδικα στο UI ...

    anjelinio:
    PALLADIN:

    Φιλε anjelinio

    anjelinio:
    Υπάρχει ομορφιά στον κώδικα και κάποιες φορές πρέπει να το κάνεις μόνο για αυτό.
     

    Χαίρομαι που υπάρχουν και άλλοι fellow programmers που το βλέπουν αυτό. (Αυτός άλλωστε είναι και ο ορισμός του πραγματικού hacker)



    ... αυτό είναι μέγιστο κομπλιμέντο τρόπον τινά Νίκο, thanx.

    Παρακαλώ! Σε λίγο οι moderators θα εξοπλιστούν και με σφουγγαρόπανα για τα σορόπια!

     


    Dimitris Papadimitriou
    Software Development Professional
    dotNETZone.gr News

    Οι απαντήσεις παρέχονται για συγκεκριμένες ερωτήσεις και χωρίς καμιά εγγύηση. Διαβάστε επίσης τους όρους χρήσης.
  •  07-05-2007, 17:10 31552 σε απάντηση της 31550

    Απ: Τι κάνεις άμα βαριέσαι να γράψεις βαρετό / βλαμμένο κώδικα στο UI ...

    Αχ καλέ ... κι ο Δημήτρης όμως δεν είναι ΣΠΟΥΔΑΙΟ παιδί ρε παιδιά ?!?!? Big Smile Big Smile

    Μ' αρέσει αυτή η αγάπη που έπεσε ξαφνικά στο μικρό μας DNZ :D

    Υ.Γ. Λοιπόν, σοβαρεύομαι, αυτό ξεκίνησε ώς σχετικά σοβαρό post :]

    Angel
    O:]
  •  09-05-2007, 11:09 31625 σε απάντηση της 31552

    Απ: Τι κάνεις άμα βαριέσαι να γράψεις βαρετό / βλαμμένο κώδικα στο UI ...

    Παιδιά να βαριέστε πιο συχνά, για να διαβάζουμε κι οι υπόλοιποι ενδιαφέροντα πράγματα !!!
    Εύγε !!


    Πάνος Αβραμίδης
  •  15-05-2007, 22:21 31826 σε απάντηση της 31625

    Απ: Τι κάνεις άμα βαριέσαι να γράψεις βαρετό / βλαμμένο κώδικα στο UI ...

    Συνημμένα: FuncGrid.rar

    Λοιπόν, σήμερα μπήκα στο γραφείο στις 8:15. Αυτό είναι κοσμοϊστορικό γεγονός για τα δεδομένα του γραφείου μας, που συνήθως δε με βλέπει πριν τις 10 … Δεν το έκανα βέβαια ούτε για να τη σπάσω στους απεργούς, ούτε από υπερβάλλοντα ζήλο. Απλώς, μετά από κάποιο meeting, προέκυψαν 4 Α4 σελίδες από tables με υπολογισμούς στο τρέχον project μου.


    Μια αρχή του Agile programming λέει ότι αν μπορείς να το κάνεις σωστά από την αρχή, τότε … κάντο. Το προηγούμενο postάκι ήταν μια καλή αρχή, αλλά τώρα χρειαζόμουν απαραιτήτως κάτι ακόμη πιο ευέλικτο, πιο άνετο ακόμα.


    Οπότε, σήμερα ήταν η μέρα που έπρεπε να το προχωρήσω λίγο το θέμα.


    Η βασική αρχή είναι η εξής:
     

    Να μπορώ να ορίσω ένα grid από calculators, στο οποίο ο καθένας έχει ένα μοναδικό όνομα, και να μπορώ με κάποιο εύκολο τρόπο να ορίζω είτε μια τιμή για κάθε grid cell, είτε μια απλή formula η οποία είναι στην ουσία το αποτέλεσμα του aggregation τιμών άλλων κελιών.


    Σκεφτείτε τα cells του Excel. Κάθε ένα έχει ένα μοναδικό όνομα. Σε κάθε ένα μπορούμε είτε να γράψουμε με το χέρι μια τιμή, είτε να ορίσουμε μια formula η οποία κατά πάσα πιθανότητα εμπεριέχει και εξαρτάται από τιμές άλλων κελιών.


    Στο απλοϊκό μου παράδειγμα, όλα τα κελιά περιέχουν doubles. Τώρα, αν σκεφτεί κανείς ένα δισδιάστατο array, θα σκεφτεί επίσης το εξής … Ένα double value είναι πολύ διαφορετικό από ένα αντικείμενο (object) το οποίο ε π ι σ τ ρ έ φ ε ι ένα double value από μια μέθοδό του. Πώς λοιπόν μπορώ να ορίσω ένα grid, όπου τα κελιά δεν ανήκουν κατά βάση στον ίδιο τύπο;


    Η απάντηση είναι απλή. Αν ακόμα και τα απλά numeric values, όπως το «2», «3.14» κτλ. είναι αποτέλεσμα μιας μεθόδου, η οποία απλώς επιστρέφει αυτή την «προεπιλεγμένη» τιμή, τότε είμαι ok !

     

    Φέρνει λίγο σε LISP και Haskell αυτή η λογική, έτσι είναι κι εκεί, τα πάντα είναι στην ουσία functions  


    Ωραία. Άρα, το πρώτο που χρειάζομαι είναι αυτό. Κάτι που να μου επιστρέφει μια στατική τιμή, και να είναι ίδιου τύπου με οτιδήποτε άλλο στο calculator grid μου.

    Αν θυμάστε, το βασικό μου interface στο προηγούμενο post ήταν το ICalculator :

     

        /// <summary>

        /// Base inteface definition for a Calculator.

        /// it just returns a double value.

        /// </summary>

        public interface ICalculator

        {

            double Value { get; }

        }

      


    Με μια πολύ απλή υλοποίηση, μπορούμε να έχουμε το εξής:

     

        /// <summary>

        /// Basic ICalculator implementation. It will return the value it was given.

        /// </summary>

        public class BaseCalculator : ICalculator {

     

            public BaseCalculator(double val) {

                m_Value = val;

            }

     

            private double m_Value = 0;

     

            public double Value {

                get {

                    return m_Value;  

                }

            }

        }

     

    Τίποτα απολύτως δύσκολο ως εδώ. Με αυτή την πολύ απλή κλάση, έχω λύσει το βασικό μου πρόβλημα. Να έχω ένα grid με ομοιογένεια όσον αφορά το base type των κελιών. Άρα, τώρα μπορώ να το χρησιμοποιήσω με τις κλάσεις του προηγούμενου post, και τα πάντα θα δουλέψουν κανονικά.


    Το επόμενο πρόβλημά μου είναι το ίδιο το grid. Χρειάζομαι έναν εύκολο “container” αυτών των ICalculator instances, το οποίο θα μπορεί να γίνει initialize σε οποιοδήποτε μέγεθος θέλω εγώ, και θα μου δίνει μια default τιμή 0.0 σε όλα τα «κελιά».

    Η επόμενη κλασούλα ήταν το CalculatorMap:

     

        /// <summary>

        /// Defines a rectangular "grid" of ICalculator instances

        /// </summary>

        public class CalculatorMap {

     

            private IDictionary<string, IDictionary<string, ICalculator>> m_Map = new Dictionary<string, IDictionary<string, ICalculator>>();

     

            public CalculatorMap(int sizeY, int sizeX) {

                if(0>=sizeY)

                    throw new ArgumentOutOfRangeException(string.Format("Cannot initialize Y Size with zero or negative integer: {0}", sizeY));

                if (0 >= sizeX)

                    throw new ArgumentOutOfRangeException(string.Format("Cannot initialize X Size with zero or negative integer: {0}", sizeX));

     

                m_SizeY = sizeY;

                m_SizeX = sizeX;

     

                Init();

            }

     

            protected virtual void Init() {

                // ok, init the map to the size we want with default BaseCalculator instances

                for (int y = 0; y < this.SizeY; y++)

                {

                    m_Map[y.ToString()] = new Dictionary<string, ICalculator>();

                    for (int x = 0; x < this.SizeX; x++)

                    {

                        m_Map[y.ToString()][x.ToString()] = new BaseCalculator(0.0);

                    }

                }

            }

     

            #region size properties

     

            private int m_SizeX = int.MinValue;

     

            /// <summary>

            /// Horizontal size of the map

            /// </summary>

            public int SizeX {

                get {

                    if (int.MinValue.Equals(m_SizeX))

                        throw new ArgumentOutOfRangeException("SizeX has not been initialized");

     

                    return m_SizeX;

                }

            }

     

            private int m_SizeY = int.MinValue;

     

            /// <summary>

            /// Vertical size of the map

            /// </summary>

            public int SizeY {

                get {

                    if (int.MinValue.Equals(m_SizeY))

                        throw new ArgumentOutOfRangeException("SizeY has not been initialized");

     

                    return m_SizeY;

                }

            }

     

            #endregion

     

            #region data retrieval properties

     

            /// <summary>

            /// Default indexer. The key is in the format Y,X identifying a position in my internal Grid

            /// </summary>

            /// <param name="key"></param>

            /// <returns></returns>

            public ICalculator this[string key] {

                get {

                    string[] keyParts = key.Split(',');

                    if (keyParts.Length != 2)

                        throw new ArgumentException(string.Format("Could not derive coords from input string: {0}", key));

     

                    return m_Map[keyParts[0]][keyParts[1]];

                }

                set {

                    // Dont let the guy feed me a null value !!!

                    if (null == value) return;

     

                    string[] keyParts = key.Split(',');

                    if (keyParts.Length != 2)

                        throw new ArgumentException(string.Format("Could not derive coords from input string: {0}", key));

     

                    m_Map[keyParts[0]][keyParts[1]] = value;

                }

            }

     

            public IEnumerable<IEnumerable<ICalculator>> Rows

            {

                get

                {

                    List<IEnumerable<ICalculator>> rows = new List<IEnumerable<ICalculator>>();

                    foreach (IDictionary<string, ICalculator> dictRow in m_Map.Values)

                        rows.Add(dictRow.Values);

     

                    return rows;

                }

            }

     

            #endregion

     

            #region get/set value overloads

     

            /// <summary>

            /// Sets the value of the given calculator to the result of evaluating the given

            /// expression string

            /// </summary>

            /// <param name="calcCoords"></param>

            /// <param name="calcExpression"></param>

            public void SetValue(string calcCoords, string calcExpression) {

                if(null!=this[calcCoords])

                    SetValue(calcCoords, CalculatorFactory.Parse(calcExpression, this));

            }

     

            /// <summary>

            /// Sets the value of the given calculator to the ICalculator instance supplied

            /// </summary>

            /// <param name="calcCoords"></param>

            /// <param name="calcExpression"></param>

            public void SetValue(string calcCoords, ICalculator valCalc) {

                if (null != this[calcCoords])

                    this[calcCoords] = valCalc;

            }

     

            /// <summary>

            /// Retrieves the vcalue at the given position

            /// </summary>

            /// <param name="coords"></param>

            /// <returns></returns>

            public double GetValue(string coords){

                return this[coords].Value;

            }

     

            #endregion

           

     

        }

     

     

     Τώρα, τα «highlights» αυτής της κλάσης είναι τα εξής:

     

    1. Θα δώσει μια default τιμή 0.0 σε όλα τα κελιά.
    2. Θα δώσει ένα όνομα σε όλα τα κελιά, της μορφής <Y coord>,<X coord>
    3. Μπορούμε να πάρουμε τον ICalculator από οποιοδήποτε κελί από τον defalut indexer της κλάσης – π.χ. myCalcMap[“4,5”] .
    4. Έχουμε τη δυνατότητα, αντί για ένα ICalculator, να χρησιμοποιήσουμε ένα string για να δώσουμε τιμή σε οποιοδήποτε κελί.

     

    Από όλα τα παραπάνω, το πιο «extreme» feature είναι μάλλον το τελευταίο. Είχα στο μυαλό μου το Excel. Όταν ορίζεις μια formula για ένα κελί του Excel, στην ουσία κάνει parse ένα string – τη formula, σε κάτι αντίστοιχο ενός ICalculator.

     

    Έτσι, καλώντας τη SetValue(string coords, string calcExpression) έδωσα στο χρήστη να περάσει ένα string του τύπου <operation>{Cell[y1,x1]:Cell[y2,x2]: ... :Cell[yN,xN]}, το οποίο κάνω parse στον αντίστοιχο AggregateCalculator με λίγο πολύ κακογραμμένο κώδικα. Δεν ήταν η πρόθεσή μου να γράψω ένα full-blown expression interpreter, απλώς κάτι που να δουλεύει αρκετά ικανοποιητικά για ένα πρωτότυπο.

     

    Με τα πολλά λοιπόν, έγραψα τις κλασούλες μου, όρισα και 4 βασικούς operators, sum, sub, mult & div και ήμουν έτοιμος να γράψω λίγο client κώδικα για να δοκιμάσω το library:

     

            static void Main(string[] args)

            {

                CalculatorMap map = new CalculatorMap(5, 5);

     

                map.SetValue("0,0", "5");

                map.SetValue("1,0", "10");

                map.SetValue("2,0", "sum{Cell[0,0]:Cell[1,0]}");

     

                PrintMap(map);

     

                map.SetValue("1,0", "11");

                Console.WriteLine();

     

                PrintMap(map);

     

                Console.ReadLine();

            }

     

           static void PrintMap(CalculatorMap map) {

                foreach (IEnumerable<ICalculator> row in map.Rows) {

                    Console.WriteLine();

                    foreach (ICalculator cell in row) {

                        Console.Write("{0},", cell.Value);

                    }

                }

            }

     

    To output ήταν το εξής, και με απογοήτευσε :

     

    5,0,0,0,0,

    10,0,0,0,0,

    15,0,0,0,0,

    0,0,0,0,0,

    0,0,0,0,0,

     

    5,0,0,0,0,

    11,0,0,0,0,

    15,0,0,0,0,

    0,0,0,0,0,

    0,0,0,0,0,

     

    Τι συνέβη; Στον κώδικα του main βλέπω ξεκάθαρα ότι ορίζω την τιμή του κελιού 2,0 ως το άθροισμα των κελιών 0,0 + 1,0. Τη πρώτη φορά που τρέχει το PrintMap η computed τιμή είναι 15, όπως και θα έπρεπε.

     

    Μετά όμως, αλλάζω την τιμή του 1,0 σε 6. Άρα στο 2,0 θα έπρεπε να βλέπω 16. Αλλά εξακολουθώ να βλέπω 15 !!!

     

    Η απάντηση είναι μέσα στο CalculatorMap, στη μέθοδο SetValue(string coords, ICalculator calcInstance) :

     

            public void SetValue(string calcCoords, ICalculator valCalc) {

                if (null != this[calcCoords])

                    this[calcCoords] = valCalc;

            }

     

    Όπως βλέπουμε, εδώ α ν τ ι κ α θ ι σ τ ώ την τιμή, με ένα νέο instance. Από μόνο του δε λέει και πολλά, αλλά σε συνδυασμό με την υλοποίηση του AggregateCalculator από το προηγούμενο post το πρόβλημα γίνεται πιο ξεκάθαρο:

     

            public AggregateCalculator(IEnumerable<ICalculator> calcs, AggregateOperation operation)

            {

                // ok, i can't be bothered to write properties, so I'll be doing my checks in here instead ...

                if (null == calcs)

                    throw new ArgumentNullException("calcs", "Cannot be initialized to null");

                if (null == operation)

                    throw new ArgumentNullException("operation", "Cannot be initialized to null");

                if (operation.GetInvocationList().Length > 1)

                    throw new ArgumentException("Cannot use multi-cast delegates for an AggregateOperation", "operation");

     

                m_Operation = operation;

                m_Children = calcs;

            }

     

    Εδώ λοιπόν, αποθηκεύω το IEnumerable<ICalculator> calcs σε μια local μεταβλητή. Αυτό σε απλά λόγια σημαίνει ότι δεν κοιτάω πια το CalculatorMap[y,x], αλλά τη local μεταβλητή μου. Αν η τιμή του original κελιού αλλάξει, δε θα το μάθω ποτέ, γιατί εγώ έχω reference στο instance της προηγούμενης τιμής ! Κι ακόμη χειρότερα … αυτή η παλιά τιμή, αν δεν είχα εγώ κρατήσει το local reference, θα είχε γίνει garbage collected. Τώρα, όσο ο Aggregate μου κρατάει το reference, το αντικείμενο θα μένει ζωντανό στη μνήμη. Και bug, και memory leak :P

     

    Η λύση είναι να ξέρει ο AggregateCalculator όχι το ίδιο το instance, αλλά το πώς θα το πάρει από τον CalculatorMap. Έτσι, κάθε φορά που θα το ζητήσει, θα πάρει πίσω την τελευταία τιμή του κελιού. Γι’ αυτό χρειαζόμουν μια νέα κλάση, την οποία ονόμασα AggregateMapCalculator:

     

    /// <summary>

        /// Extends the AggregateCalculator to take into consideration that

        /// it's part of a Map, so it can't maintain references to it's child calculators,

        /// it has to ask the map for them - they may have changed in the meantime

        /// </summary>

        public class AggregateMapCalculator : AggregateCalculator {

     

            public AggregateMapCalculator(IEnumerable<string> calcIDs, CalculatorMap parentMap, AggregateCalculator.AggregateOperation operation)

                : base(new ICalculator[0], operation) {

     

                    m_ChildIDs = calcIDs;

                    m_Parent = parentMap;

            }

     

            #region behavior properties

     

            private CalculatorMap m_Parent = null;

     

            /// <summary>

            /// Maintains a reference to the containing map

            /// </summary>

            public CalculatorMap Parent {

                get {

                    if (null == m_Parent)

                        throw new ArgumentNullException("Parent Map has not been initialized");

     

                    return m_Parent;

                }

            }

     

            private IEnumerable<string> m_ChildIDs = null;

     

            /// <summary>

            /// The list of the IDs of all the child ICalculators, as they appear

            /// in the parent map

            /// </summary>

            public IEnumerable<string> ChildIDs {

                get {

                    if (null == m_ChildIDs)

                        throw new ArgumentNullException("ChildIDs");

     

                    return m_ChildIDs;

                }

            }

     

        #endregion

     

            /// <summary>

            /// Retrieves the children calculators as they are defined "currently"

            /// on the map

            /// </summary>

            protected virtual IEnumerable<ICalculator> Children {

                get {

                    List<ICalculator> calcs = new List<ICalculator>();

                    foreach (string calcId in this.ChildIDs) {

                        calcs.Add(m_Parent[calcId]);

                    }

     

                    return calcs;

                }

            }

     

            /// <summary>

            /// ok, implement the base interface, by just calling my

            /// delegate method to get back the result, whatever that may be

            /// </summary>

            public override double Value

            {

                get {

                    return m_Operation(this.Children);

                }

            }

       

        }

     

    Η μόνη διαφορά του από τον προηγούμενο είναι ότι ξέρει πλέον τον «πατέρα» του, και ζητάει από αυτόν τα instances των ICalculators που χρειάζεται. Έτσι, η τιμή που επιστρέφει είναι η σωστή.

     

    Το επόμενο test run output ήταν σαφώς πιο σωστό:

     

    5,0,0,0,0,

    10,0,0,0,0,

    15,0,0,0,0,

    0,0,0,0,0,

    0,0,0,0,0,

     

    5,0,0,0,0,

    11,0,0,0,0,

    16,0,0,0,0,

    0,0,0,0,0,

    0,0,0,0,0,

     

     

    Προσπάθησα να κάνω ένα πιο εντυπωσιακό πρωτότυπο, ένα winapp με ένα grid κι ένα textbox όπου θα μπορούσαμε να γράφουμε values και formulas για κάθε ένα κελί, έτσι ώστε να φανεί πιο «έμπρακτα» το πόσο δυνατό είναι αυτό το μοντέλο – νοοτροπία προγραμματισμού, αλλά δυστυχώς είμαι web boy, και έχω και μια δουλειά κατά τη διάρκεια της μέρας, και τα παράτησα μόλις τα βρήκα λίγο σκούρα με το πώς ακριβώς θα έφτιαχνα το grid, τα events κτλ κτλ. Θα το αφήσω ανοιχτό, μήπως βρεθεί κάποιος και βοηθήσει με το UI ... δόξα τω θεώ, 2000 άτομα γίναμε !!!

     

    Τα sources είναι και πάλι attached στο post, σε .Net 2.0 / VS2005.

     

    Happy coding !!!

     

     


    Angel
    O:]
  •  29-08-2007, 15:33 34572 σε απάντηση της 31826

    Church numerals

    > Φέρνει λίγο σε LISP και Haskell αυτή η λογική, έτσι είναι κι εκεί, τα πάντα είναι στην ουσία functions 

    Για να ακριβολογούμε, και στη Lisp και στη Haskell οι αριθμοί είναι απλώς αριθμοί. Είναι όμως ενδιαφέρον να δει κανείς πώς παριστάνονται οι αριθμοί στο απλό, untyped, lambda calculus (όπου όντως δεν υπάρχει κανένας τύπος δεδομένων και τα πάντα είναι lambda): Church numerals

    Σκέφτηκα να παραθέσω το URL μια και και το συγκεκριμένο thread περπατάει σε πιο "φιλοσοφικά" μονοπάτια. Χαίρομαι πάντα να βλέπω ανθρώπους που βλέπουν πιο μακριά από το CD εγκατάστασης του Visual Studio.

    --
    D. Souflis
    "Reality is that which, when you stop believing in it, doesn't go away" P. K. D.
  •  29-08-2007, 16:11 34573 σε απάντηση της 34572

    Απ: Church numerals

    Καλησπέρα φίλε μου...

     

    Σε ευχαριστώ που αναφέρεις τα Church numerals, καθώς μου δίνεις την ευκαιρία να θυμίσω ότι ο Alonzo Church

    ήταν ένας από τους μεγαλύτερους hackers... και να φανταστούμε ότι δεν υπήρχαν υπολογιστές όταν πραγματοποίησε τις μεγαλύτερες hackιες του.

     

    Church numerals in C#

     

    delegate D D(D d);

    D zero = f => x => x

    D one = f => x => f(x)

    D two = f => x => f(f(x))

    And more…

     

     


    Palladinos Nick
    Software Engineer
    -----------------------
    The limits of my language mean the limits of my world. (Ludwig Wittgenstein)
Προβολή Τροφοδοσίας RSS με μορφή XML
Με χρήση του Community Server (Commercial Edition), από την Telligent Systems