perl 在Linux中随机化txt文件,但保证不重复行

pkbketx9  于 8个月前  发布在  Perl
关注(0)|答案(7)|浏览(89)

我有一个名为test.txt的文件,看起来像这样:

Line 1
Line 2
Line 3
Line 3
Line 3
Line 4
Line 8

我需要一些代码,这将随机这些行**,但保证**,相同的文字不能出现在连续行即“行3”必须分裂,而不是出现两次,甚至三次在一排。
我已经看到了这个问题的许多变化,但到目前为止,没有一个处理重复的行。
到目前为止,我已经测试了以下内容:

shuf test.txt

awk 'BEGIN{srand()}{print rand(), $0}' test.txt | sort -n -k 1 | awk 'sub(/\S /,"")'*

awk 'BEGIN {srand()} {print rand(), $0}' test.txt | sort -n | cut -d ' ' -f2-

cat test.txt | while IFS= read -r f; do printf "%05d %s\n" "$RANDOM" "$f"; done | sort -n | cut -c7-

perl -e 'print rand()," $_" for <>;' test.txt | sort -n | cut -d ' ' -f2-

perl -MList::Util -e 'print List::Util::shuffle <>' test.txt

所有这些都使文件中的行随机化,但通常最终相同的行在文件中连续出现。
有什么办法可以让我这么做吗?
这是编辑前的数据。您可以看到数字82576483出现在连续的行中

REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>83476098</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441754</ORD-AUTH-C><ORD-AUTH-V>94.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5759148</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576786</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>24.79</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576324</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441754</ORD-AUTH-C><ORD-AUTH-V>98.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5759148</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576113</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>28.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82590483</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>25.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576883</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>17.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>83476483</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>

**注意:**添加了提示以突出显示感兴趣的行;数据文件中不存在提示

这就是我需要在数字82576483分散在文件中而不是在连续行上时发生的情况

REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>83476098</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441754</ORD-AUTH-C><ORD-AUTH-V>94.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5759148</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576786</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>24.79</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576324</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441754</ORD-AUTH-C><ORD-AUTH-V>98.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5759148</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576113</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>28.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82590483</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>25.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576883</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>17.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>83476483</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
ylamdve6

ylamdve61#

一种有效的方法,至少与重复随机尝试相比:
1.对所有唯一字符串进行排序
1.对于每个重复,
1.确定它可以放置的位置。
1.随便挑一个
1.在那里插入副本。

use strict;
use warnings;

use List::Util qw( shuffle );

my %counts; ++$counts{ $_ } while <>;

my @strings = shuffle keys %counts;

for my $string ( keys( %counts ) ) {
   my $count = $counts{ $string };
   for ( 2 .. $count ) {
      my @safe =
         grep { $_ == 0        || $strings[ $_ - 1 ] ne $string }
         grep { $_ == @strings || $strings[ $_ - 0 ] ne $string }
         0 .. @strings;

      my $pick = @safe ? $safe[ rand( @safe ) ] : rand( @strings+1 );

      splice( @strings, $pick, 0, $string );
   }
}

print( @strings );

(Just使用perl -e'...' Package 以从shell运行。
经过测试。可能有更好的方法。

pod7payv

pod7payv2#

一般方法:

  • 使用关联数组(linecnt[])来记录一行被看到的次数
  • linecnt[]分成两个独立的普通数组:single[1]=<lineX>; single[2]=<lineY>multi[1]=<lineA_copy1>; multi[2]=<lineA_copy2>; multi[3]=<lineB_copy1>
  • 虽然我们在两个数组中至少有一个条目(single[] / multi[])散布我们的打印(即,打印random(single[]),打印randome(multi[]),打印random(single[]),打印randome(multi[]));**注意:**显然不是 * 真正 * 随机的,但这允许我们最大限度地分离重复的机会,同时限制CPU开销(即,不需要重复 Shuffle 希望一个'随机'排序,分裂重复)
  • 如果我们有任何single[]条目,则打印random(single[])
  • 如果我们有任何multi[]条目离开然后打印random(multi[]);**注意:*假设OP的注解re:tough!! * 意味着如果这是所有剩下的,可以连续打印重复

一个awk的想法:

$ cat dupes.awk

function print_random(a, acnt,     ndx) {
    ndx=int(1 + rand() * acnt)
    print a[ndx]
    if (acnt>1) { a[ndx]=a[acnt]; delete a[acnt] }
    return --acnt
}

BEGIN { srand() }

      { linecnt[$0]++ }

END   { for (line in linecnt) {
            if (linecnt[line] == 1)
               single[++scnt]=line
            else
               for (i=1; i<=linecnt[line]; i++)
                   multi[++mcnt]=line
            delete linecnt[line]
        }

        while (scnt>0 && mcnt>0) {
              scnt=print_random(single,scnt)
              mcnt=print_random(multi,mcnt)
        }

        while (scnt>0)
              scnt=print_random(single,scnt)

        while (mcnt>0)
              mcnt=print_random(multi,mcnt)
      }

注意:

  • srand()不是真正的随机(例如,两次快速连续的运行可以生成相同的精确输出)
  • 可以添加额外的步骤来确保快速、连续的运行不会生成精确的输出(例如,提供一个用于srand()的操作系统级种子)

运行OP的样本数据集:

$ awk -f dupes.awk test.txt
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>83476098</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>83476483</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576883</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82590483</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N>

REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576113</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576786</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576324</CUST-ACNT-N>

注意:

  • 为简洁起见,数据线被切断
  • 添加空白行以突出显示 a) 第1个交错single[] / multi[]条目块和 b) 第2个完成剩余single[]条目块
  • 重复运行会产生不同的结果

处理重复的示例...

$ cat test.txt
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>83476098</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**99999999**</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**99999999**</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576786</CUST-ACNT-N>

运行awk脚本的结果:

$ awk -f dupes.awk test.txt
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576786</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**99999999**</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>83476098</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N>

REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**99999999**</CUST-ACNT-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N>

注意:

  • 添加空白行以突出显示 a) 第1个交错single[] / multi[]条目块和 b) 第2个完成剩余multi[]条目块
  • 重复运行会产生不同的结果
qcuzuvrc

qcuzuvrc3#

ruby有一些简洁的语法。
https://stackoverflow.com/a/65843200可以根据您的数据轻松修改:

ruby -e '

regex = /<CUST-ACNT-N>\d+<\/CUST-ACNT-N>/

arr = readlines.map {|line| {:k => line[regex], :v => line}}
arr = arr.sort_by {|kv| kv[:k]}
mid = arr.size.succ / 2
arr = arr[0..mid-1].zip(arr[mid..-1]).flatten.compact.map {|kv| kv[:v]}
idx = (1..arr.size-1).find { |i| arr[i] == arr[i-1] }

puts idx ? arr.rotate(idx) : arr 

' file.txt
gywdnpxw

gywdnpxw4#

另一种方法:首先打乱行,然后逐行,收集重复的内容。对于每一行,检查现有的重复内容,如果可能的话,将其放入。在输入以这种方式处理后,然后从头开始检查结果,尝试放置剩余的重复内容

use warnings;
use strict;
use feature 'say';    
use List::Util qw(shuffle any);

# Push dupes to data unless same as last element or has been added already
sub add_dupes {
    my ($data, $dupes, $mask) = @_;

    for my $idx (0..$#$dupes) {
        next if $dupes->[$idx] eq $data->[-1];
        next if any { $idx == $_ } @$mask;

        push @$data, $dupes->[$idx];
        push @$mask, $idx;
    }
}

my @lines = <>; 
chomp @lines;

my @res = shift @lines;

foreach my $line (shuffle @lines) {
    if ($line eq $res[-1]) { push @dupes, $line }
    else                   { push @res, $line }

    # Redistribute dupes found so far if possible
    add_dupes(\@res, \@dupes, \@mask_dupes);
}

# Redistribute remaining (unused) dupes   
my @final;
foreach my $line (@res) {
    if ($line eq $final[-1]) { push @dupes, $line }
    else                     { push @final, $line }

    add_dupes(\@final, \@dupes, \@mask_dupes);
}

say "\nFinal (", scalar @final, " items):";
say for @final;

它将发现的重复存储在一个数组中,并检查每行是否可以插入现有的重复。它使用一个辅助掩码数组来标记已使用的重复索引。
注意到

  • 首先 Shuffle 是有帮助的,因为许多连续的重复行将以压倒性的概率被移动
  • 重复数组是为每行数据搜索的,所以原则上最坏的情况是 O(N2)(或者,更确切地说,O(NM))。我认为,在任何方法中都必须以某种方式完成,但应该可以最小化这些交叉搜索。

然而,dupes的数组应该很短,而且大多数时候不会搜索整个数组。所以如果输入不是很大,有很多dupes,这应该会很好。

  • 如果最后碰巧没有重复的,我们就不必要地复制了一个数组,但这并不是一个可怕的罪过,如果它是一次。

用不同的输入进行测试,有多行的许多重复,但需要更多的测试。(至少,添加基本的诊断打印并重复运行-它每次都会 Shuffle ,所以重复运行会有帮助-并检查输出。)

y4ekin9u

y4ekin9u5#

使用任何awk:

$ cat tst.awk
match($0,/<CUST-ACNT-N>[^<]+<\/CUST-ACNT-N>/) {
    key = substr($0,RSTART,RLENGTH)
    gsub(/^<CUST-ACNT-N>|<\/CUST-ACNT-N>$/,"",key)
    keys[NR] = key
    lines[NR] = $0
}
END {
    srand()
    maxAttempts = 1000
    while ( (output == "") && (++attempts <= maxAttempts) ) {
        output = distribute()
    }
    printf "%s", output
    if ( output == "" ) {
        print "Error: Failed to distribute the input." | "cat>&2"
        exit 1
    }
}

function distribute(    iters,numLines,maxIters,tmpLines,tmpKeys,idx,i,ret) {
    for ( idx in keys ) {
        tmpKeys[idx] = keys[idx]
        tmpLines[idx] = lines[idx]
        numLines++
    }

    maxIters = 1000
    while ( (numLines > 0) && (++iters <= maxIters) ) {
        idx = int(1+rand()*numLines)

        if ( tmpKeys[idx] != prev ) {
            ret = ret tmpLines[idx] ORS
            prev = tmpKeys[idx]
            for ( i=idx; i<numLines; i++ ) {
                tmpKeys[i] = tmpKeys[i+1]
                tmpLines[i] = tmpLines[i+1]
            }
            numLines--
        }
    }

    if ( numLines ) {
        ret = ""
    }
    return ret
}
$ awk -f tst.awk file
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>83476098</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>83476483</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576324</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441754</ORD-AUTH-C><ORD-AUTH-V>98.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5759148</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82590483</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>25.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576113</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>28.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441754</ORD-AUTH-C><ORD-AUTH-V>94.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5759148</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576883</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>17.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576786</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>24.79</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>

所以,在一次尝试中,它尝试了1000次,(maxIters)从未处理的行集合中随机找到下一行输出,该行与它刚刚添加到输出中的行不同,但最终仍可能失败,它尝试了1000次(maxAttempts)来产生输出。这仍然可能失败-如果你愿意,可以增加这些值,但你仍然可以得到无法按照你喜欢的方式组织的输出(例如,只有两行输入,其中两行都是相同的)。
您可以通过更改以下代码来提高效率并增加成功的机会:

ret = ret tmpLines[idx] ORS
        prev = tmpKeys[idx]
        for ( i=idx; i<numLines; i++ ) {
            tmpKeys[i] = tmpKeys[i+1]
            tmpLines[i] = tmpLines[i+1]
        }
        numLines--

创建/使用仅由键+行组成的二级数组,这些行与刚刚处理的行不具有相同的键,因此我们不需要在其上方进行if ( tmpKeys[idx] != prev )测试,并且当有其他键可供选择时,我们不会冒idx = int(1+rand()*numLines)以上随机找到相同键1000次的风险。该增强将作为练习:-)。

ih99xse1

ih99xse16#

使用TXR Lisp:

$ txr spread-sort.tl < data
Line 2
Line 4
Line 3
Line 1
Line 3
Line 8
Line 3
$ txr spread-sort.tl < data
Line 4
Line 3
Line 1
Line 3
Line 8
Line 3
Line 2
$ txr spread-sort.tl < data
Line 4
Line 3
Line 8
Line 3
Line 1
Line 3
Line 2

代码:

(set *random-state* (make-random-state))

(let ((dupstack (vec)))
  (labels ((distrib (single)
             (build
               (pend single)
               (each ((i 0..(len dupstack)))
                 (iflet ((item (pop [dupstack i])))
                   (add item)))
               (upd dupstack (remq nil))))
           (distrib-push (dupes)
             (prog1
               (distrib nil)
               (vec-push dupstack dupes))))
    (flow (get-lines)
      sort-group
      shuffle
      (mapcar [iff cdr distrib-push distrib])
      (mapcar distrib)
      tprint)))

这不是一个正确的算法,因为如果输入具有高的重复率,则存在正确的排序,例如:

1
2
2

它将不一致地产生分开重复的两个2 1 2顺序。
算法的主要流程是flow形式。从标准输入中获取行,然后通过sort-groupsort-group将重复的行分组并排序,得到一个字符串列表的列表。不重复的行是长度为1的列表。我们随机 Shuffle 这个列表的列表,这意味着重复的列在一起。
然后,我们使用两个通道来分发副本,这两个通道使用一个名为dupstack的向量。
在第一次传递中,我们Map列表的列表,使得单例通过distrib传递,重复通过distrib-push传递。这以下面描述的方式移动重复。在此传递之后,一些项保留在dupstack中;所以列表的列表并没有所有的项目。我们进行了另一次传递,这次只是将每个列表传递给distrib,它将项目分发到dupstack之外。
dupstack是列表的向量,列表是重复行的列表。例如[dupstack 0]可能包含("Line 3" "Line 3" "Line 3")等。
distrib的工作原理是:它扫过dupstack,从每个元素的前面弹出一个元素,并将其附加到输入列表,返回该输入列表。如果我们使用此操作进行Map,这意味着我们访问的每个列表,我们从每个重复集合中添加一个项目。每次扫过这个堆栈后,我们使用(upd dupstack (remq nil))压缩它,以清除它已变为空的列表。
函数distrib-push用于处理包含多个元素的列表时的第一遍(由Lisp cdr函数返回非空表示)。distrib-push所做的是用一个空列表调用distrib,只是为了收集任何可用的副本,每一个都有一个。这些从dupstack中挑选出来的项目然后 * 替换 * 当前的项目。这些项目,相同的字符串,被推入重复堆栈中的新槽中。

aydmsdu9

aydmsdu97#

下面是一个示例文件:

$ cat file
A Line 0
A Line 1
A Line 2
A Line 3
A Line 4
B Line 5
B Line 6
B Line 7
B Line 8
C Line 9
C Line 10
C Line 11
D Line 12

将第一列定义为键,并限制每个键都不能与同一个键相邻,然后随机化文件。在这个限制下,结果将是随机的,因为A的个数比B的个数多,并且A必须位于序列的开头。(奇数项的偶数索引数比奇数项的多,因为0的奇偶性是偶数。)
一般做法是:
1.将所有相似的关键行组合在一起;
1.从输入线的组中随机选择奇数或偶数,以便分配所定义的组;
1.随机选择剩余的行进行输出。
这在Ruby中很容易做到:

ruby -e '
BEGIN{keys=Hash.new { |h, k| h[k] = []} }
data=$<.read.split(/\R/)
data.each.with_index{|s,i| 
    s.match(/^(\S+)/); keys[$1]<<i 
} # regex for key goes here
olines=(0..data.length-1).to_a; nlines=Hash.new()
grp_cnt=keys.values.map{|sa| sa.length if sa.length>1}.compact.sum
keys.sort_by{|k,v| [-v.length, v[0]]}.each{|k, grp| 
    if grp.length>1 then
        evens, odds=olines.partition{|n| n.even?}
        if grp_cnt.to_f/data.length > 0.6 then
            pool=evens.length>odds.length ? evens[0...grp.length] : odds.reverse[0...grp.length]
        else
            pool=evens.length>odds.length ? evens : odds 
        end
        if pool.length<grp.length then pool=olines end
    else
        pool=olines
    end
    
    this_grp=pool.sample(grp.length)
    grp.zip(this_grp).each{|ks, vs| nlines[ks]=vs}
    
    olines.reject!{|line| this_grp.include?(line) }      # remove the used lines
}
nlines.sort_by{|k,v| v}.each{|v,k| puts "Line #{v} in => Line #{k} out; \"#{data[k]}\" => \"#{data[v]}\""}
' file

图纸:

Line 0 in => Line 0 out; "A Line 0" => "A Line 4"
Line 12 in => Line 1 out; "A Line 1" => "D Line 12"
Line 2 in => Line 2 out; "A Line 2" => "A Line 2"
Line 10 in => Line 3 out; "A Line 3" => "C Line 10"
Line 3 in => Line 4 out; "A Line 4" => "A Line 3"
Line 7 in => Line 5 out; "B Line 5" => "B Line 7"
Line 1 in => Line 6 out; "B Line 6" => "A Line 1"
Line 5 in => Line 7 out; "B Line 7" => "B Line 5"
Line 4 in => Line 8 out; "B Line 8" => "A Line 0"
Line 6 in => Line 9 out; "C Line 9" => "B Line 6"
Line 11 in => Line 10 out; "C Line 10" => "C Line 11"
Line 8 in => Line 11 out; "C Line 11" => "B Line 8"
Line 9 in => Line 12 out; "D Line 12" => "C Line 9"

这是很容易改变的,以适应OP示例输入。只有键和输出行的正则表达式被改变:

ruby -e '
BEGIN{keys=Hash.new { |h, k| h[k] = []} }
data=$<.read.split(/\R/)
data.each.with_index{|s,i| 
    s.match(/<CUST-ACNT-N>([^<]+)</); keys[$1]<<i 
} # regex for key goes here
olines=(0..data.length-1).to_a; nlines=Hash.new()
grp_cnt=keys.values.map{|sa| sa.length if sa.length>1}.compact.sum
keys.sort_by{|k,v| [-v.length, v[0]]}.each{|k, grp| 
    if grp.length>1 then
        evens, odds=olines.partition{|n| n.even?}
        if grp_cnt.to_f/data.length > 0.6 then
            pool=evens.length>odds.length ? evens[0...grp.length] : odds.reverse[0...grp.length]
        else
            pool=evens.length>odds.length ? evens : odds 
        end
        if pool.length<grp.length then pool=olines end
    else
        pool=olines
    end
    
    this_grp=pool.sample(grp.length)
    grp.zip(this_grp).each{|ks, vs| nlines[ks]=vs}
    
    olines.reject!{|line| this_grp.include?(line) }      # remove the used lines
}
nlines.sort_by{|k,v| v}.each{|v,k| puts "#{data[v]}"}
' file

图纸:

REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>83476483</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82590483</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>25.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>83476098</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576324</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441754</ORD-AUTH-C><ORD-AUTH-V>98.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5759148</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576883</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>17.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441754</ORD-AUTH-C><ORD-AUTH-V>94.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5759148</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576786</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>24.79</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>82576113</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>28.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>
REC-TYPE-C>CHARGE INVOICE</REC-TYPE-C><CUST-ACNT-N>**82576483**</CUST-ACNT-N><CUST-NAME-T>TEST TEN</CUST-NAME-T><ORD-AUTH-C>0044441552</ORD-AUTH-C><ORD-AUTH-V>21.99</ORD-AUTH-V><OUT-DOCM-D>01/09/2023</OUT-DOCM-D><ORD-N>5758655</ORD-N>

相关问题