3

I have a list of doubles List<double[]> which I want to convert to a List<T> where T is a class.

The array of double contains 17 values e.g. [1.0, 2.0, 3.0, 4.0, 5.0,.. 17.0 ]. Then I have a class which defines 17 string properties e.g P1, P2, .... , P17

So every element of an List<double[]> is a array of doubles and, every element in the array represents a value for the property in the class of type T.

Is it possible to map each index of the given array of doubles to a property of class of type T. So I convert a List<double[]> to List<T> where T is class.

I know it can be done by manually iterate over the list read each array and then read value from each index of the array and pass it to corresponding property of a class. But it a lot to do when I have many class with 10+ properties.

EDIT: An example of one of the classes is given below

    /// <summary>
/// Defines the properties of a centroid.
/// </summary>
public class Centroid
{
    // ReSharper disable InconsistentNaming
    /// <summary>
    /// Calls made to contacts
    /// </summary>
    public string CONTACT_CALLS { get; set; }

    /// <summary>
    /// Duration of calls made to contacts
    /// </summary>
    public string CONTACT_CALLS_SEC { get; set; }

    /// <summary>
    /// Calls made to non contacts
    /// </summary>      
    public string UNKNOWN_CALLS { get; set; }

    /// <summary>
    /// Duration of calls made to non contacts
    /// </summary>
    public string UNKNOWN_CALLS_SEC { get; set; }

    /// <summary>
    /// Number of SMS sent to contacts
    /// </summary>
    public string SMS_OUT_CONTACTS { get; set; }

    /// <summary>
    /// Number of SMS sent to non contacts
    /// </summary>
    public string SMS_OUT_UNKNOWN { get; set; }

    /// <summary>
    /// Percentage of CPU usaed
    /// </summary>
    public string CPU_USAGE { get; set; }

    /// <summary>
    /// Percentage of RAM used
    /// </summary>
    public string RAM_USAGE { get; set; }

    /// <summary>
    /// Number of system application
    /// </summary>
    public string SYS_APPS { get; set; }

    /// <summary>
    /// Number of user applications
    /// </summary>
    public string USER_APPS { get; set; }

    /// <summary>
    /// Number of system services
    /// </summary>
    public string SYS_SERVICES { get; set; }

    /// <summary>
    /// Number of user services
    /// </summary>
    public string USER_SERVICES { get; set; }

    /// <summary>
    /// Number of bytes sent
    /// </summary>
    public string BYTES_TX { get; set; }

    /// <summary>
    /// Number of bytes received
    /// </summary>
    public string BYTES_RX { get; set; }

    /// <summary>
    /// Latitute of the location
    /// </summary>
    public string LOC_LAT { get; set; }

    /// <summary>
    /// Longitude of the location
    /// </summary>
    public string LOC_LON { get; set; }

    /// <summary>
    /// The Time Slice
    /// </summary>
    public string TIME_SLICE { get; set; }

    // ReSharper restore InconsistentNaming
}

EDIT2: Method which creates a list from list, NOTE _profiler.FinalCentroid is of type List<double[]>

    private void CreateListOfCentroids()
    {
        _centroidsList = new CentroidsList {Centroids = new List<Centroid>()};

        foreach (var centroid in _profiler.FinalCentroid.Select(doublese => new Centroid
        {
            CONTACT_CALLS = doublese[0].ToString(CultureInfo.InvariantCulture),
            CONTACT_CALLS_SEC = doublese[1].ToString(CultureInfo.InvariantCulture),
            UNKNOWN_CALLS = doublese[2].ToString(CultureInfo.InvariantCulture),
            UNKNOWN_CALLS_SEC = doublese[3].ToString(CultureInfo.InvariantCulture),
            SMS_OUT_CONTACTS = doublese[4].ToString(CultureInfo.InvariantCulture),
            SMS_OUT_UNKNOWN = doublese[5].ToString(CultureInfo.InvariantCulture),
            CPU_USAGE = doublese[6].ToString(CultureInfo.InvariantCulture),
            RAM_USAGE = doublese[7].ToString(CultureInfo.InvariantCulture),
            SYS_APPS = doublese[8].ToString(CultureInfo.InvariantCulture),
            USER_APPS = doublese[9].ToString(CultureInfo.InvariantCulture),
            SYS_SERVICES = doublese[10].ToString(CultureInfo.InvariantCulture),
            USER_SERVICES = doublese[11].ToString(CultureInfo.InvariantCulture),
            BYTES_TX = doublese[12].ToString(CultureInfo.InvariantCulture),
            BYTES_RX = doublese[13].ToString(CultureInfo.InvariantCulture),
            LOC_LAT = doublese[14].ToString(CultureInfo.InvariantCulture),
            LOC_LON = doublese[15].ToString(CultureInfo.InvariantCulture),
            TIME_SLICE = doublese[16].ToString(CultureInfo.InvariantCulture)
        }))
        {
            _centroidsList.Centroids.Add(centroid);
        }
    }//end method
Khurram Majeed
  • 2,291
  • 8
  • 37
  • 59
  • 13
    Does your class *really* need to have 17 different properties with those names? Wouldn't it make more sense for it to keep the values as a collection? – Jon Skeet May 13 '13 at 16:26
  • What does the resulting class look like? (example code) If you really need this (and I don't think you do), I think you'll need to do some form of code generation to accomplish it. – Robert Harvey May 13 '13 at 16:27
  • @JonSkeet The properties have different names and different min/max values. I only used p1, p2 etc for simplicity. – Khurram Majeed May 13 '13 at 16:30
  • See this: http://stackoverflow.com/questions/16450871/passing-properties-as-parameters-to-be-got-and-set – Daniel Möller May 13 '13 at 16:31
  • @RobertHarvey please see updated OP – Khurram Majeed May 13 '13 at 16:32
  • How does your code that creates the class know what are the property names to map the double values to? – Robert Harvey May 13 '13 at 16:34
  • 2
    @KhurramMajeed: That significantly affects the question then. With the names as they were in the original loop, they're predictable in a loop (`"P" + i`). Now it's completely different. (Ick to the property names, btw.) Given that you'd have to list the property names in the right order *somewhere*, I can't see why you wouldn't just have assignment statements in a constructor taking `IList` or whatever. – Jon Skeet May 13 '13 at 16:37
  • @JonSkeet: Presumably you could reorder the property names to be in correct alignment with the list of doubles. It seems dodgy, though. – Robert Harvey May 13 '13 at 16:43
  • @RobertHarvey I have added the method in my post above – Khurram Majeed May 13 '13 at 16:45
  • 1
    @KhurramMajeed: Yes, that's exactly what I do in my apps. It's ugly and a pain in the ass, but unless you have a lot of classes like that, it's going to be the fastest and most reliable way, and it documents things for the next person who has to read your code. Copy/paste is your friend. :) – Robert Harvey May 13 '13 at 16:52
  • Note that you don't necessarily need to change the type. The consumer of the class should probably be the one calling ToString(), not you. Also, if this is just about creating ViewModel classes, there might be better ways to do this. Have a look at [Automapper](https://github.com/AutoMapper/AutoMapper), and see if it does what you want. – Robert Harvey May 13 '13 at 16:58
  • 2
    @RobertHarvey: I wouldn't want to trust that the order they're returned from reflection represents the order in source code. – Jon Skeet May 13 '13 at 16:58
  • @JonSkeet: Yes, that's my concern as well. – Robert Harvey May 13 '13 at 16:59
  • 2
    Why are your properties `string`? I think it would be much better if they were `double`. – svick May 13 '13 at 17:11

4 Answers4

5

To post the obvious, why not just assign the properties in the constructor?
You are going to the work of creating the properties.
Assigning the values in the constructor is less keystrokes than the property.

List<double[]> ld = new List<double[]>();
List<PropDouble> lpd = new List<PropDouble>();
foreach (double[] da in ld) { lpd.Add(new PropDouble(da)); }

public class PropDouble
{
    public double P0 { get; set; }
    public double P1 { get; set; }
    public PropDouble(double[] doubles) { P0 = doubles[0]; P1 = doubles[1]; }
}

or

public class PropDouble
{
    private double[] doubles;
    public double P0 { get { return doubles[0]; } set { doubles[0] = value; } }
    public double P1 { get { return doubles[1]; } set { doubles[1] = value; } }
    public PropDouble(double[] Doubles) { doubles = Doubles; }
}
paparazzo
  • 44,497
  • 23
  • 105
  • 176
  • +1, beautifully simple. Works in the case of `Activator.CreateInstance`, etc. – user7116 May 13 '13 at 17:58
  • +1 I honestly can't think of a scenario where this wouldn't work (over the more complicated solution I recommended). I feel dumb. :) – JDB May 13 '13 at 18:38
2

EDIT: Since, as pointed out by Blam, the most obvious answer would be a simple constructor, I will update this answer for a more complex scenario.

Say that you get your arrays from different locations. They may differently ordered or there may be missing values. In that case, you can use custom attributes and reflection to map your properties to different array indices. Each map is named so that you can use different indices (or none at all) for different arrays.

Note that reflection is going to cost performance in a big way. For a handful of objects, this cost will barely be noticable, but if you are processing thousands or more objects then you may want to consider refactoring.

This is the custom attribute you use to map a property to an index

[AttributeUsage( AttributeTargets.Property, AllowMultiple = true )]
class ArrayToPropertyMap : Attribute
{
    public string ArrayName
    {
        get;
        set;
    }

    public int ArrayIndex
    {
        get;
        set;
    }

    /* Call this function to do the actuall mapping (target is updated) */
    public static IEnumerable<U> Map<U, T>( IEnumerable<T[]> source, string arrayName ) where U : new()
    {
        if ( !source.Any() )
            return new U[] { };

        var l_mappedProperties =
            typeof( U )
            .GetProperties( System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance )
            .Where(
                p => ( p.PropertyType == typeof( T ) ) && Attribute.IsDefined( p, typeof( ArrayToPropertyMap ) ) )
            .Select(
                p => new
                {
                    Property = p,
                    Attribute = p.GetCustomAttributes( typeof( ArrayToPropertyMap ), true )
                                 .Cast<ArrayToPropertyMap>()
                                 .Where( a => a.ArrayName == arrayName )
                                 .FirstOrDefault()
                } )
            .Where(
                p => p.Attribute != null )
            .Select(
                p => new
                {
                    Property = p.Property,
                    Index = p.Attribute.ArrayIndex
                } );

        var l_result = new List<U>();

        foreach ( var array in source )
        {
            var l_target = new U();
            foreach ( var l_mappedProperty in l_mappedProperties )
                l_mappedProperty.Property.SetValue( l_target, array[l_mappedProperty.Index], null );
            l_result.Add( l_target );
        }

        return l_result;
    }

}

This is an example class (so you can see it working)

class LotsaProps1
{

    [ArrayToPropertyMap( ArrayName = "Array1", ArrayIndex = 0 )]
    [ArrayToPropertyMap( ArrayName = "Array2", ArrayIndex = 3 )]
    public string Prop1
    {
        get;
        set;
    }

    [ArrayToPropertyMap( ArrayName = "Array1", ArrayIndex = 2 )]
    [ArrayToPropertyMap( ArrayName = "Array2", ArrayIndex = 2 )]
    public string Prop2
    {
        get;
        set;
    }

    /* Notice that Prop3 is not mapped to Array1 */
    [ArrayToPropertyMap( ArrayName = "Array2", ArrayIndex = 1 )]
    public string Prop3
    {
        get;
        set;
    }

    [ArrayToPropertyMap( ArrayName = "Array1", ArrayIndex = 1 )]
    [ArrayToPropertyMap( ArrayName = "Array2", ArrayIndex = 0 )]
    public string Prop4
    {
        get;
        set;
    }
}

This part just runs the example

class Program
{
    static void Main( string[] args )
    {

        /* You should already have the arrays... */
        string[][] arr1 = new string[][] { 
            new string[] { "Value A 1", "Value A 2", "Value A 3" },
            new string[] { "Value A 4", "Value A 5", "Value A 6" },
            new string[] { "Value A 7", "Value A 8", "Value A 9" },
        };
        string[][] arr2 = new string[][] { 
            new string[] { "Value B 1", "Value B 2", "Value B 3", "Value B 4" },
            new string[] { "Value B 5", "Value B 6", "Value B 7", "Value B 8" },
            new string[] { "Value B 9", "Value B 10", "Value B 11", "Value B 12" },
        };

        /* ...so this is really the only code you'd need to add to your
              business logic: */
        var l_objs1 = ArrayToPropertyMap.Map<LotsaProps1, string>( arr1, "Array1" );
        var l_objs2 = ArrayToPropertyMap.Map<LotsaProps1, string>( arr2, "Array2" );

        /* This code is just used to show that the example works: */
        Console.WriteLine( "Array1:" );
        foreach ( var l_obj in l_objs1 )
        {
            Console.Write( "Prop1='" + l_obj.Prop1 + "'; " );
            Console.Write( "Prop2='" + l_obj.Prop2 + "'; " );
            Console.Write( "Prop3='" + l_obj.Prop3 + "'; " );
            Console.WriteLine( "Prop4 = '" + l_obj.Prop4 + "'" );
        }
        Console.WriteLine( "Array2:" );
        foreach ( var l_obj in l_objs2 )
        {
            Console.Write( "Prop1='" + l_obj.Prop1 + "'; " );
            Console.Write( "Prop2='" + l_obj.Prop2 + "'; " );
            Console.Write( "Prop3='" + l_obj.Prop3 + "'; " );
            Console.WriteLine( "Prop4 = '" + l_obj.Prop4 + "'" );
        }

        Console.ReadKey( true );

    }
}

Output

Array1:
Prop1='Value A 1'; Prop2='Value A 3'; Prop3=''; Prop4 = 'Value A 2'
Prop1='Value A 4'; Prop2='Value A 6'; Prop3=''; Prop4 = 'Value A 5'
Prop1='Value A 7'; Prop2='Value A 9'; Prop3=''; Prop4 = 'Value A 8'
Array2:
Prop1='Value B 4'; Prop2='Value B 3'; Prop3='Value B 2'; Prop4 = 'Value B 1'
Prop1='Value B 8'; Prop2='Value B 7'; Prop3='Value B 6'; Prop4 = 'Value B 5'
Prop1='Value B 12'; Prop2='Value B 11'; Prop3='Value B 10'; Prop4 = 'Value B 9'
JDB
  • 25,172
  • 5
  • 72
  • 123
  • I have 5,000-10,000 items in each List, So I assume that solution provided @Blam is better suited for my problem. Nevertheless thank you very much for your detailed answer. – Khurram Majeed May 16 '13 at 10:12
0
using System;

class Test
{
    public string P1 { get; set; }

    public string P2 { get; set; }
}

class MainClass
{
    static T MapArray<T>(double[] array, string propertyStartWith) where T: new()
    {
        T obj = new T();
        Type t = typeof(T);
        for (int i = 0; i < array.Length; i++)
        {
            var property = t.GetProperty(propertyStartWith + (i + 1).ToString());
            property.SetValue(obj, Convert.ChangeType(array[i], property.PropertyType), null);
        }
        return obj;
    }

    public static void Main (string[] args)
    {
        double[] d = new double[] {
            Math.PI, Math.E
        };
        Test t = MapArray<Test> (d, "P");
        Console.WriteLine (t.P1);
        Console.WriteLine (t.P2);
    }
}
Denis
  • 5,894
  • 3
  • 17
  • 23
0

Here is a simple implementation without error checking, but you could specify your own conversion function. Currently it does Convert.ChangeType by default:

void Main()
{
    var list = new List<double> { 1.0, 1.1, 2.2, 3.3 };
    var instances = new List<A> { new A(), new A(), new A(), new A() };
    ConvertZipMap(list, instances, item => item.P);

    // 1
    // 1
    // 2
    // 3
}

class A {
    public int P { get; set; }
}

void ConvertZipMap<TSource,TTarget,TProperty>(
    IEnumerable<TSource> source,
    IEnumerable<TTarget> target,
    Expression<Func<TTarget, TProperty>> propertySelector,
    Func<TSource, TProperty> conversion = null) {

    // create setter
    var member = (MemberExpression)propertySelector.Body;
    var param = Expression.Parameter(typeof(TProperty), "value");
    var setExpr = Expression.Lambda<Action<TTarget, TProperty>>(
        Expression.Assign(member, param),
        propertySelector.Parameters[0],
        param);
    var set = setExpr.Compile();

    // resolve conversion method
    conversion = conversion ?? (x => (TProperty)Convert.ChangeType(x, typeof(TProperty)));

    // convert -> zip -> map
    foreach(var zip in source.Select(conversion).Zip(target, (value, item) => new { item, value })) {
        set(zip.item, zip.value);
    }
}

The key part to this answer is how to create a setter from a getter expression.

Community
  • 1
  • 1
m0sa
  • 10,712
  • 4
  • 44
  • 91