リスト内包表記は、既存のリストに基づいて新しいリストを作成するために、一部のプログラミング言語で利用できる構文です。これは、マップ関数やフィルタ関数とは異なり、数学における集合構成記法(集合内包表記)の形式に従います。
数学の集合構成記法で以下の例を考えてみましょう。
またはしばしば
これは次のように読むことができます。は「2 回」の数の集合です「そのような自然数の集合の要素またはメンバーです()、 そして2乗はより大きい。
最小の自然数 x = 1 は、条件 x 2 > 3 を満たさない (条件 1 2 > 3 は偽) ため、2 · 1 は S に含まれません。次の自然数 2 は、他のすべての自然数と同様に、条件 (2 2 > 3) を満たします。したがって、x は 2、3、4、5、... から構成されます。集合Sは「2 × x」のすべての数から構成されるため、S = {4、6、8、10、...} で与えられます。言い換えれば、S は 2 より大きいすべての偶数の集合です。
この注釈付きの例では、次のようになります。
リスト内包表記は、入力リストまたはイテレータからリストを順番に生成することを表すために、同じ構文要素を持っています。
出力リストの要素の生成順序は、入力リストの項目の順序に基づいています。
Haskellのリスト内包表記構文では、このセット構築構造は同様に次のように記述されます。
s = [ 2 * x | x <- [ 0 .. ], x ^ 2 > 3 ]ここで、リスト[0..]ははx^2>3述語を表し、 は2*x出力式を表します。
リスト内包表記は、定義された順序で結果を返します(集合の要素とは異なります)。また、リスト内包表記は、リスト全体を生成するのではなく、リストの要素を順序どおりに生成できるため、例えば、前述のHaskellにおける無限リストの要素の定義が可能になります。
関連する構造は、「リスト内包表記」という用語が使われる以前から存在していました。SETLプログラミング言語( 1969年)には、リスト内包表記に似た集合形成構造があります。例えば、このコードは2からNまでのすべての素数を出力します。
print([n in [2..N] | ∀ m in {2..n - 1} | n mod m > 0]);コンピュータ代数システムAxiom (1973) には、ストリームを処理する同様の構造があります。
このような構造に対して「内包表記」という用語が初めて使用されたのは、 1977年にロッド・バーストールとジョン・ダーリントンが彼らの関数型プログラミング言語NPLについて記述した時である。デビッド・ターナーは回顧録「関数型プログラミング言語の歴史」[ 1 ] の中で次のように回想している。
NPL は Burstall によってPOP2に実装され、Darlington のプログラム変換に関する研究に使用されました (Burstall & Darlington 1977)。この言語は、一階述語論理で、強力な(ただし多相的ではない) 型付け、純粋関数型、値渡しでした。また、「セット式」も備えていました。
setofeven (X) <= <:x : x in X & even(x):>}}
「リスト内包表記」という用語に付された脚注で、ターナーは次のように述べている。
私は当初、これらをツェルメロ・フレンケル集合論にちなんでZF式と呼んでいましたが、フィル・ワドラーがリスト内包表記というより適切な用語を考案しました。
バーストールとダーリントンによるNPLの研究は、1980年代の多くの関数型プログラミング言語に影響を与えたが、リスト内包表記を含むものはすべてではなかった。例外は、1985年にリリースされたターナーの影響力のある純粋で遅延評価の関数型プログラミング言語であるMirandaである。その後開発された標準的な純粋遅延評価の関数型言語Haskellは、リスト内包表記を含むMirandaの多くの機能を取り入れている。
内包表記はデータベースのクエリ表記法として提案され[ 2 ] 、 Kleisliデータベースクエリ言語に実装された[ 3 ] 。
Python言語では、バージョン2.7からセット内包表記の構文が導入されました。リスト内包表記と形式は似ていますが、セット内包表記はリストではなくPythonのセットを生成します。
s : set [ str ] = { v for v in "ABCDABCD" if v not in "CB" } print ( s ) # {'A', 'D'} と出力print ( type ( s )) # <class 'set'> と出力Racketのセット内包表記は、リストではなくRacketのセットを生成します。
( for/set ([ v "ABCDABCD" ] #:unless ( member v ( string->list "CB" ))) v ))Python言語はバージョン2.7で、リスト内包表記に似た形式を持つものの、リストではなくPythonの辞書を生成する辞書内包表記の新しい構文を導入しました。
s : dict [ str ] = { key : val for key , val in enumerate ( "ABCD" ) if val not in "CB" } print ( s ) # {0: 'A', 3: 'D'} と出力されますRacketのハッシュテーブル内包表記は、Racketのハッシュテーブル(Racketの辞書型の実装の1つ)を生成します。
( for/hash ([( val key ) ( in-indexed "ABCD" )] #:unless ( member val ( string->list "CB" ))) ( values key val ))Glasgow Haskell Compiler には、並列リスト内包表記( zip-内包表記とも呼ばれる)という拡張機能があり、リスト内包表記構文内で複数の独立した修飾子の分岐を許可します。カンマで区切られた修飾子は依存 (「ネスト」) しますが、パイプで区切られた修飾子の分岐は並列に評価されます (これはマルチスレッド化を意味するものではなく、単に分岐が圧縮されていることを意味します)。
-- 通常のリスト内包表記a = [( x , y ) | x <- [ 1 .. 5 ], y <- [ 3 .. 5 ]] -- [(1,3),(1,4),(1,5),(2,3),(2,4) ...-- zip リスト内包表記b = [( x , y ) | ( x , y ) <- zip [ 1 .. 5 ] [ 3 .. 5 ]] -- [(1,3),(2,4),(3,5)]-- 並列リスト内包表記c = [( x , y ) | x <- [ 1 .. 5 ] | y <- [ 3 .. 5 ]] -- [(1,3),(2,4),(3,5)]Racket の標準ライブラリには、並列バージョンとネストバージョンの内包表記が含まれており、名前の「for」と「for*」で区別されます。たとえば、ベクトル内包表記「for/vector」と「for*/vector」は、シーケンスを並列またはネストして反復処理することでベクトルを作成します。以下は、Haskell のリスト内包表記の例を Racket で記述したコードです。
> ( for*/list ([ x ( in-range 1 6 )] [ y ( in-range 3 6 )]) ( list x y )) ' (( 1 3 ) ( 1 4 ) ( 1 5 ) ( 2 3 ) ( 2 4 ) ( 2 5 ) ( 3 3 ) ( 3 4 ) ( 3 5 ) ( 4 3 ) ( 4 4 ) ( 4 5 ) ( 5 3 ) ( 5 4 ) ( 5 5 )) > ( for/list ([ x ( in-range 1 6 )] [ y ( in-range 3 6 )]) ( list x y )) ' (( 1 3 ) ( 2 4 ) ( 3 5 ))Pythonでは、次のように記述できます。
# 通常のリスト内包表記a : list [ tuple [ int , int ]] = [( x , y ) for x in range ( 1 , 6 ) for y in range ( 3 , 6 )] print ( a ) # [(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), ... と出力されます# 並列/zip リスト内包表記b : list [ tuple [ int , int ]] = [ x for x in zip ( range ( 1 , 6 ), range ( 3 , 6 ))] print ( b ) # [(1, 3), (2, 4), (3, 5)] と出力されますJuliaでは、ほぼ同じ結果を以下のようにして得ることができます。
# 通常の配列内包表記a :: Vector { Tuple { Int , Int }} = [( x , y ) for x in 1 : 5 for y in 3 : 5 ]# 並列/zip配列内包表記b :: Vector { Tuple { Int , Int }} = [ x for x in zip ( 1 : 3 , 3 : 5 )]唯一の違いは、リストの代わりに、Juliaでは配列を使うという点です。
元々のNPLの用途と同様に、これらは基本的にデータベースアクセス言語である。
このため、理解の概念がより重要になります。なぜなら、リスト全体を取得してそれに対して操作を行うことは計算上不可能だからです(最初の「リスト全体」は、拡張マークアップ言語(XML)データベース全体である可能性があります)。
XPathでは、次の式が成り立ちます。
/ライブラリ/ブック//段落[ @style = 'first-in-chapter' ]概念的には、各ステップでリストが生成され、次のステップで前のステップの出力の各要素にフィルタ関数が適用される一連の「ステップ」として評価される。[ 4 ]
XQueryでは完全なXPathが利用できますが、より強力な内包表記であるFLWORステートメントも使用されます。 [ 5 ]
for $ b in // book where $ b [ @pages < 400 ] order by $ b // title return <shortBook> <title> { $ b // title } </title> <firstPara> {( $ book // paragraph )[ 1 ]} </firstPara> </shortBook>ここでは、XPath //book が評価されてシーケンス (別名リスト) が作成されます。where 句は機能的な「フィルタ」であり、order by は結果をソートし、 XML スニペットは実際には匿名関数であり、他の関数型言語に見られる「マップ」アプローチを使用して、シーケンス内の各要素の XML を構築/変換します。<shortBook>...</shortBook>
したがって、別の関数型言語では、上記の FLWOR ステートメントは次のように実装できます。
map ( newXML ( shortBook , newXML ( title , $ 1. title ), newXML ( firstPara , $ 1...)) filter ( lt ( $ 1. pages , 400 ), xpath (// book ) ) )C# 3.0には、言語統合クエリ(LINQ)と呼ばれる関連機能群があり、オブジェクト列挙型を操作するためのクエリ演算子のセットを定義しています。
using System.Collections.Generic ; using System.Linq ;IEnumerable <int> s = Enumerable.Range ( 0 , 100 ) .Where ( x = > x * x > 3 ) .Select ( x = > x * 2 ) ;また、構造化照会言語(SQL)を彷彿とさせる代替的な内包表記構文も提供しています。
using System.Collections.Generic ; using System.Linq ;IEnumerable < int > s = from x in Enumerable . Range ( 0 , 100 ) where x * x > 3 select x * 2 ;LINQ は、一般的なリスト内包表記の実装よりも優れた機能を提供します。内包表記のルート オブジェクトがインターフェースを実装している場合IQueryable、内包表記のメソッドを連鎖的に実行するだけでなく、コマンドのシーケンス全体が抽象構文木(AST) オブジェクトに変換され、それが IQueryable オブジェクトに渡されて解釈および実行されます。
これにより、IQueryable で以下のことが可能になります。
C++にはリスト内包表記を直接サポートする言語機能はありませんが、演算子オーバーロード(例えば、、、のオーバーロード|)>>を>>=使用して、「埋め込み」クエリドメイン固有言語(DSL)に表現力豊かな構文を提供しています。あるいは、コンテナ内の要素を選択する消去・削除イディオムfor_eachと、それらを変換するSTLアルゴリズムを使用してリスト内包表記を構築することもできます。
歴史的に、この<algorithm>ヘッダーには要素の範囲に対するイテレータベースのアルゴリズムのみが含まれていました。[ 6 ]これは後にC++20で、制約付きアルゴリズム(名前空間内std::ranges)がこのヘッダーに追加され、拡張されました。制約付きアルゴリズムは、イテレータではなく範囲に対して動作します。[ 7 ]
import std ;std :: vectorを使用します。template < typename Collection , typename Pred , typename Trans > Collection comprehend ( Collection && source , const Pred & predicate , const Trans & transformation ) { // 宛先を初期化Collection d = std :: forward < Collection > ( source );// 要素をフィルタリングするd.erase ( std :: ranges :: remove_if ( d , predicate ) , d.end ( ) ) ;// 変換を適用するstd :: ranges :: for_each ( d , transformation );return d ; }int main ( int argc , char * argv []) { vector < int > range ( 10 ); // range は 10 個の要素のリストで、すべてゼロですstd :: ranges :: iota ( range , 1 ); // range には 1, 2, ..., 10 が含まれますvector < int > result = comprehend ( range , []( int x ) -> bool { return x * x <= 3 ; }, []( int & x ) -> void { x *= 2 ; } ); // result には 4, 6, ..., 20 が含まれます}C++20では、などの追加ヘッダー<ranges>が追加され、構成可能な範囲アルゴリズムと任意の範囲に対する遅延評価std::ranges::viewsビューが提供されるようになりました。ライブラリ(とも略されるstd::views)[ 8 ]を使用すると、これは次のように記述できます。
using std :: vector ; using std :: ranges :: to ; using std :: views :: filter ; using std :: views :: transform ;vector < int > range ( 10 ); // range は 10 個の要素を持つリストで、すべてゼロです。std :: ranges :: iota ( range , 1 ); // range には 1, 2, ..., 10 が含まれます。vector < int > result = range | filter ([]( int x ) -> bool { return x * x > 3 ; }) | transform ([]( int x ) -> int { return x * 2 ; }) | to < vector > ();Java 8で導入された Streams API では、遅延評価される範囲のようなオブジェクトであるストリームが導入されました。java.util.stream.Streamインターフェースを実装する任意の型をストリームで使用できます。[ 9 ]toArray()これらは、またはによって収集されtoList()、要素がまたはに蓄積されObject[]ますList<T>。
import java.util.List ;List <Integer> numbers = List.of ( 1 , 2 , 3 , 4 , 5 ) ; List <Integer> doubledEven = numbers.stream ( ) . filter ( x - > x % 2 == 0 ) .map ( x - > x * 2 ) .toList ( ) ;System.out.println ( doubledEven ) ; // [ 4 , 8 ]Rust には遅延評価されるイテレータがあります。トレイトを実装する型は、std::iter::Iteratorイテレータとともに使用できます。[ 10 ]collect()メソッドを使用すると、イテレータアダプタのチェーンが作成され、最終的に実際のコレクション (通常は ) に収集されますVec<T>。
let numbers = vec! [ 1 , 2 , 3 , 4 , 5 ]; let doubled_even : Vec < i32 > = numbers . iter () . filter ( |& x | x % 2 == 0 ) . map ( | x | x * 2 ) . collect ();println! ( "{:?}" , doubled_even ); // [4, 8]