6

我正在编写可以对范围进行映射/折叠操作的库。我需要和操作员一起做这些。我对函数式编程不是很熟悉,我暂时选择*了map和||fold。所以要找到(蛮力算法) cos(x)区间的最大值8 < x < 9

double maximum = ro::range(8, 9, 0.01) * std::cos  || std::max;

在上面,ro::range可以用任何 STL 容器替换。

如果地图/折叠运算符有任何约定,我不想有所不同。我的问题是:是否有数学符号或是否有任何语言使用运算符进行映射/折叠?

** 编辑 **

对于那些询问的人,下面是 RO 目前可以做什么的小演示。 scc是可以评估 C++ 片段的小实用程序。

// Can print ranges, container, tuples, etc directly (vint is vector<int>) :
scc 'vint V{1,2,3};  V'
{1,2,3}

// Classic pipe. Alogorithms are from std::
scc 'vint{3,1,2,3} | sort | unique | reverse'
{3, 2, 1}

// Assign 42 to [2..5)
scc 'vint V=range(0,9);   range(V/2, V/5) = 42;  V'
{0, 1, 42, 42, 42, 5, 6, 7, 8, 9}


// concatenate vector of strings ('add' is shotcut for std::plus<T>()):
scc 'vstr V{"aaa", "bb", "cccc"};  V || add'
aaabbcccc

// Total length of strings in vector of strings
scc 'vstr V{"aaa", "bb", "cccc"};  V * size ||  (_1+_2)'
9

// Assign to c-string, then append `"XYZ"` and then remove `"bc"` substring :
scc 'char s[99];  range(s) = "abc";  (range(s) << "XYZ") - "bc"'
aXYZ


// Remove non alpha-num characters and convert to upper case
scc '(range("abc-123, xyz/") | isalnum) * toupper'
ABC123XYZ


// Hide phone number:
scc "str S=\"John Q Public  (650)1234567\";  S|isdigit='X';  S"
John Q Public  (XXX)XXXXXXX
4

3 回答 3

11

这实际上更像是一个评论而不是一个真正的答案,但它太长了,无法放入评论中。

至少如果我对术语的记忆正确, map 本质上是std::transform,而 fold 是std::accumulate。假设这是正确的,我认为尝试编写自己的代码充其量是不明智的。

如果你想使用 map/fold 风格的语义,你可以这样做:

std::transform(std::begin(sto), std::end(sto), ::cos);
double maximum = *std::max_element(std::begin(sto), std::end(sto));

虽然std::accumulate更像是通用折叠,但std::max_element基本上是一个fold(..., max);如果您更喜欢单个操作,您可以执行以下操作:

double maximum = *(std::max_element(std::begin(sto), std::end(sto),
    [](double a, double b) { return cos(a) < cos(b); });

为此,我敦促您重新考虑重载运算符。我上面给出的任何一个例子对于几乎所有合理的 C++ 程序员来说都应该是清楚的。你给出的例子对大多数人来说是完全不透明的。

在更一般的层面上,我会敦促在重载运算符时要格外小心。运算符重载在正确使用时非常有用——能够为任意精度整数、矩阵、复数等内容重载运算符,使使用这些类型的代码没有重载运算符的代码更具可读性和可理解性。

不幸的是,当您以意想不到的方式使用运算符时,情况正好相反——这些用法肯定是非常出乎意料的——事实上,完全进入了“相当令人惊讶”的范围。如果这些运算符在特定领域被很好地理解,但与 C++ 中的其他用途相反,可能会有问题(但至少有一点理由)。但是,在这种情况下,您似乎是在“从整体上”发明一个符号——我不知道有人使用任何运算符 C++ 支持重载来表示折叠或映射(也没有任何视觉上相似或类似的东西)方法)。简而言之,以这种方式使用重载是一个糟糕且不合理的想法。

于 2012-12-10T06:58:36.293 回答
5

在我知道的语言中,没有标准的折叠方式。Scala 使用运算符/::\方法名称,Lisp 有reduce,Haskell 有foldl.

map另一方面,就像map在我所知道的所有语言中一样,它更常见。

于 2012-12-10T06:05:49.397 回答
2

以下是fold准人类可读的中缀 C++ 语法的实现。请注意,代码不是很健壮,仅用于演示这一点。它支持更常见的 3 参数fold运算符(范围、二元运算和中性元素)。

这很容易成为滥用(您刚刚说过“强奸”吗?)操作员超载的有趣方式,并且是用 900 磅炮弹击中自己脚部的最佳方式之一。

    enum { fold } fold_t;

template <typename Op>
struct fold_intermediate_1
{
    Op op;
    fold_intermediate_1 (Op op) : op(op) {}
};

template <typename Cont, typename Op, bool>
struct fold_intermediate_2
{
    const Cont& cont;
    Op op;
    fold_intermediate_2 (const Cont& cont, Op op) : cont(cont), op(op) {}
};

template <typename Op>
fold_intermediate_1<Op> operator/(fold_t, Op op)
{
    return fold_intermediate_1<Op>(op);
}

template <typename Cont, typename Op>
fold_intermediate_2<Cont, Op, true> operator<(const Cont& cont, fold_intermediate_1<Op> f)
{
    return fold_intermediate_2<Cont, Op, true>(cont, f.op);
}

template <typename Cont, typename Op, typename Init>
Init operator< (fold_intermediate_2<Cont, Op, true> f, Init init)
{
    return foldl_func(f.op, init, std::begin(f.cont), std::end(f.cont));
}

template <typename Cont, typename Op>
fold_intermediate_2<Cont, Op, false> operator>(const Cont& cont, fold_intermediate_1<Op> f)
{
    return fold_intermediate_2<Cont, Op, false>(cont, f.op);
}

template <typename Cont, typename Op, typename Init>
Init operator> (fold_intermediate_2<Cont, Op, false> f, Init init)
{
    return foldr_func(f.op, init, std::begin(f.cont), std::end(f.cont));
}

foldr_funcfoldl_func(左右折叠的实际算法)在别处定义。

像这样使用它:

foo myfunc(foo, foo);
container<foo> cont;
foo zero, acc;

acc = cont >fold/myfunc> zero; // right fold
acc = cont <fold/myfunc< zero; // left fold 

这个词fold在这里被用作一种穷人的新保留词。可以定义这种语法的几种变体,包括

<<fold/myfunc<< >>fold/myfunc>>
<foldl/myfunc> <foldr/myfunc>
|fold<myfunc| |fold>myfunc|

内部运算符的优先级必须与外部运算符相同或更高。这是 C++ 语法的限制。

对于map,只需要一个中间体,语法可以是例如

mapped = cont |map| myfunc;

实施它是一个简单的练习。

哦,请不要在生产中使用这种语法,除非你非常清楚自己在做什么,而且可能即使你知道;)

于 2012-12-10T10:31:39.543 回答