1.问题
如何随机取数组中的多个值?给定一个数组,需要从此数组中随机取出不重复几项;

2.应用举例
从20个会员中,随机抽出5个做为幸运奖;
对于这个应用,一个办法是可以在数据库层面解决,如(sql server):
select top 5 * from  (select top 20 * from userid order by userid desc) as b order by newid()
可是有时候,我们需要在编程语言中完成这样的功能,而不是从数据库,下边说的就是使用编程语言实现的方法;

3.实现思路
思路一:写一个无限循环,或者数组长度的循环,使用随机函数,每次从数组中随机抽取1项,同时判断此项是否已被抽取,如果被抽取,则继续循环重新抽取,直到取出不重复5项时,跳出循环;
这办法简单吧?但这个办法不太好,如果需求要求从10万个中随机取出1万个,使用此办法,那么将有1/10的概率每次都取出了曾经取出的那项了,于是需要重新再取,效率自然不高。另外,无限循环或循环数组长度次总让人心里觉得不踏实。

思路二:写一个循环,循环次数为要取出的个数,即取几个就刚好循环几次,每取一次,将取出的项从数组中删除,下次循环再取时,保证不会重复,少了是否重复的判断。

4.几种语言的实现代码
这里采用第二种思路,下面有as,js,C#的实现代码。

ActionScript 2.0代码:
//从一个给定的数组arr中,随机返回num个不重复项
function getArrayItems(arr:Array, num:Number):Array {
    
//新建一个数组,将传入的数组复制过来,用于运算,而不要直接操作传入的数组;
    var temp_array:Array = new Array();
    for (var index in arr) {
        temp_array.push(arr[index]);
    }
    //取出的数值项,保存在此数组
    var return_array:Array = new Array();
    
for (var i = 0; i<num; i++) {
        
//判断如果数组还有可以取出的元素,以防下标越界
        if (temp_array.length>0) {
            
//在数组中产生一个随机索引
            var arrIndex:Number = Math.floor(Math.random()*temp_array.length);
            
//将此随机索引的对应的数组元素值复制出来
            return_array[i] = temp_array[arrIndex];
            
//然后删掉此索引的数组元素,这时候temp_array变为新的数组
            temp_array.splice(arrIndex, 1);
        } 
else {
            
//数组中数据项取完后,退出循环,比如数组本来只有10项,但要求取出20项.
            break;
        }
    }
    
return return_array;
}
//测试
var my_array:Array = new Array();
for (var i = 0; i<20; i++) {
    my_array[i] 
= "Num:"+i;
}
my_array 
= getArrayItems(my_array, 5);
for (var i = 0; i<my_array.length; i++) {
    trace(my_array[i]);
}

JavaScript代码,和AS类似:
<script language="javascript">
//从一个给定的数组arr中,随机返回num个不重复项
function getArrayItems(arr, num) {
    
//新建一个数组,将传入的数组复制过来,用于运算,而不要直接操作传入的数组;
    var temp_array = new Array();
    for (var index in arr) {
        temp_array.push(arr[index]);
    }

    
//取出的数值项,保存在此数组
    var return_array = new Array();
    
for (var i = 0; i<num; i++) {
        
//判断如果数组还有可以取出的元素,以防下标越界
        if (temp_array.length>0) {
            
//在数组中产生一个随机索引
            var arrIndex = Math.floor(Math.random()*temp_array.length);
            
//将此随机索引的对应的数组元素值复制出来
            return_array[i] = temp_array[arrIndex];
            
//然后删掉此索引的数组元素,这时候temp_array变为新的数组
            temp_array.splice(arrIndex, 1);
        } 
else {
            
//数组中数据项取完后,退出循环,比如数组本来只有10项,但要求取出20项.
            break;
        }
    }
    
return return_array;
}

//测试
var my_array = new Array();
for (i = 0; i < 20; i++)
{
   my_array[i] 
= "Num:"+i;
}

my_array 
= getArrayItems(my_array,5);
for (i = 0; i < my_array.length; i++)
{
   alert(my_array[i]);
}

</script>

C#代码:
        public static IList<string> getListItems(IList<string> list, int num)
        
{
            
//新建一个泛型列表,将传入的列表复制过来,用于运算,而不要直接操作传入的列表;    

            
//这样写是引用复制,不对啦,谢谢Osamede指出.
            
//IList<string> temp_list = list;

            
//另外这样写也要注意,也不是深度复制喽,关于深度复制可以做为一个新话题来说,这儿就不说啦;
            IList<string> temp_list = new List<string>(list);

            
//取出的项,保存在此列表
            IList<string> return_list = new List<string>();

            
//Random random = new Random(unchecked((int)DateTime.Now.Ticks));
            Random random = new Random();

            
for (int i = 0; i < num; i++)
            
{
                
//判断如果列表还有可以取出的项,以防下标越界
                if (temp_list.Count > 0)
                
{
                    
//在列表中产生一个随机索引
                    int arrIndex = random.Next(0, temp_list.Count);
                    
//将此随机索引的对应的列表元素值复制出来
                    return_list.Add(temp_list[arrIndex]);
                    
//然后删掉此索引的列表项
                    temp_list.RemoveAt(arrIndex);
                }

                
else
                
{
                    
//列表项取完后,退出循环,比如列表本来只有10项,但要求取出20项.
                    break;
                }

            }

            
return return_list;
        }

测试:
static void Main(string[] args)
        
{
            IList
<string> temp_list = new List<string>();
            
for (int i = 0; i < 20; i++)
            
{
                temp_list.Add(
"Num:" + i.ToString());
            }


            temp_list 
= getListItems(temp_list, 5);

            
for (int i = 0; i < temp_list.Count; i++)
            
{
                WL(temp_list[i]);
            }


            RL();
        }


        
Helper methods

posted on 2007-05-28 22:28 三千 阅读(1608) 评论(6)  编辑 收藏 所属分类: [I ]. Flash技术[E]. C#[K]. JavaScript[A]. ActionScript

评论:
#1楼  2007-05-29 10:48 | Osamede      
"//新建一个泛型列表,将传入的列表复制过来,用于运算,而不要直接操作传入的列表;
IList<string> temp_list = list;
"
这条语句能把list中的内容“复制”到temp_list中吗?
list应该是一个reference,操作完后temp_list也是一个reference,并不会复制堆栈上的内容吧。:)

  回复  引用  查看    
#2楼 [楼主] 2007-05-29 12:44 | YAO.NET℡      
@Osamede
谢谢,范了个愚蠢的错误啊.
先写的as版,然后从as改过来的,就给疏忽了,呵呵;

  回复  引用  查看    
#3楼  2007-05-29 13:50 | 助燃      
有个更简单的办法,我们知道List支持sort,而sort的规则是可以通过回调来自定义的,只要我们将sort的回调函数返回一个-1到1的随机整数,就可以实现将List随机打乱,然后取出前n项即可。
  回复  引用  查看    
#4楼 [楼主] 2007-05-30 12:25 | YAO.NET℡      
@助燃
嗯.这个办法也可以.

  回复  引用  查看    
#5楼  2007-10-08 16:44 | Anubis [未注册用户]
Java代码:
public static List<String> getListItems(List<String> list, int num)
{
//新建一个泛型列表,将传入的列表复制过来,用于运算,而不要直接操作传入的列表;
List<String> temp_list = list;

//取出的项,保存在此列表
List<String> return_list = new ArrayList<String>();

java.util.Random random = new java.util.Random();

for (int i = 0; i < num; i++)
{
//判断如果列表还有可以取出的项,以防下标越界
if (null != temp_list && temp_list.size() > 0)
{
//在列表中产生一个随机索引
int arrIndex = random.nextInt(temp_list.size());
//将此随机索引的对应的列表元素值复制出来
return_list.add(temp_list.get(arrIndex));
//然后删掉此索引的列表项
temp_list.remove(arrIndex);
}
else
{
//列表项取完后,退出循环,比如列表本来只有10项,但要求取出20项.
break;
}
}
return return_list;
}

//呵呵
  回复  引用    
#6楼  2007-10-08 16:55 | Anubis [未注册用户]
//java版,有了一点点修改,:)
public static List<String> getListItems(List<String> list, int num)
{
//新建一个泛型列表,将传入的列表复制过来,用于运算,而不要直接操作传入的列表;
List<String> temp_list = list;

//取出的项,保存在此列表
List<String> return_list = new ArrayList<String>();

java.util.Random random = new java.util.Random();

for (int i = 0; i < num; i++)
{
//判断如果列表还有可以取出的项,以防下标越界
if (null != temp_list && temp_list.size() > 0)
{
//在列表中产生一个随机索引
int arrIndex = random.nextInt(temp_list.size());
//将此随机索引的对应的列表元素值复制出来
return_list.add(temp_list.get(arrIndex));
//然后删掉此索引的列表项
temp_list.remove(arrIndex);

//如果要求从20项中选择19项,就没必要循环19次,对吗
if(temp_list.size() == num)
{
return temp_list;
}
}
else
{
//列表项取完后,退出循环,比如列表本来只有10项,但要求取出20项.
break;
}
}
return return_list;
}
  回复  引用    

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
博客园首页

新闻频道

社区

小组

博问

网摘

闪存

  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2007-05-31 19:23 编辑过
成果网帮您增加网站收入


相关链接: