1

While solving problems in Python I often had to use Python's collections.Counter object.

When I started solving the same problems in C# I couldn't find an equivalent method or class for it.

I was just wondering if there is any?

Explanation of collections.Counter

Python's collections.Counter takes in an iterable and returns a hash-map, where the keys are all the unique elements of the original iterable and the corresponding value is the number of times that key appears in the original iterable.

4
  • 2
    You might want to describe what it does. C# folks don't all speak Python Commented Sep 2, 2023 at 2:53
  • 1
    Looking at the docs (docs.python.org/3/library/collections.html#collections.Counter), Nope, there is no C# equivalent. It would be pretty easy to write. Don't subclass `Dictionary<TKey, int> though, make that a private member Commented Sep 2, 2023 at 2:57
  • well that's a bummer :( Commented Sep 2, 2023 at 14:14
  • 1
    Check out the CountBy operator from the MoreLinq package. Commented Sep 2, 2023 at 14:20

3 Answers 3

2

Here is a performant implementation of a ToCounter LINQ operator, that takes an enumerable sequence and returns a dictionary with the elements of the sequence as keys. The int value of the resulting dictionary represents the number of times each element appears in the source sequence:

public static Dictionary<TSource, int> ToCounter<TSource>(
this IEnumerable<TSource> source,
IEqualityComparer<TSource>? comparer = default) where TSource : notnull
{
    ArgumentNullException.ThrowIfNull(source);

    Dictionary<TSource, int> dictionary = new(comparer);
    foreach (TSource item in source)
    {
        CollectionsMarshal.GetValueRefOrAddDefault(dictionary, item, out _)++;
    }
    return dictionary;
}

The CollectionsMarshal.GetValueRefOrAddDefault is an advanced API, that allows to add or update a dictionary with a single GetHashCode invocation for the key. The default value of the int type is 0, which makes the implementation pretty simple (no need to declare a ref local variable).

Sign up to request clarification or add additional context in comments.

Comments

2

I made a NuGet package a few days ago just for 'fun' Pythonize.Counter.Net. I've never thought someone will actually want to search for it. Then I found this thread.

Example usage:

var value = "abacaba";
var counter = new Counter<char>(value);
var counts = counter.Get();
// Output:
// {['a',: 4], ['b', 2], ['c', 1] }

Comments

1

Here is a real quick implementation of a class that does what you are thinking about. Feel free to add some extra features:

public class CountedCollection<T> : IEnumerable<KeyValuePair<T, int>>
{
    private readonly Dictionary<T, int> _countDictionary;

    public CountedCollection()
    {
        if (!typeof(IEquatable<T>).IsAssignableFrom(typeof(T)))
        {
            throw new ArgumentException($"The type {typeof(T).Name} must implement IEquatable of <{typeof(T).Name}> to be usable with CountedCollection");
        }
        _countDictionary = new Dictionary<T, int>();
    }

    public CountedCollection(IEqualityComparer<T> comparer)
    {
        _countDictionary = new Dictionary<T, int>(comparer);
    }

    public void Add(T item)
    {
        ++TotalCount;
        if (_countDictionary.TryGetValue(item, out var curCount))
        {
            _countDictionary[item] = ++curCount;
        }
        else
        {
            _countDictionary.Add(item, 1);
        }
    }

    public void Add (IEnumerable<T> items)
    {
        foreach (var item in items)
        {
            Add(item);
        }
    }

    public int TotalCount { get; private set; } = 0;
    public int DistinctCount => _countDictionary.Count;

    public IEnumerator<KeyValuePair<T, int>> GetEnumerator()
    {
        foreach (var item in _countDictionary)
        {
            yield return item;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

It's very lightly tested.

It implements the "Collection Initialization Pattern" and it can be initialized with an existing collection, so both of these work (the second one initializes it using an array of integers):

var dict1 = new CountedCollection<string> { "abc", "xyz", "abc", "123" };
var dict2 = new CountedCollection<int> { new[] { 34, 12, 65, 12, 101 } };

The class provides two constructors. If T doesn't implement IEquatable<T>, then you can provide your own implementation of an IEqualityComparer<T>. This also allows you to do case independent counting of instances. For example:

var dict3 = new CountedCollection<string>(StringComparer.OrdinalIgnoreCase) 
{ 
    "abc", 
    "xyz", 
    "ABC", 
    "123", 
    "Xyz"
};

That code will show two instances of "abc" and two instances of "xyz".

Because you can bring your own IEqualityComparer<T>, T is not constrained to implement IEquatable<T>, the first constructor will throw an ArgumentException if T fails to implement IEquatable<T> (things must be comparable for equality somehow).

For example, this will throw:

var dict4 = new CountedCollection<CountedCollection<string>>();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.