【25卒】 私が メガベンSWE からオファーをもらうために学んだスキル (コーディングテスト編) 【IT就活】※上位層向け
※ 注意点
今回この記事内で述べる内容は,あくまで個人の主観に基づくものであり,必ずしも万人に当てはまるとは限らないことにご留意ください.
また,各企業における選考過程に関する情報公開は禁止されているため,記事内において一部不鮮明な説明に止めざるを得ない箇所があることをご理解頂きながら読み進めていただますと幸いです.
0. はじめに
皆さん、こんにちは。おむらいすです。
今回の記事の内容は、筆者が 25 卒として就職活動を行い、その結果オファーをいただけた企業(群)のうち、他の企業と比較した際の「選考過程におけるプロセスの特殊性」や「IT業界内での会社の注目度」等を鑑みて、皆さんからの記事作成に関する要望が特に多いと思われる企業(群)の 1 つ、 メガベンチャーSWE(ソフトウェアエンジニア)職 の選考で求められるスキルに関するものとなります。
オファーをいただくまでに筆者が行なったこと、選考を通じて求められるスキルを身につける上にあたって役立つ書籍等に関して、個人の経験をもとに共有させていただけたらと思います。
なお、冒頭でも申し上げたように、選考過程に関する情報公開は禁止されているため、選考の具体的なプロセスやその内容に関して、本記事内で記載することはできかねること、ご了承ください。
本記事内で紹介する情報は、全てインターネット、書店等で販売されている書籍等を通じてパブリックにアクセスできるものとなっています。本記事を通じて得られる、これらの情報源を生かし自身のITスキルを高め、もし選考に臨まれる際には、個々人の能力を最大限発揮できるようなことに繋がれば幸いです。
この記事に対して良いフィードバックをいただけるようでしたら、筆者がオファーをいただいた他のIT企業に関しても同様の記事も作成しようと思うので、ぜひよろしくお願いします!
1. ITメガベンチャー業界における独自の選考プロセス「コーディングテスト」に求められる力
さて、皆さんは新卒時の就職活動におけるプロセスに関して、どのような理解をされているでしょうか?
ほとんどの方は、以下のようなフローに沿って選考プロセスが進むと認識されていると思います。
実際、この理解は概ね正しいです。一方で、独自の専門的な能力が求められる業界においては、選考過程の中で特殊な課題や選考プロセスを採用しているということも少なくありません。
例えば、コンサル業界における選考プロセス内では、「ケース面接」と呼ばれる、特定のビジネス課題に関する解決案を、何らかの論理的な根拠に基づいて面接官に提案する形式面接が行われることが多く、コンサル業界を希望する学生たちの間では、それに向けた独自の対策を行うことが常識となっています。
そして、IT業界に関しても同様に、いくつかの外資系IT企業における選考では、「コーディング面接」と呼ばれる、ある技術的な課題に対して、面接官とのディスカッション等を通じながら、プログラミングを用いた実装を行い、できるだけ品質の高い解決案を提供するといった形式の面接が有名です。
また、IT業界においては、他にもこれに類似した形式の選考プロセスとして「コーディングテスト」と呼ばれる、オンライン上で決められた実装課題に対して、プログラミング言語を用いた実装を行い、制限時間内に提出するという形式の選考プロセスが有名です。内容自体は先述の「コーディング面接」と似ていますが、面接官とのコミュニケーションが何を媒体として行われるか、すなわちそれが「会話」主体であるか、あるいは「提出されたコード内容」主体であるかという点で異なっています。
今回紹介するメルカリ, リクルート, サイバーエージェント, LINEヤフー, DeNA を始めとしたメガベンチャー企業における選考フローにおいても、ステップの一環に「コーディングテスト」が組み込まれています。
なお、その他に「コーディングテスト」を選考フローに組み込んでいる会社では、
Google, Amazon, Indeedを始めとした外資系IT企業が有名です。
いわゆる一般的な選考プロセスにおける「ES」「SPI」「GD」「個別面接」に関しては、ネットやSNSにいくらでも記事が挙がっていると思いますが、「コーディングテスト」に関しては、なかなか充実した情報がないというのが現状です。
そういった課題点も踏まえながら、本記事では、この独自の選考プロセス「コーディングテスト」で求められる能力に関して整理してゆきつつ、それに向けた対策方法についての有用な記事となることを目指してゆきたいと思います。
早速ですが、「コーディングテスト」において求められる能力はどのようなものでしょうか?筆者は「コーディングテスト」において求められる能力は、大きく分けて以下の 2 点の能力に分類されると考えます。
アルゴリズム力
実装デザイン力
まず本セクションでは、それぞれの能力についての内容を紹介してゆきたいと思います。
1-1. アルゴリズム力って?
アルゴリズム力とは、与えられた問題に対して最適なアルゴリズムを選択し、実装する能力を指します。効率的なアルゴリズムを考え出すためには、問題の本質を理解し、適切なデータ構造やアルゴリズムを選択する能力が必要となってきます。
これに関しては、次に示す実装デザイン力と同様に一朝一夕に身につけることができるのではなく、多くの問題に取り組んで経験を積むことが大切です。
とはいっても、後述に示すように、対策に役立つサイト及びそのための方法はすでに確立されているため、それに沿って対策を行えば道筋は立てやすい方だと思います。
1-2. 実装デザイン力って?
実装デザイン力とは、アルゴリズムを実際のプログラムとして実装する際の設計力や技術力のことを指します。良い実装デザイン力があると、効率的で保守性の高いコードを書くことができます。具体的には、アルゴリズム以外の、変数や関数の命名、コードの構造化などについてが該当します。
これに関しても同様に、一朝一夕に身につくものではなく、加えて対策に役立つ方法も確立しているとは言い難いです。具体的にどうすれば能力が伸びる等は断言できないのですが、それに向けたグッドプラクティス等に関しては、後半のセクション内で紹介できたらなと思います。
1-3. まとめ
まとめると、
アルゴリズム力=問題を解決するためのアルゴリズムを考え出す能力
実装デザイン力=アルゴリズムを実際のコードとして実装する際の設計力
ということですね。
アルゴリズム力は問題解決の土台を築き、実装デザイン力はその土台を使って実際にコードを書く力というように、両者は互いに補完関係にあるわけです。
それでは、次のセクションにおいて、これらの能力の重要性について具体例を通じて理解を深めてゆきましょう。
2. アルゴリズムだけでない!わかりやすいコードを書くことの大切さ
同じ内容を実装するコードであっても、初めてコードに目を通す第三者がその内容対して抱く心象は千差万別です。
たとえ、どんなに関数内部のアイデアが素晴らしくても、そのコードの実装が良くなければ、誰もその関数を使ったり、メンテナンスしたいとは思わないでしょう。
具体例を通じて考えてみましょう。
以下に示すコードは、0 より大きい整数に対する、約数のリストを返す関数をPythonを用いて実装したものになります。
【改善前のコード】
def f(n,flg):
ret = []
for i in range(1,n+1):
if (temp:=i*i) > n:
break
elif temp==n:
ret.append(i)
elif n%i==0:
ret.append(i)
ret.append(n//i)
if flg:
ret.sort()
return ret
上の実装をパッと見ただけでは、この関数が何をやっているのかだったり、それぞれの引数がどのような役目を果たすのか等、わかりませんね。
また、各オブジェクトの型が明示されていない以上、コードに修正を加えたりする際も、迂闊にコードをいじることができなさそうです。
これでは、誰もこの関数を使いたいとは思わなくなってしまいそうなので、もう少し上記のコードに改善を加えてみましょう。
【改善後のコード】
def calculate_divisors(num: int, sorted: bool = True) -> list[int]:
"""
Calculate the list of divisors of 'num'.
Time complexity: O(sqrt)
Args:
num (int): The number for which divisors are calculated.
sorted (bool, optional): Whether to return the divisors in sorted order. Defaults to True.
Returns:
list[int]: List of divisors of 'num'.
Raises:
AssertionError: If 'num' is not greater than 0.
Examples:
>>> calculate_divisors(10)
[1, 2, 5, 10]
>>> calculate_divisors(15, sorted=False)
[1, 15, 3, 5]
>>> calculate_divisors(7)
[1, 7]
"""
assert num > 0
divisors: list[int] = []
for d in range(1, num + 1):
# NOTE: Note that the divisors of num up to the smaller one are at most O(sqrt).
if (sq := d * d) > num:
break
elif sq == num:
divisors.append(d)
break
elif num % d == 0:
divisors.append(d)
divisors.append(num // d)
if sorted:
divisors.sort()
return divisors
ということで、先ほどのコードに改善を加えたものが上のコードです。
変数の命名を修正 + 明示的に型を示し、コード内部に適切なdocstringを付け加えることで、何をやっているか第三者にわかりやすいコードになるようにしました。
本質的な実装の内部構造に関しては、何も変わっていません。
あくまで、コード内部の体裁のみに関する修正です。
しかしながら、「修正前のコード」と「修正後のコード」に対する第三者のコードに対して抱く心象は大きく異なると思います。
「修正前のコード」はともかく、「修正後のコード」だったら、「この関数なら使えそうだな、アレンジできそうだな」と思う方も多いのではないでしょうか。
次のセクションでは、この「修正前のコード」「修正後のコード」を比較対象に用いながら、コーディングテストの際に心象の良いコードを書くうえで意識すると良いポイントに関して、いくつか筆者の考えを共有したいと思います。
3. これをやれば受かる!コーディングテストの通過率を上げるポイント 6 選
3-1. Pythonを用いる場合は型を明示した実装を行う
このポイントは、コーディングテストにおいて Python を用いる方に向けたものとなります。Java, Go をはじめとしたその他の静的言語を用いる場合は実装の段階で「型」を強制的に考える必要があるので、必然的に考慮の必要はなくなります。
Pythonといえば、近年最もメジャーなプログラミング言語としての地位を築き上げたことで有名です。シンプルでわかりやすい文法、21 世紀の技術革命ともいえる機械学習・AI分野におけるサードパーティライブラリの充実に伴う教育分野でのデファクトスタンダード化、インターネットにおける解説記事の多さ… 等、理由を挙げればキリがありません。おそらく筆者含めほとんどのZ世代の方々が、初めて触れたプログラミング言語はPythonなのではないでしょうか。
そういった背景も鑑みて、まず一番初めのポイントとしては、Python(に限らず全ての動的言語にいえることではありますが)における弱点にフォーカスした内容を紹介します。
さて、プログラミングをしているとわかると思いますが、プログラミング言語には「静的言語」と「動的言語」の大きく 2 つのグループがあります。
前者は、プログラミング時に用いることができるオブジェクトに厳しい制約及びチェックが入るため、実装時にエラーが発生しやすかったり、実装が難しくなりがちという点があります。
一方後者は、プログラムの実行時にコード内のオブジェクトの型が決まるという特性上、柔軟性の高い実装ができること、及び簡潔で直感的な実装になりやすいです。
こういった背景もあり、入門者には後者の動的言語の方が好まれがちです。
ですが、それゆえの弱点も存在します。プログラムの実行時にコード内のオブジェクトの型が決まるという性質上、以下のようなことができてしまったりします。
var = 'hello'
print(type(var)) # <class 'str'>
var = 1000
print(type(var)) # <class 'int'>
def func():
return 'hello world'
var = func
print(type(var)) # <class 'function'>
変数に、あらゆる型のオブジェクトを入れることができてしまうわけですね。
これでは、コード内部で使用されるオブジェクトがどのような特性を持ったものであるかわからなくなったり、セキュリティホールの原因になるであろうことは容易に予測できます。
「改善前のコード」においても、"n", "flg", "ret" などがどのような属性を持つオブジェクトなのか不明なため、コードを書き換えようにもなかなか難しいわけです。
そこで登場するのが、以下に示す「型サポート」機能です。
この機能を用いることで、コード内部のオブジェクトに対して、そのオブジェクトがどのような特性を持っているべきか(とはいっても強制できるわけではないことに注意!)を明示的に示し、その指定のもとでコード内部で不適切なメソッドや操作が行われていないかをmypyなどを用いることで、静的に確認することができるわけです。
本記事ではこれら機能に関して深く立ち入りませんが、インターネットにいくらでも解説記事が転がっているので、そちら等を参考にしながら実装の際に意識してみるといいと思います。
https://docs.python.org/ja/3/library/typing.html
「修正後のコード」では以下の箇所が対応します。
def calculate_divisors(num: int, sorted: bool = True) -> list[int]:
...
divisors: list[int] = []
...
そもそも、悩むくらいなら初めからPythonを使わずに、より頑強な静的言語であるJava, Go などの言語を用いればすべて解決なのですが、そんなことをいってしまっては元も子もないですね…すみません m(_ _)m
3-2. 実装にはdocstring + アノテーションコメントを書く
以降は、全ての言語問わず重要なポイントとなります。
まず、実装したクラス、関数、メソッドに対してはdocstringを書くようにするのがオススメです。これがあると、エディタで関数にカーソルを合わせた時に、docstringが自動で表示されるため、わかりやすくなるためです。
「修正後のコード」では以下の箇所が対応します。
"""
Calculate the list of divisors of 'num'.
Time complexity: O(sqrt)
Args:
num (int): The number for which divisors are calculated.
sorted (bool, optional): Whether to return the divisors in sorted order. Defaults to True.
Returns:
list[int]: List of divisors of 'num'.
Raises:
AssertionError: If 'num' is not greater than 0.
Examples:
>>> calculate_divisors(10)
[1, 2, 5, 10]
>>> calculate_divisors(15, sorted=False)
[1, 15, 3, 5]
>>> calculate_divisors(7)
[1, 7]
"""
書き方及びフォーマットは言語ごとにいろいろあるので、そちらに関しては各自調べて実装を行なってみてください。なお、以下のような声もありましたが、忘れてください…
アノテーションコメントに関しては、以下の記事を参考にしてください。
これは、アルゴリズムパートのプログラム内部において、実装の複雑なところを説明する際などに役に立つ記法だと思います。
https://qiita.com/taka-kawa/items/673716d77795c937d422
3-3. 階層的に実装を分ける = 「 1 つのファイルに 1 つのクラス」
実装をしていると、何でもかんでも 1 つのファイルにクラス、関数、変数定義を入れがちになってしまいますが、ファイルを階層的に管理する方がその後メンテナンスをしやすくなります。
例えば、以下のようなファイルクラス分けなどが考えられます。
[ファイル管理の例]
project/
├── main.py
├── utils/
│ ├── file_handling.py
│ └── data_processing.py
├── models/
│ ├── base_model.py
│ ├── model_a.py
│ └── model_b.py
└── tests/
├── test_file_handling.py
├── test_data_processing.py
├── test_model_a.py
└── test_model_b.py
この構成では、プロジェクトのメインファイルであるmain.pyがトップレベルに、utils/フォルダにはファイル操作やデータ処理に関するモジュールが、models/フォルダにはデータモデル関連のクラスが、tests/フォルダには各モジュールに対するテストがそれぞれ格納されています。
このようにフォルダをシンプルに整理すると、プロジェクトの構造を把握しやすくし、コードのメンテナンス性が向上するのでオススメです。
もちろん、これはあくまで一例であるため、プロジェクトや課題に応じて適切なファイル構成を考えてみると良いでしょう。一つのポイントとしては、何か障害や機能修正があったと仮定したときに、修正ないしは参照する必要のあるディレクトリ・ファイルの個数を最小化するということをイメージしながら実装を考えると良いです。
3-4. 品質を高めるために 「リンター + フォーマッター」 をかける
リンターはコードの品質を検証し、潜在的なバグやスタイルガイドに準拠していない部分を見つけ出すツールです。フォーマッターはコードを一貫したスタイルに整形するツールです。これらのツールを組み合わせて使用することで、コードの品質を維持し、読みやすさを向上させることにつながります。
ここでは、Pythonを例に挙げて説明させていただこうと思います。
Pythonには、多くのリンターやフォーマッターが存在し、特に有名なものとしては「flake8」や「pylint」などのリンター、そして「black」や「autopep8」などのフォーマッターが有名です。
うまく使いこなすことで、以下のようなメリットがあります。
コードの品質向上:
リンターがコードを解析して潜在的なバグやスタイル違反を指摘することで、コードの品質を向上する。
フォーマッターがコードを整形することで、一貫性のあるスタイルでコードが書かれるようになる。
読みやすさの向上:
コードが一貫したスタイルで整形されることで、他の開発者がコードを理解しやすくなる。
スタイルガイドに準拠したコードは、コードの読みやすさを高める。
作業効率の向上:
フォーマッターが自動でコードを整形するため、手作業での整形作業が不要になる。
リンターが問題を指摘することで、バグを早期に発見し修正できる。
Visual Studio Codeを用いて開発されている方は、おそらくすでに「拡張機能」等で使用しているかもしれませんが、改めて確認してみることをオススメします。
3-5. 実装後のリファクタリングに多くの時間をかける
ひとまずの実装を終えた皆さん、お疲れ様です!
多くのコーディングテストの場合、いくつかの公開テストケースが公開されていると思います。早速、テストケースを通じて自分の実装の正確性を確認しましょう。
テストケースは通りましたか?
通らなかった皆さん、それは実装のどこかに誤りがあるということです。
時間の許す限り、エラー部分を解明し、実装の修正について検討しましょう。
通った皆さん、おめでとうございます!
ひとまず、あなたのコードの妥当性は公開テストケースの範囲では保証されたということになりますね。要求されている実装要件を自身の中で理解し、コードとして落とし込めたことの証ともいえるでしょう。
きっとここまで形に持ってくるだけでも大変なことですし、「正直早くテストなんか終わらせて、YouTubeを見たり、ご飯を食べたり、ゲームをしたい!」という気持ちがむくむくと心の奥底から湧き上がっていることと思います。
ですが、果たしてそれで満足して「コード提出」のボタンを押しテストを終了してしまってもいいのでしょうか。
答えは否、実装を終えた後にしか見えない課題点というのも多々あります。
実装している際は、実装の完成に焦点が行きがちであり、プログラム全体の質を検討するという意味では、実装し終えた後の方が冷静になって判断することができるからです。冷静になって自分のコードを振り返ってみると、本来そのクラスの責務でない処理がいつの間にかメソッドに入っていたり、マジックナンバーまみれの謎実装になっていたり、実装当初はシンプルだった 1 つのクラスがとんでもない神クラスに膨れ上がっていたりすることに気がつくでしょう。
そして、これらの実装をリファクタリングするには、既存のクラス設計を見直したり、構造を 1 から練り直したりする必要があるため、変更に大きなコストがかかり、結果としてそのリファクタリングに当初の実装完了までに要した時間と同じくらいの時間がかかることが少なくありません。筆者の場合、あるメガベンチャー企業のコーディングテストでは、実装に数時間かかった上に、リファクタリングには実に追加で数時間ほどを要することもありました。
とはいっても、リファクタリングをどのように行えば良いのかといわれても、実際の業務経験が少ない学生は解答に困ることでしょう。そういった点を踏まえて、次のサブセクションでは、リファクタリングに取り組むにあたってのアプローチに関して紹介したいと思います。
3-6. リファクタリングでは有名本のベストプラクティスを真似する
以下にコード品質の観点から書かれたオススメの書籍を 2 冊紹介します。
いずれも神書籍で、この 2 冊の本に書かれていることさえ守ればいいと思います。当然全て覚えることはできなくとも、リファクタリングの際に適宜参照して、辞書的な使い方ができれば良いと思っています。
本当に神書籍なのでオススメです。経験の浅い学生エンジニアは自分で考えるよりも、その道で経験を積んできた先人たちのアドバイスに乗っかる方がよいでしょう。いずれも時間のある学生ならば、覚悟を決めて本気を出せば 1 日で読むことができます。
自身の作成したコードを眺め、以下の本で書かれているプラクティスを適用できないか、時間の許す限りそういった改善点を探すといいかもしれません。
4. 対策におすすめのサイト
4-1. 「アルゴリズム力」編
応募する会社にもよりますが、新卒採用におけるあらゆるコーディングテストで、ほぼ確信を持って満点を取るに至るラインは、AtCoderにおける青色 ( ≒レーティング 1600 程度 ) が必要だと感じます。とはいっても、正直なところこれは新卒の学生に求めるにしては、かなり敷居の高いラインです。
しかし、これは Google, Amazon, Indeed などの外資系大手 IT 企業も含んだ場合に対して、どんな問題に対しても満点を目指す場合であり、日系大手IT企業に限れば、AtCoderにおける緑色( ≒ レーティング 800 程度 ) でアルゴリズム力に関してはほとんどの場合実力としては十分です。
なお、全く例外がないわけではありません。
今回紹介するメガベンチャー以外も含めると、いくつかの企業に関しては、他社と比較した際アルゴリズム問題の難易度が高くなったりもします。
(筆者の周りの声も踏まえると、自信を持ってほとんどの企業のアルゴリズムパートで満点をとる上では、おおよそAtCoderにおける水色程度の実力が必要であるように感じます。)とはいっても、こういった企業においては、合格にあたって満点を取る必要があるわけではないので、さほど気にする必要はないです。
とりあえずの初期段階としては、一部の例外にこだわるのではなく、まずは大局的なものの見方に沿って行動をするべきです。したがって、始めのマイルストーンとして、AtCoderで緑色到達を目標にアルゴリズム力を磨くことを目標にしましょう。それ以降は、自身の実装デザイン力との兼ね合いを考慮しつつ、それ以上のランクを目指すのが良い戦略であると思います。
AtCoderで問題を解くあたっては、以下のAtcoder Problemsと呼ばれるサイトにおけるフィルタリング機能を用いるのがおすすめです。自身の希望に合った問題を難易度に基づいて選定し、適切な演習を積むことができます。なお、コーディング問題に取り組む上でアルゴリズム等についても並行して学ぶ必要があるので、併せて紹介する以下の書籍らを通じて、問題演習と並行して知識を入れることも忘れないようにしましょう。
その他のサイトとしては、以下に示す、LeetCodeと呼ばれる実際のBig Tech企業で出題された問題を解いて勉強することのできるサイトもオススメです。傾向として、こちらの方が実際の業務等であり得そうなシチュエーションを題材とした、データ構造に関する問題が多いように思えます。ついでに、全部英語なのでコーディングテストの勉強をしているだけで、英語の勉強にもなるのでコスパがいいです笑
こちらの場合の目安としては、6 - 8 割くらいのmedium問題を解くことができるラインを目指すといいと思います。( easy問題は必ずできるようにしましょう。hard問題はできなくても問題ないです。)
https://leetcode.com/problemset/
1 つ注意点として補足しておきますが、演習を通じた学習にあたって、競技プログラミングで用いられている独特の実装方式に影響されすぎないようにしましょう。ここでは、いくつかの慣習のうち、実際の実装デザインでは良しとされない実装の例を挙げておきます。
[x] 'a', 'tmp1' などの短すぎて意味不明な変数名
[x] repマクロ を代表とする独自マクロ
[x] 何でもかんでもグローバル空間上に変数定義
[x] #include<bits/stdc++.h>, import * from math などの全インポート
もちろん、この実装方式が悪いということではありません。
それどころか、プログラムの提出までの速度も競技性の一種である競技プログラミングにおいて、この書き方はむしろ合理的で正しいとすらいえます。大切なのは、どのような立場で自分がプログラムを組む必要があるか、正しく認識するということです。独自の実装方式に影響されず、適所適材、適切な方針に基づいて実装を行えるようにしましょう。
4-2. 「実装デザイン力」編
これに関しては、先述のアルゴリズム力の場合と同様に、解きながら覚える、ということは難しいです。というのも、アルゴリズム力のような、与えられた課題に対して絶対的な評価指標というものは存在しない上に、時と場合に応じて求められるデザインというのも変化してゆくからです。
対策法としては、優れたOSSの実装を見て、なぜこのような書き方をしているのかを ChatGPT-4 などに聞いて、そこで溜まった知見をNotion等でまとめて管理したり、はたまた優秀なエンジニアリング力を持つ先輩や社員さんと一緒に業務に携われるサークル、アルバイトやインターンに潜り込んで、そういった方の実装するコード内部で用いられているアーキテクチャや設計思想を調べながら理解するしかないと思います。
とはいっても、これに関しては正直学生の間だけで経験を積むのは難しいと感じます。そのため、先に示したセクション 3 における「これをやれば受かる!コーディングテストの通過率を上げるポイント 6 選」のポイントを守るだけでも、学生に求めるコーディングテストの通過水準を超えるという面では、十分であると思っています。(業務経験を積んでいるとなると、また話は違うのかもしれませんが…)
次回予告
ここまで読んでくださり、ありがとうございます。
さて次の記事にあたる、
【25卒】 私が メガベンチャーSWE からオファーをもらうまでに必要としたスキル (面接編) 【IT就活】
に関する予告になりますが、就職活動における選考過程において、最も重要な
面接スキル
に関するもので、日系大手ITメガベンチャー企業で求められる面接のポイントにフォーカスした記事の予定です。
それでは、次回の更新をお待ちください!<(_ _)>