2

I've got a method that computes a list. At certain points in the algorithm a single element from the list needs to be chosen. It doesn't really matter which element is chosen, but I'd like to leave it up to the user to decide.

Right now, I've added an extension method IList<T>.Random() which simply takes a random element. .First() would have worked equally as well. Supposing I want to let the user pick which method is used, or perhaps an entirely different method, how would that look?

I was thinking about using an enum with limited options, and then I could wrap each of these calls in a switch and call the appropriate function. But maybe some sort of lambda function would be more appropriate?

This method needs to be used in two different places, once on a List<char> and once on a List<string>. I want to use the same method for both.


This isn't a GUI app. I'm trying to decide how to design the API.

Specifically, I want to have a field like

public Func<IList<T>, T> SelectElement = list => list.First();

Which would then be used in the method,

 public string Reverse(string pattern, IList<object> args = null, IDictionary<string, object> kwargs = null)

But generic fields aren't possible. So I'm looking for an alternative solution. One would be to make the SelectElement method an argument to Reverse(), then I could make it generic... but I was hoping to keep it at a class-level for re-usability. Don't want to pass any more args to the function if I can help it.

Edit: full source code

Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • What's your client medium? I'd say a DropDownList or RadioButtonList would be appropriate. – Nathan Taylor Dec 02 '10 at 19:49
  • @Nathan: It's not a gui app. Updated Q. – mpen Dec 02 '10 at 19:54
  • i have read the question three times and still do not understand what you are asking :) you have a list of objects that you want to sort and ... here you are losing me... and you want the user to choose how to sort this list? please clarify. – akonsu Dec 02 '10 at 20:09
  • @akonsu: "Of sorts" is an expression. I don't mean it needs to be sorted. I want to let the user choose a method of selecting a single element within the list. – mpen Dec 02 '10 at 20:21
  • oh, ok. i am not a native english speaker. what stops you from passing a lambda expression, that would choose an item, to your Reverse method as a parameter? – akonsu Dec 02 '10 at 20:24
  • @akonsu: I can't do that because the element-chooser method needs to work on two different types (chars and strings), and that prevents me from making it generic. – mpen Dec 02 '10 at 20:29
  • http://idioms.thefreedictionary.com/of+sorts or http://idioms.yourdictionary.com/of-sorts – mpen Dec 02 '10 at 20:32
  • I dont' have a code sample, but have you looked at a PredicateBuilder for this sort of thing? LINQPad has some samples of it. – jcollum Dec 02 '10 at 20:48
  • @jcollum: I think PredicateBuilder is overkill for this. The selection algo doesn't need to be complicated. It'll probably just be First, Last, or Random. I just need a syntactically valid way to define it. – mpen Dec 02 '10 at 20:53
  • I wonder if you can leverage the fact that strings are just sequences of chars... – jcollum Dec 02 '10 at 21:02
  • @jcollum: I think you're misinterpreting the problem. Writing a selection method is not a problem, it's allowing the user to pass in a lambda function that will work for both List *and* List – mpen Dec 02 '10 at 21:14
  • This sounds a lot like a Strategy Pattern. – jcollum Dec 02 '10 at 23:16

4 Answers4

2

how about this:


    public class MyClass
    {
        public static class C<T>
        {
            public static Func<IList<T>, T> SelectElement;
        }

        public int Test(IList<int> list)
        {
            return C<int>.SelectElement(list);
        }
    }

    static class Program
    {
        static void Main(string[] args)
        {
            MyClass.C<char>.SelectElement = xs => xs.First();
            MyClass.C<int>.SelectElement = xs => xs.First();

            var list = new List<int>(new int[] { 1, 2, 3 });

            var c = new MyClass();

            var v = c.Test(list);
            Console.WriteLine(v);
        }
    }
akonsu
  • 28,824
  • 33
  • 119
  • 194
  • You've got syntax errors... and where's `xs` defined? Also, how would the user (programmer) change `SelectElement`? There's no setter. Furthermore, you can't have generic properties. – mpen Dec 02 '10 at 20:44
  • i have not got syntax errors, it compiles. perhaps i forgot to escape a left bracket or two. sorry. – akonsu Dec 02 '10 at 20:45
  • you can have generic properties. if the class has the T parameter as the argument. – akonsu Dec 02 '10 at 20:50
  • Yeah.. it wasn't displaying correctly. The other problems are still relevant though. How do you "set" `SelectElement`? `xs.First()` is only meant to be the default value. – mpen Dec 02 '10 at 20:51
  • @akonsu: Closer :) I only want to have to specify the selector once though. – mpen Dec 02 '10 at 21:01
  • a related issue: you cannot assign a generic method to an implicitly typed variable: var v = Enumerable.Last; i think this is a limitation of the language. same as what you are trying to solve. F# might be more siutable here. – akonsu Dec 04 '10 at 12:00
1

Here's an extremely basic example I put together using a generic method that takes in a Func<IEnumerable<T>, T> for selecting an item from the list and then returns the result. I've done a few examples of how to call it:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            //Simple list.
            var list = new List<int> { 1, 2, 3, 4 };

            // Try it with first
            var result = DoItemSelect(list, Enumerable.First);
            Console.WriteLine(result);

            // Try it with last
            result = DoItemSelect(list, Enumerable.Last);
            Console.WriteLine(result);

            // Try it with ElementAt for the second item (index 1) in the list.
            result = DoItemSelect(list, enumerable => enumerable.ElementAt(1));
            Console.WriteLine(result);
        }

        public static T DoItemSelect<T>(IEnumerable<T> enumerable, Func<IEnumerable<T>, T> selector)
        {
            // You can do whatever you method does here, selector is the user specified func for
            // how to select from the enumerable.  Here I just return the result of selector directly.
            return selector(enumerable);
        }
    }
}

If you want to limit the choices a user has you could follow the route of an enum and make this method a private method and then have a way to convert the enum to the appropriate selector delegate to pass to the underlying private method.

Joshua Rodgers
  • 5,317
  • 2
  • 31
  • 29
  • Nice, but won't work. There's a catch: "This method needs to be used in two different places, once on a List and once on a List" -- perhaps I should have said *within the same method*. Which means `T` can't be computed. So, in your example, there would actually be two enumerables used within `DoItemSelect`, one `List` and one `List`, both of which need to be passed into `selector`. – mpen Dec 02 '10 at 20:39
  • 1
    Yeah, I can see where the problem arises now. This will require a bit more thought to find an optimal solution. – Joshua Rodgers Dec 02 '10 at 20:49
0
public Func<IList<object>, object> SelectElement = list => list.First();

private T _S<T>(IEnumerable<T> list)
{
    return (T)SelectElement(list.Cast<object>().ToList());
}

I can make the anonymous method work on objects, thereby avoiding generics, and then add a helper method which is what I'll actually use to call it. A little ugly, but seems to work.

mpen
  • 272,448
  • 266
  • 850
  • 1,236
0

This works for chars and strings. Haven't tested with other types. Built this before I saw Ralph's code, which is practically the same.

LINQPad code:

void Main()
{
    var chars = new List<char>(); 
    var strings = new List<string>(); 

    chars.AddRange(new char[] {'1','2','4','7','8','3'});
    strings.AddRange(new string[] {"01","02","09","12","28","52"}); 

    chars.Dump(); 
    strings.Dump(); 

    Func<IList<object>, string> SelectFirst = ( list ) 
        => list.First().ToString();
    Func<IList<object>, string> SelectLast = ( list ) 
        => list.Last().ToString();
    Func<IList<object>, string> SelectRandom = ( list ) 
        => list.ElementAt( new Random().Next(0, list.Count())).ToString(); 

    SelectBy(SelectFirst, strings.Cast<object>().ToList()).Dump(); 
    SelectBy(SelectFirst, chars.Cast<object>().ToList()).Dump(); 

    SelectBy(SelectLast, strings.Cast<object>().ToList()).Dump(); 
    SelectBy(SelectLast, chars.Cast<object>().ToList()).Dump(); 

    SelectBy(SelectRandom, strings.Cast<object>().ToList()).Dump(); 
    SelectBy(SelectRandom, chars.Cast<object>().ToList()).Dump(); 
}

private string SelectBy(Func<IList<object>, string> func, IList<object> list)
{   
    return func(list); 
}
jcollum
  • 43,623
  • 55
  • 191
  • 321