Juliaのsort関数 Order.by徹底解説!初心者向けサンプルコード付き

2025-05-27

byキーワード引数とは?

byキーワード引数は、sortsort!関数が要素を比較する際に、要素そのものではなく、要素から計算された「キー」に基づいてソートを行うために使用されます。つまり、「何を基準に並び替えるか」を指定する引数です。

具体的な説明

sort関数は、与えられたコレクション(配列など)の要素を昇順にソートした新しい配列を返します。sort!関数は、元のコレクションを直接ソートします。

通常のソートでは、要素そのものの値が比較されます。しかし、by引数を使うと、各要素に対して指定した関数が適用され、その関数の返り値が比較の基準として使われます。

例を用いて説明

  1. 文字列の長さでソートする

    words = ["apple", "banana", "cat", "dog", "elephant"]
    sorted_words = sort(words, by = length)
    println(sorted_words) # 出力: ["cat", "dog", "apple", "banana", "elephant"]
    

    この例では、by = lengthと指定することで、文字列の長さに基づいてソートしています。length関数は文字列の長さを返すので、短い文字列から順に並びます。

  2. タプルの2番目の要素でソートする

    data = [(1, 5), (3, 2), (2, 8), (4, 1)]
    sorted_data = sort(data, by = x -> x[2])
    println(sorted_data) # 出力: [(4, 1), (3, 2), (1, 5), (2, 8)]
    

    この例では、by = x -> x[2]と指定することで、タプルの2番目の要素に基づいてソートしています。x -> x[2]は匿名関数で、タプルの2番目の要素を返す関数です。

  3. 絶対値でソートする

    numbers = [-3, 1, -5, 2, -4]
    sorted_numbers = sort(numbers, by = abs)
    println(sorted_numbers) # 出力: [1, 2, -3, -4, -5]
    

    この例では、by = absと指定することで、数値の絶対値に基づいてソートしています。abs関数は数値の絶対値を返すので、絶対値が小さい順に並びます。

  • コードの可読性向上
    ソートの基準を明確に記述できるため、コードの意図が伝わりやすくなります。
  • 複雑なデータ構造のソート
    タプルや構造体など、複数の要素を持つデータ構造を特定の要素に基づいてソートできます。
  • 柔軟なソート
    要素そのものの値ではなく、任意の基準でソートできます。


  1. byに渡す関数の型エラー

    • エラー
      byに渡す関数が、コレクションの要素を引数として受け取り、比較可能な値を返す関数でない場合、型エラーが発生します。

    • data = [1, 2, "3", 4]
      sort(data, by = x -> length(x)) # エラー: 文字列と整数でlengthが使えない
      
    • トラブルシューティング
      • byに渡す関数が、コレクションの要素の型に合った処理を行うか確認します。
      • typeof関数や@code_warntypeマクロを使って、関数の引数と返り値の型を調べます。
      • コレクションの要素の型を統一するか、型に応じた処理を行う関数をbyに渡します。
  2. 比較できない値を返す関数

    • エラー
      byに渡す関数が、<などの比較演算子で比較できない値を返す場合、エラーが発生します。

    • data = [1, 2, 3]
      sort(data, by = x -> println(x)) # エラー: printlnは何も返さないため比較できない
      
    • トラブルシューティング
      • byに渡す関数が、数値、文字列、タプルなど、比較可能な値を返すか確認します。
      • printlnなどの副作用を持つ関数をbyに渡さないようにします。
  3. ソートの安定性

    • 問題
      同じキーを持つ要素の順序が、ソート前後で変わることがあります。
    • 説明
      Juliaのsort関数はデフォルトで安定ソートではありません。安定ソートとは、同じキーを持つ要素の順序を保持するソートアルゴリズムです。
    • トラブルシューティング
      • 安定ソートが必要な場合は、sort関数にstable = trueキーワード引数を指定します。
      data = [(1, "a"), (2, "b"), (1, "c")]
      sort(data, by = x -> x[1], stable = true) # 安定ソート
      
  4. パフォーマンス

    • 問題
      byに複雑な関数を渡すと、ソートのパフォーマンスが低下することがあります。
    • トラブルシューティング
      • byに渡す関数をできるだけ単純なものにします。
      • 必要であれば、事前にキーを計算して、キーの配列に基づいてソートします。
      • @benchmarkマクロを使って、ソートのパフォーマンスを計測します。
  5. 予期しないソート結果

    • 問題
      byに渡す関数のロジックが期待通りでなく、予期しないソート結果になることがあります。
    • トラブルシューティング
      • byに渡す関数のロジックを慎重に確認し、テストケースを作成して動作を確認します。
      • byに渡す関数が、ソートの基準として意図した値を返すか確認します。
      • byに渡す関数が、すべての要素に対して一貫した比較基準を提供するか確認します。


文字列の長さでソートする (昇順)

words = ["apple", "banana", "cat", "dog", "elephant"]
sorted_words = sort(words, by = length)
println(sorted_words) # 出力: ["cat", "dog", "apple", "banana", "elephant"]
  • by = lengthは、各文字列に対してlength関数を適用し、その返り値(文字列の長さ)を比較の基準として使用します。
  • words配列の文字列を、それぞれの文字列の長さに基づいて昇順にソートします。

文字列の長さでソートする (降順)

words = ["apple", "banana", "cat", "dog", "elephant"]
sorted_words = sort(words, by = length, rev = true)
println(sorted_words) # 出力: ["elephant", "banana", "apple", "dog", "cat"]
  • rev = trueを追加することで、降順にソートします。

タプルの2番目の要素でソートする

data = [(1, 5), (3, 2), (2, 8), (4, 1)]
sorted_data = sort(data, by = x -> x[2])
println(sorted_data) # 出力: [(4, 1), (3, 2), (1, 5), (2, 8)]
  • by = x -> x[2]は、匿名関数を使って各タプルの2番目の要素を抽出し、比較の基準として使用します。
  • data配列の各タプルを、2番目の要素に基づいて昇順にソートします。

構造体のフィールドでソートする

struct Person
    name::String
    age::Int
end

people = [Person("Alice", 30), Person("Bob", 25), Person("Charlie", 35)]
sorted_people = sort(people, by = person -> person.age)
println(sorted_people)
# 出力: [Person("Bob", 25), Person("Alice", 30), Person("Charlie", 35)]
  • by = person -> person.ageは、各Personオブジェクトのageフィールドを抽出し、比較の基準として使用します。
  • Person構造体の配列を、ageフィールドに基づいて昇順にソートします。

絶対値でソートする

numbers = [-3, 1, -5, 2, -4]
sorted_numbers = sort(numbers, by = abs)
println(sorted_numbers) # 出力: [1, 2, -3, -4, -5]
  • by = absは、各数値に対してabs関数(絶対値を返す関数)を適用し、その返り値を比較の基準として使用します。
  • numbers配列の数値を、絶対値に基づいて昇順にソートします。

複数条件でのソート(タプルの場合)

data = [(1, "b"), (2, "a"), (1, "a"), (2, "b")]
sorted_data = sort(data, by = x -> (x[1], x[2]))
println(sorted_data) # 出力: [(1, "a"), (1, "b"), (2, "a"), (2, "b")]
  • by = x -> (x[1], x[2])のようにタプルを返すことで、複数のソート条件を指定できます。
  • タプルの最初の要素、次に二番目の要素でソートします。

sort!関数を使って元の配列を直接ソートする

numbers = [3, 1, 4, 2]
sort!(numbers)
println(numbers) # 出力: [1, 2, 3, 4]
  • sort!関数は、元の配列を直接ソートします。
data = [(1, "a"), (2, "b"), (1, "c")]
sorted_data = sort(data, by = x -> x[1], stable = true)
println(sorted_data) # 出力: [(1, "a"), (1, "c"), (2, "b")]
  • stable=trueを使うことで、同じキーを持つ要素の順番を保持します。


キーの事前計算とソート

byに渡す関数が複雑な場合、事前にキーを計算して、キーの配列に基づいてソートすることでパフォーマンスを向上させることができます。

data = [(1, "apple"), (3, "banana"), (2, "cat")]

# キーを事前に計算
keys = [x[2] for x in data] # 文字列の配列を作成

# キーのインデックスでソート
sorted_indices = sortperm(keys)

# ソートされたインデックスに基づいて元のデータを並び替え
sorted_data = data[sorted_indices]

println(sorted_data) # 出力: [(1, "apple"), (3, "banana"), (2, "cat")]
  • sortpermsortするキーのindexを返すので、並び替えの処理を分けて、複雑な処理を高速化できます。
  • このインデックスを使って、元のdata配列を並び替えます。
  • sortperm(keys)関数は、keys配列をソートしたときのインデックスの配列を返します。

複数条件でのソート (タプルを使わない方法)

複数条件でソートする場合、byにタプルを返す代わりに、複数のソートを組み合わせることもできます。

data = [(1, "b"), (2, "a"), (1, "a"), (2, "b")]

# 最初の条件でソート
sorted_data1 = sort(data, by = x -> x[1])

# 最初の条件が同じ要素を、次の条件でソート
sorted_data2 = sort(sorted_data1, by = x -> x[2])

println(sorted_data2) # 出力: [(1, "a"), (1, "b"), (2, "a"), (2, "b")]
  • この方法は、条件が増えるほどコードが複雑になる可能性があります。
  • 最初に最初の条件でソートし、次にその結果を次の条件でソートします。

OrderedCollections.jlパッケージのsort関数を使う

OrderedCollections.jlパッケージには、安定ソートを提供するsort関数があります。

using OrderedCollections

data = [(1, "a"), (2, "b"), (1, "c")]
sorted_data = sort(data, by = x -> x[1])

println(sorted_data) # 出力: [(1, "a"), (1, "c"), (2, "b")]
  • パッケージをインストールする必要があります。] add OrderedCollections
  • 安定ソートが必要な場合に便利です。
  • OrderedCollections.sortはデフォルトで安定ソートです。

比較関数を直接定義してソートする

byに渡す関数が複雑な場合、比較関数を直接定義してsort関数に渡すこともできます。

data = [(1, "b"), (2, "a"), (1, "a"), (2, "b")]

function compare(x, y)
    if x[1] < y[1]
        return true
    elseif x[1] > y[1]
        return false
    else
        return x[2] < y[2]
    end
end

sorted_data = sort(data, lt = compare)

println(sorted_data) # 出力: [(1, "a"), (1, "b"), (2, "a"), (2, "b")]
  • 複雑な比較ロジックを実装する場合に便利です。
  • 比較関数は、2つの要素を引数として受け取り、最初の要素が2番目の要素より小さい場合にtrueを返し、それ以外の場合はfalseを返す必要があります。
  • ltキーワード引数に比較関数を渡します。

構造体を利用して比較関数を定義する

構造体を利用して、比較関数を構造体内部に定義する事で、可読性を高める事ができます。

struct SortableData
    x::Int
    y::String
end

function Base.isless(a::SortableData, b::SortableData)
    if a.x < b.x
        return true
    elseif a.x > b.x
        return false
    else
        return a.y < b.y
    end
end

data = [SortableData(1, "b"), SortableData(2, "a"), SortableData(1, "a"), SortableData(2, "b")]
sorted_data = sort(data)

println(sorted_data)
  • 構造体内部に比較ロジックを定義することで、コードの可読性を向上させることができます。
  • sort関数は、構造体の配列をソートする際に、このisless関数を使用します。
  • Base.isless関数をオーバーライドすることで、構造体同士の比較ロジックを定義できます。