【问题标题】:Implementing `sumproduct` in UNIX shell在 UNIX shell 中实现 `sumproduct`
【发布时间】:2016-12-07 17:13:43
【问题描述】:

我有一些来自脚本thescript 的输出,内容如下:

202 1 0 1 0 0 0

现在我想根据${SUM_MASK} 的值,选择性地将这个数字与awk 相加:

SUM_MASK=1,1,0,0,0,0,0

我想要类似的东西:

thescript | awk <SOMETHING>

其中thescript输出的每个数字乘以${SUM_MASK}中对应的数字,得到:

203

由于: 203 = 202 * 1 + 1 * 1 + 0 * 0 + 1 * 0 + 0 * 0 + 0 * 0 + 0 * 0

这类似于电子表格软件中的sumproduct 函数。

以下代码片段可以解决问题,但我想避免使用进程替换:

SUM_MASK="1,1,0,0,0,0,0"; paste <(thescript) <(echo ${SUM_MASK} | tr ',' '\n') | awk '{ SUM += $1 * $2 } END { print SUM }'

和命名管道:

SUM_MASK="1,1,0,0,0,0,0"; mkfifo fA; mkfifo fB; thescript > fA & echo ${SUM_MASK} | tr ',' '\n' > fB & paste fA fB | awk '{ SUM += $1 * $2 } END { print SUM }' > result.text; rm -f fA fB

我怎样才能做到这一点?

【问题讨论】:

  • 为什么需要awk
  • 我认为元素的数量并不总是固定为 7?
  • 顺便说一句,我会考虑从函数或脚本的命令行获取两个输入,并统一格式(例如,允许空格 逗号用于两者)。读者有点不明白为什么选择了您请求的调用约定(一个参数取自预定义变量,另一个来自标准输入)。
  • @CharlesDuffy 我不一定需要awk,但我也不确定是否使用bash。我认为awk 的问题会少一些。我当然可以从命令行获得输入,但变量已经到位。无论哪种方式,它只是语法略有不同,仅此而已。唯一重要的是它必须是单行的,因为我使用的服务不允许我定义 shell 脚本。 @MarkSetchell 这会改变你想到的实现中的任何内容吗?
  • 我收到了很多非常有趣的贡献。鉴于这个问题,我可能应该接受 CharlesDuffy 或 glennjackmann 的答案,尽管 karafka 很可能是我真正需要的答案。

标签: bash shell unix awk


【解决方案1】:
echo "202 1 0 1 0 0 0" |
awk -v summask="1,1,0,0,0,0,0" '
    BEGIN {split(summask, mask, /,/)}
    {   sumproduct=0
        for (i=1; i<=NF; i++) {
            sumproduct +=  $i * mask[i]
        }
        print sumproduct
    }
'
203

【讨论】:

  • 这绝对是该问题的最佳答案之一。
【解决方案2】:

这里不需要像awk 这样的外部工具——bash 只能通过内置功能解决这个问题。将以下实现视为一个函数:

sumproduct() {
  local -a sum_inputs sum_mask
  local idx result

  # read your sum_inputs into an array from stdin
  IFS=', ' read -r -a sum_inputs # this could be <<<"$1" to use the first argument

  # and your sum_mask from the like-named variable
  IFS=', ' read -r -a sum_mask <<<"$SUM_MASK" # or <<<"$2" for the second argument

  # ...iterate over array elements in sum_inputs; find the corresponding sum_mask; math.
  result=0
  for idx in "${!sum_inputs[@]}"; do
    (( result += ${sum_mask[$idx]} * ${sum_inputs[$idx]} ))
  done
  echo "$result"
}

对此进行测试:

echo "202 1 0 1 0 0 0" | SUM_MASK=1,1,0,0,0,0,0 sumproduct

...正确产生:

203

【讨论】:

  • 不错的解决方案。一个可能的小改进是整理 result 变量的更新:(( result += sum_mask[idx] * sum_inputs[idx] ))
  • 我不确定这是否适用于该服务使用的 bash 版本,但我会对其进行测试和报告。不过,非常好的解决方案!
  • @norok2,这里的一切都与 2006 年发布的 bash 3.2 兼容,所以我们谈论的是整整十年。实际上,我很确定(但尚未测试)兼容性可以追溯到 2.x 系列,即 90 年代后期。
  • (因此,如果它与 兼容,那么您的 shell 可能是 /bin/sh,根本不是 bash!但是,如果您将其从函数转换为带有#!/bin/bash shebang,那么它将保证使用 bash 运行,除非显式 sh scriptname 类型调用强制使用基线 POSIX shell;该转换可能只是在顶部添加 #!/bin/bash 并在底部添加 sumproduct "$@" )。
【解决方案3】:

您实际上不需要求和积,而是需要掩码求和,例如,如果您有很多掩码列,这应该会更快。

$ awk -v mask='1,1,0,0,0,0,0' 'BEGIN {n=split(mask,m,","); 
                                      for(i=1; i<=n; i++) if(m[i]) ix[i]} 
                                     {sum=0; 
                                      for(i in ix) sum += $i; 
                                      print sum}' file

203

【讨论】:

  • 我看不出这个答案与 2 小时前 @glennjackman 发布的答案有何不同。
  • 首先,没有乘法。其次,主循环的大小仅基于非零条目,而不是完整的掩码大小。否则,计算总和的方法就只有这么多。
  • 啊,确实。谢谢!这是一个加号。
  • 严格来说不是 sumproduct 实现,但很可能是我用例的最佳答案。非常感谢!
【解决方案4】:

使用一位数的乘法器,您可以制作一个简单的循环

SUM_MASK=1,1,0,0,0,0,0
offset=0
sum=0;
for i in 202 1 0 1 0 0 0; do
   j="${SUM_MASK:$offset:1}"
   ((sum += i * j ))
   ((offset+=2))
done
echo "${sum}"

此解决方案可用于脚本prodsum,可使用thescript | prodsum 调用:

offset=0
sum=0;
for i ; do
   j="${SUM_MASK:$offset:1}"
   ((sum += i * j ))
   ((offset+=2))
done
echo "${sum}"

编辑:当 SUB_MASK 可以有数字>9时,请使用以下内容:

SUM_MASK=1,10,0,0,0,0,0
sum=0;
remaining_mask="$SUM_MASK"

for i in 202 1 0 1 0 0 0; do
   j="${remaining_mask%%,*}"
   remaining_mask="${remaining_mask#*,}"
   ((sum += i * j ))
done
echo "${sum}"

【讨论】:

  • 假设你的被乘数永远是个位数是……相当的假设。有一个10 在面具和一切都搞砸了。
  • @Charles 当我阅读 OPS 问题时,可以将 SUM_MASK 视为一组布尔值。我首先想排除乘法,在测试$j -eq 0 后只写(( sum += i ))。当我想支持大于 10 的值时,我应该将 $remaining_mask 拆分为第一个 , 之前和之后的部分。
  • 如果只允许单个数字,为​​什么要使用逗号,而不是将掩码表示为1100000?在数据中使用逗号意味着它们做了一些有用的事情;最好有一种能反映实际能力的格式,而不是一种看起来可以支持多位掩码值但如果使用过就会中断的格式。
猜你喜欢
  • 2011-04-25
  • 2010-12-02
  • 2023-03-25
  • 1970-01-01
  • 2012-12-14
  • 2013-03-29
  • 2012-08-27
  • 1970-01-01
相关资源
最近更新 更多