4

次のコードのContainsIngredientsメソッドで、p.Ingredients値を明示的に数回参照する代わりに、キャッシュすることはできますか?これは、説明のために作成した非常に簡単な例ですが、作業中のコードは、たとえば、 pの奥深くにある値を参照しています。p.InnerObject.ExpensiveMethod()。Value

編集: http://www.albahari.com/nutshell/predicatebuilder.htmlのPredicateBuilderを使用しています

public class IngredientBag
{
    private readonly Dictionary<string, string> _ingredients = new Dictionary<string, string>();

    public void Add(string type, string name)
    {
        _ingredients.Add(type, name);
    }

    public string Get(string type)
    {
        return _ingredients[type];
    }

    public bool Contains(string type)
    {
        return _ingredients.ContainsKey(type);
    }
}

public class Potion
{
    public IngredientBag Ingredients { get; private set;}
    public string Name {get; private set;}        

    public Potion(string name) : this(name, null)
    {

    }

    public Potion(string name, IngredientBag ingredients)
    {
        Name = name;
        Ingredients = ingredients;
    }

    public static Expression<Func<Potion, bool>> 
        ContainsIngredients(string ingredientType, params string[] ingredients)
    {
        var predicate = PredicateBuilder.False<Potion>();
        // Here, I'm accessing p.Ingredients several times in one 
        // expression.  Is there any way to cache this value and
        // reference the cached value in the expression?
        foreach (var ingredient in ingredients)
        {
            var temp = ingredient;
            predicate = predicate.Or (
                p => p.Ingredients != null &&
                p.Ingredients.Contains(ingredientType) &&
                p.Ingredients.Get(ingredientType).Contains(temp));
        }

        return predicate;
    }

}


[STAThread]
static void Main()
{
    var potions = new List<Potion>
    {
        new Potion("Invisibility", new IngredientBag()),
        new Potion("Bonus"),
        new Potion("Speed", new IngredientBag()),
        new Potion("Strength", new IngredientBag()),
        new Potion("Dummy Potion")
    };

    potions[0].Ingredients.Add("solid", "Eye of Newt");
    potions[0].Ingredients.Add("liquid", "Gall of Peacock");
    potions[0].Ingredients.Add("gas", "Breath of Spider");

    potions[2].Ingredients.Add("solid", "Hair of Toad");
    potions[2].Ingredients.Add("gas", "Peacock's anguish");

    potions[3].Ingredients.Add("liquid", "Peacock Sweat");
    potions[3].Ingredients.Add("gas", "Newt's aura");

    var predicate = Potion.ContainsIngredients("solid", "Newt", "Toad")
        .Or(Potion.ContainsIngredients("gas", "Spider", "Scorpion"));

    foreach (var result in 
                from p in potions
                where(predicate).Compile()(p)
                select p)
    {
        Console.WriteLine(result.Name);
    }
}
4

5 に答える 5

10

メモ化を検討しましたか?

基本的な考え方はこれです。高価な関数呼び出しがある場合は、最初の呼び出しで高価な値を計算しますが、その後はキャッシュされたバージョンを返す関数があります。関数は次のようになります。

static Func<T> Remember<T>(Func<T> GetExpensiveValue)
{
    bool isCached= false;
    T cachedResult = default(T);

    return () =>
    {
        if (!isCached)
        {
            cachedResult = GetExpensiveValue();
            isCached = true;
        }
        return cachedResult;

    };
}

これは、これを書くことができることを意味します。

    // here's something that takes ages to calculate
    Func<string> MyExpensiveMethod = () => 
    { 
        System.Threading.Thread.Sleep(5000); 
        return "that took ages!"; 
    };

    // and heres a function call that only calculates it the once.
    Func<string> CachedMethod = Remember(() => MyExpensiveMethod());

    // only the first line takes five seconds; 
    // the second and third calls are instant.
    Console.WriteLine(CachedMethod());
    Console.WriteLine(CachedMethod());
    Console.WriteLine(CachedMethod());

一般的な戦略として、それは役立つかもしれません。

于 2008-09-15T22:28:10.070 に答える
2

ラムダから呼び出す別の静的関数にブール式を単純に記述できませんか-p.Ingredientsをパラメーターとして渡します...

private static bool IsIngredientPresent(IngredientBag i, string ingredientType, string ingredient)
{
    return i != null && i.Contains(ingredientType) && i.Get(ingredientType).Contains(ingredient);
}

public static Expression<Func<Potion, bool>>
                ContainsIngredients(string ingredientType, params string[] ingredients)
{
    var predicate = PredicateBuilder.False<Potion>();
    // Here, I'm accessing p.Ingredients several times in one 
    // expression.  Is there any way to cache this value and
    // reference the cached value in the expression?
    foreach (var ingredient in ingredients)
    {
        var temp = ingredient;
        predicate = predicate.Or(
            p => IsIngredientPresent(p.Ingredients, ingredientType, temp));
    }

    return predicate;
}
于 2008-09-15T20:39:08.770 に答える
1

この場合、Memoization を使用できない場合は、スタックをキャッシュとしてしか使用できないため、かなり制限されます。必要なスコープで新しい変数を宣言する方法がありません。私が考えることができるのは(そして、それがきれいだと主張しているわけではありません)、あなたが望むことを行い、必要な構成可能性を保持するのは次のようなものです...

private static bool TestWith<T>(T cached, Func<T, bool> predicate)
{
    return predicate(cached);
}

public static Expression<Func<Potion, bool>>
                ContainsIngredients(string ingredientType, params string[] ingredients)
{
    var predicate = PredicateBuilder.False<Potion>();
    // Here, I'm accessing p.Ingredients several times in one 
    // expression.  Is there any way to cache this value and
    // reference the cached value in the expression?
    foreach (var ingredient in ingredients)
    {
        var temp = ingredient;
        predicate = predicate.Or (
            p => TestWith(p.Ingredients,
                i => i != null &&
                     i.Contains(ingredientType) &&
                     i.Get(ingredientType).Contains(temp));
    }

    return predicate;
}

複数の TestWith 呼び出しからの結果を、必要に応じてより複雑なブール式に結合する (各呼び出しで適切な高価な値をキャッシュする) か、複雑な深い階層に対処するために、2 番目のパラメーターとして渡されるラムダ内にそれらをネストすることができます。

ただし、コードを読むのは非常に困難であり、すべての TestWith 呼び出しでさらに多くのスタック遷移を導入する可能性があるため、パフォーマンスが向上するかどうかは、ExpensiveCall() がどれだけ高価であったかに依存します。

注として、私が知る限り、式コンパイラはそのレベルの最適化を行わないため、別の回答で示唆されているように、元の例にはインライン化はありません。

于 2008-09-16T06:02:17.857 に答える
0

この場合、私はノーと言うでしょう。コンパイラーは、p.Ingredients変数を 3 回使用し、変数をスタックやレジスター、またはそれが使用するものの近くに保持することを理解できると思います。

于 2008-09-15T20:43:50.447 に答える
0

激動の知性はまさに正しい答えを持っています。

使いやすくするために、使用している型から null と例外の一部を削除できることをお勧めします。

    public class IngredientBag
    {
      private Dictionary<string, string> _ingredients = 
new Dictionary<string, string>();
      public void Add(string type, string name)
      {
        _ingredients[type] = name;
      }
      public string Get(string type)
      {
        return _ingredients.ContainsKey(type) ? _ingredients[type] : null;
      }
      public bool Has(string type, string name)
      {
        return name == null ? false : this.Get(type) == name;
      }
    }

    public Potion(string name) : this(name, new IngredientBag())    {    }

次に、この構造にクエリパラメーターがある場合...

Dictionary<string, List<string>> ingredients;

このようにクエリを書くことができます。

from p in Potions
where ingredients.Any(i => i.Value.Any(v => p.IngredientBag.Has(i.Key, v))
select p;

PS、なぜ読み取り専用ですか?

于 2008-09-15T21:44:55.650 に答える