1. 序章
このチュートリアルでは、ラムダ関数とプログラミングでのそれらの使用について説明します。
2. 「ラムダ」はどこから来たのですか?
匿名関数、ラムダ式、または関数リテラルはすべて同じものです。 Lambda(または\ lambda)は、Pythonなどの一部の言語で無名関数に付けられた名前です。 これらは、明示的な識別子に拘束されない関数です。 この名前は、博士によって導入された計算を表現するための数学システムであるラムダ計算に由来しています。 1930年代のアロンゾチャーチ。
博士 Churchの素晴らしい仕事は、数学の基礎を探求し、複雑な関数を単純な1引数の「ラムダ」式に変換することを目的としていました。 これらは名前のない小さな関数であり、カリー化と呼ばれるプロセスを通じてより大きな関数を作成するために使用されます。 カリー化とは、マルチ引数関数が単純な1引数関数に分解されることを意味します。
これは、複雑な関数を匿名化された一口サイズのチャンクに分解できることを意味します。
さらに、非常に単純なタスクに対して完全な関数を定義する必要がない場合は、単純な無名関数を作成すると便利です。
3. 基本的な実装
プログラミングには、このための多くのアプリケーションがありますが、最大のものは、並べ替え、クロージャ、およびカリー化です。 読みやすくするために、Python、JavaScript、およびC++で次のコードを実装します。
ラムダ関数の構文はかなり単純です。
Pythonの例をいくつか示します。
lambda x : x+1
lambda x,y : x+y
「ラムダ」という単語が無名関数を象徴していることがわかります。 コロンの前に続くのは、関数に必要な変数です。 コロンの後に続くのは、関数のロジックです。
JavaScriptでは状況が少し異なります。
//Traditional Anonymous Function
function(a){
return a + 1;
}
これは無名関数です。 名前を付けることはできません。 このため、JavaScriptのほとんどのアプリケーションでは、代わりに矢印「=>」関数を使用します。
// we can define them anonymously:
a => a + 1;
// or issue a name:
let myfunction = a => a+1;
この矢印関数には名前を付けることができ、Pythonで実装されているラムダ関数によく似ています。
矢印の左側には使用される変数があり、右側には実装されるロジックがあることがわかります。
C ++に移ると、矢印表記が持続していることがわかります(少なくともC ++ 11以降)。 それらが次の一般的な構造を持っていることがわかります。
[ capture clause ] (parameters) -> return-type
{
method
}
前の例と同じようにサンプル関数を作成できます。
[](double a) -> double {return a + 1}
もちろん、他の変数にアクセスしたい場合は、次のようにキャプチャ句でそれらを指定できます。
[variable](double a) -> double {
if (a < variable) {
return 0;
} else {
return a;
}
}
これらのキャプチャ句は、ラムダ関数をより大きな関数内にネストできることがよくあるため、C++では興味深いものです。
4. 並べ替え
多くの場合、特定のロジックによってリスト内のアイテムを並べ替えることを目的としています。 ロジックが単純な場合、ラムダ関数を使用してこれを実現できます。
Pythonの各エントリの長さでリストを並べ替えたいとしましょう。
a= ['somethingevenlonger', 'somethinglonger', 'something']
a.sort(key=lambda x : len(x))
print(a)
これの出力は次のとおりです。
['something', 'somethinglonger', 'somethingevenlonger']
この例では、ラムダ関数が次のようになっていることがわかります。
lambda x : len(x)
この関数は、引数「x」の長さを返します。
sortメソッドは、このラムダ関数をキーとして使用します。
同様に、JavaScriptでは、矢印関数を使用して文字列を並べ替えることができます。
const arr = [1,2,3,4,5,6]
function sortNums(arr) {
let sorted = arr.sort((a, b) => b - a);
console.log(sorted.join(", "));
}
// Log to console
console.log(arr)
sortNums(arr)
次のコードの出力は次のとおりです。
[1 ,2 ,3 ,4 ,5 ,6]
6, 5, 4, 3, 2, 1
C ++を使用すると、同じ結果を得ることができます。
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
int main() {
auto vectors = std::vector<int> { 7, 5, 16, 8 };
auto Lambda = [] (int s1, int s2) -> bool {
return s1 < s2;
};
std::sort(vectors.begin(), vectors.end(), Lambda);
for (auto v : vectors)
std::cout<<v<<'.';
}
ここで使用されるラムダ関数はLambdaと呼ばれ、2つの整数引数を取ることに注意してください。 引数のサイズの比較に基づいて、「True」または「False」ブールステートメントを返します。
端末が値を順番に出力する方法を確認できます。
5.7.8.16.
5. クロージャ
closure という用語は、「ラムダ関数」または「無名関数」という用語と同じ意味で使用されることがあります。 ただし、クロージャは実際には非ローカル変数が割り当てられた関数のインスタンスです。 これらは、スコープ内の変数を収集できる匿名オブジェクトと考えることができます。
Pythonで次の例を使用して、これを示すことができます。
def a(x):
def b(y):
return x + y
return b # Returns a closure.
bにはa(x)メソッドのx変数が必要であることがわかります。 b 内で定義されていないため、前のスコープ( a )のxを使用します。
または、ラムダ関数を使用して同じ結果を得ることができます。
def c(x):
return lambda y: x + y # Return a closure.
これらは両方とも数学的に同じ結果を生成します。
result1 = a(4)
print(result1(4))
result2 = c(4)
print (result2(4))
# Both of these return 8
JavaScriptでは、クロージャーを実装することもできます。
function wrapper(x) {
let f1 = (x) => {x=x+1;console.log(x);}
return f1;
}
let x = 0;
let lambda = ()=>{x=x+1;console.log(x);}
lambda();
lambda();
lambda();
let w=wrapper();
w(x);
w(x);
w(x);
console.log(x);
このスクリプトは次を出力します。
1
2
3
4
4
4
3
ラッパー関数を呼び出しても、実際には変数xの値が変更されていないことがわかります。 これは、独自のローカル変数xを変更しているためです。
以下は、この概念の別のC++実装です。
#include <iostream>
#include <functional>
std::function<void(void)> WrapClosure() {
int x = 1;
return [x](){std::cout << x << std::endl;};
}
int main() {
int x = 1;
auto lambda = [&x](){
x += 1;
std::cout << x << std::endl;
};
std::function<void(void)> wrapper = WrapClosure();
lambda();
lambda();
lambda();
std::cout << "x is equal to "<<x<<std::endl;
wrapper();
wrapper();
wrapper();
std::cout << "x is equal to "<<x<<std::endl;
}
出力は次のとおりです。
2
3
4
x is now equal to 4
1
1
1
x is now equal to 4
ラムダにアドレス( & ) からバツより大きな主関数スコープで使用される変数。 これは、クロージャーを使用するときに注意することが重要です。 ラムダのスコープが制限されているため、 x を使用するだけでは、メイン関数のx変数は変更されません。
6. カリー化
カリー化は、複雑な関数を取得して一連の1引数関数に変換するプロセスです。これには多くの利点があります。 最も明白なのは、関数を変更でき、同じコードからさまざまな関数を進化させることができるということです。
Pythonで2つの数値を乗算しようとしているとしましょう。
def multiply(x,y):
return x*y
def multiplier(x):
return lambda y: x*y
これまでのところ、これらの関数は両方とも同じことをします。 ただし、2番目の関数を使用すると、追加の関数を作成できます。
twice = multiplier(2)
tentimes = multiplier(10)
これらの関数を呼び出して、他の目的に再利用できるようになりました。 これらの関数の呼び出しは次のようになります。
twice(4)
tentimes(4)
これは、以下のコードを使用するのと同じです。
multiply(4,2)
multiply(4,10)
JavaScriptを使用して、この概念を実装することもできます。
function multiply(x,y) {
return x*y;
}
function multiplier(x) {
return (y)=>x*y;
}
let timesfour = multiplier(4);
let timesfive = multiplier(5);
console.log(timesfour(2));
console.log(multiply(4,2));
console.log(timesfive(5));
console.log(multiply(5,5));
出力を確認できます。
8
8
25
25
同じことがC++にも当てはまります。
#include <iostream>
#include <functional>
void multiply(int x,int y){
std::cout<<x*y<<std::endl;
}
auto multiplier(int x){
return [x](int y){std::cout<<x*y;};
}
int main(){
multiply(2,3);
auto timestwo = multiplier(2);
timestwo(3);
}
ここでは、 timestwo(3)と multiplier(2,3)の両方が同じ値6を出力します。
7. フィルタリング
Pythonには、フィルターと呼ばれる一般的に使用される関数があります。 この関数を使用すると、ブール論理式を使用して値を解析できます。 構文は次のとおりです。
filter(object, iterable)
関数の最初の引数はラムダ関数にすることができます。 2番目は反復可能である必要があり、これの最も簡単な例はおそらくリストです。
これは、「X未満のすべてのアイテム」または「Yに等しいすべてのアイテム」を表示する必要があるリストを処理するためによく使用されます。
以下のコードは、これらのリスト操作の最初の使用法を示しています。
iterable = [1,2,3,4,6]
a = filter(lambda x : x<3, iterable)
print(list(a))
同様に、JavaScriptでは、次のようになります。
iterable=[1,3,4,5,6]
console.log(iterable.filter(element => element > 4));
これは以下を出力します:
[ 5, 6 ]
そして最後に、C ++を使用して、次のような独自のフィルター関数を作成できます。
#include <iostream>
#include <algorithm>
#include <vector>
int main () {
std::vector<int> iterable = {1,2,3,5,15};
std::vector<int> result (iterable.size());
// copy only positive numbers:
auto it = std::copy_if (iterable.begin(), iterable.end(), result.begin(), [](int i){return !(i>4);} );
result.resize(std::distance(result.begin(),it)); // shrink container to new size
std::cout << "result contains:";
for (int& x: result) std::cout << ' ' << x;
return 0;
}
これにより、次のように出力されます。
result contains: 1 2 3
8. マッピング
マッピングはフィルター関数に似ていますが、ブール式を使用して値をフィルター処理する代わりに、実際に値を変更します。
Pythonでこれを行うためのもう1つの興味深いツールは、map関数です。
構文は次のとおりです。
map(object, iterable_1, iterable_2, ...)
これは、同じオブジェクトを複数の反復可能オブジェクトに使用できることを意味します。
これは、1つ以上の反復可能オブジェクト内のすべてのアイテムに同じ変換を適用する場合に役立ちます。
numbers_list = [1, 1, 1, 1, 1, 1, 1]
mapped_list = list(map(lambda a: a + 1, numbers_list))
print(mapped_list)
次のコードの出力は次のとおりです。
[2, 2, 2, 2, 2, 2, 2]
ご覧のとおり、リスト内のすべてのアイテムに「1」が追加されています。
JavaScriptの矢印関数を使用して、配列のmapメソッドを使用して同様の動作を実現できます。
iterable=[1,3,4,5,6]
console.log(iterable.map(element => element + 4));
これにより、配列内のすべての数値に4が加算され、次の値が返されます。
[ 5, 7, 8, 9, 10 ]
C ++では、これを実装することもできます。
#include <iostream>
#include <algorithm>
#include <vector>
int main () {
std::vector< int > arr = { 1,2,3,4,5,6,7,8,9 };
std::for_each( arr.begin(), arr.end(),
[](int & x)
{ //^^^ take argument by reference
x += 2;
std::cout<<x<<'.';
});
return 0;
}
ベクトルの各アイテムに2を追加していることがわかります。 出力は次のようになります。
3.4.5.6.7.8.9.10.11.
9. 結論
この記事では、ラムダ関数とプログラミングでのそれらの使用について説明しました。