【问题标题】:Unscramble words Challenge - improve my bash solution解读单词挑战 - 改进我的 bash 解决方案
【发布时间】:2019-02-19 07:55:32
【问题描述】:

有一个夺旗挑战

我有两个文件;一个有这样的乱码,大约有 550 个条目

dnaoyt
cinuertdso
bda
haey
tolpap
...

第二个文件是一个包含大约 9,000 个条目的字典

radar
ccd
gcc
fcc
historical
...

目标是找到正确的、未加扰的单词版本,该版本包含在字典文件中。

我的方法是对第一个文件中第一个单词的字符进行排序,然后查找第二个文件中的第一个单词是否具有相同的长度。如果是这样,那么也对其进行排序并进行比较。

这是我功能齐全的 bash 脚本,但速度很慢。

#!/bin/bash

while IFS="" read -r p || [ -n "$p" ]
do
    var=0
    ro=$(echo $p | perl -F -lane 'print sort @F')
    len_ro=${#ro}
    while IFS="" read -r o || [ -n "$o" ]
    do
        ro2=$(echo $o | perl -F -lane 'print sort @ F')
        len_ro2=${#ro2}
        let "var+=1"
        if [ $len_ro == $len_ro2 ]; then
            if  [ $ro == $ro2 ]; then
                echo $o >> new.txt
                echo $var >> whichline.txt
            fi
        fi
    done < dictionary.txt
done < scrambled-words.txt

我也尝试将所有字符转换为 ASCII 整数并对每个单词求和,但在比较时我意识到不同字符模式的总和可能具有相同的总和。

[编辑] 对于记录: - 字典中没有字谜 - 要获得标志,您需要将未加扰的单词导出为一个 blob,然后用它制作一个 SHA-Hash(这就是标志) - 链接到想要文件的人的 ctf https://challenges.reply.com/tamtamy/user/login.action

【问题讨论】:

  • 它有效吗?速度的最佳改进是用另一种语言来做,比如 Perl(整个事情),Python,......它的运行速度比 bash 快 MUCH 因为一旦解释它就不会创建一堆 shell跑步。可能还有其他更有效的算法,但这种语言变化会产生巨大的影响。
  • 除了语言选择之外,对于scrambled-words.txt 的每一行,您处理完整的dictionary.txt 文件。你的问题可能更适合Code Review - 不过请确保它是on topic
  • 有两件事立即脱颖而出:(1) 嵌套循环意味着您正在重新阅读 dictionary.txt 的全部内容,以了解 scrambled-words.txt 中的每个单词。如果你很聪明,你只需要读一次字典。避免任何嵌套循环。 (2) 你调用perl 550×9900 次,效率非常低。您实际上应该最多只需要每个文件调用一次。要么用 perl 编写整个脚本,要么如果你坚持使用 bash,请避免调用 perl。
  • 另外,你会得到错误的字谜匹配:你输入的单词“listen”会显示为匹配“silent”,因为它们都排序为“eilnst”。
  • 你能把这两个文件提供给我们吗?

标签: bash perl string-comparison scramble


【解决方案1】:

您最好从字典文件中创建一个查找字典(以排序的单词为关键字)。

你的循环体被执行了 550 * 9,000 = 4,950,000 次 (O(N*M))。

我建议的解决方案执行两个循环,每个循环最多 9,000 次 (O(N+M))。

奖励:它可以免费找到所有可能的解决方案。

#!/usr/bin/perl

use strict;
use warnings qw( all );
use feature qw( say );

my $dict_qfn      = "dictionary.txt";
my $scrambled_qfn = "scrambled-words.txt";

sub key { join "", sort split //, $_[0] }

my %dict;
{
   open(my $fh, "<", $dict_qfn)
      or die("Can't open \"$dict_qfn\": $!\n");

   while (<$fh>) {
      chomp;
      push @{ $dict{key($_)} }, $_;
   }
}

{
   open(my $fh, "<", $scrambled_qfn)
      or die("Can't open \"$scrambled_qfn\": $!\n");

   while (<$fh>) {
      chomp;
      my $matches = $dict{key($_)};
      say "$_ matches @$matches" if $matches;
   }
}

如果对于您提供的尺寸,这只需要您解决方案的百万分之一的时间,我不会感到惊讶(如果您要增加尺寸,它的扩展性比您的要好得多)。

【讨论】:

    【解决方案2】:

    我会用 gawk 做这样的事情

    gawk '
    NR == FNR {
        dict[csort()] = $0
        next
    }
    
    {
        print dict[csort()]
    }
    
    function csort(    chars, sorted) {
        split($0, chars, "")
        asort(chars)
        for (i in chars)
            sorted = sorted chars[i]
    
        return sorted
    }' dictionary.txt scrambled-words.txt
    

    【讨论】:

      【解决方案3】:

      这是我使用sortjoin 提出的无perl 解决方案:

      sort_letters() {
          # Splits each letter onto a line, sorts the letters, then joins them
          #   e.g. "hello" becomes "ehllo"
          echo "${1}" | fold-b1 | sort | tr -d '\n'
      }
      
      
      # For each input file...
      for input in "dict.txt" "words.txt"; do
          # Convert each line to [sorted] [original]
          #  then sort and save the results with a .sorted extension
          while read -r original; do
              sorted=$(sort_letters "${original}")
              echo "${sorted} ${original}"
          done < "${input}" | sort > "${input}.sorted"
      done
      
      # Join the two files on the [sorted] word
      #   outputting the scrambled and unscrambed words
      join -j 1 -o 1.2,2.2 "words.txt.sorted" "dict.txt.sorted"
      

      【讨论】:

        【解决方案4】:

        我尝试了一些非常相似的东西,但有点不同。

        #!/bin/bash
        
        exec 3<scrambled-words.txt
        while read -r line <&3; do
           printf "%s" ${line} | perl -F -lane 'print sort @F'
        done>scrambled-words_sorted.txt
        exec 3>&-
        
        exec 3<dictionary.txt
        while read -r line <&3; do
           printf "%s" ${line} | perl -F -lane 'print sort @F'
        done>dictionary_sorted.txt
        exec 3>&-
        
        printf "" > whichline.txt
        exec 3<scrambled-words_sorted.txt
        while read -r line <&3; do
           counter="$((++counter))"
           grep -n -e "^${line}$" dictionary_sorted.txt | cut -d ':' -f 1 | tr -d '\n' >>whichline.txt   printf "\n" >>whichline.txt
        done   
        exec 3>&-
        

        如您所见,我没有创建new.txt 文件;相反,我只创建 whichline.txt 与单词不匹配的空白行。您可以轻松地将它们粘贴起来创建new.txt

        脚本背后的逻辑几乎是你背后的逻辑,除了我少调用perl 并且我保存了两个支持文件。 我认为(但我不确定)创建它们并仅循环一个文件会比 perl 的 ~5kk 调用更好。这种方式“仅”调用了大约 10k 次。

        最后,我决定使用grep,因为它(可能)是最快的正则表达式匹配器,并且搜索整行长度在正则表达式中是固有的。

        请注意@benjamin-w 所说的仍然有效,在这种情况下,grep 会回复很糟糕,我没有处理它!

        我希望这可以帮助[:

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-06-10
          • 2022-01-16
          • 2017-08-19
          • 2019-08-17
          • 2010-09-06
          • 2010-09-12
          • 2011-09-06
          • 1970-01-01
          相关资源
          最近更新 更多