具有任意数量参数的函数的C#记忆

具有任意数量参数的函数的C#记忆,c#,performance,function,memoization,C#,Performance,Function,Memoization,我试图为具有任意数量参数的函数创建一个记忆接口,但我失败了,我觉得我的解决方案不是很灵活。我试图为一个函数定义一个接口,该接口在执行时自动被记忆,并且每个函数都必须实现这个接口。下面是一个双参数指数移动平均函数的示例: class EMAFunction:IFunction { Dictionary<List<object>, List<object>> map; class EMAComparer : IEqualityComparer&l

我试图为具有任意数量参数的函数创建一个记忆接口,但我失败了,我觉得我的解决方案不是很灵活。我试图为一个函数定义一个接口,该接口在执行时自动被记忆,并且每个函数都必须实现这个接口。下面是一个双参数指数移动平均函数的示例:

class EMAFunction:IFunction
{
    Dictionary<List<object>, List<object>> map;

    class EMAComparer : IEqualityComparer<List<object>>
    {
        private int _multiplier = 97;

        public bool Equals(List<object> a, List<object> b)
        {
            List<object> aVals = (List<object>)a[0];
            int aPeriod = (int)a[1];

            List<object> bVals = (List<object>)b[0];
            int bPeriod = (int)b[1];

            return (aVals.Count == bVals.Count) && (aPeriod == bPeriod);
        }

        public int GetHashCode(List<object> obj)
        {
            // Don't compute hash code on null object.
            if (obj == null)
            {
                return 0;
            }

            List<object> vals = (List<object>) obj[0];
            int period = (int) obj[1];

            return (_multiplier * period.GetHashCode()) + vals.Count;

        }
    }

    public EMAFunction()
    {
        NumParams = 2;
        Name = "EMA";
        map = new Dictionary<List<object>, List<object>>(new EMAComparer());
    }
    #region IFunction Members

    public int NumParams
    {
        get;
        set;
    }

    public string Name
    {
        get;
        set;
    }

    public object Execute(List<object> parameters)
    {
        if (parameters.Count != NumParams)
            throw new ArgumentException("The num params doesn't match!");

        if (!map.ContainsKey(parameters))
        {
            //map.Add(parameters,
            List<double> values = new List<double>();
            List<object> asObj = (List<object>)parameters[0];
            foreach (object val in asObj)
            {
                values.Add((double)val);
            }
            int period = (int)parameters[1];

            asObj.Clear();
            List<double> ema = TechFunctions.ExponentialMovingAverage(values, period);
            foreach (double val in ema)
            {
                asObj.Add(val);
            }
            map.Add(parameters, asObj);
        }
        return map[parameters];
    }

    public void ClearMap()
    {
        map.Clear();
    }

    #endregion
}
private void MemoizeTest()
{
    DataSet dataSet = DataLoader.LoadData(DataLoader.DataSource.FROM_WEB, 1024);
    List<String> labels = dataSet.DataLabels;

    Stopwatch sw = new Stopwatch();
    IFunction emaFunc = new EMAFunction();
    List<object> parameters = new List<object>();
    int numRuns = 1000;
    long sumTicks = 0;
    parameters.Add(dataSet.GetValues("open"));
    parameters.Add(12);

    // First call

    for(int i = 0; i < numRuns; ++i)
    {
        emaFunc.ClearMap();// remove any memoization mappings
        sw.Start();
        emaFunc.Execute(parameters);
        sw.Stop();
        sumTicks += sw.ElapsedTicks;
        sw.Reset();
    }
    Console.WriteLine("Average ticks not-memoized " + (sumTicks/numRuns));


    sumTicks = 0;
    // Repeat call
    for (int i = 0; i < numRuns; ++i)
    {
        sw.Start();
        emaFunc.Execute(parameters);
        sw.Stop();
        sumTicks += sw.ElapsedTicks;
        sw.Reset();
    }
    Console.WriteLine("Average ticks memoized " + (sumTicks/numRuns));
}
类函数:IFunction
{
词典地图;
类EMAComparer:IEqualityComparer
{
私有整数乘数=97;
公共布尔等于(列表a、列表b)
{
List aVals=(List)a[0];
int aPeriod=(int)a[1];
List bVals=(List)b[0];
int bPeriod=(int)b[1];
返回(aVals.Count==bVals.Count)&&(aPeriod==bPeriod);
}
public int GetHashCode(列表对象)
{
//不要在空对象上计算哈希代码。
if(obj==null)
{
返回0;
}
列表VAL=(列表)对象[0];
int period=(int)obj[1];
返回(_乘数*period.GetHashCode())+vals.Count;
}
}
公共职能()
{
NumParams=2;
Name=“EMA”;
map=newdictionary(newemacomparer());
}
#区域IFunction成员
公共国际货币
{
得到;
设置
}
公共字符串名
{
得到;
设置
}
公共对象执行(列表参数)
{
if(parameters.Count!=NumParams)
抛出新ArgumentException(“num参数不匹配!”);
如果(!map.ContainsKey(参数))
{
//map.Add(参数,
列表值=新列表();
List asObj=(List)参数[0];
foreach(asObj中的对象val)
{
值。添加((双)val);
}
int period=(int)参数[1];
asObj.Clear();
列表ema=TechFunctions.ExponentialMovingAverage(值,周期);
foreach(ema中的双val)
{
asObj.Add(val);
}
map.Add(参数,asObj);
}
返回映射[参数];
}
public void ClearMap()
{
map.Clear();
}
#端区
}
以下是我对该功能的测试:

class EMAFunction:IFunction
{
    Dictionary<List<object>, List<object>> map;

    class EMAComparer : IEqualityComparer<List<object>>
    {
        private int _multiplier = 97;

        public bool Equals(List<object> a, List<object> b)
        {
            List<object> aVals = (List<object>)a[0];
            int aPeriod = (int)a[1];

            List<object> bVals = (List<object>)b[0];
            int bPeriod = (int)b[1];

            return (aVals.Count == bVals.Count) && (aPeriod == bPeriod);
        }

        public int GetHashCode(List<object> obj)
        {
            // Don't compute hash code on null object.
            if (obj == null)
            {
                return 0;
            }

            List<object> vals = (List<object>) obj[0];
            int period = (int) obj[1];

            return (_multiplier * period.GetHashCode()) + vals.Count;

        }
    }

    public EMAFunction()
    {
        NumParams = 2;
        Name = "EMA";
        map = new Dictionary<List<object>, List<object>>(new EMAComparer());
    }
    #region IFunction Members

    public int NumParams
    {
        get;
        set;
    }

    public string Name
    {
        get;
        set;
    }

    public object Execute(List<object> parameters)
    {
        if (parameters.Count != NumParams)
            throw new ArgumentException("The num params doesn't match!");

        if (!map.ContainsKey(parameters))
        {
            //map.Add(parameters,
            List<double> values = new List<double>();
            List<object> asObj = (List<object>)parameters[0];
            foreach (object val in asObj)
            {
                values.Add((double)val);
            }
            int period = (int)parameters[1];

            asObj.Clear();
            List<double> ema = TechFunctions.ExponentialMovingAverage(values, period);
            foreach (double val in ema)
            {
                asObj.Add(val);
            }
            map.Add(parameters, asObj);
        }
        return map[parameters];
    }

    public void ClearMap()
    {
        map.Clear();
    }

    #endregion
}
private void MemoizeTest()
{
    DataSet dataSet = DataLoader.LoadData(DataLoader.DataSource.FROM_WEB, 1024);
    List<String> labels = dataSet.DataLabels;

    Stopwatch sw = new Stopwatch();
    IFunction emaFunc = new EMAFunction();
    List<object> parameters = new List<object>();
    int numRuns = 1000;
    long sumTicks = 0;
    parameters.Add(dataSet.GetValues("open"));
    parameters.Add(12);

    // First call

    for(int i = 0; i < numRuns; ++i)
    {
        emaFunc.ClearMap();// remove any memoization mappings
        sw.Start();
        emaFunc.Execute(parameters);
        sw.Stop();
        sumTicks += sw.ElapsedTicks;
        sw.Reset();
    }
    Console.WriteLine("Average ticks not-memoized " + (sumTicks/numRuns));


    sumTicks = 0;
    // Repeat call
    for (int i = 0; i < numRuns; ++i)
    {
        sw.Start();
        emaFunc.Execute(parameters);
        sw.Stop();
        sumTicks += sw.ElapsedTicks;
        sw.Reset();
    }
    Console.WriteLine("Average ticks memoized " + (sumTicks/numRuns));
}
private void MemoizeTest()
{
DataSet DataSet=DataLoader.LoadData(DataLoader.DataSource.FROM_WEB,1024);
列表标签=dataSet.DataLabels;
秒表sw=新秒表();
iffunction emaFunc=新的EMAFunction();
列表参数=新列表();
int numRuns=1000;
长sumTicks=0;
parameters.Add(dataSet.GetValues(“open”);
增加(12);
//第一个电话
对于(int i=0;i
更新:
感谢您指出我的n00bish错误…我总是忘记在秒表上调用Reset


我也看到了…它不提供n参数记忆,但我的接口方法没有多大优势,因为我必须为每个函数编写一个类。有没有一种合理的方法可以将这些想法合并到更健壮的东西中?我想让用户在不编写类f的情况下更容易记忆函数或他们打算使用的每个功能。

StopWatch.Stop不会重置秒表,因此您在每次启动/停止时都会累积时间

比如说

  Stopwatch sw = new Stopwatch();

  sw.Start();
  System.Threading.Thread.Sleep(100);
  sw.Stop();
  Debug.WriteLine(sw.ElapsedTicks);

  sw.Start();
  System.Threading.Thread.Sleep(100);
  sw.Stop();
  Debug.WriteLine(sw.ElapsedTicks);
给出了以下结果

228221
454626

您可以使用
StopWatch.Restart
(Framework 4.0)每次重新启动秒表,或者如果不是Framework 4.0,您可以使用
StopWatch.Reset
重置秒表。

首先,您需要调用
sw.Reset()
在测试之间。否则,第二次测试的结果将与第一次测试的时间相加


其次,您可能不应该在
GetHashCode()中使用
vals.GetHashCode()
在比较器上覆盖,因为这将导致您为对象获取不同的哈希代码,这些对象的
值将计算为
true
,用于
等于
覆盖。目前,我担心的是确保等效对象始终获得相同的哈希代码,而不是试图获得代码的均匀分布。如果哈希值为ODE不匹配,
Equals
将永远不会被调用,因此您将多次处理相同的参数。

这如何?首先编写一个单参数记忆器:

static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
    var d = new Dictionary<A, R>();
    return a=> 
    {
        R r;
        if (!d.TryGetValue(a, out r))
        {
            r = f(a);
            d.Add(a, r);
        }
        return r;
    };
}  
静态函数记忆(此函数)
{
var d=新字典();
返回a=>
{
R;
如果(!d.TryGetValue(a,out r))
{
r=f(a);
d、 加(a,r);
}
返回r;
};
}  
简单明了。现在编写一个函数tuplifier:

static Func<Tuple<A, B>, R> Tuplify<A, B, R>(this Func<A, B, R> f)
{
    return t => f(t.Item1, t.Item2);
}
static Func<A, B, R> Detuplify<A, B, R>(this Func<Tuple<A, B>, R> f)
{
    return (a, b) => f(Tuple.Create(a, b));
}
静态函数组(此函数f)
{
返回t=>f(t.Item1,t.Item2);
}
和一个解提升器:

static Func<Tuple<A, B>, R> Tuplify<A, B, R>(this Func<A, B, R> f)
{
    return t => f(t.Item1, t.Item2);
}
static Func<A, B, R> Detuplify<A, B, R>(this Func<Tuple<A, B>, R> f)
{
    return (a, b) => f(Tuple.Create(a, b));
}
static Func Detuplify(此函数)
{
返回(a,b)=>f(Tuple.Create(a,b));
}
现在,两个参数的回忆录很简单:

static Func<A, B, R> Memoize<A, B, R>(this Func<A, B, R> f)
{
    return f.Tuplify().Memoize().Detuplify();
}
静态函数记忆(此函数)
{
返回f.Tuplify().Memoize().Detuplify();
}
要编写一个三参数的回忆录,只需遵循以下模式:制作一个三元组、一个三解乘法器和一个三回忆录

当然,如果你不需要它们,就没有必要去买
new List<Func<object>>(new Func<object>[] {
        () => { "Entered func A1".Dump(); return 1; },
        () => { "Entered func A2".Dump(); return default(int); },
        () => { "Entered func B1".Dump(); return String.Empty; },
        () => { "Entered func B2".Dump(); return default(string); },
        () => { "Entered func C1".Dump(); return new {Name = String.Empty}; },
        () => { "Entered func C2".Dump(); return null; },
    })
    .ForEach(f => {
        var f1 = MemoizationExtensions.Memoize(f);
        Enumerable
            .Range(1,3)
            .Select(i=>new {Run=i, Value=f1()})
            .Dump();
    });