リストから任意の数の要素をランダムに且つ重複なしで抽出したい

なんか暑すぎて、ほげーとプログラム書いてたら、なんだか 簡単な問題もスマートにやれない僕がいたのでメモ。

問題

問題はいたって簡単。 リストから任意の数の要素を、ランダムに抽出。要素は重複してはならない。簡便化するために、元のリストは要素に重複が無い事を前提とする。

[1, 2, 3, 4, 5]

 ↓ 3つ抽出するとする

[1, 3, 5] => ok
[1, 3, 3] => ng  値が重複している

当然、任意の数(ここでは3)以上の要素が存在していたら、任意の数だけ要素を返すの必須。

こんな風に考えた。

そういば、randomモジュールに 「random.choice」とかいうのがあったなーあれ使えば一発かぁとか思ったけど、そうでもなかった

  • random.choice は リストからランダムに一つの要素しか返さない。
  • 結果のリストには要素の重複は認められない。

なので、最初もうほげーとやってたから、ループで回して、choiceした要素が、結果のリストになければ、カウントアップしてカウントアップした結果が、任意の数に達したら、結果リスト返すでいいかと考えたけど、それって何か破綻してるなぁという事に気づいた。
なので、チョイスする回数は、任意の数とイコールなるように考えてやってみたら、なんか長ったらしくなって、もっとスマートなやり方ないかなぁ と もやもやしながらブログ書いてる。

(どうして)こうなった。

簡単に言うとこんな感じ

  • ランダムで抽出
  • 抽出した要素を結果リストにappend
  • 抽出した要素を元リストからremove
  • 任意の数の回数だけ、ループ
#!/usr/bin/env python
#-*- coding:utf8 -*-
import random

def random_choice_unique(lists, num=4):
    if not lists:
        return []

    if len(lists) <= num:
        return lists

    copy_lists = lists[:]
    results = []
    for i in range(num):
        t = random.choice(copy_lists)
        results.append(t)
        copy_lists.remove(t)

    return results

l = ['1', '2', '3', '4', '5']
print random_choice_unique(l)

l = [1, 2, 3, 4, 5]
print random_choice_unique(l)

l = [[0, 1], [1, 2], [3, 4], [5, 6], [7, 8]]
print random_choice_unique(l)

l = [{'ae': 35}, {'shin': 30}, {'haru': 860}, {'key': 3}, {'easy': 1126}]
print random_choice_unique(l)

# remove はオブジェクトでも大丈夫。
class BuchoLover():

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return self.name + " is a bucho lover"

l = [BuchoLover(name) for name in ['ae35', 'shin', 'm_432', 'haru', 'feiz']]
print random_choice_unique(l)

実行結果: http://codepad.org/8LF8wBa7

ぶっちゃけ、やりたい事はこれで、実現できるけど、もうちょっとこう、python的な感じでスマートにやれるんじゃないかなぁとか考えてる。修行足らん。
あー。七夕の時に、「#tanzaku まともにプログラムが書けるようになりたい」て書けば良かった。
関係ないけど、http://www.youtuberepeat.com/ って、youtubeに 足りない機能を、(ユーザーにとって)簡単に補完できるという意味で、なんかいいなぁと思った。

すいません。ずっと少女時代きいてました。

http://www.youtuberepeat.com/watch/?v=FJe9rIY21Yo&feature=related

追記 (20110709)

コメント欄で はたぼう先生に教えてもらいました。それ random.sample で できるよry)的なw。なんという良しなな関数w ちょっと感動した。後ちゃんとhelp読もうね > 俺

#!/usr/bin/env python
#-*- coding:utf8 -*-
import random

def random_choice_unique(lists, num=4):
    if not lists:
        return []

    if len(lists) <= num:
        return lists

    return random.sample(lists, num)

l = ['1', '2', '3', '4', '5']
print random_choice_unique(l)

l = [1, 2, 3, 4, 5]
print random_choice_unique(l)

l = [[0, 1], [1, 2], [3, 4], [5, 6], [7, 8]]
print random_choice_unique(l)

l = [{'ae': 35}, {'shin': 30}, {'haru': 860}, {'key': 3}, {'easy': 1126}]
print random_choice_unique(l)

class BuchoLover():

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return self.name + " is a bucho lover"

l = [BuchoLover(name) for name in ['ae35', 'shin', 'm_432', 'haru', 'feiz']]
print random_choice_unique(l)

実行結果:http://codepad.org/AjmZyyyQ

flag_boy++