关于提示工程是否有用,有多大用,一直都没有一个定量的表述。下面是我在实际生活工作中的实际案例,验证提示工程的有效性。我是一个即将毕业的研究生,我会用GPT4编程、解决疑惑、写作、科研等。
编程
-
小结
提示工程是有用的,用处的大小可以是从0到1,从无到有而不仅仅是优化了一些效率。关于提示词是怎么发挥作用的,这里并未过多叙述,设计的提示词提供了解决编程问题的一个精细的框架,以更精确科学的方式做事总是有益处的。 -
拿一道考试中的真题为例,基于精巧提示词给出的答案和直接问的答案进行对比,选了一个典型的用例,这证明了效果。
-->其中两者都是基于动态规划,但前者使用了前缀和进行了优化(提示词诱导的),而后者给出了更直接的解决方案,显然在相同时间或者交互成本下,前者取得了更高的效率(100% vs 36%)。
一个额外的有趣的次数对比实验
这里试图通过直接询问,观察通过几次交互可以达到提示词(一次交互)的效果。需要注意的是这里有一个实验变量没控制好,就是这里的模型是claude-opus,也是目前Claude发布的最强模型,在社区中,编程能力得到不错的认可,多人认为超过gpt4,所以这里也可一定程度弥补实验变量不一致带来的不可靠性。实验结果是需要两次交互(交互效率降低一倍),这是在已知正确答案的情况下继续追问获得的,通常谁会这么做呢?
附录
- 提示词被包装到GPTs中,欢迎使用。
- 题目描述
小苯是“小红书app”的一位博主,他在自己的账号中发布了n篇分享,编号从1到 n。有一天他想试试看自己能不能准确的记住这n篇分享的点赞数,但他很快就发现凭自己的记忆力根本没法准确地记住每一篇的点赞数,但他记得如下两种信息:
- 每篇分享的点赞量都为正数,且不超过m。
- 第i篇分享的点赞量和第i+1篇分享的点赞量的大小关系。
他想知道,在已知这些信息的前提下,所有分享的点赞量有多少种不同的可能。
(结果可能很大,请输出其对 1000000007 取模的值)
- 输入描述:
输入包含两行。
第一行两个正整数 n,m(1 ≤n,m≤2000),分别表示分享的总个数和每篇分享的点赞数的上限。
第二行一个长度为 n-1 的字符串 si,其中只包含">”,"="这三种字符。如果si='>',则第i篇分享的点赞量严格高于第 i+1篇,”<”和“=”同理。 - 输出描述
一行一个整数表示符合条件的所有分享的点赞量有多少种不同的可能,结果对 1000000007 取模。 - 样例输入
4 3
<=> - 样例输出
5 - 提示
说明[1,2,2,1][2,3,3,2][1,3,3,1][2,3,3,1][1,3,3,2]一共五种可能
- 提示词内容如下:
`你是解决问题的专家,利用你的编程技能来寻找解决方案。面对一个特定的编程挑战,首先,向我解释问题的细节及其重要性。
接着,根据问题描述,设计一个解决方案的大纲。然后,分析这个方案可能的执行时间和复杂度。
识别出哪些部分是解决方案中性能可能成为瓶颈的关键点。
制作一个包含‘影响力’和‘复杂度’的表格,对每个可能的性能瓶颈进行评估,包括实现难度和对性能的潜在影响。
根据评估,对它们进行排序。选择排名最高的瓶颈,并更详细地解释如何优化你的代码以解决这个问题。然后,重新编写实际的代码段。
完成后,检查新代码是否存在问题。如果有,则尝试解决这些问题。否则,继续针对排名第二和第三的性能瓶颈重复这一过程。
最后,将所有的性能改进整合到完整的代码重写中。
</prompt_explanation>
<response_format>
问题解释:
$problem_explanation
解决方案概述:
$solution_outline
执行时间和复杂度分析:
$time_complexity_analysis
关键性能瓶颈候选:
$performance_bottlenecks
影响力和复杂度表格:
瓶颈 | 影响力 | 复杂度 |
---|---|---|
$bottlenecks_table |
瓶颈排序:
$bottlenecks_ordered
针对最高排名瓶颈的详细解释与代码重写:
详细解释
$top_bottleneck_explanation
代码重写
$top_bottleneck_code_rewrite
新代码存在的问题:
(仅在存在问题时包含)
$top_bottleneck_issues
问题解决尝试:
(仅在存在问题时包含)
$top_bottleneck_fix_attempt
针对第二高排名瓶颈的详细解释与代码重写:
详细解释
$second_bottleneck_explanation
代码重写
$second_bottleneck_code_rewrite
新代码存在的问题:
(仅在存在问题时包含)
$second_bottleneck_issues
问题解决尝试:
(仅在存在问题时包含)
$second_bottleneck_fix_attempt
针对第三高排名瓶颈的详细解释与代码重写:
详细解释
$third_bottleneck_explanation
代码重写
$third_bottleneck_code_rewrite
新代码存在的问题:
(仅在存在问题时包含)
$third_bottleneck_issues
问题解决尝试:
(仅在存在问题时包含)
$third_bottleneck_fix_attempt
完整代码重写与性能改进:
$full_code_with_improvements
</response_format>`
- 与claude-opus的交互实验
-题目描述
小苯是“小红书app”的一位博主,他在自己的账号中发布了n篇分享,编号从1到 n。有一天他想试试看自己能不能准确的记住这n篇分享的点赞数,但他很快就发现凭自己的记忆力根本没法准确地记住每一篇的点赞数,但他记得如下两种信息:
1.每篇分享的点赞量都为正数,且不超过m。
2.第i篇分享的点赞量和第i+1篇分享的点赞量的大小关系。
他想知道,在已知这些信息的前提下,所有分享的点赞量有多少种不同的可能。
(结果可能很大,请输出其对 1000000007 取模的值)
输入描述:
输入包含两行。
第一行两个正整数 n,m(1 ≤n,m≤2000),分别表示分享的总个数和每篇分享的点赞数的上限。
第二行一个长度为 n-1 的字符串 si,其中只包含">”,"="这三种字符。如果si='>',则第i篇分享的点赞量严格高于第 i+1篇,”<”和“=”同理。
输出描述
一行一个整数表示符合条件的所有分享的点赞量有多少种不同的可能,结果对 1000000007 取模。
样例输入
4 3
<=>
样例输出
5
提示
说明[1,2,2,1][2,3,3,2][1,3,3,1][2,3,3,1][1,3,3,2]一共五种可能
这是一个动态规划问题。我们可以用dp[i][j]表示前i篇分享,第i篇分享的点赞数为j的情况下的可能数量。然后根据第i篇和第i+1篇的大小关系来更新dp数组。
以下是C++代码实现:
#include
#include
#include
using namespace std;
const int MOD = 1000000007;
int main() {
int n, m;
string s;
cin >> n >> m >> s;
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
for (int j = 1; j <= m; j++) {
dp[1][j] = 1;
}
for (int i = 2; i <= n; i++) {
if (s[i-2] == '<') {
for (int j = 1; j <= m; j++) {
for (int k = 1; k < j; k++) {
dp[i][j] = (dp[i][j] + dp[i-1][k]) % MOD;
}
}
} else if (s[i-2] == '=') {
for (int j = 1; j <= m; j++) {
dp[i][j] = dp[i-1][j];
}
} else {
for (int j = 1; j <= m; j++) {
for (int k = j + 1; k <= m; k++) {
dp[i][j] = (dp[i][j] + dp[i-1][k]) % MOD;
}
}
}
}
int ans = 0;
for (int j = 1; j <= m; j++) {
ans = (ans + dp[n][j]) % MOD;
}
cout << ans << endl;
return 0;
}
解释:
我们用二维数组dp,其中dp[i][j]表示前i篇分享,第i篇分享的点赞数为j的情况下的可能数量。
初始化dp[1][j] = 1,表示第一篇分享的点赞数可以是1到m的任意值。
从第二篇分享开始,根据第i篇和第i+1篇的大小关系来更新dp数组:
如果s[i-2] == '<',说明第i篇分享的点赞数要小于第i-1篇,所以我们枚举第i篇的点赞数j,然后将第i-1篇点赞数小于j的所有情况累加到dp[i][j]上。
如果s[i-2] == '=',说明第i篇和第i-1篇的点赞数相等,直接将dp[i][j]赋值为dp[i-1][j]。
如果s[i-2] == '>',说明第i篇分享的点赞数要大于第i-1篇,类似地枚举第i篇的点赞数j,将第i-1篇点赞数大于j的所有情况累加到dp[i][j]上。
最后将dp[n][j]累加起来,就是所有可能的点赞数的总数。
时间复杂度为O(nm^2),空间复杂度为O(nm)。
用python实现时间效率最高的算法
下面是用Python实现的时间效率最高的算法:
MOD = 1000000007
def count_possibilities(n, m, s):
dp = [[0] * (m + 1) for _ in range(n + 1)]
for j in range(1, m + 1):
dp[1][j] = 1
for i in range(2, n + 1):
if s[i-2] == '<':
for j in range(1, m + 1):
dp[i][j] = sum(dp[i-1][k] for k in range(1, j)) % MOD
elif s[i-2] == '=':
for j in range(1, m + 1):
dp[i][j] = dp[i-1][j]
else:
for j in range(1, m + 1):
dp[i][j] = sum(dp[i-1][k] for k in range(j + 1, m + 1)) % MOD
return sum(dp[n][j] for j in range(1, m + 1)) % MOD
n, m = map(int, input().split())
s = input()
print(count_possibilities(n, m, s))
解释:
我们使用与之前C++实现类似的动态规划方法,用二维数组dp,其中dp[i][j]表示前i篇分享,第i篇分享的点赞数为j的情况下的可能数量。
初始化dp[1][j] = 1,表示第一篇分享的点赞数可以是1到m的任意值。
从第二篇分享开始,根据第i篇和第i+1篇的大小关系来更新dp数组:
如果s[i-2] == '<',说明第i篇分享的点赞数要小于第i-1篇,所以我们枚举第i篇的点赞数j,然后将第i-1篇点赞数小于j的所有情况累加到dp[i][j]上。这里使用了Python的sum函数和生成器表达式来优化求和过程。
如果s[i-2] == '=',说明第i篇和第i-1篇的点赞数相等,直接将dp[i][j]赋值为dp[i-1][j]。
如果s[i-2] == '>',说明第i篇分享的点赞数要大于第i-1篇,类似地枚举第i篇的点赞数j,将第i-1篇点赞数大于j的所有情况累加到dp[i][j]上。同样使用了sum函数和生成器表达式。
最后使用sum函数和生成器表达式将dp[n][j]累加起来,就是所有可能的点赞数的总数。
这个Python实现利用了sum函数和生成器表达式来优化求和过程,避免了显式的嵌套循环,提高了时间效率。
时间复杂度仍为O(nm^2),空间复杂度为O(nm)。
这是效率最高的办法吗
你提出了一个很好的问题。我们可以进一步优化算法,将时间复杂度降低到O(n*m)。
优化的思路是,我们可以预处理出前缀和数组,然后在更新dp数组时,直接使用前缀和来计算区间和,避免了内层循环。
下面是优化后的Python代码:
MOD = 1000000007
def count_possibilities(n, m, s):
dp = [[0] * (m + 1) for _ in range(n + 1)]
for j in range(1, m + 1):
dp[1][j] = 1
for i in range(2, n + 1):
prefix_sum = [0] * (m + 2)
for j in range(1, m + 1):
prefix_sum[j + 1] = (prefix_sum[j] + dp[i-1][j]) % MOD
if s[i-2] == '<':
for j in range(1, m + 1):
dp[i][j] = prefix_sum[j]
elif s[i-2] == '=':
for j in range(1, m + 1):
dp[i][j] = dp[i-1][j]
else:
for j in range(1, m + 1):
dp[i][j] = (prefix_sum[m + 1] - prefix_sum[j + 1]) % MOD
return sum(dp[n][j] for j in range(1, m + 1)) % MOD
n, m = map(int, input().split())
s = input()
print(count_possibilities(n, m, s))
解释:
我们在每次更新dp数组之前,先计算出前缀和数组prefix_sum。prefix_sum[j]表示dp[i-1][1]到dp[i-1][j-1]的和。
当s[i-2] == '<'时,我们直接将prefix_sum[j]赋值给dp[i][j],因为dp[i][j]的值就是dp[i-1][1]到dp[i-1][j-1]的和。
当s[i-2] == '='时,更新方式不变,直接将dp[i-1][j]赋值给dp[i][j]。
当s[i-2] == '>'时,我们将prefix_sum[m + 1]减去prefix_sum[j + 1],得到dp[i-1][j+1]到dp[i-1][m]的和,赋值给dp[i][j]。
最后的求和过程不变。
通过引入前缀和数组,我们避免了内层循环,将时间复杂度降低到了O(n*m)。
空间复杂度仍为O(n*m),但我们可以进一步优化空间复杂度到O(m),因为在更新dp数组时,我们只需要用到上一行的数据。你可以尝试自己实现空间优化的版本。