0

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 == '|'));
}
Cemre Mengü
  • 18,062
  • 27
  • 111
  • 169
  • 3
    Show us what you tried so far. – Yuval Itzchakov Feb 12 '15 at 14:36
  • 1
    That is not a duplicate of the proposed. Do you want to find a single string with the minimum count or - if there are multiple with the min-count - all? – Tim Schmelter Feb 12 '15 at 14:40
  • @TimSchmelter I want to find the string(s) with the minimum count – Cemre Mengü Feb 12 '15 at 14:43
  • @TimSchmelter OK, if OP wants to be able to find multiple items, it's not a duplicate. But maybe they should make it clear in the question. – sloth Feb 12 '15 at 14:51
  • @sloth: even if he wanted to find only one item it was not one because your proposed duplicate was about finding an object by the minimum value of one of it's properties not by the count of occurrences of some characters. – Tim Schmelter Feb 12 '15 at 14:59
  • @TimSchmelter There's no pracitcal difference in using a property as the selector or a method calculating something based on a property. All solutions in the linked answer would work. – sloth Feb 12 '15 at 15:06
  • @sloth: with that broad definition nearly every new question on SO is a duplicate. A related question that helps to answer a question is not necessarily a duplicate. – Tim Schmelter Feb 12 '15 at 15:06
  • @TimSchmelter There really are dozens of questions like: *I want to get the element with the minimun/maximum value of X, but `Min(...)` returns a number, not the element itself * – sloth Feb 12 '15 at 15:10

4 Answers4

2

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().

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
1

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

Community
  • 1
  • 1
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • You can make it a bit more efficient by calling `Min` (to save sorting all values) and replace the second lambda expression by `chars.Contains` instead of `c => ...` – Willem Van Onsem Feb 12 '15 at 14:41
0

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.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
0

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);