除了列表和联盟的替代方案

时间:2013-09-18 02:42:26

标签: c# performance list union except

很抱歉提前发帖,感谢任何人花时间到最后并给我反馈。

我有关于列表和数组操作的性能相关问题。

我编写了一个软件来对从传感器阵列收集的数据执行一些操作。为了让它运行得更快,我目前正在尝试编写一些优化。

收集的数据是N-by-M双打数组(实际上是作为扩展List<List<double>>的类实现的)。对于i的任何值,我们总是this.Count() == Nthis[i].Count() == M。基本上,它是一个矩形阵列。

此数组中的每个数据点都与X-by-Y地图上的某些点相关联。基本上,想象一下这是我用数据制作的图像,以快速清晰的方式表示它。因此,对于每个数据点,存在与其相关的List<int[]>个映射点。这一事实由List<int[]>[,] pointsLocal表示。我还保存了一个静态List<int[]>[,],我存储了相同的信息:这样我可以在精心设计循环中修改pointsLocal到我的闲暇时间,并在下次调用这些方法时获得一个新的副本。相同的传感器将始终与相同的点相关联,这就是我拥有该局部阵列的原因。有些点(实际上大多数点)与多个传感器有关,因此存在于许多列表中。

在我的代码的其他部分,我能够正确识别阵列的某些传感器存在某些问题,然后数据包含错误。这在private List<List<bool>> faultyData中表示。如果传感器输出错误,那么我必须假设所有与之相关的点可能都有故障,因此我不关心那些地图点的进一步分析结果。

我的代码的计算部分聚合来自每个地图点的数组中所有传感器的数据。我想要做的是预先确定一个我不必进行任何分析的地图点子集。

PointsEqualityComparerint[]的自定义比较运算符,我使用它是因为地图点由其2D坐标标识。

public class Sinogram : List<List<double>>
{
    //various enums
   private List<List<bool>> faultyData; //faultyData[i][j] is true if there is an error in the data
    //constructors
    //some methods
    public void dataAnalysis()
    {
        List<int[]>[,] pointsLocal = new List<int[]>[this.Count(), this[0].Count()];
        List<int[]> faultyPoints = new List<int[]>();
        //Fill pointsLocal with the correlated points from the static array
        PointsEqualityComparer myComparer = new PointsEqualityComparer();
        //Point selection parts (see later for the two different implementations)
        //Actual analysis parts (not here because it is not relevant to my question, but it works)
    }
}

比较器类如下。我已经发现GetHashCode方法必须返回尽可能唯一的结果以提高性能,因此我按照您在此代码段中的说明实现了它。

 public class PointsEqualityComparer : IEqualityComparer<int[]>
 {
    public bool Equals(int[] p1, int[] p2)
    {
        bool result = (p1.Count() == p2.Count()) && (p1.Count() == 2) && (p1[0] == p2[0]) && (p1[1] == p2[1]);
        return result;
    }

    public int GetHashCode(int[] obj)
    {
        return ((obj[0] + obj[1]) * (obj[0] + obj[1] + 1) + obj[1]);
    }
}

现在是棘手的部分。我对代码部分有两种不同的实现,我实际上选择哪些地图点是有趣的。有趣的是,我指的是我将要从传感器聚合数据的地图点。我通过实际识别出错误的点并将它们从列表中删除来选择它们。

在我的第一个实现中,我遍历所有map-points列表。如果相应的传感器出现故障,我将这些点添加到故障点列表中(避免重复)。一旦我遍历了所有点并生成了错误列表的完整列表,我通过删除它们来更新allPairsLocal。 faultyPoints列表可能变得相当大,特别是在某些情况下,当许多传感器报告错误时(最大理论大小超过2000000个元素,如果所有传感器报告错误并且我正在尝试创建1920 * 1080地图来绘图作为高清图像)

for (int i = 0; i <this.Count; i++)
{
    for (int j = 0; j < this[i].Count; j++)
    {
        if (faultyData[i][j])
        {
            faultyPoints = faultyPoints.Union<int[]>(allPairsLocal[i, j], myComparer).ToList();
        }
    }
}
for (int i = 0; i <this.Count; i++)
{
    for (int j = 0; j < this[i].Count; j++)
    {
        allPairsLocal[i, j] = allPairsLocal[i, j].Except(faultyPoints, myComparer).ToList();
    }
}

在我的第二个实现中,我尝试使用较小的faultyPoints列表。因此,我所做的是,对于每个传感器报告错误,使用其列表从所有其他传感器中删除地图点(以及它自己的地图点)。这样可以使列表的尺寸更小,但代价是更加环节。

for (int i = 0; i <this.Count; i++)
{
    for (int j = 0; j < this[i].Count; j++)
    {
        if (faultyData[i][j])
        {
            faultyPoints = allPairsLocal[i, j]. ToList();
            for (int x = 0; x < this.Count; x++)
            {
                for (int y = 0; y < this[x].Count; y++)
                {
                    allPairsLocal[x, y] = allPairsLocal[x, y].Except(faultyPoints, myComparer).ToList();
                }
            }
        }
    }
}

这两种实现都非常慢,我想这至少部分是因为数据集的大小。两者都比在整个数据集上执行数据分析步骤更长。 有没有办法进行类似的操作,但实施速度更快?有些步骤可能是平行的,但这并不能真正改变实质。是否存在使用O(1)方法实现我在此处使用Union和Except的数据结构?

再次感谢您阅读了我的整个帖子。我感谢任何反馈,即使它不是一个完整的答案,而且我更能说清楚我能做些什么。

2 个答案:

答案 0 :(得分:2)

如果我理解正确,一旦填充pointsLocal数组,我们就会为每个传感器(i,j)提供以下内容:

  • this[i][j] =来自传感器(i,j)
  • 的数据
  • pointsLocal[i,j] =传感器(i,j)
  • 的地图点列表
  • faultyData[i][j] =如果传感器(i,j)的数据不正确,则为true,否则为

考虑“反转”您的数据,以便给定地图点(x,y),您可以有效地

  • 找出该点是否有问题(即任何传感器报告地图点的数据有误)
  • 获取报告与地图点相关的数据的传感器列表

为此,我们可以创建一个使用您已编写的比较器的字典。每个密钥都是表示地图点的(x,y)对(即int[2]);返回的值(如果有)是有助于该点的已知传感器列表。返回值null表示地图点被故障传感器“感染”,应该被忽略。如果字典中根本不存在给定的对,则意味着没有传感器对该点有贡献。

var mapPoints = new Dictionary<int[], List<int[]>)(PointsEqualityComparer);

for (int i = 0; i <this.Count; i++)
{
    for (int j = 0; j < this[i].Count; j++)
    {
        foreach (var point in pointsLocal[i,j]) 
        {
            if (faultyData[i][j])
            {
                // infected point
                mapPoints[point] = null;  
            }
            else
            {
                // Add the current sensor's indices (i,j) to the list of 
                // known sensors for the current map point

                List<int[]> sensors = null;
                if (!mapPoints.TryGetValue(point, out sensors)) 
                {
                    sensors = new List<int[]>();
                    mapPoints[point] = sensors;
                }

                // null here means that we previously determined that the
                // current map point is infected 
                if (sensors != null) 
                {
                    // Add sensor to list for this map point
                    sensors.Add(new int[] { i, j });
                }
            }
        }
    } 
}

现在,您可以枚举所有地图点,将每个点分类为好或坏:

var faultyPoints = new List<int[]>();  // not sure you really need this? 
var goodPoints = new List<int[]>();
foreach (var point in mapPoints.Keys)
{
    var sensors = mapPoints[point];
    if (sensors == null)
         faultyPoints.Add(point);
    else
         goodPoints.Add(point);
}

最后,您可以枚举每个好地图点的传感器,进行分析:

foreach (var point in goodPoints) 
{
    var sensors = mapPoints[point]; 
    // for current point, aggregate data for each sensor listed in "sensors"
}

请注意,我没有更改allPairsLocal,因为分析步骤似乎没有必要。但是,如果你真的需要从中删除错误的地图点,你也可以有效地做到这一点:

for (int i = 0; i <this.Count; i++)
{
    for (int j = 0; j < this[i].Count; j++)
    {
        var points = allPairsLocal[i][j];
        var cleanedUp = new List<int[]>();
        foreach (var point in points) 
        {
            // Important: do NOT use 'faultyPoints' here. It will kill performance
            if (mapPoints[point] != null)
            {
               cleanedUp.Add(point); 
            }
        }
        allPairsLocal[i][j] = cleanedUp;   
    }
}

所有这一切的性能提升来自于使用Dictionary来查找单个地图点时,只要您需要知道它是否有故障或其贡献的传感器是什么。如果你的哈希函数是好的,查找本质上是一个恒定时间操作(摊销)。

您可以在此处进行一些优化。例如,您是否真的需要知道传感器索引来为每个地图点进行聚合?或者你只需​​要数据值?如果是后者,则您的词典将为Dictionary<List<double>>。最后,通过使用Linq(而不是循环)来执行许多枚举,可以使代码更紧凑。

答案 1 :(得分:1)

是的,你是对的。这是因为联盟和运营复杂性除外 你有N-by-M传感器表(你在上面将它命名为Lists of map-points)。每个传感器都会影响一系列点(您将其命名为allPairsLocal[i, j])。并且每个点阵列都是全局预定点阵列(points on a X-by-Y map)的子集 如果我是对的,那么:

  1. X-by-Y地图上的点 - 这是一个全局点数组。更重要的是,因为你可以比较点,你可以对它们进行排序并保持这个数组排序(我的意思是可能没有实际排序,但具有良好的读操作复杂性)。使用Dictionary<int[], int>作为关键点坐标,值 - 订单索引(在插入所有点后设置)。
  2. 现在我们有一组传感器(让我们从步骤1中将Dictionary<int[], int>命名为点)。我们需要构造2个映射 - 一个sensors2points(将其命名为s2p)和points2sensors(将其命名为p2s)。您有allPairsLocal作为sensors2points,看起来像List<int[]>[][],即每个传感器的点坐标列表。但我们需要将索引列表保留到每个传感器的点坐标,即将int[]转换为points中的订单索引:

    // straight and inverted mappings
    var s2p = new List<int>[N*M];
    var p2s = new List<List<int>>(point.Count);
    //and initialize p2s inner lists
    for (int i = 0; i < p2s.Count; i++)
        p2s[i] = new List<int>();
    
    for (int i = 0; i < N * M; i++)
    {
        s2p[i] = new List<int>(allPairsLocal[i/M][i%M].Count);
    
        //convert list of points coordinates to list of it's indices
        // and construct inverted mapping
        foreach(int[] p in allPairsLocal[i/M][i%M])
        {
            // points[p] - index of point p in Dictionary if you remember
            s2p[i].Add(points[p]);
            p2s[points[p]].Add(i);
        }            
    }
    
  3. 我认为很明显,步骤1和2只需要在初始化时执行一次。然后选择您需要的有趣点:

    //I don't know which set you need as a result - valid points or sensors so I do both
    
    // false - correct, true - error. Initialized with false
    BitArray sensorsMask = new BitArray(sensors.Count);
    BitArray pointsMask = new BitArray(points.Count);
    
    for (int i = 0; i < N * M; i++)
    {
        if (faultyData[i / M][i % M])
            sensorsMask[i] = true; // means error in sensor
    
        foreach(int p in s2p[i])
            pointsMask[p] = true;
    }
    
    // so you can get only valid sensors
    var validSensors = new List<int>();
    for (int i = 0; i < N * M; i++)
        if (!sensorsMask[i])
            validSensors.Add(i);
    
    // or only valid points
    var validPoints = new List<int[]>();
    foreach (var pair in points)
        if (!pointsMask[pair.Value])
            validPoints.Add(points.Key);
    

    这可能不是非常有效的方式(很难说出你想要得到什么),但它比使用套装更好。我的意思是玩mask-array vs sets。希望它会有所帮助。