map / fold运算符(用c ++开头)

时间:2012-12-10 05:47:08

标签: c++ stl functional-programming

我正在编写library,可以对范围进行地图/折叠操作。我需要与运营商合作。我对函数式编程不太熟悉,我暂时选择了*用于地图,||用于折叠。因此,要在区间cos(x)中找到(强力算法)最大值8 < x < 9

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

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

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

**编辑**

对于那些问过的人,下面是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

3 个答案:

答案 0 :(得分:10)

这真的是一个评论,而不是一个真正的答案,但它太长,不适合评论。

至少如果我对术语的记忆正确,地图基本上是std::transform,折叠是std::accumulate。假设这是正确的,我认为尝试编写自己的文章充其量是不明智的。

如果你想使用地图/折叠样式语义,你可以这样做:

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 ++支持重载意味着折叠或映射(也没有任何其他视觉上相似或类似的东西)办法)。简而言之,以这种方式使用重载是一个糟糕且不合理的想法。

答案 1 :(得分:5)

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

另一方面,

map更常见于我所知道的所有语言中的map

答案 2 :(得分:2)

以下是准人类可读的中缀C ++语法中fold的实现。请注意,代码不是很强大,只能证明这一点。它支持更常见的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;

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

哦,请不要在制作中使用这种语法,除非你非常清楚自己在做什么,甚至可能会这样做;)