2013-12-17 43 views
2

我已经采用一个DataTable作为参数,并返回NormalData类型的对象为在数据表中 NormalData定义中的每个列的函数重构LINQ方法

public class NormalData 
{ 
    //AttributeName = ColumnName of DataTable 
    public string AttributeName { get; set; } 

    //each column will have its mean and standard deviation computed 
    public double Mean { get; set; } 
    public double StandardDeviation { get; set; } 

    //a DataTable with three columns will create an IEnumerable<NormalData> 
    //with a count of three 
} 

以下工作,但我想一个我如何实现这第二个观点:

public static IEnumerable<NormalData> GetNormalDataByTableColumns(DataTable dt) 
{ 
    //get list of column names to iterate over 
    List<string> columnList = GetDataTableColumnNames(dt); 
    List<NormalData> normalDataList = new List<NormalData>(); 
    for (int i = 0; i < columnList.Count; i++) 
    { 
     //creates a NormalData object for each column in the DataTable 
     NormalData normalData = new NormalData(); 

     //find average 
     normalData.Mean = GetColumnAverage(dt, columnList[i]); 

     //find stDev 
     normalData.StandardDeviation = GetColumnStDev(dt,columnList[i],normalData.Mean); 
     normalData.AttributeName = columnList[i]; 

     //add to NormalDataList 
     normalDataList.Add(normalData); 
    } 

    return normalDataList; 
} 

private static List<string> GetDataTableColumnNames(DataTable dt) 
{ 
    return (from DataColumn dc in dt.Columns 
     select dc.ColumnName).ToList(); 
} 

private static double GetColumnAverage(DataTable dt, string columnName) 
{ 
    return dt.AsEnumerable().Average(x => x.Field<double>(columnName)); 
} 

private static double GetColumnStDev(DataTable dt, string columnName,double average) 
{ 
    var squaredDiffs = (dt.AsEnumerable() 
     .Sum(x => (x.Field<double>(columnName) - average) * 
     x.Field<double>(columnName) - average)); 

    return Math.Sqrt(squaredDiffs/dt.Rows.Count); 
} 

我的感觉是糟糕的设计是参数列表GetColumnAverageGetColumnStDev都必须参加。实际上,他们应该只需要一个数值类型列表(不一定是双精度值,但是此时硬编码)来计算它们的值。然而,这是我今天早上得到这个工作的唯一途径。在这个设计中我打破了什么规则?我该如何修改这个以使GetColumn..函数只接受在columnListfor循环中迭代的DataColumn

编辑:average变量为每列更改,不能重新使用。或者是否有可能这是好的设计,如果我不需要计算标准偏差并且是,只有平均值,我需要重载这些方法的版本?

+4

这个问题似乎是题外话,因为它所需要的代码审查,而不是要求特定的编程问题。 – Servy

+1

@Servy当然我有偏见,但我不同意。我相信有一个基本的LINQ方法因子的概念,我没有正确实现,而这正是我所掌握的。但是,如果大多数人同意你的意见,那么对于被移植到CodeReview的问题我没有任何问题。 – wootscootinboogie

+0

除非您希望方法返回名为St. Dev的对象,否则请考虑更有意义的名称。 – Magus

回答

1
  1. 可以使用,而不是毫无意义的i的有意义的迭代器columnName一个foreach循环替换您for循环。

    或者,您可以用Select替换循环。

  2. 我会把列选择逻辑放在那个循环中,从计算中分离出来。
  3. 你不需要,如果您使用以下身份的平均传递给StdDev功能:

    (从http://en.wikipedia.org/wiki/Standard_deviation

你的循环就变成了:

foreach (string columnName in columnList) 
{ 
    var columnData = dt.AsEnumerable().Select(x => x.Field<double>(columnName)); 

    //creates a NormalData object for each column in the DataTable 
    NormalData normalData = new NormalData(); 
    normalData.Mean = columnData.Average(); 
    normalData.StandardDeviation = StdDev(columnData); 
    normalData.AttributeName = columnName; 

    //add to NormalDataList 
    normalDataList.Add(normalData); 
} 

有一个辅助方法:

public static double StdDev(IEnumerable<double> seq) 
{ 
    long count = 0; 
    double sum = 0; 
    double sumOfSquares = 0; 
    foreach(var value in seq) 
    { 
     sum += value; 
     sumOfSquares += value * value; 
     count++; 
    } 
    double average = sum/count; 
    double averageSquare = sumOfSquares/count; 
    return Math.Sqrt(averageSquare - average * average); 
} 
1

免责声明:已经有一段时间,因为我已经触及.NET所以这都可能是废话:)

当你看到像这样的一个或两个参数静态方法,我总是问我自己“可以这样方法属于对象本身”,所以作为一个例子,如果你有这样的方法:

public static String FullAddress(Address address){ 
//Build full address from address properties 
} 

这将是昭然若揭,我认为这种方法应该属于地址对象即直接address.FullAddress上。

此外,我看方法名称,看看我传递的是否真的与任务相关。对于像平均值和标准差这样的东西,这些方法只应该真正考虑这些值,而不管它们是来自DataTable,文件还是其他任何东西。

考虑到这一点我的第一个任务是提取平均计算,即(只是显示界面,你猜实现)

interface NormalCalculator { 
    double CalculateAverage(IEnumerable<double> values) 
    double CalculateStDev(IEnumerable<double> values) 
} 

回到我最初说,大约看到一个方法的单输入,你询问这些方法是否可以直接在IEnumerable上生存,而不是作为单独的类。现在,我们只能将其作为扩展方法来执行,并且您知道已经有一种平均扩展方法。

那么问题是为什么不把另一个扩展方法添加到IEnumerable来计算标准偏差。

喜欢的东西:Standard Deviation in LINQ

另外,我不认为这将是太多滥用添加一个扩展方法来获取得到一个即值

public static class DataTableExtensions 
{ 
    public static IEnumerable<Double> Values(this DataTable dt, DataColumn column) 
    { 
     return dt.AsEnumerable().Select(x => x.Field<double>(column.ColumnName)); 
    } 
} 

然后所有的留在你的转化方法是:

public static IEnumerable<NormalData> GetNormalDataByTableColumns(DataTable dt) 
    { 
     dt.Columns.Select(c => { 
      var values = dt.Values(c); 
      return new NormalData(values.Average(), values.StDev, c.ColoumnName); //Added constructor too as it makes sense to me, but can use named args if not 
     }); 
    } 

最后,这基本上是一个映射器/适配器/变压器,无论你怎么称呼它(即它需要在一个对象并将其映射/调整或转换为另一个)。

我不知道如果你的测试在你的应用程序中很多,但将它作为一个静态工具方法意味着每次你可能想要测试使用NormalData的东西时,你必须用正确的初始数据创建一个DataTable建立。我会把它移到一个单独的映射/转换类中,即

interface INormalDataMapper { 
    NormalData Map(DataTable dataTable); 
} 

这是你可以用你的POCO对象模拟Map方法返回。虽然这可能是太多的信息:)。

克里斯

1

怎么是这样的:

public sealed class NormalData 
{ 
    private readonly string _attributeName; 
    private uint _count; 
    private double _sum; 
    private double _sumOfSquares; 

    private NormalData(string attributeName) 
    { 
     _attributeName = attributeName; 
    } 

    public string AttributeName 
    { 
     get { return _attributeName; } 
    } 

    public double Mean 
    { 
     get { return _count == 0 ? double.NaN : _sum/_count; } 
    } 

    public double StandardDeviation 
    { 
     get 
     { 
      if (_count == 0) return double.NaN; 

      var diff = _sumOfSquares - (_sum * _sum/_count); 
      return Math.Sqrt(diff/_count); 
     } 
    } 

    public double EstimatedStandardDeviation 
    { 
     get 
     { 
      if (_count < 2) return double.NaN; 

      var diff = _sumOfSquares - (_sum * _sum/_count); 
      return Math.Sqrt(diff/(_count - 1)); 
     } 
    } 

    public void Add(double value) 
    { 
     _count = checked(_count + 1); 
     _sum += value; 
     _sumOfSquares += (value * value); 
    } 

    public static NormalData Create(string attributeName) 
    { 
     return new NormalData(attributeName, 0, 0, 0); 
    } 
} 

public static IEnumerable<NormalData> GetNormalDataByTableColumns(DataTable dt) 
{ 
    var normalDataList = dt.Columns.Cast<DataColumn>().Select(c => NormalData.Create(c.ColumnName)).ToList(); 

    foreach (DataRow row in dt.Rows) 
    { 
     foreach (NormalData item in normalDataList) 
     { 
      double value = row.Field<double>(item.AttributeName); 
      item.Add(value); 
     } 
    } 

    return normalDataList; 
}