首页 > 学技术 > 技术网文 > Linux Shell > 正文

[保留] 洗牌的技巧


来源 chinaunix.net 酷勤网整理

参看:

[url=http://bbs.chinaunix.net/viewthread.php?tid=307558]如何用shell实现“洗牌”效果?
[url=http://bbs.chinaunix.net/viewthread.php?tid=800110&extra=page%3D2]文本文件随机取几万行怎么取?





                                      洗牌的技巧




 


    洗牌问题:

    洗一副扑克,有什么好办法?既能洗得均匀,又能洗得快?即相对于一个文件来说怎样
高效率的实现乱序排列?

    关于洗牌问题,其实已经有了一个很好的shell解法,这里另外给三个基于AWK的方法,
有错误之处还请不吝指出。



方法一穷举:
    类似于穷举法,构造一个散列来记录已经打印行出现行的次数,如果出现次数多于一
次则不进行处理,这样可以防止重复,但缺点是加大了系统的开销。



awk -v N=`sed -n '$=' data` '
BEGIN{
FS="\n";
RS=""
}
{
srand();
while(t!=N){
  x=int(N*rand()+1);
  a[x]++;
  if(a[x]==1)
    {
        print $x;t++
    }
  }
}
' data


方法二变换:
    基于数组下标变换的办法,即用数组储存每行的内容,通过数组下标的变换
交换数组的内容,效率好于方法一。


#! /usr/awk

BEGIN{
srand();
}


{
 b[NR]=$0;
}


END{

 C(b,NR);
 for(x in b)
  {
    print b[x];
  }

}


function C(arr,len,i,j,t,x){

 for(x in arr)
  {
      i=int(len*rand())+1;
      j=int(len*rand())+1;
      t=arr;
      arr=arr[j];
      arr[j]=t;
  }

}




方法三散列:

    三个方法中最好的。
    利用AWK中散列的特性(详细请看:info gawk 中的7.x ),只要构造一个
随机不重复的散列函数即可,因为一个文件每行的linenumber是独一无二的,所
以用:

    随机数+每行linenumber    ------对应------>    那一行的内容

即为所构造的随机函数。
    从而有:


    awk 'BEGIN{srand()}{b[rand()NR]=$0}END{for(x in b)print b[x]}' data







    其实大家担心的使用内存过大的问题不必太在意,可以做一个测试:

测试环境:
PM 1.4GHz CPU,40G硬盘,内存256M的LAPTOP
SUSE 9.3  GNU bash version 3.00.16 GNU Awk 3.1.4

产生一个五十几万行的随机文件,大约有38M:

od /dev/urandom |dd  count=75000 >data



拿效率较低的方法一来说:

洗牌一次所用时间:

time awk -v N=`sed -n '$=' data` '
BEGIN{
FS="\n";
RS=""
}
{
srand();
while(t!=N){
  x=int(N*rand()+1);
  a[x]++;
  if(a[x]==1)
    {
        print $x;t++
    }
  }
}
' data

结果(文件内容省略):


real    3m41.864s
user    0m34.224s
sys     0m2.102s


所以效率还是勉强可以接受的。

方法二的测试:

time awk -f awkfile datafile

结果(文件内容省略):

real    2m26.487s
user    0m7.044s
sys     0m1.371s

效率明显好于第一个。


接着考察一下方法三的效率:

time awk 'BEGIN{srand()}{b[rand()NR]=$0}END{for(x in b)print b[x]}' data

结果(文件内容省略):

real    0m49.195s
user    0m5.318s
sys     0m1.301s


对于一个38M的文件来说已经相当不错了。
玩的愉快! 




 寂寞烈火 回复于:2006-07-31 10:55:37

楼主莫非是dbcat的马甲?


 waker 回复于:2006-07-31 10:57:16

dbcat是楼主的马甲:mrgreen:


 寂寞烈火 回复于:2006-07-31 10:58:34

引用:原帖由 waker 于 2006-7-31 10:57 发表
dbcat是楼主的马甲:mrgreen: 


很像很像,dbcatMM很久没露面了,~


 waker 回复于:2006-07-31 11:02:28

随机不重复的散列函数即可,因为一个文件每行的linenumber是独一无二的,所
以用:

    随机数+每行linenumber    ------对应------>    那一行的内容

即为所构造的随机函数。

b[rand()NR]
机率非常小,但不能排除重复可能
用 b[rand()" "NR]如何?


 r2007 回复于:2006-07-31 11:07:39

引用:原帖由 waker 于 2006-7-31 11:02 发表
随机不重复的散列函数即可,因为一个文件每行的linenumber是独一无二的,所
以用:

    随机数+每行linenumber    ------对应------>    那一行的内容

即为所构造的随机函数。

b[rand()NR]
机率非 ... 


同楼上观点一样
也可用逗号b[rand(),NR],默认是\034字符
散列的思路真棒,赞!


 苏蓉蓉 回复于:2006-07-31 11:10:30

引用:原帖由 waker 于 2006-7-31 11:02 发表
随机不重复的散列函数即可,因为一个文件每行的linenumber是独一无二的,所
以用:

    随机数+每行linenumber    ------对应------>    那一行的内容

即为所构造的随机函数。

b[rand()NR]
机率非 ... 



问世间此山是否最高?天若有情天亦老,爱你爱到忘不了 


 寂寞烈火 回复于:2006-07-31 11:23:49

引用:原帖由 苏蓉蓉 于 2006-7-31 11:10 发表


问世间此山是否最高?天若有情天亦老,爱你爱到忘不了  


晕~,waker好福气~  


 r2007 回复于:2006-07-31 11:30:39

引用:原帖由 寂寞烈火 于 2006-7-31 11:23 发表

晕~,waker好福气~   



曾子曰:机率非常小,但不能排除重复可能
我就是那个重复的
强烈申请和waker同等待遇


 phpman 回复于:2006-07-31 13:41:48

干嘛呢? 干嘛呢~
打情骂翘啊?  :m01:


 sxqsir 回复于:2006-07-31 16:53:59

高手们,能不能对列的取数也来个随机,而不仅仅是行??这个shell一直是我期待的


 寂寞烈火 回复于:2006-07-31 17:10:54

引用:原帖由 sxqsir 于 2006-7-31 16:53 发表
高手们,能不能对列的取数也来个随机,而不仅仅是行??这个shell一直是我期待的 


取出指定范围的随机数了,对行/列都是一样的呀


 sxqsir 回复于:2006-07-31 17:19:37

引用:原帖由 寂寞烈火 于 2006-7-31 17:10 发表

取出指定范围的随机数了,对行/列都是一样的呀 


老兄,列的取数没有随机的,我测过了。


 寂寞烈火 回复于:2006-07-31 17:31:39

引用:原帖由 sxqsir 于 2006-7-31 17:19 发表

老兄,列的取数没有随机的,我测过了。 


是这样的么?
/home/lee#cat txt
1 2 3 4 5 6 7
2 4 6 8 0
3 5 7 9
4 7 8 2 1
4 5 6 2 7
/home/lee#cat Rand
#!/bin/bash
while read line;do
array=($line)
echo ${array[$((RANDOM%${#array[@]}))]}
done<txt
/home/lee#sh Rand
2
8
3
2
6


 sxqsir 回复于:2006-07-31 17:39:50

你怎么只取出一列?
我用第三条shell:
awk 'BEGIN{srand()}{b[rand()" "NR]=$0}END{for(x in b)print b[x]}' data
data是一个n*n的文本文件,排序结果是行取随机,但每一行的内容(即列)没有改变。


 waker 回复于:2006-07-31 17:51:02

 awk 'BEGIN{srand()}{for(i=1;i<=NF;i++)a[rand(),i]=$i;for(i in a)c=c" "a;sub(/^./,"",c);b[rand(),NR]=c;delete a;c=""}END{for(x in b)print b[x]}' urfile


难道别人写那么多一点儿启发性都没有?


 sxqsir 回复于:2006-07-31 18:24:45

引用:原帖由 waker 于 2006-7-31 17:51 发表
 awk 'BEGIN{srand()}{for(i=1;i<=NF;i++)a[rand(),i]=$i;for(i in a)c=c" "a;sub(/^./,"",c);b[rand(),NR]=c;delete a;c=""}END{for(x in b)print b[x]}' urfile

[/ ... 


人笨了一点,但一定会好好学习,多谢老大指点,但运行结果是这样的:
awk: Syntax error
 at line 1 of program << BEGIN{srand()}{for(i ... >>
 context is
        BEGIN{srand()}{for(i=1;i<=NF;i++)a[rand(),i]=$i;for(i in a)c=c" "a;su
b(/^./,"",c);b[rand(),NR]=c;delete >>>  a; <<<
awk: illegal statement
 at line 1 of program << BEGIN{srand()}{for(i ... >>

环境是sco5.0.5 ksh


 寂寞烈火 回复于:2006-07-31 18:34:46

引用:原帖由 sxqsir 于 2006-7-31 18:24 发表

人笨了一点,但一定会好好学习,多谢老大指点,但运行结果是这样的:
awk: Syntax error
 at line 1 of program << BEGIN{srand()}{for(i ... >>
 context is
        BEGIN{srand()}{for(i=1; ... 


用nwak/gwak 
waker老大常怎么说
引用:
$CO是仅次于M$的bugware生产大户




 sxqsir 回复于:2006-07-31 18:58:57

os是死的,程序是活的


 waker 回复于:2006-08-01 08:32:51

仔细man一下你$co中awk怎么用吧,看看它的delete怎么用,唾沫不是天天都有


 sxqsir 回复于:2006-08-01 19:28:59

查了一下,delete后面是跟数组元素,不知道delete a 怎么理解,如果delete a的话又变成取随机行了,跟楼主的命令效果一样。其实我只想在一个文本中随机取出一些字符生成另一个文本,我想这应该就是洗牌吧,还望高人们有空的时候再费些唾沫在这里,有机会请喝茶。


 waker 回复于:2006-08-01 19:43:34

for(i in a){c=c" "a;delete a}写在循环里行不行哪?


 waker 回复于:2006-08-01 19:44:35

不知道delete a 怎么理解
如果你还没尝试过GREAT GNU AWK,就不用理解了


 大蚂蚁 回复于:2006-08-01 20:37:34

楼主终于露出行踪了,好贴!


 sxqsir 回复于:2006-08-01 22:31:15

引用:原帖由 waker 于 2006-8-1 19:43 发表
for(i in a){c=c" "a;delete a}写在循环里行不行哪? 


不行,还只是行随机,ksh,bash效果一样,不知老大什么环境下通过。算了,先告一段落了,弄别的去的了。


 waker 回复于:2006-08-02 10:23:58

怎么试的?贴上来看看


 sxqsir 回复于:2006-08-02 16:51:49

bash-2.03# cat f1
梁白垛刊承卿猜罢搏群霍妹督菠课蕉
稗槐骨瘤趁卷猴迁庞绢祈的旗龟荣爆
砰毕肩吹份寇济寄境弄缄根龚蛮缔跺
槐帕免捌稗氨率腊葫蒂凯妹椒欠闹钳
辩惊古鹿理弛零礼咖霉鼻缉况鸽垄久
bash-2.03# awk 'BEGIN{srand()}{b[rand()" "NR]=$0}END{for(x in b)print b[x]}' f1
稗槐骨瘤趁卷猴迁庞绢祈的旗龟荣爆
槐帕免捌稗氨率腊葫蒂凯妹椒欠闹钳
辩惊古鹿理弛零礼咖霉鼻缉况鸽垄久
砰毕肩吹份寇济寄境弄缄根龚蛮缔跺
梁白垛刊承卿猜罢搏群霍妹督菠课蕉
bash-2.03# awk 'BEGIN{srand()}{for(i=1;i<=NF;i++)a[rand(),i]=$i;for(i in a)c=c"
"a;sub(/^./,"",c);b[rand(),NR]=c;delete a;c=""}END{for(x in b)print b[x]}'
 f1
梁白垛刊承卿猜罢搏群霍妹督菠课蕉
槐帕免捌稗氨率腊葫蒂凯妹椒欠闹钳
砰毕肩吹份寇济寄境弄缄根龚蛮缔跺
稗槐骨瘤趁卷猴迁庞绢祈的旗龟荣爆
辩惊古鹿理弛零礼咖霉鼻缉况鸽垄久
bash-2.03# awk 'BEGIN{srand()}{for(i=1;i<=NF;i++)a[rand(),i]=$i;for(i in a){c=c"
 "a;sub(/^./,"",c);b[rand(),NR]=c;delete a};c=""}END{for(x in b)print b[x]
}' f1
辩惊古鹿理弛零礼咖霉鼻缉况鸽垄久
梁白垛刊承卿猜罢搏群霍妹督菠课蕉
砰毕肩吹份寇济寄境弄缄根龚蛮缔跺
槐帕免捌稗氨率腊葫蒂凯妹椒欠闹钳
稗槐骨瘤趁卷猴迁庞绢祈的旗龟荣爆
bash-2.03#


 r2007 回复于:2006-08-02 17:36:38

不知道什么编码,自行测试。
awk 'BEGIN{srand()}{gsub("..","& ");for(i=1;i<=NF;i++)a[rand(),i]=$i;for(i in a){c=c""a;delete a;};b[rand(),NR]=c;c=""}END{for(x in b)print b[x]}'


 sxqsir 回复于:2006-08-02 18:21:59

thanks!


 gawk 回复于:2006-08-02 23:11:10

好帖留名,^_^




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=801667
转载请注明作者名及原文出处



收藏本页到: