.NET 9 に対応
昨年(2024年)末、IronPython 3.4.2 がリリースされました。
.NET 8 が正式なターゲットになり、加えて .NET 9 でも動作可能になりました。
折角ですので、.NET 9 で新たに導入された LINQ のメソッドを IronPython 3.4.2 で試してみます。
IronPython に関する細かい点は過去の拙稿もご参照ください。
環境
- Windows 11 Pro(Ver. 24H2)および Ubuntu 24.04 on WSL2
- .NET Runtime 9.0.0 (64-bit)
- IronPython 3.4.2 (3.4.2.1000)
検証
LINQ に新たに導入されたメソッドの詳細については、優れた記事が数多く見つかりますのでここでは触れません。
早速、3つのメソッド(Index
, CountBy
, AggregateBy
)について検証します。
0. 準備
以下で LINQ の拡張メソッドが使えるようになります。
import System, clr
clr.AddReference('System.Core')
clr.ImportExtensions(System.Linq)
IronPython での LINQ の扱いはこちらに詳しく書きました。
1. Index メソッド
想定した結果が得られますが、戻り値がValueTuple[Int32,TSource]
ではそのまま使えないので、タプルに変換しています。
l = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
[*map(tuple, l.Index())]
# [(0, 'A'), (1, 'B'), (2, 'C'), (3, 'D'), (4, 'E'), (5, 'F'), (6, 'G'), (7, 'H'), (8, 'I'), (9, 'J')]
Index
メソッドを IronPython で使う機会は限られるでしょう。
お気づきかと思いますが Python は20年以上も前の Ver. 2.3 からenumerate
関数を用意しています。
[*enumerate(l)]
# [(0, 'A'), (1, 'B'), (2, 'C'), (3, 'D'), (4, 'E'), (5, 'F'), (6, 'G'), (7, 'H'), (8, 'I'), (9, 'J')]
とはいえ、IronPython + LINQ の可動域が拡がったことは間違いありません。
2. CountBy メソッド
CountBy
と次項のAggregateBy
メソッドは次のデータを使って検証します。
気象庁からダウンロードしたデータを検証用に加工したもので、データ構造に特別な意味はありません。
# (日付, 都市, (日の最高気温, 最低気温))
temperature = [
('2024/12/1', '仙台', (11.0, 4.9)),
('2024/12/1', '東京', (16.6, 6.7)),
('2024/12/1', '福岡', (16.3, 7.5)),
('2024/12/2', '仙台', (13.0, 3.6)),
('2024/12/2', '東京', (16.8, 7.6)),
('2024/12/2', '福岡', (19.8, 10.3)),
('2024/12/3', '仙台', (16.6, 5.4)),
('2024/12/3', '東京', (17.5, 7.4)),
('2024/12/3', '福岡', (17.5, 8.8)),
('2024/12/4', '仙台', (13.9, 4.9)),
('2024/12/4', '東京', (17.8, 8.4)),
('2024/12/4', '福岡', (13.6, 7.8)),
('2024/12/5', '仙台', (10.5, 3.2)),
('2024/12/5', '東京', (15.5, 8.9)),
('2024/12/5', '福岡', (13.7, 8.1)),
('2024/12/6', '仙台', (10.1, 2.5)),
('2024/12/6', '東京', (16.7, 5.2)),
('2024/12/6', '福岡', (14.4, 11.3)),
('2024/12/7', '仙台', (6.5, 0.6)),
('2024/12/7', '東京', (12.4, 5.9)),
('2024/12/7', '福岡', (11.9, 7.0))
]
上記データから、2024年12月1日~7日のうち1日の気温差が8℃以上になった日数を都市ごとにカウントしてみます。
from System.Collections.Generic import EqualityComparer
from System.Linq.Enumerable import ToDictionary
ToDictionary(
temperature
.Where(lambda data: round(data[2][0] - data[2][1], 1) >= 8.0)
.CountBy(lambda data: data[1], EqualityComparer[str].Default)
)
# Dictionary[str, Int32]({'東京' : 5, '福岡' : 3, '仙台' : 3})
簡潔です。👏
CountBy
メソッドの戻り値はKeyValuePair[TKey,Int32]
のシーケンスです。
IronPython では、.NET のDictionary
もdict
同様に操作できるので、ToDictionary
メソッドを使ってDictionary[TKey,Int32]
に変換しておくと便利に使えます。
(IronPython の辞書型についてはこちらに書きました。)
なお、C# や F# と違って IronPython ではCountBy
の第2引数を省略できません。
通常のデータ型であれば、上の例のようにEqualityComparer[TKey].Default
と書いておけばよいでしょう。
余談ですが、この例ではToDictionary
を静的メソッドとして使っています。
LINQ らしく拡張メソッドにしたいのですが、その場合は引数を要求されます。
具体的には次の形です。
# 強引に拡張メソッドで記述する例
# 読みやすさ優先で改行するために ( ) で囲んでいます
(
temperature
.Where(lambda data: round(data[2][0] - data[2][1], 1) >= 8.0)
.CountBy(lambda data: data[1], EqualityComparer[str].Default)
.ToDictionary(lambda kv: kv.Key, lambda kv: kv.Value) # 引数を入れないとエラー
)
# Dictionary[str, Int32]({'東京' : 5, '福岡' : 3, '仙台' : 3})
一方、辞書型ではなく、タプルのリストにするならばこんな感じでしょうか。
[
*temperature
.Where(lambda data: round(data[2][0] - data[2][1], 1) >= 8.0)
.CountBy(lambda data: data[1], EqualityComparer[str].Default)
.Select(lambda kv: kv.Deconstruct())
]
# [('東京', 5), ('福岡', 3), ('仙台', 3)]
IronPython ではKeyValuePair.Deconstruct
メソッドで Key と Value のタプルが返ります。
3. AggregateBy メソッド
こちらも痒いところに手が届く感じかもしれません。
先ほどの気温データで検証します。
2024年12月1日~7日の期間中の最高気温と最低気温を都市ごとに集計してみます。
ToDictionary(
temperature.AggregateBy(
lambda data: data[1], # 都市をキーにしてグループ化
(-100.0, 100.0), # アキュムレータの初期値として仮の最高気温・最低気温を設定
lambda acc, data: (max(acc[0], data[2][0]), min(acc[1], data[2][1])),
EqualityComparer[str].Default # 等値比較子は省略不可
)
)
# Dictionary[str, object]({'仙台' : (16.600000000000001, 0.59999999999999998), '東京' : (17.800000000000001, 5.2000000000000002), '福岡' : (19.800000000000001, 7.0)})
# IronPython は浮動小数点の丸め表示に消極的です
グループ単位の集計作業が簡潔に表現できました。
AggregateBy
メソッドもCountBy
同様、最後の等値比較子が省略できませんが、問題なく使うことができました。
以上で検証を終了いたします。