sandeep
sandeep

Reputation: 2882

LINQ to count Continues repeated items(int) in an int Array?

Here is an scenario of my question: I have an array, say:

{ 4, 1, 1, 3, 3, 2, 5, 3, 2, 2 }

The result should be something like this (array element => its count):

4 => 1
1 => 2
3 => 2
2 => 1
5 => 1
3 => 1
2 => 2

I know this can be achieved by for loop.

But google'd a lot to make this possible using lesser lines of code using LINQ without success.

Upvotes: 5

Views: 3163

Answers (10)

var array = new int[] {1,1,2,3,5,6,6 };
var arrayd = array.Distinct();
var arrayl= arrayd.Select(s => { return array.Where(s2 => s2 == s).Count(); }).ToArray();

Output

arrayl=[0]2 [1]1 [2]1 [3]1 [4]2

Upvotes: 0

var array = new int[] {1,1,2,3,5,6,6 };
foreach (var g in array.GroupBy(i => i))
{
    Console.WriteLine("{0} => {1}", g.Key, g.Count());
}

Upvotes: 1

Rafe
Rafe

Reputation: 5295

Behold (you can run this directly in LINQPad -- rle is where the magic happens):

var xs = new[] { 4, 1, 1, 3, 3, 2, 5, 3, 2, 2 };

var rle = Enumerable.Range(0, xs.Length)
                    .Where(i => i == 0 || xs[i - 1] != xs[i])
                    .Select(i => new { Key = xs[i], Count = xs.Skip(i).TakeWhile(x => x == xs[i]).Count() });

Console.WriteLine(rle);

Of course, this is O(n^2), but you didn't request linear efficiency in the spec.

Upvotes: 1

Amy B
Amy B

Reputation: 110151

I already wrote the method you need over there. Here's how to call it.

foreach(var g in numbers.GroupContiguous(i => i))
{
  Console.WriteLine("{0} => {1}", g.Key, g.Count);
}

Upvotes: 1

Brad Rem
Brad Rem

Reputation: 6026

Here's a LINQ expression that works (edit: tightened up code just a little more):

var data = new int[] { 4, 1, 1, 3, 3, 2, 5, 3, 2, 2 };
var result = data.Select ((item, index) =>
                        new
                        {
                            Key = item,
                            Count = (index == 0 || data.ElementAt(index - 1) != item) 
                                ? data.Skip(index).TakeWhile (d => d == item).Count ()
                                : -1
                        }
                          )
                  .Where (d => d.Count != -1);

And here's a proof that shows it working.

Upvotes: 3

Martin Liversage
Martin Liversage

Reputation: 106906

I believe the most optimal way to do this is to create a "LINQ-like" extension methods using an iterator block. This allows you to perform the calculation doing a single pass over your data. Note that performance isn't important at all if you just want to perform the calculation on a small array of numbers. Of course this is really your for loop in disguise.

static class Extensions {

  public static IEnumerable<Tuple<T, Int32>> ToRunLengths<T>(this IEnumerable<T> source) {
    using (var enumerator = source.GetEnumerator()) {
      // Empty input leads to empty output.
      if (!enumerator.MoveNext())
        yield break;

      // Retrieve first item of the sequence.
      var currentValue = enumerator.Current;
      var runLength = 1;

      // Iterate the remaining items in the sequence.
      while (enumerator.MoveNext()) {
        var value = enumerator.Current;
        if (!Equals(value, currentValue)) {
          // A new run is starting. Return the previous run.
          yield return Tuple.Create(currentValue, runLength);
          currentValue = value;
          runLength = 0;
        }
        runLength += 1;
      }

      // Return the last run.
      yield return Tuple.Create(currentValue, runLength);
    }
  }

}

Note that the extension method is generic and you can use it on any type. Values are compared for equality using Object.Equals. However, if you want to you could pass an IEqualityComparer<T> to allow for customization of how values are compared.

You can use the method like this:

var numbers = new[] { 4, 1, 1, 3, 3, 2, 5, 3, 2, 2 };
var runLengths = numbers.ToRunLengths();

For you input data the result will be these tuples:

4 1 
1 2 
3 2 
2 1 
5 1 
3 1 
2 2 

Upvotes: 8

Jodrell
Jodrell

Reputation: 35716

This not short enough?

public static IEnumerable<KeyValuePair<T, int>> Repeats<T>(
        this IEnumerable<T> source)
{
    int count = 0;
    T lastItem = source.First();

    foreach (var item in source)
    {
        if (Equals(item, lastItem))
        {
            count++;
        }
        else
        {
           yield return new KeyValuePair<T, int>(lastItem, count);
           lastItem = item;
           count = 1;
        }
    }

    yield return new KeyValuePair<T, int>(lastItem, count);
}

I'll be interested to see a linq way.

Upvotes: 2

Jon Skeet
Jon Skeet

Reputation: 1502016

(Adding another answer to avoid the two upvotes for my deleted one counting towards this...)

I've had a little think about this (now I've understood the question) and it's really not clear how you'd do this nicely in LINQ. There are definitely ways that it could be done, potentially using Zip or Aggregate, but they'd be relatively unclear. Using foreach is pretty simple:

// Simplest way of building an empty list of an anonymous type...
var results = new[] { new { Value = 0, Count = 0 } }.Take(0).ToList();

// TODO: Handle empty arrays
int currentValue = array[0];
int currentCount = 1;

foreach (var value in array.Skip(1))
{
    if (currentValue != value)
    {
        results.Add(new { Value = currentValue, Count = currentCount });
        currentCount = 0;
        currentValue = value;
    }
    currentCount++;
}
// Handle tail, which we won't have emitted yet
results.Add(new { Value = currentValue, Count = currentCount });

Upvotes: 3

Parv Sharma
Parv Sharma

Reputation: 12705

var array = new int[]{};//whatever ur array is
array.select((s)=>{return array.where((s2)=>{s == s2}).count();});

the only prob with is tht if you have 1 - two times you will get the result for 1-two times

Upvotes: 0

Rajesh Subramanian
Rajesh Subramanian

Reputation: 6490

Try GroupBy through List<int>

        List<int> list = new List<int>() { 4, 1, 1, 3, 3, 2, 5, 3, 2, 2 };
        var res = list.GroupBy(val => val);
        foreach (var v in res)
        {
            MessageBox.Show(v.Key.ToString() + "=>" + v.Count().ToString());
        }

Upvotes: -4

Related Questions