Cemre Mengü
Cemre Mengü

Reputation: 18764

LINQ - Select string with minimum count of char occurences

I have a list of strings and I want to pick the string that contains the minimum count of ~ or > or |

All the solutions I have come up with gives the minimum count instead of the string that has the minimum count .

Any ideas ?

What I have so far:

private string FilterShortestPath(List<string> paths)
{
   return paths.Min(x => x.Count(c => c == '~' || c == '>' || c == '|'));
}

Upvotes: 0

Views: 1018

Answers (4)

Wojciech Kozaczewski
Wojciech Kozaczewski

Reputation: 326

The following code

var badChars = new[] { '~', '>', '|' };
var strings = new[]
{
    "I ~ love >> cookies |||",
    "I >>>> love pizza ~",
    "I hate tildas~~"
};

var grouped = from str in strings
              let count = str.Count(c => badChars.Contains(c))
              orderby count ascending
              select new { Text = str, Count = count };

var tuple = grouped.First();
Console.WriteLine("Text is '{0}' with {1} occurences", tuple.Text, tuple.Count);

should do the thing for a single minimum. However, since you possibly need multiple minimums, the following one should help:

var badChars = new[] { '~', '>', '|' };
var strings = new[]
{
    "I ~ love >> cookies |||",
    "I >>>> love pizza ~",
    "I hate tildas~~",
    "Bars are also | | bad"
};

var grouped = (from str in strings
              let count = str.Count(c => badChars.Contains(c))
              orderby count ascending
              select new { Text = str, Count = count }).ToList();

int min = grouped[0].Count;

var minimums = grouped.TakeWhile(g => g.Count == min);

foreach(var g in minimums)
    Console.WriteLine("Text is '{0}' with {1} occurences", g.Text, g.Count);

Upvotes: 0

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477676

You can do this using the IEnumerable<T>.Count method with a predicate and that predicate checks if the char is stored in a collection of accepted characters.

String[] strings = new String[] {"<todo|>","~foo"};
HashSet<char> accept = new HashSet<char>(new char[] {'~','>','|'});
strings.ArgMin(x => x.Count(accept.Contains));

With:

public static TS ArgMin<TS,TF> (this IEnumerable<TS> source, Func<TS,TF> f) where TF : IComparable<TF> {
    if(source == null) {
        throw new ArgumentException("Source must be effective.");
    }
    IEnumerator<TS> en = source.GetEnumerator();
    if(!en.MoveNext()) {
        throw new ArgumentException("You need to provide at least one argument.");
    }
    TS x0 = en.Current, xi;
    TF f0 = f(x0), fi;
    while(en.MoveNext()) {
        xi = en.Current;
        fi = f(xi);
        if(f0.CompareTo(fi) > 0) {
            f0 = fi;
            x0 = xi;
        }
    }
    return x0;
}

ArgMin returns the item in the IEnumerable<T> that resulted after applying f in the smallest value. In case multiple values result in the same value, the first is selected. If no items are provided, or the list is not effective, an exception is thrown.

Upvotes: 0

Guru Stron
Guru Stron

Reputation: 143373

If i got your task correctly:

var strings = new []{"~~~~", "wwqewqe>", "||"};
var chars = new[] {'~', '|', '>'};
var result = strings.OrderBy(s => s.Count(c => chars.Contains(c))).First();

The problem with you solution is that Enumerable.Min accepts selector and not a condition. If you want to use it with condition you can look here for options

Upvotes: 1

Tim Schmelter
Tim Schmelter

Reputation: 460288

If you want to find all strings with the min-count you could use Where:

private IEnumerable<string> FilterShortestPath(IEnumerable<string> paths, params char[] chars)
{
    if(paths == null || !paths.Any()) 
        return Enumerable.Empty<string>();
    int minCount = paths.Min(str => (str ?? "").Count(chars.Contains));
    return paths.Where(str => (str ?? "").Count(chars.Contains) == minCount);
}

Usage:

var minCountStrings = FilterShortestPath(list, '~', '>' , '|');

Note that you can use this method also if you only want one due to it's deferred execution. Then you just have to use FilterShortestPath(list, '~', '>' , '|').First().

Upvotes: 2

Related Questions