5

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.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
sandeep
  • 2,862
  • 9
  • 44
  • 54
  • 3
    It's hard to see how the array length can be fixed at 6 if an example is 4, 1, 1, 3, 3, 2, 5, 3, 2, 2... – Jon Skeet Jul 04 '12 at 13:28
  • Now check the question please – sandeep Jul 04 '12 at 13:29
  • Why the output has duplicate keys for numbers "2" and "3"? Shouldn't you rather expect an output to be "4 => 1, 1 => 2, 3 => 3, 2 => 3, 5 =>1"? – Wiktor Zychla Jul 04 '12 at 13:32
  • 2
    Are you actually looking for [run-length encoding](http://en.wikipedia.org/wiki/Run_length_encoding)? – phipsgabler Jul 04 '12 at 13:33
  • of-course there should be duplicate occurances @WiktorZychla – sandeep Jul 04 '12 at 13:35
  • @phg, I believe you are right, essentially counting the number of repeats – Jodrell Jul 04 '12 at 13:37
  • 3
    @sandeep: It's not really "of course" - your question isn't clear given that you never use the word "consecutive" which is the important bit here. The fact that three people *all* answered the question assuming you mean just counting the occurrences of elements suggests you should edit to make it clearer... – Jon Skeet Jul 04 '12 at 13:37
  • Got it now. However, have no idea how to do this in linq. – Wiktor Zychla Jul 04 '12 at 13:38
  • @JonSkeet: In question I have mentioned it – sandeep Jul 04 '12 at 13:40
  • @sandeep: The word "consecutive" never appears in the question. It's simply unclear. Yes, you can work it out eventually, but that's a long way from it being *clear*. – Jon Skeet Jul 04 '12 at 13:42

10 Answers10

8

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 
Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • Is there an adavantage in using `GetEnumerator` or is it just to avoid the `foreach` as requested? – Jodrell Jul 04 '12 at 13:56
  • @Jodrell: As I have to do special handling of the first element it has to go outside the main loop. Using the enumerator allows me to do that and still only retrieve and examine each element exactly once. – Martin Liversage Jul 04 '12 at 14:01
3

(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 });
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
3

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.

Brad Rem
  • 6,036
  • 2
  • 25
  • 50
  • In a way this is the correct answer because it uses LINQ as stated in the question. Curious about the efficiency of this approach I counted the number of enumerators created and the number of items accessed given the input array of 10 elements. 17 enumerators were created and 101 elements were accessed compared to the most optimal approach where 1 enumerator is created and 10 elements accessed. – Martin Liversage Jul 04 '12 at 14:27
2

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.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
Jodrell
  • 34,946
  • 5
  • 87
  • 124
1

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);
}
Community
  • 1
  • 1
Amy B
  • 108,202
  • 21
  • 135
  • 185
1

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.

Rafe
  • 5,237
  • 3
  • 23
  • 26
1
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());
}
slfan
  • 8,950
  • 115
  • 65
  • 78
0
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

Parv Sharma
  • 12,581
  • 4
  • 48
  • 80
0
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
Tagc
  • 8,736
  • 7
  • 61
  • 114
  • Welcome to Stack Overflow! Take a minute to read through [How to Answer](http://stackoverflow.com/questions/how-to-answer) - this looks helpful but it would benefit from some explanation of what the code does, consider [edit](http://stackoverflow.com/posts/41385887/edit)-ing that in? – Timothy Truckle Jan 07 '17 at 20:37
-4

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());
        }
Rajesh Subramanian
  • 6,400
  • 5
  • 29
  • 42