Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Split collection into objects based on condition and occurrence

Given is the following array (each block [] represents an entry):

[A=1] [A=5] [S=3] [A=7] [C=3] [T=2] [F=9] [Z=4] [N] [C=3] [E=8]
[A=7] [N] [Z=6] [Q=1] [P=2] [Y=7] [S=3] [N] 

I need to split it in to objects of type 'N' (NObject) where as every other character represents a specific property of that NObject object until the next occurence of 'N'. Until the first occurrence of 'N', the characters belong to another object (let's call it the PObject). So the tasks should fulfill the follwing:

  1. Map each character to a PObject property
  2. When the first 'N' occurrs, create a new NObject
  3. Map each character to a property of that NObject
  4. If another N character occurs, create a new NObject

Currently, in pseudocode my solution looks like the following which I find is far from ideal.

PObject pobject = new PObject();
NObject nobject;

CollectionOfKeyValuePairs collection = MyArray.Split('=').MapKeysValues()

foreach(entry in collection) {
    switch(entry.Key):
        case A:
            (nobject ?? (CommonBase) pobject).A += entry.Value; break;
        case B:
            (nobject ?? (CommonBase) pobject).B += entry.Value; break;
        case C:
            (nobject ?? (CommonBase) pobject).C += entry.Value; break;
        case E:
            pobject.E += entry.Value; break;
        case F:
            (nobject ?? (CommonBase) pobject).F += entry.Value; break;
        case G:
            (nobject ?? (CommonBase) pobject).G += entry.Value; break;
        case H:
            (nobject ?? (CommonBase) pobject).H += entry.Value; break;
        ...
        ...
        ...
        case N:
             nobject = new NObject();
        ....
        ....
    }
}

Which gives me exactly what I want:

[pobject]
A = 23
B = 63
C = 23
...

[nobject]
A = 34
B = 82
C = 12
...

[nobject]
H = 236
K = 2
...

[nobject]
// N occurred in array, but no properties followed

But with over 30 possible property identifiers (which means 30 switch conditions) and a property assigned only based on the fact that nobject may be null (and creating a new one each 'N' char occurrence): The code is incredibly smelly. But I don't know how to do it different, maybe with builtin collection functions, LINQ or anything other.

like image 737
user2644817 Avatar asked Aug 02 '13 07:08

user2644817


2 Answers

You could use Dictionary to store key-value pairs instead of explicitly create properties for each possible case. Something like:

List<Dictionary<char,int>> listOfPNObjects = new List<Dictionary<char,int>>();
listOfPNObjects.Add(new Dictionary<char,int>())    //create default P dictionary
foreach(entry in collection) {
    if(entry.Key == N)
    {
            listOfPNObjects.Add(new Dictionary<char,int>());
    }
    else
    {
          listOfPNObjects[listOfPNObjects.Count - 1].Add(entry.key, entry.value);
    }

}
like image 175
ne2dmar Avatar answered Nov 09 '22 04:11

ne2dmar


I've rewritten your code by using reflection and LINQ:

var objects = keyValuePairList
    .Aggregate<KeyValuePair<string, dynamic>, List<CommonBase>>(
        new List<CommonBase>(), (a, p) =>
            {
                CommonBase cObject;
                if (p.Key == "N")
                {
                    cObject = new NObject();
                    a.Add(cObject);
                }
                if (a.Count == 0)
                {
                    cObject = new PObject();
                    Process(p, ref cObject);
                    a.Add(cObject);
                }
                else
                {
                    cObject = a.Last();
                    Process(p, ref cObject);
                }
                return a;
            });

Inside the Process method is where you may handle the processing of the properties, based on their type:

private static void Process(
        KeyValuePair<string, dynamic> kvPair,
        ref CommonBase cObject)
{
    var propertyInfo = typeof(CommonBase).GetProperty(kvPair.Key);
    switch (propertyInfo.PropertyType.FullName)
    {
        case "System.Int32":
            propertyInfo
                .SetValue(cObject,
                    (int)propertyInfo.GetValue(cObject) + (int)kvPair.Value);
            break;
    }
}
like image 20
Alex Filipovici Avatar answered Nov 09 '22 02:11

Alex Filipovici