我正在开发一个绘制鼠标跟踪的项目。 MouseInfo
类的定义如下:
public class MouseInfo {
public readonly long TimeStamp;
public readonly int PosX;
public readonly int PosY;
public int ButtonsDownFlag;
}
我需要找到一种从List<MouseInfo>
提取鼠标位置的方法ButtonsDownFlag
至少有2个连续1
并将它们组合在一起,以便我可以区分点击次数和draggings,然后将用于绘图。
我目前正在做的方法是遍历列表,并将找到的值逐个添加到其他列表中,这非常慢,价格昂贵且代码看起来很乱。我想知道是否还有更优雅的方式呢? 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)
我想要两个列表(或类似的演示文稿)
((t2, x2, y2), (t2, x3, y3), (t7, x7, y7), (t7, x8, y8), (t7, x9, y9))
和
((x5, y5, 1), (xb, yb, 2), (xd, yd, 2), (xe, ye, 2))
注意:
TimeStamp
更改为第一个元素TimeStamp
,以便我可以在以后的绘图中进行分组。TimeStamp
,但我关心ButtonDownFlag
ButtonDownFlag
,第二个列表中是否存在TimeStamp
。答案 0 :(得分:1)
有一种方法可以使用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();
这种方法确实有一个限制,就是不要用相同的时间戳“标记”每个拖动序列。
另一种方法是在数据捕获时执行此逻辑。捕获每个数据点时,如果它具有“ButtonDown”值,请检查以前的数据点。如果该数据点也是“ButtonDown”,则将它们(或者序列中最多的数量)添加到“dragList”中,否则将其添加到“clickList”。 对于这个选项,我也很想添加一些逻辑来分离你的不同拖拽序列。你通过更改后续点的时间戳来完成此操作,我宁愿尝试将“dragList”创建为字典。将每个拖拽序列放入不同的密钥中。
答案 1 :(得分:1)
我不认为这很容易理解,但它类似于你在APL中处理这个问题的方式(我用Excel来解决它)。我也不承诺这有多快 - 通常foreach
比LINQ快,即使只是少量。
使用扩展方法实现APL的扫描和压缩操作符以及追加/前置到IEnumerable
:
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();
完成后,drags
包含List<List<MouseInfo>>
,其中每个子列表都是一个拖动。您可以使用SelectMany
代替最后一个(外部)Select
来代替平面List<MouseInfo>
。
clicks
只包含点击次数List<MouseInfo>
。
请注意,我缩写了MouseInfo
字段名称。
BTW,使用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);
}
}
答案 2 :(得分:0)
只是尝试分享一些知识 - 我发现GroupAdjacent很好地解决了我的问题(以及后期绘图的一些调整)。
性能肯定不是最好的(与循环相比)但我觉得代码更优雅!
参考:https://blogs.msdn.microsoft.com/ericwhite/2008/04/20/the-groupadjacent-extension-method/
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;
}