Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

Suppose we have a model of a human muscular system containing the following muscles each within a specific muscle group:

  • Arms
    • Biceps
    • Triceps
    • Forearms
  • Torso Front
    • Abs
    • Chest
    • Shoulders
  • Torso Back
    • Trapezius
    • Lats
    • Rear Shoulders
    • Lower Back
  • Lower Body
    • Quads
    • Hams
    • Hip & Butt
    • Calves

Now, given a list of selected muscles, I need to find combinations following the given rules:

If all muscles are selected from a group:

Full body (All muscles) 
Arms 
Torso
Torso Front
Torso Back
Lower Body

If there are full groups together:

Arms and torso
Arms and torso front
Arms and torso back
Arms and lower body
Torso and lower body
Torso front and lower body
Torso back and lower body

When both the front and back of the torso is selected it can be considered just "torso".

If there are more than 2 muscles selected from a group but not all:

Parts of torso (This is the case where there are muscles selected from both front- and back-torso)
Parts of torso back
Parts of lower body

"Parts of torso front" and "Parts of arms" never occur on their own as they only have a total of 3 muscles, thus those cases can be represented by their names: eg. "biceps and triceps", etc.

Combinations of these aforementioned "parts"

Parts of arms and torso
Parts of arms and lower body
Parts of torso and lower body
Parts of  arms, torso and lower body.

Parts = groups are not full and there are more than 2 muscles selected. This "rule of 2" is the most important one.


Examples of output

biceps, triceps and chest: parts of arms and torso

biceps, triceps, calves, tai biceps, triceps, quads, hams, hip & butt: Parts of arms and lower body

biceps, abs, calves: Parts of arms, torso and lower body

biceps, triceps: Biceps and triceps

biceps, triceps, forearms: Arms

chest, shoulders, abs, trapezius, lats, rear shoulders, lower back: Torso

and so on, there are multiple possible variations.


Now my problem is that I have no idea how to write this as clean and modular as possible, so that rules can be added or removed if necessary.

I could just write a massive if-elseif-else pile that would consider all cases, but you know why I won't.

How would you tackle this kind of task?

share|improve this question
    
I'd suggest the Decorator pattern, but I'm not sure if it won't cause maintenace problems with time. –  superM Aug 3 '12 at 10:32
2  
+1 for well explained and well asked question. –  Matsemann Aug 3 '12 at 10:46
3  
@superM I read 'the Doctor pattern' after seeing all the body parts. –  Matsemann Aug 3 '12 at 10:47
    
@Matsemann really? I was afraid it was too hard to understand. –  Gerstmann Aug 3 '12 at 13:18
    
What happens if you select all of the front torso and some of the back torso? –  psr Aug 3 '12 at 21:46
add comment

6 Answers

up vote 4 down vote accepted
  • Create a class "MuscleGroup"
  • Init each object of that class with the list of muscles belonging to that class, and the name of the group
  • add a method PrintRelatedParts(SetOfMuscles som) to that class (SetOfMuscles should be some container type for the given input)
  • the method should count how many of the given muscles in som are in the muscle group. If there are 0, it prints nothing, if there are 1 or 2, it prints the names of the muscles, if there are more than 2, it prints "parts of ...", and if there are all muscles of that group, it prints just the name of the group.
  • add some logic to combine the output of all muscle group objects with "and" and ","

The essential decision you have to program here is just a decision between 4 cases, not a "massive if-elseif-else pile".

share|improve this answer
    
Your are hard building test case for a printable format. If it changes, you will have to change the heart of your API. I'm not certain it is an open schema. –  cl-r Aug 3 '12 at 15:00
1  
@cl-r: the output is specified in the original question, blame the autor if you don't like it. Of course, anyone can add an additional level of indirection and replace "PrintRelatedParts" by a function "GetRelatedParts", returning a set of symbolic constants from an enumeration "BodyParts" which includes the muscle names as well as the muscle group names and "parts of", and then finally map that constants to an output string. But that does not change the general suggestion in my answer how to avoid too many if-else constructs. –  Doc Brown Aug 3 '12 at 15:24
    
This answer does not address this point of OP: When both the front and back of the torso is selected it can be considered just "torso". –  kahoon Aug 3 '12 at 21:18
    
@kahoon: yes, I missed that point. If one needs a general approach for that, one can design a "Muscle group collection" containing a set of muscle groups and giving that collection the ability to interpret the "GetRelatedParts" of the contained muscle groups in the right manner. –  Doc Brown Aug 3 '12 at 22:21
    
@DocBrown Sorry if you think I wanted give some blame or something lake that. I was just thinking at natural API's evolution, and point out that if problem arise in future, it takes less time to solve it now than after hundred lines of code. –  cl-r Aug 4 '12 at 15:12
add comment

I would not think too much about making it extensible. Human bodies are not going to change in a foreseeable future. I would model the data structures after who the users are. Are they chiropractors? Physical therapists? Gym rats? A few conditionals in one place is often better than introducing two design patterns and a handful of classes.

share|improve this answer
    
Sadly, the specs for what I'm working on are so volatile that a modular system is a must. –  Gerstmann Aug 3 '12 at 13:26
    
Human bodies are not going to change in foreseeable future? true AND false : perhaps the body itlself will evolve slowly, but the science go further in retail : what's happen if now you have to manage front arm muscles, and behind arms muscle? or elbow muscles and wrist muscle, so investigate Pattern loosely coopled is nice for future, mathematical coding approach reserved of if problem may need to rebuild all. –  cl-r Aug 3 '12 at 13:42
add comment

I hate generalizing examples like this because you are always guessing and your brilliant generalization might get destroyed by the new rule they come up with. So if possible ask the domain experts if your abstraction makes sense to them.

That being said, it sounds like you have a hierarchy. It's mostly obvious from what you list, plus torso is a parent of front torso and back torso. Each node in the hierarchy can describe itself with a string. Your leaf nodes just print their name (e.g. "biceps"). Most of your interior nodes follow your "rule of 2" (either combining the string values of the children or replacing with another value as described in the question). The torso node basically follows a "rule of 1", using the child's input only if there is exactly 1 child. There is an implicit root node ("whole body node"?) that handles the "and" and "," logic.

You might have 4 different node classes here, or a strategy pattern, or 3 strategies one of which takes the "rule of X" parameter.

This records a bit more semantic information and might generalize better - until you find out that a finger and a toe combine into "digits" even though they are otherwise in a completely different branch of the hierarchy. But I would probably do this over Doc Brown's simpler solution if I believed there would be many more such cases in the future and the domain experts thought they would fit this model.

share|improve this answer
add comment

EDIT The following is probably what Doc Brown was already suggesting, except that I'm longwinded and make a lot of fuss about the set abstraction.

I'm not sure object oriented patterns are the right approach here. I think the main abstraction you need is simply a set.

Your primitive elements are "biceps", "triceps" etc. Your "universal" set is the complete set of those elements. "arms" and "torso front" are just sets - subsets of the universal set.

For your more complex criteria, you'd need a reasonable set of operations on sets - union, intersection, difference, counting elements etc. There are design patterns that you could use to specify functions based on those, but I would think "Abstract Syntax Tree" rather than "GOF pattern", and even that is probably overkill.

All you really need to define all your classification rules is a sequence (list, array, ... - maybe even a set) of records (or objects). The classification test itself can just be whatever representation of a function is most convenient in your language - maybe literally a function or lambda, maybe some kind of delegate. Accept the set of primitive muscles as a parameter and return true/false. That function is then just a field of each record.

You could define a short fat class hierarchy - a base class with a pure "test" method and a derived class for each classification test - but this seems like overkill to me. If you can include functions (or function pointers) in records, just include a field to hold the function. A more complex class hierarchy, attempting to define the muscle-set relationships within the class relationships, would just be OOP for it's own sake IMO - lot's of code to write for little or no benefit.

You might define a tree, but that would be a data structure rather than a class hierarchy, and even then only for optimization if you're dealing with a lot of muscles. If you're really dealing with a lot more than 14 muscles, you may even have some automatic tree-building logic. That would probably be based on a partial order (topological sort) based on comparing sets of combinations of primitive muscles, with is-subset-of as the ordering function. And that could be a fair bit of work...

  • The result wouldn't naturally form a tree - a small set may be a subset of many different larger sets - so there's definitely issues to work out. Either you make some arbitrary choices, or you build a DAG (Directed Acyclic Graph) rather than a tree.

  • The sets are of combinations, not muscles. Either you deal with a combinatorial explosion, or you need to deal with specifications of sets and classification tests in a more sophisticated way.

IOW, don't waste time on it unless you're sure you really need it.

Depending on how you use this precisely, the results may be a set too - a set of classifications that apply rather than a set of muscles. Each member of that would refer to one of your classification tests - depending on language and other issues, maybe by some kind of ID rather than by a pointer/reference to the record/object.

share|improve this answer
    
Do you think my 'answer 2' solve problem complexity mentionned in your post? –  cl-r Aug 6 '12 at 12:53
    
If the problem got big enough, a database may be a good idea. Other than that, I've not spent enough time studying your answer to comment. –  Steve314 Aug 6 '12 at 20:59
add comment

Here, briefly in Java :

if may be replaced by the Observable/Observer Pattern, more useful, stable and secure.

A type is defined in enum, each typeName of refer to an Observer, the ordinal() position in enum give the bit of the type [ordinalBitOfType].

A muscle have a set of one or more ordinalBitOfType

The Observable have a HashSet of ordinalBitOfType to request: enumType.ARMS.ordinal() and enumType.TORSO.ordinal()

Then it activates observers. All concerned Observers print "hello" and can update Map or something else in the Observable, others do nothing.

EDIT
In Observable/Observer Pattern only one if by Observer is needed to respond at : is myBit contained in the Observable.Set?

If you create New types, you have to create new Obervers, don't worry about already Observers jobs, they will work fine, just put the new bits in the right muscles.

You can had Observable Pattern in a Observer for nested if.
Take care, you cannot decide the order of the Observers called by the JVM.

EDIT 2 : some rewriting + O'Reilly book

The clearest book about Pattern design I've read : "Head First Design Patterns", encourage to master it.

EDIT 3

I open a second answer because I realize I was blinded by the if/then/else problem, and also because Observable instance passed to an Observer which can modify it, is a good pattern for limited and stable parameters to manage intricated ifs.

share|improve this answer
    
I don't mean to be rude, but you might want to get a design pattern books off your shelf once again. The Observer pattern has just about nothing to do with this situation. Also, maintaining those HashSets seem like even more trouble than a gigantic switch statement. –  pwny Aug 3 '12 at 13:18
    
@pwny it's not a book's report, but a tool I use with complex if decision ... certainly too briefly explained! But if you have in the Observable passed in the Observer a fieldHashSet for the Observer selection, and another one to collect Observers actions, you are certain to grap all that you want. Take care : switch is not useful for two selections with break between. To be more complete, all Observers have the same AbstractObserver.class to manage common update() method and other utility, and I've add method to verify if Observer.class exists when I load Observable. –  cl-r Aug 3 '12 at 13:33
1  
I'm truly sorry, but I don't understand at all what you're saying :-/ I've seen my share of buzz-word mash-ups as a software engineering student, but I can't make sense of this for the life of me. –  pwny Aug 3 '12 at 13:43
    
@pwny Don't worry, it just Java's experience, writed by a non English developper. Perhaps load Java SDK and build some classes Observable/Observer with "Head First Design Patterns" from O'Reilly. I hope you will be rewarded for the effort done. I believe that it's a good research to do for ExplodingRat, even appearences feel bad. –  cl-r Aug 3 '12 at 13:54
add comment

Answer 2 :

The real problem is to link body's elements together.

For this case we have only two types : muscles and bones [here, grouped in body part].
So if we begin to develop group (of bones) we certainly will be able to detail them to bones granularity.

It is obvious that add a new group of nerves will not be difficult if link model between bones and muscles is adequate.

I'm thinking in Java with OODB to give sample.
I use the OpenSource db4objects, it is also Java, possibility to use EmbeddedObjectContainer loaded with the API, no server, no DBA's problem.
Think it like a disk with useful database tools to manage complex I/O like Transparent Activation/Transparent Persistence, and other powerful tools like a manager, and it is able to migrate to huge Versant professional OODB.

Your job : work with a unique class with three fields :

package ElemBody;

import java.util.HashSet;
import java.util.Set;

public final class ElementBody {

    // In true life fields are 'private' with getters and setters
    public int typ; // TYP_ELEM.BONES.ordinale() Indexed
    public String name; // indexed with UniqueKey
    // HashSet (and not SortedSet for optimization) Element linked
    public Set<ElementBody> linkedElem = new HashSet<ElementBody>();

    // Print utility    
    @Override
    public final String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append(name);
        sb.append(" - ");
        for (final ElementBody el : linkedElem) {
            sb.append(el.name);
            sb.append(", ");
        }
        sb.append(linkedElem.size());
        sb.append(".");
        return sb.toString();
    }
}

The main, (tedious) loading/linking data and quering :

package ElemBody;

import java.io.File;
import java.util.Iterator;

import com.db4o.Db4oEmbedded;
import com.db4o.EmbeddedObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.config.EmbeddedConfiguration;
import com.db4o.constraints.UniqueFieldValueConstraint;
import com.db4o.query.Query;

public class PrmBody {
    public enum TYP_ELEM {
        BONES, MUSCLES, NERVES // here, place for futur types
    }

    public static void main(final String[] args) {
        // one of possible db4o's configuration
        final EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
        // Managing index and unique key
        config.common().objectClass(ElementBody.class).objectField("type")
            .indexed(true);
        config.common().objectClass(ElementBody.class).objectField("name")
            .indexed(true);
        config.common().add(
            new UniqueFieldValueConstraint(ElementBody.class, "name"));
        // You have also an InMemory option, here the file one
        // Because of name's uniqueKey option, delete for test, in true live
        // read before create/update
        new File("Q:/tmp/ElementBody.db4o").delete();
        final EmbeddedObjectContainer eoc = Db4oEmbedded.openFile(config,
        // "ElementBody.db4o"); // in "user.home" directory
            "Q:/tmp/ElementBody.db4o");

        // Tedious option to load database.
        // Use .properties ou CSV files

        // Create BONES
        for (final String s : new String[]{"Arms", "Torso Front", "Torso Back",
            "LowerBody"}) {
            final ElementBody el = new ElementBody();
            el.typ = TYP_ELEM.BONES.ordinal();
            el.name = s;
            eoc.store(el); // save new instance
        }

        // Create MUSCLES
        final String[] muscles = {"Biceps", "Triceps", "Forearms",//
            "Abs", "Chest", "Shoulders", //
            "Trapezius", "Lats", "Rear Shoulders", "Lower Back", //
            "Quads", "Hams", "Hip & Butt", "Calves"};
        for (final String s : muscles) {
            final ElementBody el = new ElementBody();
            el.typ = TYP_ELEM.MUSCLES.ordinal();
            el.name = s;
            eoc.store(el);
        }

        // Simplified control of read type form db4o
        final Query queryA = eoc.query();
        queryA.constrain(ElementBody.class);
        queryA.descend("name").constrain("Arms").equal();
        final ElementBody arm = (ElementBody) queryA.execute().iterator()
            .next();
        for (final String s : new String[]{"Biceps", "Triceps", "Forearms",
            "Shoulders"}) {
            final Query query = eoc.query();
            query.constrain(ElementBody.class);
            query.descend("name").constrain(s).equal();
            final ObjectSet<ElementBody> musT = query.execute();
            musT.get(0).linkedElem.add(arm);
            arm.linkedElem.add(musT.get(0));
            eoc.store(musT.get(0));
        }
        eoc.store(arm);

        final Query queryB = eoc.query();
        queryB.constrain(ElementBody.class);
        queryB.descend("name").constrain("Torso Front").equal();
        final ElementBody tf = (ElementBody) queryB.execute().get(0);
        for (final String s : new String[]{"Abs", "Chest", "Shoulders"}) {
            final Query query = eoc.query();
            query.constrain(ElementBody.class);
            query.descend("name").constrain(s).equal();
            final ElementBody mus = (ElementBody) query.execute().get(0);
            mus.linkedElem.add(tf);
            tf.linkedElem.add(mus);
            eoc.store(mus);
        }
        eoc.store(tf);

        final Query queryC = eoc.query();
        queryC.constrain(ElementBody.class);
        queryC.descend("name").constrain("Torso Back").equal();
        final ElementBody tb = (ElementBody) queryC.execute().get(0);
        for (final String s : new String[]{"Trapezius", "Lats",
            "Rear Shoulders", "Lower Back", "Shoulders"}) {
            final Query query = eoc.query();
            query.constrain(ElementBody.class);
            query.descend("name").constrain(s).equal();
            final ElementBody mus = (ElementBody) query.execute().get(0);
            mus.linkedElem.add(tb);
            tb.linkedElem.add(mus);
            eoc.store(mus);
        }
        eoc.store(tb);
        eoc.commit(); // End load part // // //

        // Two samples of request
        final Query r1 = eoc.query();
        r1.constrain(ElementBody.class);
        r1.descend("typ").constrain(TYP_ELEM.BONES.ordinal()).equal();
        for (final Object obj : r1.execute()) {
            final ElementBody el = (ElementBody) obj;
            System.out.println("\n" + TYP_ELEM.BONES + "\t" + el.name);
            final Iterator<ElementBody> it = el.linkedElem.iterator();
            while (it.hasNext()) {
                System.out.println("\t" + it.next());
            }
        }

        final Query r2 = eoc.query();
        r2.constrain(ElementBody.class);
        r2.descend("typ").constrain(TYP_ELEM.MUSCLES.ordinal()).equal();
        r2.descend("name").constrain("Q").smaller();
        r2.descend("name").orderAscending();

        final ObjectSet<ElementBody> osEl = r2.execute();
        System.out.println("---------------\n" + osEl.size()
            + " muscles in db with A to P prime letter name.");
        final Iterator<ElementBody> itEl = osEl.iterator();
        int sumLink = 0;
        while (itEl.hasNext()) {
            final ElementBody elTmp = itEl.next();
            System.out.print(elTmp.name + ", ");
            sumLink += elTmp.linkedElem.size();
        }
        System.out.println("\nThose muscles have " + sumLink
            + " links to bones, LowerBody's ones excluded.");
        eoc.close();
    }
}

I've add “Shoulders” in two 'Torso' to show how you can feed back your links.

With samples of (SODA) query, you will be able to pick all needed groups.

Console:

BONES   Arms
    Forearms - Arms, 1.
    Triceps - Arms, 1.
    Shoulders - Arms, Torso Back, Torso Front, 3.
    Biceps - Arms, 1.

BONES   Torso Front
    Abs - Torso Front, 1.
    Shoulders - Arms, Torso Back, Torso Front, 3.
    Chest - Torso Front, 1.

BONES   Torso Back
    Lower Back - Torso Back, 1.
    Trapezius - Torso Back, 1.
    Rear Shoulders - Torso Back, 1.
    Shoulders - Arms, Torso Back, Torso Front, 3.
    Lats - Torso Back, 1.

BONES   LowerBody
---------------
9 muscles in db with A to P prime letter name.
Abs, Biceps, Calves, Chest, Forearms, Hams, Hip & Butt, Lats, Lower Back, 
Those muscles have 6 links to bones, LowerBody's ones excluded.

In my sense, it is not a mathematical model, but an intuitive one :

  • what is the real question? → where are real objects? (do not begin with abstraction, or technical solution, if no needed, and decrypt the real problem, behind the beautiful requirements paper)
  • who really works? → what is done? (take the place of the objects, then imagine how you are now represented : hum, not so good representation doesn't it?)
  • who and what is really needed (a lazy developer want to verify/test less possible objects and more simple objects than an 'all home made' one)
  • we manage data? → a database is needed,
  • can we be more simple? → this take time, a night is generally a good elapse time
  • is request the only problem for final user? → right! look him working, he is the only men able to understand what he want, here the problem : experience will show you how decrypt those 'not said but critical' things.

If all [or part] of this is already made → use it

Unless it is for highly stress usage (or OS only known by one genius) all is already written, and often well written and optimized, and secure.

Here I've used something like 'internal reverse index model' (Perhaps exists under another official name) :
Database have indexes on fields (columns for SGBDRs) as all database you know
In OODB we add list/set/BTree/.. to link an instance of an object type to another one of same type, or to another instance of a different type (but always the same instances type linked in one list).

If your project came bigger and more complex you will be able to use such methods without difficulties.
No complexity to load numerous CSV files, no real difficulties to add queries,

  • One class, three fields, and basic methods (your job, already made, only add query for an object before create or update if you do not want delete database each tim - and other pitfall to avoid, listen in reference doc)
  • One OODB (imported) (just configure it, add TA/TP, you can put it inMemory)
  • One class for CSV file (import CsvReader)
  • One class to load Csv in DB (Little job, model in PrmBody)
  • Some simple classes for dedicated queries (not painfull if you understand SODA query)
  • I use JSON, but take your prefered web tool for presentation (may be time eater for all public usage).

Feel free to tell us if it convenient for you.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.