【问题标题】:C# a function with one argument of type T that returns a function with one argument of type T, but in fact needs more argumentsC# 一个具有一个 T 类型参数的函数,它返回一个具有一个 T 类型参数的函数,但实际上需要更多参数
【发布时间】:2016-11-08 23:28:25
【问题描述】:

我有一个编码树的Dictionary<T, IEnumerable<T>>: 键代表树的所有节点,值是相应节点的子节点。 如果一个节点没有子节点,那么它的值就是一个空的可枚举。

例如以下treeMapT = int 的映射就是这样一个对树进行编码的映射:

treeMap[1] = { 2, 3, 4 }
treeMap[2] = { 5, 6 }
treeMap[3] = { 7 }
treeMap[4] = { }
treeMap[5] = { }
treeMap[6] = { }
treeMap[7] = { 8 }
treeMap[8] = { }

我想编写一个方法UseFunctionOnTree(T node, Dictionary<T, IEnumerable<T>> treeMap, Function F),将F 应用于给定节点,从旧的Fnode 获取一个新函数newF,并将newF 应用于所有子节点。

据我所知:

public class MapHelper<T>
{
    public delegate Function Function (T element);

    public static void UseFunctionOnTree(T node,
                           Dictionary<T, IEnumerable<T>> treeMap, Function F)
    {
        Function newF = F(node);
        foreach (T child in treeMap[node])
            UseFunctionOnTree (child, treeMap, newF);
    }
}

现在我的问题是我不知道如何定义这样的Function。 我可以这样定义:

public Function Useless(T element)
{
    DoSthWith(element);
    return Useless;
}

但是我不知道如何定义一个返回自身以外的东西的函数!

我的一个用例如下: 我有一棵 MyObject 类型的树,其中 MyObject 可能如下所示:

public class MyObject
{
    public int index;
}

我取了一些int-valued 偏移量,比如说firstOffset = 3。我希望Functionoffset 添加到node(即添加到其index)。然后我想给node的所有孩子添加一个偏移量,但是这次的偏移量应该是nextOffset = firstOffset + node.index

这是我想要的伪代码:

public Function AddOffset(T element)
{
    int firstOffset;
    // somehow make firstOffset = 3

    int newOffset = firstOffset + element.index;
    element.index = newOffset;
    return AddOffset // but this time with the new offset
}

我该怎么做?我觉得它可能可以通过一些 lambda 语句来解决......

我需要更多参数吗?从直觉的角度来看,我没有。 例如。我们有

node1node1.index = 1

node2node2.index = 100

我们将F 指定为 “将 3 添加到参数的索引并将该数字存储为 n。返回一个类似的函数,但将 n 添加到其参数的索引。”

从中我们得到F(node1) 将更改node1.index3+1=4 并将4 添加到其参数的索引,因此(F(node1))(node2) 将更改node2.index4+100=104 并将104 添加到它是参数的索引。而((F(node1))(node2))(node1) 会将node1.index 更改为104+4=108 并将108 添加到其参数的索引中,依此类推。

我还想指出,我并不总是需要/更改一个int 参数。可能是我需要几个参数,它们的类型也可能因我要解决的具体问题而异。因此,如果我不必提前决定我需要多少(以及什么类型)参数,那就太好了。它应该全部包含在Function 本身中。

感谢任何提示!

【问题讨论】:

  • 等等,你的意思是T类型的函数,它需要一个T类型的函数,它返回一个T类型的函数,它需要一个T类型的函数,并返回一个T类型的函数,它返回一个函数需要一个返回 T 类型函数的函数?反之亦然?
  • 请注意,您将index 定义为double,稍后您将像int 一样使用它。
  • 另请注意,您不能使用此修改树结构!当您将树节点存储在映射中时,您无法通过更改某些索引值来更改树结构(节点和子节点),因为Dictionary 确实假定包含的键的哈希码保持不变!
  • @ventiseis 我将双精度更正为 int。对于哈希码,我们假设它是由其他一些永远不会改变的字段定义的。不过感谢提示,我忽略了这个问题。

标签: c# recursion functional-programming delegates


【解决方案1】:

有一种方法可以做到这一点,但我不推荐它。请再考虑一下您使用的算法和数据结构。就个人而言,我会使用更面向对象的方法,但让我们尝试一下。

第一个抽象是表示使用另一个泛型类型Q进行操作的数据:

public class MapHelper<T, Q> {
     // (insert the following code here)
}

首先,我们需要更多的委托来表示您的映射:

    public delegate Q Getter(T element);
    public delegate void Setter(T element, Q value);
    public delegate Q Operation(Q left, Q right);

Getter 允许我们检索索引,Setter 可以写入新值,Operation 是一个简单的添加。我们在应用映射函数时需要这些函数:

    public static void UseFunctionOnTree(T node,
                           Dictionary<T, IEnumerable<T>> treeMap, Function F, Getter g, Setter s) {
        Function newF = F(node, g, s);
        if (!treeMap.ContainsKey(node)) return;
        foreach (T child in treeMap[node])
            UseFunctionOnTree(child, treeMap, newF, g, s);
    }

现在我们可以定义GenericAddOffset,它使用声明的委托和一个闭包来定义一个新的lambda函数:

    public static Function GenericAddOffset(T element, Getter g, Setter s, Operation o, Q left) {
        Q newOffset = o(left, g(element));
        s(element, newOffset);
        return (T element1, Getter g1, Setter s1) => {
            return GenericAddOffset(element1, g1, s1, o, newOffset);
        };
    }

这是您缺少的链接:闭包允许您使用外部范围的变量定义函数。

最后,我定义了一个辅助函数:

    public static void PrintTree(Dictionary<T, IEnumerable<T>> tree, Getter g) {
        foreach (var value in tree) {
            Console.Write("[" + g(value.Key).ToString() + "]: ");
            foreach (var node in value.Value)
                Console.Write(g(node).ToString() + " ");
            Console.WriteLine();
            Console.WriteLine();
        }
    }

现在,用:

using Helper = MapHelper<MyObject, int>;

我们可以编写如下示例程序:

class Program {

    public static void Main(string[] args) {
        var tree = new Dictionary<MyObject, IEnumerable<MyObject>>();
        Helper.Getter g = t => (int)t.index;
        Helper.Setter s = (t, o) => { t.index = o; };

        var root = new MyObject();
        var node1 = new MyObject();
        var node2 = new MyObject();
        s(root, 1);
        s(node1, 1);
        s(node2, 100);
        tree.Add(root, new[] { node1, node2 });

        Helper.PrintTree(tree, g);

        Helper.UseFunctionOnTree(root, tree,
            (t, g1, s1) => { return Helper.GenericAddOffset(t, g1, s1, (x, y) => x + y, 3); }, g, s);

        Helper.PrintTree(tree, g);
        Console.ReadLine();
    }
}

输出

[1]: 1 100

[4]: 5 104

【讨论】:

    【解决方案2】:

    只定义一个辅助函数

    public static Function MakeAddOffset(int offset)
    {
        return element => {
            int newOffset = offset + element.index;
            element.index = newOffset;
            return MakeAddOffset(newOffset);
        };
    }
    

    然后使用MakeAddOffset(3) 作为AddOffset 函数。 (您可以通过以与MakeAddOffset 相同的方式定义类似的辅助函数MakeF,将其从AddOffset 推广到任何函数F。)

    【讨论】:

    • 这正是我的想法(虽然不够清楚,无法写下来)! :)
    猜你喜欢
    • 2021-08-07
    • 2019-03-21
    • 1970-01-01
    • 1970-01-01
    • 2021-12-03
    • 2011-12-25
    • 1970-01-01
    • 1970-01-01
    • 2010-10-23
    相关资源
    最近更新 更多