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次元配列(リストのリスト)ソートには、用途に応じて様々な手法があります:

基本的なソート手法

  1. 行ごとのソート[sorted(row) for row in matrix]
  2. 列でのソートsorted(matrix, key=lambda x: x[列番号])
  3. 複数条件ソートsorted(matrix, key=lambda x: (x[0], x[1]))

パフォーマンス特性

  • 速度: list.sort() > sorted() > カスタムキー関数
  • メモリ: インプレースソート > 新規作成
  • 柔軟性: カスタムキー > 標準ソート

推奨用途

  • データ分析: 複数条件によるソート
  • 行列計算: 数値ベースのソート
  • テーブル処理: CSVライクなデータのソート
  • ゲーム開発: スコアランキング、座標ソート

適切な手法を選択し、エラーハンドリングやパフォーマンスも考慮することで、効率的で保守性の高い2次元配列処理を実現できます。特に大規模データを扱う場合は、メモリ効率とパフォーマンスを重視した実装が重要になります。

らくらくPython塾 – 読むだけでマスター

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座