Python2次元配列ソート完全ガイド – リストのリスト並び替えテクニックを徹底解説
Python で2次元配列(リストのリスト)をソートすることは、データ分析、行列操作、テーブルデータの処理において頻繁に必要となる操作です。単純な数値の並び替えから、複数の条件による複雑なソートまで、様々な手法があります。この記事では、2次元配列のソートに関するあらゆるテクニックを実例とともに詳しく解説します。
目次
1. 基本的な2次元配列のソート
行ごとのソート(各行を個別にソート)
# 各行を個別にソート
matrix = [
[3, 1, 4],
[6, 2, 5],
[9, 7, 8]
]
# 各行を昇順ソート
sorted_matrix = [sorted(row) for row in matrix]
print("行ごとソート後:")
for row in sorted_matrix:
print(row)
# [1, 3, 4]
# [2, 5, 6]
# [7, 8, 9]
列ごとのソート
# 列ごとにソート
matrix = [
[3, 1, 4],
[6, 2, 5],
[9, 7, 8]
]
# 転置→行ソート→転置で列ソートを実現
def sort_columns(matrix):
# 転置
transposed = list(zip(*matrix))
# 各行(元の列)をソート
sorted_columns = [sorted(col) for col in transposed]
# 再度転置して元の形に戻す
return list(zip(*sorted_columns))
result = [list(row) for row in sort_columns(matrix)]
print("列ごとソート後:")
for row in result:
print(row)
2. 行全体をソートキーで並び替え
特定の列を基準にした行のソート
# 学生データを成績でソート
students = [
["Alice", 85, "A"],
["Bob", 92, "A+"],
["Charlie", 78, "B"],
["Diana", 96, "A+"]
]
# 2列目(成績)で降順ソート
sorted_by_score = sorted(students, key=lambda x: x[1], reverse=True)
print("成績順(降順):")
for student in sorted_by_score:
print(student)
# ['Diana', 96, 'A+']
# ['Bob', 92, 'A+']
# ['Alice', 85, 'A']
# ['Charlie', 78, 'B']
複数の列を基準にしたソート
# 複数条件でのソート
data = [
["東京", "営業", 85],
["大阪", "開発", 92],
["東京", "開発", 78],
["大阪", "営業", 88],
["東京", "営業", 91]
]
# 1. 都市名(昇順)→ 2. 部署名(昇順)→ 3. スコア(降順)
sorted_data = sorted(data, key=lambda x: (x[0], x[1], -x[2]))
print("複数条件ソート:")
for row in sorted_data:
print(row)
3. 数値データの2次元配列ソート
行の合計値でソート
# 各行の合計値でソート
numbers = [
[1, 5, 3],
[8, 2, 1],
[4, 6, 2]
]
sorted_by_sum = sorted(numbers, key=sum)
print("行の合計値でソート:")
for row in sorted_by_sum:
print(f"{row} (合計: {sum(row)})")
# [1, 5, 3] (合計: 9)
# [8, 2, 1] (合計: 11)
# [4, 6, 2] (合計: 12)
最大値・最小値でソート
# 各行の最大値でソート
data = [
[10, 3, 7],
[2, 9, 4],
[6, 1, 8]
]
sorted_by_max = sorted(data, key=max, reverse=True)
print("各行の最大値でソート(降順):")
for row in sorted_by_max:
print(f"{row} (最大: {max(row)})")
# 各行の最小値でソート
sorted_by_min = sorted(data, key=min)
print("各行の最小値でソート(昇順):")
for row in sorted_by_min:
print(f"{row} (最小: {min(row)})")
4. 文字列データの2次元配列ソート
辞書順ソート
# 文字列データの辞書順ソート
words_matrix = [
["cat", "dog", "bird"],
["apple", "banana", "cherry"],
["zebra", "ant", "elephant"]
]
# 各行を辞書順ソート
sorted_words = [sorted(row) for row in words_matrix]
print("各行を辞書順ソート:")
for row in sorted_words:
print(row)
# 行を最初の単語でソート
sorted_by_first = sorted(words_matrix, key=lambda x: x[0])
print("最初の単語でソート:")
for row in sorted_by_first:
print(row)
文字列長でソート
# 文字列の長さでソート
text_data = [
["hello", "hi", "goodbye"],
["python", "java", "c"],
["programming", "code", "algorithm"]
]
# 各行の文字列を長さでソート
sorted_by_length = [sorted(row, key=len) for row in text_data]
print("文字列長でソート:")
for row in sorted_by_length:
print(row)
# 行を最長文字列でソート
sorted_by_max_length = sorted(text_data, key=lambda x: max(len(word) for word in x))
print("最長文字列でソート:")
for row in sorted_by_max_length:
print(row)
5. カスタムソート関数
複雑な条件でのソート
# 商品データのカスタムソート
products = [
["Laptop", 80000, 4.5, "Electronics"],
["Book", 1500, 4.8, "Education"],
["Phone", 60000, 4.2, "Electronics"],
["Chair", 25000, 4.0, "Furniture"]
]
def custom_sort_key(product):
"""カスタムソートキー関数"""
name, price, rating, category = product
# カテゴリ優先度
category_priority = {"Electronics": 1, "Education": 2, "Furniture": 3}
# (カテゴリ優先度, -評価, 価格) の順でソート
return (category_priority.get(category, 999), -rating, price)
sorted_products = sorted(products, key=custom_sort_key)
print("カスタムソート結果:")
for product in sorted_products:
print(product)
条件分岐を含むソート
def conditional_sort(data, sort_by_column, condition_func):
"""条件に応じて異なるソートを適用"""
# 条件に合う行と合わない行を分離
matching = [row for row in data if condition_func(row)]
non_matching = [row for row in data if not condition_func(row)]
# それぞれ異なる方法でソート
matching_sorted = sorted(matching, key=lambda x: x[sort_by_column])
non_matching_sorted = sorted(non_matching, key=lambda x: x[sort_by_column], reverse=True)
return matching_sorted + non_matching_sorted
# 使用例:スコアが80以上は昇順、未満は降順
scores = [
["Alice", 85],
["Bob", 92],
["Charlie", 78],
["Diana", 96],
["Eve", 72]
]
result = conditional_sort(scores, 1, lambda x: x[1] >= 80)
print("条件分岐ソート:")
for row in result:
print(row)
6. インプレースソート vs 新しい配列作成
インプレースソート(元の配列を変更)
# 元の配列を直接変更
matrix = [
[3, 1, 4],
[6, 2, 5],
[9, 7, 8]
]
print("ソート前:", matrix)
# 各行をインプレースソート
for row in matrix:
row.sort()
print("ソート後:", matrix)
# 行全体をインプレースソート
matrix.sort(key=lambda x: x[0]) # 最初の要素でソート
print("行ソート後:", matrix)
新しい配列を作成(元の配列は保持)
# 元の配列を保持しつつ新しいソート済み配列を作成
original = [
[3, 1, 4],
[6, 2, 5],
[9, 7, 8]
]
# 新しい配列を作成
row_sorted = [sorted(row) for row in original]
column_sorted = sorted(original, key=lambda x: x[1]) # 2列目でソート
print("元の配列:", original)
print("行ソート済み:", row_sorted)
print("列ソート済み:", column_sorted)
7. NumPyとの比較(参考)
純粋Pythonでの実装
# 純粋Pythonによる2次元配列ソート
def python_2d_sort(matrix, axis=0, key_column=0):
"""
純粋Pythonによる2次元配列ソート
axis=0: 行をソート, axis=1: 各行内をソート
"""
if axis == 0:
# 行をソート(指定列を基準)
return sorted(matrix, key=lambda x: x[key_column])
elif axis == 1:
# 各行内をソート
return [sorted(row) for row in matrix]
data = [[3, 1, 4], [6, 2, 5], [9, 7, 8]]
print("行ソート(1列目基準):")
result1 = python_2d_sort(data, axis=0, key_column=1)
for row in result1:
print(row)
print("各行内ソート:")
result2 = python_2d_sort(data, axis=1)
for row in result2:
print(row)
8. 大規模データでの効率的ソート
メモリ効率を考慮したソート
def memory_efficient_sort(large_matrix, key_func, chunk_size=1000):
"""メモリ効率的な大規模2次元配列ソート"""
# チャンク単位で処理
sorted_chunks = []
for i in range(0, len(large_matrix), chunk_size):
chunk = large_matrix[i:i + chunk_size]
sorted_chunk = sorted(chunk, key=key_func)
sorted_chunks.append(sorted_chunk)
# マージソート的にチャンクを結合
result = []
for chunk in sorted_chunks:
result.extend(chunk)
# 最終ソート
return sorted(result, key=key_func)
# 使用例(サンプルデータ)
large_data = [[i, i*2, i*3] for i in range(10000, 0, -1)]
sorted_data = memory_efficient_sort(large_data, key=lambda x: x[1], chunk_size=2000)
print(f"大規模データソート完了: {len(sorted_data)}行")
print(f"最初の5行: {sorted_data[:5]}")
9. 実用的な応用例
CSVライクなデータのソート
# CSV形式のデータをソート
csv_data = [
["名前", "年齢", "部署", "給与"], # ヘッダー
["田中", 28, "営業", 400000],
["佐藤", 35, "開発", 550000],
["鈴木", 42, "営業", 480000],
["高橋", 31, "開発", 520000]
]
def sort_csv_data(data, sort_column, reverse=False, has_header=True):
"""CSV形式データのソート"""
if has_header:
header = data[0]
body = data[1:]
else:
header = None
body = data
# 列名または列番号を解決
if isinstance(sort_column, str):
if header is None:
raise ValueError("ヘッダーがありません")
column_index = header.index(sort_column)
else:
column_index = sort_column
# ソート実行
sorted_body = sorted(body, key=lambda x: x[column_index], reverse=reverse)
# 結果を構築
if header is not None:
return [header] + sorted_body
return sorted_body
# 給与でソート
sorted_by_salary = sort_csv_data(csv_data, "給与", reverse=True)
print("給与順(降順):")
for row in sorted_by_salary:
print(row)
成績表のソート
class GradeBook:
def __init__(self):
self.grades = [
["学生名", "数学", "英語", "理科", "平均"],
["山田", 85, 78, 92, 0],
["田中", 92, 88, 85, 0],
["佐藤", 78, 95, 89, 0],
["鈴木", 88, 82, 78, 0]
]
self.calculate_averages()
def calculate_averages(self):
"""平均点を計算"""
for i in range(1, len(self.grades)):
math, english, science = self.grades[i][1:4]
average = round((math + english + science) / 3, 1)
self.grades[i][4] = average
def sort_by_subject(self, subject):
"""科目別ソート"""
header = self.grades[0]
if subject not in header:
return None
column_index = header.index(subject)
body = self.grades[1:]
sorted_body = sorted(body, key=lambda x: x[column_index], reverse=True)
return [header] + sorted_body
def get_ranking(self):
"""総合ランキングを取得"""
ranked = self.sort_by_subject("平均")
for i, row in enumerate(ranked[1:], 1):
row.append(f"{i}位")
return ranked
# 使用例
gradebook = GradeBook()
ranking = gradebook.get_ranking()
print("成績ランキング:")
for row in ranking:
print(row)
10. 特殊なソートパターン
ジグザグソート
def zigzag_sort(matrix):
"""ジグザグパターンでのソート"""
result = []
for i, row in enumerate(matrix):
if i % 2 == 0:
# 偶数行は昇順
result.append(sorted(row))
else:
# 奇数行は降順
result.append(sorted(row, reverse=True))
return result
data = [
[5, 2, 8, 1],
[9, 3, 6, 4],
[7, 1, 9, 2],
[4, 6, 3, 8]
]
zigzag = zigzag_sort(data)
print("ジグザグソート:")
for i, row in enumerate(zigzag):
print(f"行{i}: {row}")
螺旋状ソート
def spiral_sort(matrix):
"""螺旋状にソートされた値を配置"""
# 全要素を取得してソート
all_elements = []
for row in matrix:
all_elements.extend(row)
all_elements.sort()
# 螺旋状に配置
rows, cols = len(matrix), len(matrix[0])
result = [[0] * cols for _ in range(rows)]
top, bottom = 0, rows - 1
left, right = 0, cols - 1
index = 0
while top <= bottom and left <= right:
# 上辺を左から右へ
for j in range(left, right + 1):
result[top][j] = all_elements[index]
index += 1
top += 1
# 右辺を上から下へ
for i in range(top, bottom + 1):
result[i][right] = all_elements[index]
index += 1
right -= 1
# 下辺を右から左へ
if top <= bottom:
for j in range(right, left - 1, -1):
result[bottom][j] = all_elements[index]
index += 1
bottom -= 1
# 左辺を下から上へ
if left <= right:
for i in range(bottom, top - 1, -1):
result[i][left] = all_elements[index]
index += 1
left += 1
return result
# 使用例
original = [
[9, 2, 7],
[4, 1, 8],
[5, 6, 3]
]
spiral = spiral_sort(original)
print("螺旋状ソート:")
for row in spiral:
print(row)
11. エラーハンドリングと安全なソート
安全なソート関数
def safe_2d_sort(matrix, sort_key=None, reverse=False):
"""エラーハンドリングを含む安全な2次元配列ソート"""
try:
# 入力検証
if not matrix:
return []
if not all(isinstance(row, (list, tuple)) for row in matrix):
raise TypeError("全ての行がリストまたはタプルである必要があります")
# 空の行をフィルタリング
non_empty_rows = [row for row in matrix if row]
if not non_empty_rows:
return matrix
# ソートキーの検証
if sort_key is not None:
if callable(sort_key):
# 関数の場合はそのまま使用
key_func = sort_key
elif isinstance(sort_key, int):
# 整数の場合は列インデックス
max_columns = max(len(row) for row in non_empty_rows)
if sort_key >= max_columns:
raise IndexError(f"列インデックス {sort_key} が範囲外です")
key_func = lambda x: x[sort_key] if len(x) > sort_key else None
else:
raise ValueError("sort_keyは関数または整数である必要があります")
else:
key_func = None
# ソート実行
sorted_rows = sorted(non_empty_rows, key=key_func, reverse=reverse)
# 空の行を元の位置に戻す
result = []
empty_positions = [i for i, row in enumerate(matrix) if not row]
sorted_index = 0
for i in range(len(matrix)):
if i in empty_positions:
result.append(matrix[i])
else:
result.append(sorted_rows[sorted_index])
sorted_index += 1
return result
except Exception as e:
print(f"ソートエラー: {e}")
return matrix
# テストケース
test_cases = [
[[3, 1, 4], [6, 2, 5], [9, 7, 8]], # 正常
[[1, 2], [], [3, 4, 5]], # 空行を含む
[], # 空の配列
[["a", "b"], ["c", "d"]] # 文字列
]
for i, case in enumerate(test_cases):
print(f"テストケース {i + 1}:")
result = safe_2d_sort(case, sort_key=0)
print(f"結果: {result}\n")
12. パフォーマンス最適化
ソート手法の比較
import time
import random
def benchmark_2d_sorts(size=1000):
"""2次元配列ソート手法のパフォーマンス比較"""
# テストデータ生成
test_data = [[random.randint(1, 1000) for _ in range(3)]
for _ in range(size)]
methods = {
"sorted()": lambda data: sorted(data, key=lambda x: x[0]),
"list.sort()": lambda data: data.sort(key=lambda x: x[0]) or data,
"custom_key": lambda data: sorted(data, key=lambda x: (x[0], x[1])),
"multiple_sorts": lambda data: sorted(sorted(data, key=lambda x: x[1]),
key=lambda x: x[0])
}
results = {}
for name, method in methods.items():
test_copy = [row[:] for row in test_data] # ディープコピー
start_time = time.time()
method(test_copy)
end_time = time.time()
results[name] = end_time - start_time
print(f"データサイズ: {size}行")
for method, time_taken in sorted(results.items(), key=lambda x: x[1]):
print(f"{method:15}: {time_taken:.4f}秒")
benchmark_2d_sorts(5000)
13. 実世界での応用例
売上データの分析
class SalesAnalyzer:
def __init__(self, sales_data):
self.data = sales_data # [日付, 商品, 売上, 地域]
def sort_by_date(self):
"""日付順ソート"""
return sorted(self.data, key=lambda x: x[0])
def sort_by_sales(self, reverse=True):
"""売上順ソート"""
return sorted(self.data, key=lambda x: x[2], reverse=reverse)
def sort_by_region_and_sales(self):
"""地域別→売上順ソート"""
return sorted(self.data, key=lambda x: (x[3], -x[2]))
def get_top_products(self, n=5):
"""売上上位商品"""
sorted_data = self.sort_by_sales()
return sorted_data[:n]
# 使用例
sales_data = [
["2024-01-15", "商品A", 150000, "東京"],
["2024-01-16", "商品B", 200000, "大阪"],
["2024-01-14", "商品C", 180000, "東京"],
["2024-01-17", "商品A", 160000, "名古屋"],
["2024-01-15", "商品D", 220000, "大阪"]
]
analyzer = SalesAnalyzer(sales_data)
print("売上上位3商品:")
top_products = analyzer.get_top_products(3)
for product in top_products:
print(f"{product[1]}: {product[2]:,}円 ({product[3]})")
print("\n地域別売上順:")
regional_sales = analyzer.sort_by_region_and_sales()
for sale in regional_sales:
print(f"{sale[3]} - {sale[1]}: {sale[2]:,}円")
まとめ
Python の2次元配列(リストのリスト)ソートには、用途に応じて様々な手法があります:
基本的なソート手法
- 行ごとのソート –
[sorted(row) for row in matrix] - 列でのソート –
sorted(matrix, key=lambda x: x[列番号]) - 複数条件ソート –
sorted(matrix, key=lambda x: (x[0], x[1]))
パフォーマンス特性
- 速度:
list.sort()>sorted()> カスタムキー関数 - メモリ: インプレースソート > 新規作成
- 柔軟性: カスタムキー > 標準ソート
推奨用途
- データ分析: 複数条件によるソート
- 行列計算: 数値ベースのソート
- テーブル処理: CSVライクなデータのソート
- ゲーム開発: スコアランキング、座標ソート
適切な手法を選択し、エラーハンドリングやパフォーマンスも考慮することで、効率的で保守性の高い2次元配列処理を実現できます。特に大規模データを扱う場合は、メモリ効率とパフォーマンスを重視した実装が重要になります。
■らくらくPython塾 – 読むだけでマスター
■プロンプトだけでオリジナルアプリを開発・公開してみた!!
■AI時代の第一歩!「AI駆動開発コース」はじめました!
テックジム東京本校で先行開始。
■テックジム東京本校
「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。
<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。
<月1開催>放送作家による映像ディレクター養成講座
<オンライン無料>ゼロから始めるPython爆速講座
