首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >简单通用序列发生器

简单通用序列发生器
EN

Code Review用户
提问于 2017-10-05 15:07:28
回答 4查看 765关注 0票数 6

我一直在改进一些与泛型和lambda一起工作的旧序列生成器,以便为指定的T支持二进制操作符:

公共抽象类Sequence:IEnumerable { public IEnumerator GetEnumerator() => Generate().GetEnumerator();IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();受保护的抽象IEnumerable生成();}公共类GeometricSequence:Sequence {私有只读T _first;私有只读双_ratio;私有只读发展,双,可扩展;公共IEnumerator(T first,双重比率,,double,乘){=第一;=比率;_multiply =乘?抛出新的ArgumentNullException(nameof(乘));}受保护的IEnumerable生成(){ var current = _first;产生返回电流;而(true) {屈服返回(current = _multiply(current,_ratio));}

我从来没有真正喜欢过它们,但是现在我正在试验表达式树,我认为我也重写了这些序列,以去掉lambda参数。这并不是一个很大的改变,但我认为现在使用起来要容易得多,而不必指定可以更改为其他操作的二进制操作,即使是预期的添加。没有办法控制它。在这里,操作被刻录到每个类中。

我添加了一个接口并删除了Generate方法。具体的序列现在必须只实现GetEnumerator

代码语言:javascript
复制
public interface ISequence<T> : IEnumerable<T> { }

public abstract class Sequence<T> : ISequence<T>
{
    public abstract IEnumerator<T> GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

以下是我目前拥有的序列:

FibonacciSequence

代码语言:javascript
复制
public class FibonacciSequence<T> : Sequence<T>
{
    private readonly T _one;
    private readonly Func<T, T, T> _add;

    public FibonacciSequence(T one)
    {
        _one = one;
        var leftParameter = Expression.Parameter(typeof(T), "left");
        var rightParameter = Expression.Parameter(typeof(T), "right");
        var add = Expression.Add(leftParameter, rightParameter);
        _add = Expression.Lambda<Func<T, T, T>>(add, leftParameter, rightParameter).Compile();
    }

    public override IEnumerator<T> GetEnumerator()
    {
        yield return _one;
        yield return _one;

        var previous = _one;
        var current = _add(_one, _one);

        yield return current;

        while (true)
        {
            var newCurrent = _add(previous, current);
            yield return newCurrent;
            previous = current;
            current = newCurrent;
        }
    }
}

GeometricSequence

代码语言:javascript
复制
public class GeometricSequence<T> : Sequence<T>
{
    private readonly T _first;
    private readonly T _ratio;
    private readonly Func<T, T> _multiply;

    public GeometricSequence(T first, T ratio)
    {
        _first = first;
        _ratio = ratio;
        var leftParameter = Expression.Parameter(typeof(T), "left");
        var ratioConstant = Expression.Constant(ratio);
        var multiply = Expression.Multiply(leftParameter, ratioConstant);
        _multiply = Expression.Lambda<Func<T, T>>(multiply, leftParameter).Compile();
    }    

    public override IEnumerator<T> GetEnumerator()
    {
        var current = _first;
        yield return current;

        while (true)
        {
            yield return (current = _multiply(current));
        }
    }
}

LinearSequence

代码语言:javascript
复制
public class LinearSequence<T> : Sequence<T>
{
    private readonly T _first;
    private readonly T _constant;
    private readonly Func<T, T> _increment;

    public LinearSequence(T first, T step)
    {
        _first = first;
        _constant = step;
        var leftParameter = Expression.Parameter(typeof(T), "left");
        var stepConstant = Expression.Constant(step);
        var multiply = Expression.Add(leftParameter, stepConstant);
        _increment = Expression.Lambda<Func<T, T>>(multiply, leftParameter).Compile();
    }

    public override IEnumerator<T> GetEnumerator()
    {
        var current = _first;
        yield return current;

        while (true)
        {
            yield return (current = _increment(current));
        };
    }
}

HarmonicSequence

代码语言:javascript
复制
public class HarmonicSequence<T> : Sequence<T>
{
    private readonly LinearSequence<T> _linear;
    private readonly T _first;
    private readonly Func<T, T, T> _divide;

    public HarmonicSequence(LinearSequence<T> linear, T first)
    {
        _linear = linear;
        _first = first;
        var leftParameter = Expression.Parameter(typeof(T), "left");
        var rightParameter = Expression.Parameter(typeof(T), "right");
        var add = Expression.Divide(leftParameter, rightParameter);
        _divide = Expression.Lambda<Func<T, T, T>>(add, leftParameter, rightParameter).Compile();
    }
    
    public override IEnumerator<T> GetEnumerator()
    {
        return _linear.Select(divisor => _divide(_first, divisor)).GetEnumerator();
    }
}

对于每个序列,我都有一个静态助手类来简化创建:

代码语言:javascript
复制
public static class FibonacciSequence
{
    public static FibonacciSequence<T> Create<T>(T one) => new FibonacciSequence<T>(one);
}

public static class GeometricSequence
{
    public static GeometricSequence<T> Create<T>(T first, T ratio) => new GeometricSequence<T>(first, ratio);
}

public static class LinearSequence
{
    public static LinearSequence<T> Create<T>(T first, T step) => new LinearSequence<T>(first, step);
}

public static class HarmonicSequence
{
    public static HarmonicSequence<T> Create<T>(LinearSequence<T> linear, T first) => new HarmonicSequence<T>(linear, first);
}

示例

对于所有支持所需操作符的类型,例如具有时间跨度的fibonacci序列,这都很好地工作。

代码语言:javascript
复制
FibonacciSequence.Create(one: 1).Take(5).Dump();
FibonacciSequence.Create(one: TimeSpan.FromSeconds(1)).Take(5).Dump();
GeometricSequence.Create(first: 10, ratio: 0.5).Take(5).Dump();
LinearSequence.Create(first: 1, step: 3).Take(5).Dump();
HarmonicSequence.Create(LinearSequence.Create(first: 1, step: 3), first: 4).Take(5).Dump();
EN

回答 4

Code Review用户

回答已采纳

发布于 2017-10-05 16:23:41

遗憾的是,您必须使用如此复杂的机制,因为语言不支持足够强的类型约束。鉴于该语言的局限性,这看起来是一种优雅的解决方案。

我觉得这个命名有点奇怪。考虑到GeometricSequence,我希望线性函数被称为ArithmeticSequence。或者,LinearSequence可以与ExponentialSequence相匹配。

而且,在HarmonicSequence中,我不理解first这个名字。如果是numerator,我会发现这一点更清楚,尽管我肯定有人会认为它应该是dividend。此外,从自然性的角度来看,我将考虑更改构造函数参数的顺序。

其中三个枚举器可以通过重写来简化为只有一个yield return。斐波纳契

代码语言:javascript
复制
    public override IEnumerator<T> GetEnumerator()
    {
        yield return _one;

        var previous = _one;
        var current = _one;

        while (true)
        {
            yield return current;

            var newCurrent = _add(previous, current);
            previous = current;
            current = newCurrent;
        }
    }

在我看来,在下一次调用GetNext()之前不会返回的预计算值的开销对于简化来说是一个微不足道的代价。

HarmonicSequence<T>似乎是一个专门的行业。是因为你没有制作一个通用的ISequence<T> YAGNI版本吗?这里有几种选择:

  1. 保持原样。
  2. HarmonicSequence<T>替换为一个类,该类采用一般序列的比例倒数,但为了别名的方便而保留静态HarmonicSequence.Create<T>(LinearSequence<T>, T)
  3. 将普通类从HarmonicSequence<T>中剔除,并使后者成为一个子类,其唯一声明的成员是构造函数。
  4. 而用HarmonicSequence.Create<T>(T dividend, T divisorStart, T divisorStep) => new ReciprocalSequence<T>(dividend, LinearSequence.Create(divisorStart, divisorStep));代替静态方法。(还有一个4b选项,可以同时使用两个静态实用程序方法)。
  5. 但将所有静态创建者方法的返回类型设为ISequence<T>

当我开始解决这个问题时,我只考虑了选项1和2,然后我想到了返回类型第5点的变化,我重新考虑了答案。从抽象的角度来看,我倾向于将5作为对接口的编码而不是对实现的编码,但是您的用例可能会使您远离它。

票数 4
EN

Code Review用户

发布于 2017-10-05 19:47:32

另一个建议是,您正在构建的表达式不会根据传递给构造函数的值更改(除了GeometricSequence之外,您仍然可以创建一个参数而不是一个常量)。

您可以在静态构造函数中构建表达式,因为它只按类型更改。或者创建一个静态的延迟字段来构建表达式并编译它们。

这样,如果您多次调用它,它们就可以重用值函数,而不是每次构建表达式并编译它们。

票数 3
EN

Code Review用户

发布于 2017-10-06 06:37:13

当我跟踪CharlesNRice的建议以使表达式变懒时,我意识到实际上我多次创建相同的表达式,因此我将它们移动到一个新的实用程序类BinaryOperation中,可以在任何地方重用,以后也可以在其他项目中重用:

代码语言:javascript
复制
public delegate T BinaryFunc<T>(T left, T right);

public static class BinaryOperation<T>
{
    private delegate BinaryExpression BinaryExpressionFunc(Expression left, Expression right);

    private static readonly Lazy<BinaryFunc<T>> AddFunc = new Lazy<BinaryFunc<T>>(() => CreateBinaryFunc(Expression.Add));
    private static readonly Lazy<BinaryFunc<T>> SubtractFunc = new Lazy<BinaryFunc<T>>(() => CreateBinaryFunc(Expression.Subtract));
    private static readonly Lazy<BinaryFunc<T>> MultiplyFunc = new Lazy<BinaryFunc<T>>(() => CreateBinaryFunc(Expression.Multiply));
    private static readonly Lazy<BinaryFunc<T>> DivideFunc = new Lazy<BinaryFunc<T>>(() => CreateBinaryFunc(Expression.Divide));

    public static BinaryFunc<T> Add => AddFunc.Value;
    public static BinaryFunc<T> Subtract => SubtractFunc.Value;
    public static BinaryFunc<T> Multiply => MultiplyFunc.Value;
    public static BinaryFunc<T> Divide => DivideFunc.Value;

    private static BinaryFunc<T> CreateBinaryFunc(BinaryExpressionFunc binaryExpression)
    {
        var leftParameter = Expression.Parameter(typeof(T), "left");
        var rightParameter = Expression.Parameter(typeof(T), "right");
        var binaryOperation = binaryExpression(leftParameter, rightParameter);
        return Expression.Lambda<BinaryFunc<T>>(binaryOperation, leftParameter, rightParameter).Compile();
    }
}

更新的finbonacci序列(以及其他序列)现在只包含用于创建序列的核心逻辑:

代码语言:javascript
复制
public class FibonacciSequence<T> : Sequence<T>
{
    private readonly T _one;

    public FibonacciSequence(T one)
    {
        _one = one;            
    }

    public override IEnumerator<T> GetEnumerator()
    {
        yield return _one;

        var previous = _one;
        var current = _one;

        while (true)
        {
            yield return current;

            var newCurrent = BinaryOperation<T>.Add(previous, current);
            previous = current;
            current = newCurrent;
        }
    }
}
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/177224

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档