Linq:连续检查相同值的次数

时间:2017-02-02 06:54:58

标签: c# algorithm linq

这是我的第一个问题,如果不是很清楚,你可以要求提供额外的信息。请记住,英语不是我的母语:)。

我想知道是否有可能为下一个规范提供优雅的方式。 我认为linq可能是一种可能性,但我没有足够的技术经验来使它工作:)。

备注这不是一项家庭作业,它只是一种解决这类问题的新方法。

我已经尝试过aggegrate功能,也许一个动作可以提供帮助。

我想跟踪:

  • 连续数组中出现值的最大次数。
  • 每个值应显示连续出现的值的最大次数

例如:

我们有一个包含6个元素的数组,元素为0或1

0 , 0 , 0 , 1 , 1 ,0 result : 3 times 0 , 2 times 1
0 , 0 , 1 , 1 , 1 ,0 result : 2 times 0 , 3 times 1
0 , 1 , 0 , 1 , 1 ,0 result : 1 time  0 , 2 times 1
0 , 0 , 1 , 1 , 0 ,0 result : 2 times 0 , 2 times 1

提前致谢

6 个答案:

答案 0 :(得分:1)

我认为 Linq 不是一个好方法;但简单方法会做:

// Disclamer: Dictionary can't have null key; so source must not coтtain nulls
private static Dictionary<T, int> ConsequentCount<T>(IEnumerable<T> source) {
  if (null == source)
    throw new ArgumentNullException("source");

  Dictionary<T, int> result = new Dictionary<T, int>();

  int count = -1;
  T last = default(T);

  foreach (T item in source) {
    count = count < 0 || !object.Equals(last, item) ? 1 : count + 1;
    last = item;

    int v;

    if (!result.TryGetValue(last, out v))
      result.Add(last, count);
    else if (v < count)
      result[item] = count;
  }

  return result;
}

试验:

  int[][] source = new int[][] { 
    new[] { 0, 0, 0, 1, 1, 0 },
    new[] { 0, 0, 1, 1, 1, 0 },
    new[] { 0, 1, 0, 1, 1, 0 },
    new[] { 0, 0, 1, 1, 0, 0 }, };

  string report = string.Join(Environment.NewLine, source
    .Select(array => $"{string.Join(" , ", array)} result : " + 
                         string.Join(", ", 
        ConsequentCount(array)
          .OrderBy(pair => pair.Key)
          .Select(pair => $"{pair.Value} times {pair.Key}"))));

  Console.Write(report);

结果:

0 , 0 , 0 , 1 , 1 , 0 result : 3 times 0, 2 times 1
0 , 0 , 1 , 1 , 1 , 0 result : 2 times 0, 3 times 1
0 , 1 , 0 , 1 , 1 , 0 result : 1 times 0, 2 times 1
0 , 0 , 1 , 1 , 0 , 0 result : 2 times 0, 2 times 1

答案 1 :(得分:0)

这是一种懒惰的低效方式(对我来说似乎线性复杂):

int[] arr = { 0, 0, 0, 1, 1, 0 };

string str = string.Concat(arr);              // "000110"
int max0 = str.Split('1').Max(s => s.Length); // 3
int max1 = str.Split('0').Max(s => s.Length); // 2

这是有效的O(n)版本:

int[] arr = { 0, 1, 1, 0, 0, 0 };

int i1 = 0, i = 1;
int[] max = { 0, 0 };

for (; i < arr.Length; i++)
{
    if (arr[i] != arr[i1])
    {
        if (i - i1 > max[arr[i1]]) max[arr[i1]] = i - i1; 
        i1 = i;
    }
}
if (i - i1 > max[arr[i1]]) max[arr[i1]] = i - i1;

Debug.Print(max[0] + "," + max[1]); // "3,2"

答案 2 :(得分:0)

您可以通过连续编写自己的方法进行灌浆

    public static class Extension
    {
        public static IEnumerable<IEnumerable<int>> GroupConsecutive(this IEnumerable<int> list)
        {
            var group = new List<int>();
            foreach (var i in list)
            {
                if (group.Count == 0 || i - group[group.Count - 1] == 0)
                    group.Add(i);
                else
                {
                    yield return group;
                    group = new List<int> {i};
                }
            }
            yield return group;
        }
    }

然后像这样使用它:

var groups = new[] { 0, 0, 1, 1, 0, 0 }.GroupConsecutive();
var maxGroupped = groups.GroupBy(i => i.First()).Select(i => new
{
       i.Key,
       Count = i.Max(j => j.Count())
});
foreach (var g in maxGroupped)
       Console.WriteLine(g.Count + " times " + g.Key);

答案 3 :(得分:0)

Linq会有点难看,但有可能,你选择Aggregate是可行的方法,但在任何情况下它都不会成为单行,

这样的东西会起作用,

static void Main(string[] args)
{
    var list = new List<int>() { 0, 0, 1, 1, 1, 0, 0 };
    var result = list.Aggregate(new
                                {
                                    Last = (int?)null,
                                    Counts = new Dictionary<int, int>(),
                                    Max = new Dictionary<int, int>()
                                }, (context, current) =>

                                {
                                    int count;
                                    if (!context.Counts.TryGetValue(current, out count))
                                        count = 1;

                                    if (context.Last == current)
                                        count += 1;

                                    int lastMax;
                                    context.Max.TryGetValue(current, out lastMax);
                                    context.Max[current] = Math.Max(lastMax, count);

                                    if (context.Last != current)
                                        count = 1;


                                    context.Counts[current] = count;

                                    return new { Last = (int?)current,  context.Counts, context.Max };
                                });

    Console.WriteLine(string.Join(",", list) + "  Result: ");
    var output = string.Join(",  ", result.Max.Select(x => string.Format("{0} times {1}", x.Value, x.Key)));
    Console.WriteLine(output);
    Console.ReadKey();
}

答案 4 :(得分:0)

与其他人一样,表现明智的Linq可能不适合这项工作。

我的linq唯一版本是:

from array in arrayOfArrays
let result = new { 
    Zeroes = array.TakeWhile(x => x == 0).Count(), 
    Ones = array.SkipWhile(x => x == 0).TakeWhile(x => x == 1).Count()
}
select $"{String.Join(", ", array)} result : {result.Zeroes} times 0, {result.Ones} times 1"

我不确定Linq2Objects在这里是否聪明以在内部优化查询。我们在查询中迭代多次遍历数组。就像我提前说过的那样,如果你在很多阵列上执行它,可能会有性能损失。如果有人愿意检查其他非linq解决方案的性能。

答案 5 :(得分:0)

首先感谢所有花时间和精力回答问题的人。

我选择Dmitry Bychenko作为有效答案,他是第一个提供答案的人,这是一个优雅的答案。

Matthew也表示不满,因为他向我展示了聚合函数如何与条件符合作用。

最后但并非最不重要的是胜利者的答案是最简单的。我确实增强了它以使用泛型。

 void Main()
    {
            var groups = new[] { 0, 0, 1, 1, 0,30,1,1,1,1,1 , 22, 22, 15,15,0,0,0,0,0,0 }.GroupConsecutive();

    groups.Dump();
            var maxGroupped = groups.GroupBy(i => i.First()).Select(i => new
            {
                   i.Key,
                   Count = i.Max(j => j.Count())
            });

    foreach (var g in maxGroupped)
           Console.WriteLine(g.Count + " times " + g.Key);
    }

     public static class Extension
        {

            public static IEnumerable<IEnumerable<T>> GroupConsecutive<T>(this IEnumerable<T> list) 
            {
                var group = new List<T>();
                foreach (var value in list)
                {
                    if (group.Count == 0 || value.Equals(group[group.Count-1])) 
                        group.Add(value);
                    else
                    {
                        yield return group;
                        group = new List<T> {value};
                    }
                }
                yield return group;
            }