C#选择累加器无法工作的重载方法

时间:2017-02-17 14:26:06

标签: c# linq functional-programming

我在C#中创建了一个Map函数,以多种方式执行,因为它的JavaScript等同于项目对象类型。我已经将这些方法重命名为“Select'用作过载使他们感觉更加“整合”。这是一个链条,所以忍受我,但受影响的功能看起来像这样......

    public static TResult Project<TInput, TResult>(this TInput input, Func<TInput, TResult> projectionMapping)
        => projectionMapping(input);

    public static TResult Project<TInput, TAccumulatedValue, TIncrementingValue, TResult>(this TInput input, Func<TInput, TAccumulatedValue, TResult> projectionMapping, 
        Func<TAccumulatedValue, TIncrementingValue, TAccumulatedValue> accumulator, TAccumulatedValue initialAccumulatorValue, TIncrementingValue increment)
        => projectionMapping(input, accumulator(initialAccumulatorValue, increment));

    public static IEnumerable<TResult> Select<TInput, TAccumulatedValue, TIncrementingValue, TResult>(this IEnumerable<TInput> input,
        Func<TInput, TAccumulatedValue, TResult> projectionMapping, Func<TAccumulatedValue, TIncrementingValue, TAccumulatedValue> accumulator, 
        TAccumulatedValue initialAccumulatorValue, TIncrementingValue increment)
        => input.Select(item => item.Project(projectionMapping, accumulator, initialAccumulatorValue, increment));

    // This doesn't work.
    public static IEnumerable<TResult> Select<TInput, TResult>(this IEnumerable<TInput> input,
        Func<TInput, int, TResult> projectionMapping, int initialAccumulatorValue = -1, int increment = 1)
    {
        return input.Select(projectionMapping, (acc, inc) => acc + inc,
            initialAccumulatorValue, increment);
    }

我正在使用map方法的int版本,并将累加器写入其中,如下所示......

    MyList.Add(new List<MyObject>(rowValues.Map((val, headerNumber) 
            => new MyObject(headerNumber, val), 0, 10)));

问题是,headerNumber的值永远不会改变(它始终为10) - 累加器运行一次然后为每个映射运行但是它不记得它之间的累积运行。我觉得我在这里错过了一些明显的东西,但我看不到树木的木材。

如果我输入(例如)这样的数组......

rowValues = new string[] { "Item 1", "Item 2", "Item 3" };

我希望MyObject项列表包含以下数据......

  • 10 | &#34;项目1&#34;
  • 20 | &#34;项目2&#34;
  • 30 | &#34;项目3&#34;

3 个答案:

答案 0 :(得分:1)

问题是您始终使用accumulator来呼叫initialAccumulatorValue

为了实现目标,您需要保持累积值,最简单的正确方法是使用C#迭代器方法:

public static IEnumerable<TResult> Map<TInput, TAccumulatedValue, TIncrementingValue, TResult>(this IEnumerable<TInput> input,
    Func<TInput, TAccumulatedValue, TResult> projectionMapping, Func<TAccumulatedValue, TIncrementingValue, TAccumulatedValue> accumulator,
    TAccumulatedValue initialAccumulatorValue, TIncrementingValue increment)
{
    var accumulatedValue = initialAccumulatorValue;
    foreach (var item in input)
        yield return projectionMapping(item, accumulatedValue = accumulator(accumulatedValue, increment));
}

请注意,天真尝试使用闭包和Select

的组合
var accumulatedValue = initialAccumulatorValue;
return input.Select(item => projectionMapping(item, accumulatedValue = accumulator(accumulatedValue, increment)));

根本不起作用,因为accumulatedValue将由返回的select查询的多次执行共享,因此它们将产生不正确的结果。迭代器方法没有这样的问题,因为代码实际上是在调用GetEnumerator()方法时执行的。

答案 1 :(得分:1)

我开始改变你的第三个函数,这样它只需要一个累加器函数来返回序列中的下一个索引。这允许函数具有计算增加的​​累加器值所需的状态。

public static IEnumerable<TResult> Project<TInput, TAccumulatorValue, TResult>(this IEnumerable<TInput> input,
    Func<TInput, TAccumulatorValue, TResult> projectionMapping, 
    Func<TAccumulatorValue> accumulator)
{
    return input.Select(item => projectionMapping(item, accumulator()));
}

然后你的函数接受了不起作用的范围参数,可以这样写,这样可以解决你的问题。

public static IEnumerable<TResult> Project<TInput, TResult>(this IEnumerable<TInput> input,
    Func<TInput, int, TResult> projectionMapping, int initialAccumulatorValue = 0, int increment = 1)
{
    int curValue = initialAccumulatorValue;
    return input.Project(projectionMapping, 
        () => { var ret = curValue; curValue += increment; return ret; });
}

替代地

以不同的方式思考这个问题,你可以使它更通用。您所做的只是使用projectionMapping将两个序列组合在一起以组合元素。在这种情况下,第二个序列恰好包含累加器值。然后使用它你只需使用标准的Linq函数Zip,传递累加器序列和projectionMapping函数。

要获得线性序列,我们可以使用Enumerable.Range,但要获得非线性范围,我们需要编写一个这样的Range生成器

public static IEnumerable<int> Range(int start, int increment)
{
    for (; ; )
    {
        yield return start;
        start += increment;
    }
}

实施例

展示两种解决方案

var items = new[] { "a", "b", "c" };

// use Project, returns: a10, b20, c30
var results = items.Project((s, i) => s + i.ToString(), 10, 10).ToList();

// use Zip with custom range, returns: a10, b20, c30
var results2 = items.Zip(Range(10, 10), (s, i) => s + i.ToString()).ToList();

// use Zip with standard range, returns: a1, b2, c3
var results3 = items.Zip(Enumerable.Range(1, int.MaxValue), (s, i) => s + i.ToString()).ToList();

答案 2 :(得分:0)

假设以下

public class MyObject
{
    public int Row { get; }
    public string Value { get; }

    public MyObject(int row, string value)
    {
        Value = value;
        Row = row;
    }
}

输入源为:

var rows = new[] { "Item 1", "Item 2", "Item 3", "Item 4" };

<强>解决方案

要实现您所提供的输入的特定结果,您只需使用框架中提供的Select

var result = rows.Select((v, i) => new MyObject((i+1)*10, v)).ToList();

当然,这看起来不是很好,但可以解决问题。

伊万的答案比较整洁,但如果有人发现它有用,我会坚持上述内容。