2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IronPython との付き合いかた(その3)

Last updated at Posted at 2025-01-08

.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 の拡張メソッドが使えるようになります。

IronPython 3.4.2
import System, clr
clr.AddReference('System.Core')
clr.ImportExtensions(System.Linq)

IronPython での LINQ の扱いはこちらに詳しく書きました。

1. Index メソッド

想定した結果が得られますが、戻り値がValueTuple[Int32,TSource]ではそのまま使えないので、タプルに変換しています。

IronPython 3.4.2
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関数を用意しています。

IronPython 3.4.2
[*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メソッドは次のデータを使って検証します。
気象庁からダウンロードしたデータを検証用に加工したもので、データ構造に特別な意味はありません。

IronPython 3.4.2
# (日付, 都市, (日の最高気温, 最低気温))
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℃以上になった日数を都市ごとにカウントしてみます。

IronPython 3.4.2
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 のDictionarydict同様に操作できるので、ToDictionaryメソッドを使ってDictionary[TKey,Int32]に変換しておくと便利に使えます。
(IronPython の辞書型についてはこちらに書きました。)

なお、C# や F# と違って IronPython ではCountBy第2引数を省略できません
通常のデータ型であれば、上の例のようにEqualityComparer[TKey].Defaultと書いておけばよいでしょう。

余談ですが、この例ではToDictionaryを静的メソッドとして使っています。
LINQ らしく拡張メソッドにしたいのですが、その場合は引数を要求されます。
具体的には次の形です。

IronPython 3.4.2
# 強引に拡張メソッドで記述する例
# 読みやすさ優先で改行するために ( ) で囲んでいます
(
    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})

一方、辞書型ではなく、タプルのリストにするならばこんな感じでしょうか。

IronPython 3.4.2
[
    *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日の期間中の最高気温と最低気温を都市ごとに集計してみます。

IronPython
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同様、最後の等値比較子が省略できませんが、問題なく使うことができました。

以上で検証を終了いたします。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?