Pythonリストシャッフル完全ガイド – random.shuffle・sampleの使い分けから応用テクニックまで
Pythonでリストの要素をランダムに並び替える(シャッフルする)処理は、ゲーム開発、データサンプリング、機械学習のデータ前処理など、様々な場面で必要になります。Pythonにはrandom.shuffle()とrandom.sample()という2つの主要な方法があり、それぞれ異なる特徴を持っています。この記事では、これらの関数の使い分けから実用的な応用例まで、詳しく解説します。
1. shuffle()とsample()の基本的な違い
主要な違いの比較表
| 関数 | 元リストへの影響 | 戻り値 | 要素数 | 重複 | 用途 |
|---|---|---|---|---|---|
shuffle() | 変更される | None | 同じ | なし | 元のリストをシャッフル |
sample() | 変更されない | 新しいリスト | 指定可能 | なし | サンプリング |
2. random.shuffle()の基本的な使い方
インプレース操作によるシャッフル
import random
# 基本的なシャッフル
cards = ['A', 'K', 'Q', 'J', '10', '9', '8', '7']
random.shuffle(cards)
print(cards) # ['Q', '7', 'A', '10', 'K', '9', 'J', '8'] (ランダム)
# 元のリストが変更されることに注意
original = [1, 2, 3, 4, 5]
print("シャッフル前:", original)
random.shuffle(original)
print("シャッフル後:", original) # 元のリストが変更される
数値リストのシャッフル
import random
# 数値リストのシャッフル
numbers = list(range(1, 11)) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
random.shuffle(numbers)
print("シャッフル結果:", numbers)
# 複数回シャッフル
for i in range(3):
random.shuffle(numbers)
print(f"シャッフル{i+1}回目:", numbers)
3. random.sample()の基本的な使い方
非破壊的なサンプリング
import random
# 元のリストを変更せずにサンプリング
original = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shuffled = random.sample(original, len(original))
print("元のリスト:", original) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print("シャッフル結果:", shuffled) # [7, 2, 9, 1, 5, 3, 8, 4, 6, 10]
部分的なサンプリング
import random
# 元のリストから一部をランダムに選択
data = ['apple', 'banana', 'cherry', 'date', 'elderberry', 'fig']
sample_3 = random.sample(data, 3)
sample_4 = random.sample(data, 4)
print("元データ:", data)
print("3個サンプル:", sample_3) # ['cherry', 'apple', 'elderberry']
print("4個サンプル:", sample_4) # ['fig', 'banana', 'date', 'apple']
4. シード値を使った再現可能なシャッフル
固定シードによる再現性
import random
def reproducible_shuffle_demo():
"""再現可能なシャッフルの例"""
data = [1, 2, 3, 4, 5]
# 同じシードで同じ結果を得る
random.seed(42)
shuffled1 = random.sample(data, len(data))
random.seed(42)
shuffled2 = random.sample(data, len(data))
print("1回目:", shuffled1)
print("2回目:", shuffled2)
print("同じ結果:", shuffled1 == shuffled2) # True
reproducible_shuffle_demo()
テスト用の固定シャッフル
import random
class TestableShuffler:
def __init__(self, seed=None):
self.seed = seed
def shuffle_list(self, data):
if self.seed is not None:
random.seed(self.seed)
return random.sample(data, len(data))
# テストで使用
shuffler = TestableShuffler(seed=123)
test_data = [1, 2, 3, 4, 5]
result = shuffler.shuffle_list(test_data)
print("テスト結果:", result) # 常に同じ結果
5. 大量データの効率的なシャッフル
パフォーマンス比較
import random
import time
def benchmark_shuffle_methods(size=100000):
"""シャッフル手法のパフォーマンス比較"""
# テストデータ準備
data = list(range(size))
# shuffle()のテスト
test_data1 = data.copy()
start = time.time()
random.shuffle(test_data1)
shuffle_time = time.time() - start
# sample()のテスト
start = time.time()
shuffled_sample = random.sample(data, len(data))
sample_time = time.time() - start
print(f"データサイズ: {size:,}")
print(f"shuffle(): {shuffle_time:.4f}秒")
print(f"sample(): {sample_time:.4f}秒")
print(f"差: {abs(shuffle_time - sample_time):.4f}秒")
benchmark_shuffle_methods(100000)
メモリ効率的なシャッフル
import random
def memory_efficient_shuffle(data, chunk_size=1000):
"""メモリ効率的な大量データシャッフル"""
# インデックスのリストを作成してシャッフル
indices = list(range(len(data)))
random.shuffle(indices)
# チャンク単位で処理
for i in range(0, len(indices), chunk_size):
chunk_indices = indices[i:i + chunk_size]
yield [data[idx] for idx in chunk_indices]
# 使用例
large_data = list(range(10000))
for chunk in memory_efficient_shuffle(large_data, 1000):
print(f"チャンクサイズ: {len(chunk)}, 最初の3要素: {chunk[:3]}")
break # 最初のチャンクのみ表示
6. 文字列のシャッフル
文字列をシャッフルする方法
import random
def shuffle_string(text):
"""文字列をシャッフル"""
chars = list(text)
random.shuffle(chars)
return ''.join(chars)
def shuffle_string_sample(text):
"""sample()を使った文字列シャッフル"""
chars = list(text)
shuffled_chars = random.sample(chars, len(chars))
return ''.join(shuffled_chars)
# 使用例
original_text = "Hello World"
shuffled1 = shuffle_string(original_text)
shuffled2 = shuffle_string_sample(original_text)
print(f"元の文字列: {original_text}")
print(f"シャッフル1: {shuffled1}")
print(f"シャッフル2: {shuffled2}")
単語単位でのシャッフル
import random
def shuffle_words(sentence):
"""文の単語をシャッフル"""
words = sentence.split()
random.shuffle(words)
return ' '.join(words)
def shuffle_words_sample(sentence):
"""sample()を使った単語シャッフル"""
words = sentence.split()
shuffled_words = random.sample(words, len(words))
return ' '.join(shuffled_words)
text = "Python is a powerful programming language"
print(f"元の文: {text}")
print(f"単語シャッフル: {shuffle_words(text)}")
7. ゲーム開発での応用
トランプカードのシャッフル
import random
class Deck:
def __init__(self):
suits = ['♠', '♥', '♦', '♣']
ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
self.cards = [f"{rank}{suit}" for suit in suits for rank in ranks]
self.original_deck = self.cards.copy()
def shuffle(self):
"""デッキをシャッフル"""
random.shuffle(self.cards)
def deal(self, num_cards=1):
"""カードを配る"""
if len(self.cards) < num_cards:
return None
dealt = self.cards[:num_cards]
self.cards = self.cards[num_cards:]
return dealt
def reset(self):
"""デッキをリセット"""
self.cards = self.original_deck.copy()
# 使用例
deck = Deck()
print(f"デッキサイズ: {len(deck.cards)}")
deck.shuffle()
hand = deck.deal(5)
print(f"配られたカード: {hand}")
print(f"残りカード数: {len(deck.cards)}")
クイズ問題のシャッフル
import random
class QuizShuffler:
def __init__(self, questions):
self.questions = questions
self.original_order = questions.copy()
def shuffle_questions(self):
"""問題順序をシャッフル"""
return random.sample(self.questions, len(self.questions))
def shuffle_options(self, question_dict):
"""選択肢をシャッフル"""
if 'options' in question_dict:
question_dict['options'] = random.sample(
question_dict['options'],
len(question_dict['options'])
)
return question_dict
def create_random_quiz(self, num_questions=None):
"""ランダムクイズを作成"""
if num_questions is None:
num_questions = len(self.questions)
selected = random.sample(self.questions,
min(num_questions, len(self.questions)))
# 各問題の選択肢もシャッフル
for question in selected:
self.shuffle_options(question)
return selected
# 使用例
questions = [
{"q": "Pythonの作者は?", "options": ["Guido", "Larry", "Dennis"], "answer": "Guido"},
{"q": "1+1=?", "options": ["1", "2", "3"], "answer": "2"},
{"q": "Python は何年にリリース?", "options": ["1989", "1991", "1995"], "answer": "1991"}
]
quiz = QuizShuffler(questions)
random_quiz = quiz.create_random_quiz(2)
for i, q in enumerate(random_quiz, 1):
print(f"{i}. {q['q']}")
print(f" 選択肢: {q['options']}")
8. データサイエンス・機械学習での応用
データセットのシャッフル
import random
def shuffle_dataset(features, labels):
"""特徴量とラベルを同時にシャッフル"""
# インデックスを作成してシャッフル
indices = list(range(len(features)))
random.shuffle(indices)
shuffled_features = [features[i] for i in indices]
shuffled_labels = [labels[i] for i in indices]
return shuffled_features, shuffled_labels
# サンプルデータ
features = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
labels = ['A', 'B', 'C', 'D', 'E']
shuffled_X, shuffled_y = shuffle_dataset(features, labels)
print("シャッフル後の特徴量:", shuffled_X)
print("シャッフル後のラベル:", shuffled_y)
k-分割交差検証用のシャッフル
import random
def create_k_folds(data, k=5, shuffle=True):
"""k-分割交差検証用のデータ分割"""
if shuffle:
data = random.sample(data, len(data))
fold_size = len(data) // k
folds = []
for i in range(k):
start = i * fold_size
if i == k - 1: # 最後のfoldは残り全部
end = len(data)
else:
end = (i + 1) * fold_size
folds.append(data[start:end])
return folds
# 使用例
dataset = list(range(100))
folds = create_k_folds(dataset, k=5, shuffle=True)
for i, fold in enumerate(folds):
print(f"Fold {i+1}: {len(fold)}個のサンプル, 最初の5個: {fold[:5]}")
9. A/Bテストでのランダム化
ユーザーのランダム振り分け
import random
class ABTestRandomizer:
def __init__(self, variants=['A', 'B'], weights=None):
self.variants = variants
self.weights = weights or [1] * len(variants)
def assign_users(self, user_ids):
"""ユーザーをランダムにバリアントに振り分け"""
# ユーザーIDをシャッフル
shuffled_users = random.sample(user_ids, len(user_ids))
assignments = {}
total_weight = sum(self.weights)
for i, user_id in enumerate(shuffled_users):
# 重み付きランダム選択
rand_val = random.random() * total_weight
cumulative = 0
for variant, weight in zip(self.variants, self.weights):
cumulative += weight
if rand_val <= cumulative:
assignments[user_id] = variant
break
return assignments
def get_assignment_summary(self, assignments):
"""振り分け結果のサマリー"""
summary = {}
for variant in self.variants:
summary[variant] = sum(1 for v in assignments.values() if v == variant)
return summary
# 使用例
user_ids = [f"user_{i:03d}" for i in range(100)]
ab_test = ABTestRandomizer(['A', 'B'], weights=[3, 7]) # A:B = 3:7
assignments = ab_test.assign_users(user_ids)
summary = ab_test.get_assignment_summary(assignments)
print("振り分け結果:")
for variant, count in summary.items():
print(f"バリアント{variant}: {count}人 ({count/len(user_ids)*100:.1f}%)")
10. カスタムシャッフル関数の実装
重み付きシャッフル
import random
def weighted_shuffle(items, weights):
"""重み付きシャッフル"""
if len(items) != len(weights):
raise ValueError("itemsとweightsの長さが一致しません")
# 重みに基づいてアイテムを選択
result = []
items_copy = items.copy()
weights_copy = weights.copy()
while items_copy:
# 重み付きランダム選択
total_weight = sum(weights_copy)
rand_val = random.random() * total_weight
cumulative = 0
for i, weight in enumerate(weights_copy):
cumulative += weight
if rand_val <= cumulative:
result.append(items_copy.pop(i))
weights_copy.pop(i)
break
return result
# 使用例
items = ['A', 'B', 'C', 'D', 'E']
weights = [1, 2, 3, 4, 5] # Eが最も選ばれやすい
shuffled = weighted_shuffle(items, weights)
print(f"重み付きシャッフル結果: {shuffled}")
条件付きシャッフル
import random
def conditional_shuffle(data, condition_func, shuffle_ratio=0.5):
"""条件に基づく部分的シャッフル"""
# 条件に合う要素と合わない要素を分離
matching = [item for item in data if condition_func(item)]
non_matching = [item for item in data if not condition_func(item)]
# 指定した割合でシャッフル
num_to_shuffle = int(len(matching) * shuffle_ratio)
if num_to_shuffle > 0:
# シャッフルする要素としない要素を選択
to_shuffle = random.sample(matching, num_to_shuffle)
to_keep = [item for item in matching if item not in to_shuffle]
# シャッフル実行
random.shuffle(to_shuffle)
# 結合
result = to_keep + to_shuffle + non_matching
random.shuffle(result) # 最終的な配置をランダム化
else:
result = data.copy()
random.shuffle(result)
return result
# 使用例:偶数のみ50%をシャッフル
numbers = list(range(1, 21))
result = conditional_shuffle(numbers, lambda x: x % 2 == 0, 0.5)
print(f"条件付きシャッフル結果: {result}")
11. エラーハンドリングと安全な実装
安全なシャッフル関数
import random
def safe_shuffle(data, method='shuffle', sample_size=None):
"""エラーハンドリングを含む安全なシャッフル"""
try:
if not data:
return [] if method == 'sample' else None
if not hasattr(data, '__len__'):
data = list(data)
if method == 'shuffle':
if not isinstance(data, list):
raise TypeError("shuffle()にはlistが必要です")
result = data.copy()
random.shuffle(result)
return result
elif method == 'sample':
if sample_size is None:
sample_size = len(data)
if sample_size > len(data):
raise ValueError("sample_sizeがデータサイズを超えています")
return random.sample(data, sample_size)
else:
raise ValueError(f"未知のメソッド: {method}")
except Exception as e:
print(f"シャッフルエラー: {e}")
return data if method == 'shuffle' else []
# テスト
test_cases = [
([1, 2, 3, 4, 5], 'shuffle', None),
([1, 2, 3, 4, 5], 'sample', 3),
([], 'shuffle', None),
("hello", 'sample', 3),
([1, 2], 'sample', 5) # エラーケース
]
for data, method, size in test_cases:
result = safe_shuffle(data, method, size)
print(f"{data} -> {result}")
12. パフォーマンス最適化
大規模データの最適化
import random
import time
class OptimizedShuffler:
@staticmethod
def fisher_yates_shuffle(data):
"""Fisher-Yatesアルゴリズムによる効率的シャッフル"""
result = data.copy()
for i in range(len(result) - 1, 0, -1):
j = random.randint(0, i)
result[i], result[j] = result[j], result[i]
return result
@staticmethod
def chunked_shuffle(data, chunk_size=10000):
"""チャンク単位での大規模データシャッフル"""
# データを小さなチャンクに分割
chunks = [data[i:i + chunk_size] for i in range(0, len(data), chunk_size)]
# 各チャンクをシャッフル
shuffled_chunks = []
for chunk in chunks:
random.shuffle(chunk)
shuffled_chunks.append(chunk)
# チャンクの順序もシャッフル
random.shuffle(shuffled_chunks)
# 結合
result = []
for chunk in shuffled_chunks:
result.extend(chunk)
return result
@staticmethod
def benchmark_methods(size=100000):
"""各シャッフル手法のベンチマーク"""
data = list(range(size))
methods = {
'random.shuffle': lambda d: random.shuffle(d.copy()) or d,
'random.sample': lambda d: random.sample(d, len(d)),
'fisher_yates': OptimizedShuffler.fisher_yates_shuffle,
'chunked': OptimizedShuffler.chunked_shuffle
}
results = {}
for name, method in methods.items():
start = time.time()
method(data)
results[name] = time.time() - start
return results
# ベンチマーク実行
shuffler = OptimizedShuffler()
benchmark_results = shuffler.benchmark_methods(50000)
print("シャッフル手法のパフォーマンス比較:")
for method, time_taken in sorted(benchmark_results.items(), key=lambda x: x[1]):
print(f"{method:15}: {time_taken:.4f}秒")
13. 実用的なユーティリティ関数
多目的シャッフルツール
import random
from typing import List, Any, Optional, Union
class ShuffleUtils:
@staticmethod
def multi_list_shuffle(*lists):
"""複数のリストを同期してシャッフル"""
if not lists:
return []
# 全てのリストが同じ長さかチェック
length = len(lists[0])
if not all(len(lst) == length for lst in lists):
raise ValueError("全てのリストの長さが同じである必要があります")
# インデックスをシャッフル
indices = list(range(length))
random.shuffle(indices)
# 各リストを同じ順序でシャッフル
return [[lst[i] for i in indices] for lst in lists]
@staticmethod
def preserve_groups_shuffle(data, group_key_func):
"""グループを保持したままシャッフル"""
# グループ別にデータを分類
groups = {}
for item in data:
key = group_key_func(item)
if key not in groups:
groups[key] = []
groups[key].append(item)
# 各グループ内をシャッフル
for group in groups.values():
random.shuffle(group)
# グループ順序もシャッフル
group_keys = list(groups.keys())
random.shuffle(group_keys)
# 結合
result = []
for key in group_keys:
result.extend(groups[key])
return result
@staticmethod
def smart_shuffle(data, seed=None, preserve_order=False):
"""インテリジェントシャッフル"""
if seed is not None:
random.seed(seed)
if preserve_order:
# 元の順序情報を保持
indexed_data = list(enumerate(data))
random.shuffle(indexed_data)
return [(original_idx, item) for original_idx, item in indexed_data]
else:
return random.sample(data, len(data))
# 使用例
utils = ShuffleUtils()
# 複数リストの同期シャッフル
names = ['Alice', 'Bob', 'Charlie']
ages = [25, 30, 35]
scores = [85, 90, 95]
shuffled_lists = utils.multi_list_shuffle(names, ages, scores)
print("同期シャッフル結果:")
for name, age, score in zip(*shuffled_lists):
print(f"{name}: {age}歳, {score}点")
# グループ保持シャッフル
students = [
{'name': 'Alice', 'grade': 'A', 'score': 95},
{'name': 'Bob', 'grade': 'B', 'score': 85},
{'name': 'Charlie', 'grade': 'A', 'score': 92},
{'name': 'David', 'grade': 'B', 'score': 88}
]
grouped_shuffled = utils.preserve_groups_shuffle(
students,
lambda x: x['grade']
)
print("\nグループ保持シャッフル:")
for student in grouped_shuffled:
print(f"{student['name']}: {student['grade']}グレード")
まとめ
Pythonでのリストシャッフルには、用途に応じて適切な手法を選択することが重要です:
使い分けの指針
random.shuffle()– 元のリストを直接変更したい場合(最も高速・省メモリ)random.sample()– 元のリストを保持したい場合、部分サンプリングが必要な場合- カスタム実装 – 特殊な要件(重み付き、条件付きなど)がある場合
パフォーマンス特性
- 速度:
shuffle()>sample()> カスタム実装 - メモリ:
shuffle()>sample()> カスタム実装 - 柔軟性: カスタム実装 >
sample()>shuffle()
推奨用途
- ゲーム開発: カードシャッフル、問題順序のランダム化
- データサイエンス: データセットのシャッフル、交差検証
- A/Bテスト: ユーザーのランダム振り分け
- セキュリティ: 暗号学的用途(適切な乱数生成器使用時)
適切な手法を選択し、エラーハンドリングやパフォーマンスも考慮することで、効率的で保守性の高いコードを実現できます。
■プロンプトだけでオリジナルアプリを開発・公開してみた!!
■AI時代の第一歩!「AI駆動開発コース」はじめました!
テックジム東京本校で先行開始。
■テックジム東京本校
「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。
<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。
<月1開催>放送作家による映像ディレクター養成講座
<オンライン無料>ゼロから始めるPython爆速講座

