1

好的,首先,对不起 TL;DR ...

第二,关于我想要实现的目标的一些背景。

我有一个脚本,它处理给定文件并根据一组定义的规则生成一个新文件。文件中的每一行都有一个值,脚本评估该值并根据该值和一般规则生成其他值。生成的文件包含原始值和 csv 格式的生成值。

例如:

输入文件:

row1value
row2value

输出文件:

"row1value","generatedValue1","generatedValue2","generatedValue3"
"row2value","generatedValue1","generatedValue2","generatedValue3"

例如,假设源文件的每一行都有一个冒号分隔的值,目标是用冒号分割值并将每个子值放入列中。这就是“之前”和“之后”的样子:

输入文件:

a:b:c
some:random:value

输出文件:

"a:b:c","a","b","c"
"some:random:value","some","random","value"

所以要生成行,我可能有:

function generateRow ($key) {
  // $key is the value for the current row being processed from the source file
  // $row is the array that will contain the columns to be inserted to the output file
  $row = array();

  // generate the column values
  $row = explode(":",$key);

  // make the original value ($key) the first column
  array_unshift($row, $key);

  // return the array. some other function will fputcsv it
  return $row;
}

另一个例子是,如果源文件包含这样的值行:

[prefix]:[url]

输出文件中列的值将是:

  • column1 = 完整的原始值(永远是这个)
  • 列 2 =[prefix]
  • 列 3 = [网址]
  • column4 = 为 url 的域解析 [url]
  • column5 = 为文件扩展名解析 [url]

所以我写信generateRow()来做这些事情。

好的,我拥有这一切。这一切都很闪亮,工作得很好。我收到创建新“进程”的请求,这些“进程”接收自己的文件并根据定义的规则生成带有列的新文件。我只是想提供所有这些作为背景故事,以便将其置于上下文中,以便你们更容易理解我真正想要的东西。

所以“问题”是,就目前而言,“过程”是由编码员(我)generateRow()根据规范写出来的。这对我自己或任何了解 php 的人来说都不是特别困难。但这不是“用户友好的”,因为某些利益相关者希望自己负责创建这些东西,但他们不是编码人员。

所以我现在的任务是基本上为generateRow(). 所以在我看来,我基本上需要制作一个表单,根据之前选择的项目动态生成表单元素和可能的选项。换句话说,一个表达式引擎。或谓词引擎。或规则生成器。老实说,我不能 100% 确定这些术语中哪一个(如果有的话)是最准确的,但我一直在进行大量的谷歌搜索和阅读,到目前为止,这些都是我想出的。

例如,表单会从要求用户创建条件或赋值表达式开始,

[下拉:如果|设置]

IF:如果用户单击“if”,则会显示另一个下拉列表,其中包含要检查的“变量”列表。例如,系统将提供对其他列的引用、源文件中的任何列(包括键)、导入文件名或用户先前创建的任何自定义变量(参见下面的“设置”)。然后表单将显示一个“操作”下拉菜单,其中将显示“已设置”、“未设置”、“大于”、“正则表达式”等内容。如果用户选择“已设置”或“未设置”之类的内容,则不会输出其他字段。但是,如果用户选择例如“大于”,则会显示另一个下拉列表,要求从“变量”列表中进行选择,或者在输入字段中输入值。

IF column1 "is set"           /* check if column1 is set */
IF column1 contains "foobar"  /* check if column1 contains "foobar" */
If column1 regex "^[a-z]+$"   /* check if column1 contains only letters */

一旦定义好,用户就可以在其中添加“set”表达式。为简单起见,我认为不需要嵌套条件或使用 AND|OR 来制作复合条件。

SET:因此,如果用户选择此选项,表单将指导他们构建赋值表达式。第一个下拉列表将保存用户可以为其赋值的变量,例如输出文件的列或临时变量,以便可以在其他表达式中引用它们。例子:

SET [column1] [=] ["foobar"]
SET [column2] [=] [column1]
SET [column1] [regex] [userVar1] ["^[^:]+"]
SET [userVar1] [explode] [key] [":"] 

好吧,我不确定这是呈现它的“最佳”方式,但希望你能明白。

然后我会保存这些表达式,然后编写 php 代码来评估它们;基本上将它们翻译成实际的 php 代码。我认为就那部分而言我实际上很好:我只会使用解释器模式。

但是我需要帮助的是整个表达式“映射”/“构建器”部分。实际上,如果我能得到充实的可能表达的“映射”,我认为我什至可以摆动“构建器”部分。

所以是的,这就是我的问题的核心所在:如何绘制出可能的表达方式。目前我一直在尝试将其构造/映射为 xml,但我似乎无法掌握如何做到这一点,除了硬编码每条可能的路径。首先,对我来说,这似乎有点低效,就像应该有一个更聪明的方法来做到这一点。它不可能很容易扩展......假设“进程”#1 的规则集有 2 个源文件列可供绘制,5 个输出文件列要生成......而“进程”#2 的规则集是 1 和 4?如何考虑用户可以设置的可变数量的用户定义变量。

那么,有没有人有任何提示或链接到 tuts 解释如何做这种事情?或者更好的是预制(php)解决方案会很好,但我还没有找到一些东西..

编辑:这是我现在所处位置的一个例子,希望能更好地理解我的问题。

例如,如果我只是硬编码地图(xml),这就是它会如何下降

<expressions>
  <expression type='if'>
    <variable name='column1'>
      <operator type='isset'></operator>
      <operator type='notset'></operator> 
      <operator type='equals'>
        <variable name='column1' />
        <variable name='column2' />
        <variable name='column3' />
      </operator>
      <operator type='greaterThan'>
        <variable name='column1' />
        <variable name='column2' />
        <variable name='column3' />
      </operator>
      <operator type='lessThan'>
        <variable name='column1' />
        <variable name='column2' />
        <variable name='column3' />
      </operator>
    </variable>
    <variable name='column2'>
      <operator type='equals'>
        <variable name='column1' />
        <variable name='column2' />
        <variable name='column3' />
      </operator>
      <operator type='greaterThan'>
        <variable name='column1' />
        <variable name='column2' />
        <variable name='column3' />
      </operator>
      <operator type='lessThan'>
        <variable name='column1' />
        <variable name='column2' />
        <variable name='column3' />
      </operator>
    </variable>
</expressions>

情侣笔记:

  1. 有任意数量的variable节点。每个源文件列都有一个,每个输出文件列名一个,用户动态创建的每个变量一个,等等。
  2. 有可变数量的operator类型。我只展示了 3 个示例,但到目前为止,我已经想到了 20 个
  3. 请注意某些operator类型如何需要在表单上输出额外的表单字段。例如,“if [column1] [isset]”与“if [column1] [greaterThan] [column2]”
  4. expression节点显示“if”表达式的外观。还有一个expression“set”类型的节点,它具有不同的结构,但原则上工作相同。例如,可能有一个表达式“[set] [column2] [equals] [column1]”或“[set] [column1] [regex] [sourceColumn1] [input value (regex)]”

所以现在我可以沿着路径走下去,根据树下可用的内容动态构建菜单。

但正如您所看到的,似乎有很多重复,而且将新事物加入混合(如新运算符)也将是一个皮塔饼。所以这里的问题是,我将如何更好地构建它?或者它只是不可能,唯一的解决方案是硬编码每一种可能性?

4

3 回答 3

3

哇,这是一个很好的问题!我将从回答您的直接问题开始,然后讨论一般性问题。

如果您想要现成的产品,有“产品配置”系统,例如 3dfacto 或 Drools,它们采用一组规则(在这种情况下,是关于如何构建系统规则的规则),并且可以创建一个动态表单,只允许输入有效规则。它实际上与您在上面编写的 XML 并没有太大的不同。让我们来看看发生了什么:

首先,您要定义一种语言来表达系统中的规则。您的语言的结构称为语法,我们通常使用 BNF 来描述语法。例如:

command := <if> | <set>
if := "if" <boolexpr> "then" <command>
boolexpr := <column> isset |
            <column> notset | 
            <column> "greaterThan" <column> |
            <column> "lessThan" <column>
<column> := "column1" | "column2" | ... | "column_n"
set := <column> <equals> <value> | ...
value := <column> | <number>

请注意,语法的某些部分可能需要涉及代码。例如,“列”部分是动态的,具体取决于实际列的数量。您可以查看 Drools 规则引擎的语法,尽管其中混入了很多代码。

语法的 BNF 表示和 XML 映射之间的主要区别是您已经发现的:前向和后向链接。两者代表完全相同的东西,但是 BNF 比 XML 更紧凑。这是因为子结构可以被引用而不是按原样复制。例如,我们可以<value>在不关心它是列还是字面数字的任何地方使用该规则。另一种思考方式是,通过规则的路径比规则多得多。

我们如何在代码中表示语法?一种方法是使用数组。

$column = a_function
$boolexpr = Array("or", Array("match", $column, "isset"), Array("match", $column, "notset"), Array("match", $column, "greatherthan", $column), Array("match", $column, "lessthan", $column))
$command_grammar = Array("match", "If", $boolexpr, "then", $command)

您的表单构建器系统本质上将对此语法进行抽象解释。您在问:“到目前为止,给出输入,有效的下一个输入是什么?” 你可以通过使用迄今为止输入的规则解释语法并查看接下来会发生什么来做到这一点。您提到了“解释器”模式,所以我认为您对此很熟悉。

另一种思考方式是您正在制作解析器。通常,解析器通过语法规则尝试与输入匹配的所有可能路径,并在以下情况下立即拒绝任何路径:

  1. 到目前为止的输入和路径不同
  2. 路径已完成,但还有更多输入。
  3. 输入已完成,但路径上还有更多非空语法规则。

在您的系统中,(1) 和 (2) 永远不会发生,因为您不允许输入无效规则。(3) 可能因为用户没有完全指定规则而发生,所以你可以看看接下来会出现什么语法规则来更新表单。

让我们退后一步,看看我们正在尝试做的事情:允许非程序员对计算机进行编程。一个常见的问题是,试图使此类系统变得灵活导致它们成为自己的编程语言。而且通常它们比现有语言更糟糕,然后您需要成为程序员才能使用它。我认为你有正确的想法,说一些东西,比如“或”太复杂了。限制复杂性可以避免这种图灵焦油坑。

避免该问题的另一种方法是利用您现有的语言,例如 PHP。您可以定义使任务更容易的函数,并让最终用户使用您的示例作为指南直接编写 PHP。持续的反馈在这里有所帮助。查看Processing以获取此方法的示例。

于 2013-10-11T20:56:52.700 回答
1

好的,据我所知,你想要一个规则生成系统,所以用户发送主字符串或文件并说出他想要的内容,然后规则生成器根据用户想要的规则生成规则,然后将它们作为规则发送到 generateRow(),然后 generateRow () 返回值;

1-正如你所说,你必须定义主要的操作规则,如 equal、GT、LT 或 contains、regExp 等作为可能的数组并在它们键入时将它们分组,例如 regExp 或 contains 在字符串中使用,所以我们这里有2组操作规则,字符串,数字。然后用这些组和注释制作一个 xml 文件。为每个运算符记下,例如:

<strings>
 <oprator>
  <title>equal</title>
  <php_func>stristr</php_func>
  <input_arg_count>2</input_arg_count>
  <true_return>true</true_return>
 </oprator>

 <oprator>
  <title>not_in</title>
  <php_func>stristr</php_func>
  <input_arg_count>2</input_arg_count>
  <true_return>false</true_return>
 </oprator>

</string>

3-你必须向用户询问你的输入文件是什么类型,基于堆或基于列,如果基于 col,列分隔符是什么,示例或 | 或者 :

2-在用户界面中,您必须询问将要操作的列类型。例如 col1 类型:字符串。col2 类型:int,如果用户输入是基于 cols 的。否则,如果用户输入文件只是堆文件,则规则生成器必须设置为字符串。否则必须按用户设置数据设置为特殊规则组,例如如果 col1 是字符串,则将可能的运算符设置为字符串运算符,显示用户字符串运算符。

3- 一些操作符必须有 2 个输入参数(input_arg_count),所以你必须除以询问列,在这个操作中必须操作哪些列

4-你必须收集每个带有输入的用户规则(最好使用字符串)并将其作为操作字符串发送到规则生成器。

5-规则生成器,将操作字符串除以(由您定义的除法器),然后在 switch 中,制定规则。

6-开关:在此开关中必须检查用户输入的文件类型,如果是堆,则调用你的堆类,如果基于col调用基于col的类,甚至你可以通过第一个运算符,例如,如果是第一个运算符使堆文件成为基于col的文件有| 所以,你可以通过 | 来划分文件 ,那么你有一个基于 col 的文件。这将重用操作

7-然后检查操作类型,所以在posible oprators xml文件中,您可以了解操作类型。并制作一个将运算符作为数组获取的主函数,作为另一个参数获取运算符,并作为最后一个参数从可能的运算符 xml 文件中获取可用的 php 函数。所以系统现在将使用哪个函数,哪个运算符和参数。

8-返回第一次操作的结果后。发送到下一个操作,如循环函数,因此操作将被操作。

你可以把它放到 git hub 中,所以我可以帮助你开发它。

祝你好运

于 2013-10-10T14:01:06.273 回答
0

如果在您的示例中原始值始终是一个字符串,并且规则始终对该字符串进行一些修改,以便生成的值始终只能从字符串中扣除,您是否考虑过机器学习方法?您可以让用户填写原始值和生成值的示例,并且您的算法可以尝试从示例中推断规则。

这篇论文似乎正在做一些你想要的事情(但可能稍微复杂一些;如果你所有的规则基本上都是正则表达式,它可能会更容易)。

这个选项应该是可扩展的,如果你能找到正确的算法,它应该最大限度地减少你做忙碌工作的需要,但它并不完美,如果你采用这种方法,可能会有一个用户在某个时候有一些真正的需要手写的复杂规则。

于 2013-10-12T02:09:22.030 回答