Pythonでのリストのパターンマッチング
-
04-07-2019 - |
質問
Pythonのリストでパターンマッチングを行いたい。たとえば、Haskellでは、次のようなことができます。
fun (head : rest) = ...
したがって、リストを渡すと、 head
が最初の要素になり、 rest
が最後の要素になります。
同様に、Pythonでは、タプルを自動的にアンパックできます:
(var1, var2) = func_that_returns_a_tuple()
Pythonのリストで同様のことを行いたい。現在、リストを返す関数と、次のことを行うコードの塊があります。
ls = my_func()
(head, rest) = (ls[0], ls[1:])
Pythonでは、2行ではなく1行でなんとかできるのではないかと考えました。
解決
私が知っている限り、現在のPythonで別の関数を導入せずにワンライナーにする方法はありません。例:
split_list = lambda lst: (lst[0], lst[1:])
head, rest = split_list(my_func())
ただし、Python 3.0では、可変引数引数の署名と引数のアンパックに使用される特殊な構文は、このタイプの一般的なシーケンスのアンパックでも使用できるようになるため、3.0では次のように記述できます。
head, *rest = my_func()
詳細については、 PEP 3132 をご覧ください。
他のヒント
まず、"パターンマッチング"あなたが言及した関数型言語とタプルへの割り当ては、実際にはそれほど似ていません。関数型言語では、パターンを使用して関数の部分的な定義を提供します。したがって、 f(x:s)= e
は、 f
の引数の先頭と末尾を取得し、それらを使用して e
を返すことを意味しませんが、これは、 f
の引数が x:s
(一部の x
および s
)、 then f(x:s)
は e
と同じです。
pythonの割り当ては、複数の割り当てに似ています(元々の意図だったと思われます)。したがって、たとえば、 x、y = y、x
と書くと、一時変数を必要とせずに x
と y
の値を交換できます単純な割り当てステートメントを使用します)。これは基本的に「同時」の省略形であるため、パターンマッチングとはほとんど関係ありません。 x = y
および y = x
の実行。 pythonではコンマ区切りリストの代わりに任意のシーケンスを使用できますが、このパターンマッチングを呼び出すことはお勧めしません。パターンマッチングでは、パターンに一致するものがあるかどうかを確認します。 Pythonの割り当てでは、両側のシーケンスが同じであることを確認する必要があります。
あなたが望むように思われることを行うには、通常(関数型言語でも)補助機能(他の人が述べたように)または let
または where
コンストラクト(匿名関数を使用していると見なすことができます)。例:
(head, tail) = (x[0], x[1:]) where x = my_func()
または、実際のpythonで:
(head, tail) = (lambda x: (x[0], x[1:]))(my_func())
これは、必要なワンライナーであることを除いて、補助機能を備えた他の人が提供するソリューションと本質的に同じであることに注意してください。ただし、必ずしも個別の関数よりも優れているとは限りません。
(私の答えが少し上にある場合は申し訳ありません。区別を明確にすることが重要だと思います。)
これは非常に「純粋に機能的な」アプローチであり、Haskellでは賢明なイディオムですが、おそらくPythonにはそれほど適切ではありません。このように、Pythonのパターンの概念は非常に限られています。この種の構造を実装するには、やや硬直した型システムが必要です( erlang バフはここに同意しないように招待されています)
あなたが持っているものはおそらくそのイディオムに近いでしょうが、おそらくリストの末尾で関数を再帰的に呼び出すよりも、リストの内包表記または命令型のアプローチを使用した方が良いでしょう。
記載 場合によっては before 、Pythonは実際には関数型言語ではありません。 FPの世界からアイデアを借りているだけです。本質的に Tail Recursive ではありませんが、関数型言語であるため、大量のスタックスペースを使用せずに大きなデータセットに対してこの種の再帰的操作を行うのは困難です。
拡張解凍は3.0で導入されました http://www.python.org/dev/peps/pep-3132/
HaskellやMLとは異なり、Pythonには構造のパターンマッチングが組み込まれていません。パターンマッチングを行う最もPython的な方法は、try-exceptブロックを使用することです。
def recursive_sum(x):
try:
head, tail = x[0], x[1:]
return head + recursive-sum(tail)
except IndexError: # empty list: [][0] raises IndexError
return 0
これは、スライスのインデックス付けがあるオブジェクトでのみ機能することに注意してください。また、関数が複雑になると、 head、tail
行の後の本文の何かが 行され、IndexErrorが発生する可能性があり、微妙なバグにつながります。ただし、これにより次のようなことができます。
for frob in eggs.frob_list:
try:
frob.spam += 1
except AttributeError:
eggs.no_spam_count += 1
Pythonでは、末尾再帰は通常、アキュムレータを使用したループとしてより適切に実装されます。つまり、
def iterative_sum(x):
ret_val = 0
for i in x:
ret_val += i
return ret_val
これは、99%の確率でそれを行うための1つの明白で正しい方法です。読みやすいだけでなく、高速であり、リスト以外のもの(セットなど)でも機能します。そこに発生するのを待っている例外がある場合、関数は喜んで失敗し、チェーンにそれを配信します。
pyfpm に取り組んでいます。これはPythonでScalaに似た構文を使用したパターンマッチング用のライブラリ。これを使用して、次のようにオブジェクトをアンパックできます。
from pyfpm import Unpacker
unpacker = Unpacker()
unpacker('head :: tail') << (1, 2, 3)
unpacker.head # 1
unpacker.tail # (2, 3)
または関数のarglist内:
from pyfpm import match_args
@match_args('head :: tail')
def f(head, tail):
return (head, tail)
f(1) # (1, ())
f(1, 2, 3, 4) # (1, (2, 3, 4))
さて、そもそもなぜ1行にしたいのですか?
本当にしたい場合は、いつでも次のようなトリックを行うことができます。
def x(func):
y = func()
return y[0], y[1:]
# then, instead of calling my_func() call x(my_func)
(head, rest) = x(my_func) # that's one line :)
他の回答に加えて、Pythonの同等のhead / tail操作(python3の*構文の拡張を含む)は、一般的にHaskellのパターンマッチングよりも効率が悪いことに注意してください。
Pythonリストはベクトルとして実装されているため、テールを取得するにはリストのコピーを取得する必要があります。これはリストのサイズに対してO(n)ですが、Haskellのようなリンクリストを使用する実装では、テールポインタ、O(1)操作を使用するだけです。
唯一の例外は、リストが実際に返されるのではなく、イテレータが返されるイテレータベースのアプローチです。ただし、これはリストが必要なすべての場所に適用できるわけではありません(たとえば、複数回繰り返す)。
たとえば、暗号のアプローチは、イテレータは、タプルに変換するのではなく、この動作を行います。または、バイトコードに依存しない単純な2項目のみの方法は次のようになります。
def head_tail(lst):
it = iter(list)
yield it.next()
yield it
>>> a, tail = head_tail([1,2,3,4,5])
>>> b, tail = head_tail(tail)
>>> a,b,tail
(1, 2, <listiterator object at 0x2b1c810>)
>>> list(tail)
[3, 4]
もちろん、便利な構文シュガーを用意するのではなく、ユーティリティ関数をラップする必要があります。
これを行うためのpythonクックブックにレシピがありました。私は今それを見つけることができないようですが、ここにコードがあります(私はそれをわずかに修正しました)
def peel(iterable,result=tuple):
'''Removes the requested items from the iterable and stores the remaining in a tuple
>>> x,y,z=peel('test')
>>> print repr(x),repr(y),z
't' 'e' ('s', 't')
'''
def how_many_unpacked():
import inspect,opcode
f = inspect.currentframe().f_back.f_back
if ord(f.f_code.co_code[f.f_lasti])==opcode.opmap['UNPACK_SEQUENCE']:
return ord(f.f_code.co_code[f.f_lasti+1])
raise ValueError("Must be a generator on RHS of a multiple assignment!!")
iterator=iter(iterable)
hasItems=True
amountToUnpack=how_many_unpacked()-1
next=None
for num in xrange(amountToUnpack):
if hasItems:
try:
next = iterator.next()
except StopIteration:
next = None
hasItems = False
yield next
if hasItems:
yield result(iterator)
else:
yield None
ただし、前のフレームを検査しない方法のために割り当てのアンパックを使用する場合にのみ機能することに注意する必要があります...まだ非常に便利です。
特定のユースケースでは、Haskellの fun(head:rest)= ...
をエミュレートします。関数定義は、かなり長い間パラメーターのアンパックをサポートしています:
def my_method(head, *rest):
# ...
Python 3.0以降、@ bpowah 前述のように、Pythonは割り当て時のアンパックもサポートしています。
my_list = ['alpha', 'bravo', 'charlie', 'delta', 'echo']
head, *rest = my_list
assert head == 'alpha'
assert rest == ['bravo', 'charlie', 'delta', 'echo']
アスタリスク(「スプラット」)は、「最後まで」ではなく「反復可能の残り」を意味することに注意してください。以下が正常に機能します。
first, *middle, last = my_list
assert first == 'alpha'
assert last == 'echo'
assert middle == ['bravo', 'charlie', 'delta']
first, *middle, last = ['alpha', 'bravo']
assert first == 'alpha'
assert last == 'bravo'
assert middle == []