C#在列表中快速查找连续值

C#在列表中快速查找连续值,c#,list,linq,C#,List,Linq,我正在做一个绘制鼠标轨迹的项目。MouseInfo类的定义如下: public class MouseInfo { public readonly long TimeStamp; public readonly int PosX; public readonly int PosY; public int ButtonsDownFlag; } 我需要找到一种方法从列表中提取鼠标位置,其中按钮下旗至少有2个连续的1s,并将它们组合在一起,以便我能够区分单击和拖动,然

我正在做一个绘制鼠标轨迹的项目。
MouseInfo
类的定义如下:

public class MouseInfo {
    public readonly long TimeStamp;
    public readonly int PosX;
    public readonly int PosY;
    public int ButtonsDownFlag;
}
我需要找到一种方法从
列表
中提取鼠标位置,其中
按钮下旗
至少有2个连续的
1
s,并将它们组合在一起,以便我能够区分单击和拖动,然后将其用于打印

我目前的做法是遍历列表,然后将找到的值一个接一个地添加到其他列表中,这非常缓慢、昂贵,而且代码看起来很凌乱。我想知道有没有更优雅的方法?
Linq
会有帮助吗

例如,我录制了以下内容:

(t1, x1, y1, 0)
(t2, x2, y2, 1)
(t3, x3, y3, 1)
(t4, x4, y4, 0)
(t5, x5, y5, 1)
(t6, x6, y6, 0)
(t7, x7, y7, 1)
(t8, x8, y8, 1)
(t9, x9, y9, 1)
(ta, xa, ya, 0)
(tb, xb, yb, 2) <- Yes, ButtonDownFlag can be 2 for RightClicks or even 3 for both buttons are down
(tc, xc, yc, 0)
(td, xd, yd, 2)
(te, xe, ye, 2)

注:

  • 在第一个列表中,我需要将子序列元素中的
    时间戳
    更改为第一个元素的
    时间戳
    ,以便在以后的绘图中进行分组
  • 在第二个列表中,我不关心
    时间戳
    ,但我关心
    按钮关闭标志
  • 我不介意第一个列表中是否存在
    ButtonDownFlag
    ,第二个列表中是否存在
    TimeStamp
  • 连续的“右键单击”被视为单独的“右键单击”,而不是“右键拖动”

  • 有一种方法可以使用LINQ来完成此操作,它将为属于拖动序列的所有事件生成一个列表,并为单个单击事件生成一个单独的列表

                List<MouseInfo> mainList = new List<MouseInfo>();
    
                //populate mainList with some data...
    
                List<MouseInfo> dragList = mainList.Where
                    (
                    // check if the left click is pressed
                        x => x.ButtonsDownFlag == 1
    
                        //then check if either the previous or the following elements are also clicked
                        &&
                        (
                            //if this isnt the first element in the list, check the previous one
                            (mainList.IndexOf(x) != 0 ? mainList[mainList.IndexOf(x) - 1].ButtonsDownFlag == 1 : false)
    
                            //if this isnt the last element in the list, check the next one
                            || (mainList.IndexOf(x) != (mainList.Count - 1) ? mainList[mainList.IndexOf(x) + 1].ButtonsDownFlag == 1 : false)
                        )
                    ).ToList();
    
                List<MouseInfo> clickList = mainList.Where
                    (
                        // check if the left/right or both click is pressed
                        x => (x.ButtonsDownFlag == 1 || x.ButtonsDownFlag == 2 || x.ButtonsDownFlag == 3)
    
                        //then make sure that neither of the previous or following elements are also clicked
                        &&
                        (mainList.IndexOf(x) != 0 ? mainList[mainList.IndexOf(x) - 1].ButtonsDownFlag != 1 : true)
    
                        &&                 
                        (mainList.IndexOf(x) != (mainList.Count - 1) ? mainList[mainList.IndexOf(x) + 1].ButtonsDownFlag != 1 : true)
    
                    ).ToList();
    
    List mainList=新列表();
    //用一些数据填充mainList。。。
    List dragList=mainList.Where
    (
    //检查是否按下了左键
    x=>x.ButtonsDownFlag==1
    //然后检查是否也单击了上一个或以下元素
    &&
    (
    //如果这不是列表中的第一个元素,请选中前一个元素
    (mainList.IndexOf(x)!=0?mainList[mainList.IndexOf(x)-1]。ButtonsDownFlag==1:false)
    //如果这不是列表中的最后一个元素,请选中下一个元素
    ||(mainList.IndexOf(x)!=(mainList.Count-1)?mainList[mainList.IndexOf(x)+1]。ButtonsDownFlag==1:false)
    )
    ).ToList();
    列表单击列表=主列表。其中
    (
    //检查是否按下了左/右或两次单击
    x=>(x.ButtonsDownFlag==1 | | x.ButtonsDownFlag==2 | | x.ButtonsDownFlag==3)
    //然后确保前面或后面的元素都没有被单击
    &&
    (mainList.IndexOf(x)!=0?mainList[mainList.IndexOf(x)-1].ButtonsDownFlag!=1:true)
    &&                 
    (mainList.IndexOf(x)!=(mainList.Count-1)?mainList[mainList.IndexOf(x)+1]。ButtonsDownFlag!=1:true)
    ).ToList();
    
    这种方法的局限性在于不能用相同的时间戳“标记”每个拖拽序列

    另一种选择是在数据捕获点执行此逻辑。捕获每个数据点时,如果其具有“ButtonDown”值,请检查上一个数据点。如果该数据点也是一个“按钮向下”的数据点,则将其同时添加到“dragList”中,否则将其添加到“clickList”中。
    对于这个选项,我还想添加一些逻辑来分离不同的拖动序列。如果您通过更改后续时间点的时间戳来实现这一点,那么我会尝试将您的“dragList”创建为字典。将每个拖拽序列放入不同的不同键。

    我不认为这太容易理解,但它类似于在APL中处理这个问题的方式(我使用Excel来解决)。我也不会保证这有多快——一般来说,
    foreach
    比LINQ快,即使只快一小部分

    使用扩展方法实现APL的扫描和压缩运算符,并在
    IEnumerable
    s中追加/前置:

    public static IEnumerable<TResult> Scan<T, TResult>(this IEnumerable<T> src, TResult seed, Func<TResult, T, TResult> combine) {
        foreach (var s in src) {
            seed = combine(seed, s);
            yield return seed;
        }
    }
    public static IEnumerable<T> Compress<T>(this IEnumerable<bool> bv, IEnumerable<T> src) {
        var srce = src.GetEnumerator();
        foreach (var b in bv) {
            srce.MoveNext();
            if (b)
                yield return srce.Current;
        }
    }
    public static IEnumerable<T> Prepend<T>(this IEnumerable<T> rest, params T[] first) => first.Concat(rest);
    public static IEnumerable<T> Append<T>(this IEnumerable<T> rest, params T[] last) => rest.Concat(last);
    
    完成后,
    拖动
    包含一个
    列表
    ,其中每个子列表都是一个拖动。您可以使用
    SelectMany
    而不是最后一个(外部)
    Select
    来获得一个简单的
    列表。
    
    单击
    将包含一个只需单击的
    列表

    注意,我缩写了
    MouseInfo
    字段名

    顺便说一句,使用
    for
    循环要快得多:

    var inDrag = false;
    var drags = new List<MouseInfo>();
    var clicks = new List<MouseInfo>();
    var beginTime = 0L;
    for (var i = 0; i < moves.Count; ++i) {
        var curMove = moves[i];
        var wasDrag = inDrag;
        inDrag = curMove.b == 1 && (inDrag || (i + 1 < moves.Count ? moves[i + 1].b == 1 : false));
        if (inDrag) {
            if (wasDrag)
                drags.Add(new MouseInfo { t = beginTime, x = curMove.x, y = curMove.y, b = curMove.b });
            else {
                drags.Add(curMove);
                beginTime = curMove.t;
            }
        }
        else {
            if (curMove.b != 0)
                clicks.Add(curMove);
        }
    }
    
    var inDrag=false;
    var drags=新列表();
    var clicks=newlist();
    var beginTime=0L;
    对于(变量i=0;i
    只是想分享一些知识-我发现GroupNext很好地解决了我的问题(还有一些用于后期绘图的tweek)

    性能肯定不是最好的(与for循环相比),但我觉得代码更优雅

    参考:

    公共静态类LocalExtensions{
    公共静态IEnumerable组(
    这是一个数不清的来源,
    Func键选择器){
    TKey last=默认值(TKey);
    bool-haveLast=false;
    列表=新列表();
    foreach(源中的TSource s){
    TKey k=按键选择器;
    if(haveLast){
    如果(!k.Equals(last)){
    返回新的相邻组(列表,最后一个);
    
    public static IEnumerable<TResult> Scan<T, TResult>(this IEnumerable<T> src, TResult seed, Func<TResult, T, TResult> combine) {
        foreach (var s in src) {
            seed = combine(seed, s);
            yield return seed;
        }
    }
    public static IEnumerable<T> Compress<T>(this IEnumerable<bool> bv, IEnumerable<T> src) {
        var srce = src.GetEnumerator();
        foreach (var b in bv) {
            srce.MoveNext();
            if (b)
                yield return srce.Current;
        }
    }
    public static IEnumerable<T> Prepend<T>(this IEnumerable<T> rest, params T[] first) => first.Concat(rest);
    public static IEnumerable<T> Append<T>(this IEnumerable<T> rest, params T[] last) => rest.Concat(last);
    
    // create a terminal MouseInfo for scanning along the moves
    var mterm = new MouseInfo { t = 0, x = 0, y = 0, b = 4 };
    // find the drags for button 1 except the first row
    var bRestOfDrag1s = moves.Append(mterm).Zip(moves.Prepend(mterm), (dm, em) => dm.b == 1 && dm.b == em.b).ToList();
    // number the drags by finding the drag beginnings
    var iLastDragNums = bRestOfDrag1s.Zip(bRestOfDrag1s.Skip(1), (fm, gm) => (!fm && gm)).Scan(0, (a, hm) => hm ? a + 1 : a).ToList();
    // find the drags
    var bInDrag1s = bRestOfDrag1s.Zip(bRestOfDrag1s.Skip(1), (fm, gm) => (fm || gm));
    // number each drag row by its drag number
    var dnmiDrags = bInDrag1s.Compress(Enumerable.Range(0, moves.Count)).Select(idx => new { DragNum = iLastDragNums[idx], mi = moves[idx] });
    // group by drag number and smear first timestamp along drags
    var drags = dnmiDrags.GroupBy(dnmi => dnmi.DragNum)
                         .Select(dnmig => dnmig.Select(dnmi => dnmi.mi).Select(mi => new MouseInfo { t = dnmig.First().mi.t, x = mi.x, y = mi.y, b = mi.b }).ToList()).ToList();
    
    var clicks = bInDrag1s.Select(b => !b).Compress(moves).Where(mi => mi.b != 0).ToList();
    
    var inDrag = false;
    var drags = new List<MouseInfo>();
    var clicks = new List<MouseInfo>();
    var beginTime = 0L;
    for (var i = 0; i < moves.Count; ++i) {
        var curMove = moves[i];
        var wasDrag = inDrag;
        inDrag = curMove.b == 1 && (inDrag || (i + 1 < moves.Count ? moves[i + 1].b == 1 : false));
        if (inDrag) {
            if (wasDrag)
                drags.Add(new MouseInfo { t = beginTime, x = curMove.x, y = curMove.y, b = curMove.b });
            else {
                drags.Add(curMove);
                beginTime = curMove.t;
            }
        }
        else {
            if (curMove.b != 0)
                clicks.Add(curMove);
        }
    }
    
    public static class LocalExtensions {
        public static IEnumerable<IGrouping<TKey, TSource>> GroupAdjacent<TSource, TKey>(
            this IEnumerable<TSource> source,
            Func<TSource, TKey> keySelector) {
            TKey last = default(TKey);
            bool haveLast = false;
            List<TSource> list = new List<TSource>();
            foreach (TSource s in source) {
                TKey k = keySelector(s);
                if (haveLast) {
                    if (!k.Equals(last)) {
                        yield return new GroupOfAdjacent<TSource, TKey>(list, last);
                        list = new List<TSource>();
                        list.Add(s);
                        last = k;
                    } else {
                        list.Add(s);
                        last = k;
                    }
                } else {
                    list.Add(s);
                    last = k;
                    haveLast = true;
                }
            }
            if (haveLast)
                yield return new GroupOfAdjacent<TSource, TKey>(list, last);
        }
    }
    
    class GroupOfAdjacent<TSource, TKey> : IEnumerable<TSource>, IGrouping<TKey, TSource> {
        public TKey Key { get; set; }
        private List<TSource> GroupList { get; set; }
        IEnumerator IEnumerable.GetEnumerator() {
            return ((IEnumerable<TSource>)this).GetEnumerator();
        }
        IEnumerator<TSource> IEnumerable<TSource>.GetEnumerator() {
            foreach (TSource s in GroupList)
                yield return s;
        }
        public GroupOfAdjacent(List<TSource> source, TKey key) {
            GroupList = source;
            Key = key;
        }
    }
    
    private class MouseInfo {
        public readonly long TimeStamp;
        public readonly int PosX;
        public readonly int PosY;
        public int ButtonsDownFlag;
        public MouseInfo(long t, int x, int y, int flag) {
            TimeStamp = t;
            PosX = x;
            PosY = y;
            ButtonsDownFlag = flag;
        }
        public override string ToString() {
            return $"({TimeStamp:D2}: {PosX:D3}, {PosY:D4}, {ButtonsDownFlag})";
        }
    }
    
    public Program() {
        List<MouseInfo> mi = new List<MouseInfo>(14);
        mi.Add(new MouseInfo(1, 10, 100, 0));
        mi.Add(new MouseInfo(2, 20, 200, 1));
        mi.Add(new MouseInfo(3, 30, 300, 1));
        mi.Add(new MouseInfo(4, 40, 400, 0));
        mi.Add(new MouseInfo(5, 50, 500, 1));
        mi.Add(new MouseInfo(6, 60, 600, 0));
        mi.Add(new MouseInfo(7, 70, 700, 1));
        mi.Add(new MouseInfo(8, 80, 800, 1));
        mi.Add(new MouseInfo(9, 90, 900, 1));
        mi.Add(new MouseInfo(10, 100, 1000, 0));
        mi.Add(new MouseInfo(11, 110, 1100, 2));
        mi.Add(new MouseInfo(12, 120, 1200, 0));
        mi.Add(new MouseInfo(13, 130, 1300, 2));
        mi.Add(new MouseInfo(14, 140, 1400, 2));
    
        var groups = mi.GroupAdjacent(x => x.ButtonsDownFlag);
    
        List<List<MouseInfo>> drags = groups.Where(x => x.Key == 1 && x.Count() > 1).Select(x => x.ToList()).ToList();
        foreach (var d in drags) 
            foreach (var item in d)
                Console.Write($"{item} ");
        Console.WriteLine();
    
        List<List<MouseInfo>> clicks = groups.Where(x => x.Key > 1 || (x.Key == 1 && x.Count() == 1)).Select(x => x.ToList()).ToList();
        foreach (var d in clicks) {
            foreach (var item in d)
                Console.Write($"{item} ");
            Console.WriteLine();
        }
    }
    
    [MTAThread]
    static void Main(string[] args) {
        var x = new Program();
        Console.ReadLine();
        return;
    }