Tallman

技術とか読書とかいろいろ

RubyでPriority Queue

精進でこの問題を解いてたらPriority Queueがでてきました。

atcoder.jp

"Ruby Priority Queue"でググれば実装はたくさんでてくるのですが、実際自分で組んだ方が身につくと思い実装してみました。

github.com

MaxHeapかMinHeapかを外部から注入するのについsendを使いがち。 だいたいこんなふうに動きます。

RSpec.describe 'PriorityQueue' do
  context 'direction is max' do
    it 'enqueue element' do
      pq = PriorityQueue.new(:max, [])

      pq.enqueue(3)
      pq.enqueue(9)
      pq.enqueue(6)
      pq.enqueue(1)

      expect(pq.heap.nodes).to eq([9, 3, 6, 1])
    end

    it 'dequeue element' do
      pq = PriorityQueue.new(:max, [9, 3, 6, 1])

      expect(pq.dequeue).to eq(9)
      expect(pq.heap.nodes).to eq([6, 3, 1])
    end
  end

  context 'direction is min' do
    it 'enqueue element' do
      pq = PriorityQueue.new(:min, [])

      pq.enqueue(3)
      pq.enqueue(9)
      pq.enqueue(6)
      pq.enqueue(1)

      expect(pq.heap.nodes).to eq([1, 3, 6, 9])
    end

    it 'dequeue element' do
      pq = PriorityQueue.new(:min, [1, 3, 6, 9])

      expect(pq.dequeue).to eq(1)
      expect(pq.heap.nodes).to eq([3, 9, 6])
    end
  end
end

Priority Queueというかヒープの特徴は

  • 最小値、最大値を探す時に全探査だとO(n)のところがO(1)で取れる
  • 追加、再構築はヒープの高さ(この場合二分ヒープ)がlog 2 nなので計算時間はO(log n)
  • 全探査でいちいち最大値、最小値を探すよりもO(n) - O( 1 + 2 (log n))分早い?
  • 何度も数字をいじってそのたびに最小値、最大値を求めるときに便利。

書いててわかってるか不安になってきた。

Atcoderで茶色になりました

というわけでなんとかやりました。 数学の素養が全く無い人間でも数こなせばなんとかなるものですね。

茶色に必要なこと

有名な記事で茶色についての記述にこのようなものがあります。

レッドコーダーが教える、競プロ・AtCoder上達のガイドライン【初級編:競プロを始めよう】 - Qiita

  • AtCoder Beginner Contest の A, B 問題が確実に(大方 15 分以内で)解ける
  • AtCoder Beginner Contest の C 問題も簡単なものなら解ける

ですが、参加者がかなり増えてる現在、茶色になるには

  • AtCoder Beginner Contest の A, B, C問題が確実に(大方 30 分以内で)解ける

ぐらいに茶色の難易度が上がっていると思います。この上でDが安定し解けると緑が見えてくると思います。

茶色になる上で役に立ったこと

僕は典型的な私立文系でこの前まで分数の割り算すら怪しいレベルでした。おそらく数学の素養みたいなものがなに一つなく、組み合わせや確率等アルゴリズム以前の基礎の概念からしてあやふやでした。 そんな僕に一番役に立った本は結城浩先生の「プログラマの数学」です。

プログラマの数学第2版

プログラマの数学第2版

  • 作者:結城 浩
  • 発売日: 2018/01/17
  • メディア: 単行本

この本は本当に良書です。数学の基礎がない自分でも読み進められるくらい解説が易しいですし、組み合わせ、順列、確率、再帰等競プロの常識ともいえる概念がコンパクトにまとまっていました。 この本を読んだ後はABCの茶色diffの問題を毎日少しづつ解いていきました。

f:id:sasa5740:20200503234751p:plain
精進継続中!

やっぱり精進をはじめてからグンと(当社比)レートが伸びたのでやっぱり精進が一番の近道ですね。

茶色になって思うこと

正直茶色になるのにアルゴリズムの知識はいらないと思います。Cまでは基本的に全探査で解けます。Dではじめて蟻本や螺旋本にでてくるアルゴリズムを使うと思います。
僕みたいに数学の素養が全くない人間が最初に蟻本や螺旋本に手をだすと勉強したことが自分の毎週解くべき課題にあんまり役に立たないという事態になりやすいです。
逆にいうとこれでやっとアルゴリズムというものを学ぶ準備ができたのだと思います。緑diffの問題で精進やっていくぞ。

追記

RubyAtcoderのテスト自動生成するライブラリを自作しているので、Ruby使ってる方ぜひ試してみてください。 僕はこれでうっかりWAはだいぶ減りました。

qiita.com

OSSコードリーディング Batch-loader

Batch-loaderというRubyでバッチローディングをするGemを読んで、そのきれいな実装に感心したのでまとめた。

github.com

実装自体はシンプルだけど、特定のGemに依存しているわけではないので使い方に融通が効くし、困った時にトラブルシューティングしやすい。
この記事は社内で一度発表したものだけど、上司に確認したらブログに載せても問題ないとのことだったので載せる。

READMEGem作成者のスライドの2つを読むのが一番という説がある。

Batch-loaderがやりたいこと

そもそもこのGem何がしたいの?という人はこの記事を読むといい。
Rails: N+1クエリを「バッチング」で解決するBatchLoader gem(翻訳)
DBやHTTPリクエストのN + 1問題をbatch load(一塊りでロードする)で解消するというもの。リクエストされた引数をまとめておき、値とマッピングだけコレクションしておき、実際の値の取得は最後に解決しようとする。

def load_user(post)
  BatchLoader.for(post.user_id).batch do |user_ids, loader|
    User.where(id: user_ids).each { |user| loader.call(user.id, user) }
  end
end

posts = Post.where(id: [1, 2, 3]) #  SELECT * FROM posts WHERE id IN (1, 2, 3)

users = posts.map do |post|
  load_user(post)
end

puts users # SELECT * FROM users WHERE id IN (1, 2, 3)

load_userの中身がBatch-loaderの基本的な使い方。

この記事では大きく分けて

  • 実際に複数回呼び出されるpost.user_idをどうやってbatch(一群)にしているのか
  • batchしたもの(今回の例だとuser_id)をどうやって本当のオブジェクトにしているのか

の2つに分けて書いていく。

図でまとめたやつ

最初に図でザックリまとめたやつをのっけておく。

def load_user(post)
  BatchLoader.for(post.user_id).batch do |user_ids, loader|
    User.where(id: user_ids).each { |user| loader.call(user.id, user) }
  end
end

f:id:sasa5740:20200429145512p:plain

f:id:sasa5740:20200429160902p:plain

実際に複数回呼び出されるpost.user_idをどうやってbatch(一群)にしているのか

最初に例に上げたload_userメソッド内では

BatchLoader.for(post.user_id).batch(&block)

という形の処理を書いていた。 forはnewのエイリアス。BatchLoaderのインスタンスは引数を@itemとして持つ。 batchの中身は以下。

  def batch(default_value: nil, cache: true, replace_methods: nil, key: nil, &batch_block)
    @default_value = default_value
    @cache = cache
    @replace_methods = replace_methods.nil? ? cache : replace_methods
    @key = key
    @batch_block = batch_block

    __executor_proxy.add(item: @item)

    __singleton_class.class_eval { undef_method(:batch) }

    self
  end

@key = key以上の行はオプション処理。渡したブロックは@batch_block変数に入る。

ここでは大きく分けて2つ処理がある

  • __executor_proxy.add(item: @item)
  • __singleton_class.class_eval { undef_method(:batch) }

__executor_proxy.add(item: @item)

__executor_proxyの中身はBatchLoader::Executorというクラスのプロキシオブジェクト。

  def __executor_proxy
    @__executor_proxy ||= begin
      raise NoBatchError.new("Please provide a batch block first") unless @batch_block
      BatchLoader::ExecutorProxy.new(@default_value, @key, &@batch_block)
    end
  end
class BatchLoader
  class ExecutorProxy
    def initialize(default_value, key, &block)
      @default_value = default_value
      @block = block
      @block_hash_key = [block.source_location, key]
      @global_executor = BatchLoader::Executor.ensure_current
    end

実際のBatchLoader::Executorの中身。

class BatchLoader
  class Executor
    NAMESPACE = :batch_loader

    def self.ensure_current
      Thread.current[NAMESPACE] ||= new
    end

    attr_reader :items_by_block, :loaded_values_by_block

    def initialize
      @items_by_block = Hash.new { |hash, key| hash[key] = Set.new }
      @loaded_values_by_block = Hash.new { |hash, key| hash[key] = {} }
    end
  end
end

要はスレッドごとに@items_by_block@loaded_values_by_blockという2つハッシュを持つのがExecutor。 ExecutorProxyの役割はインスタンスごとに@blockを保持し自身を通してブロックごとにBatchLoaderからアクセスするExecutorのハッシュを特定すること。

   # in ExecutorProxy
    def items_to_load
      global_executor.items_by_block[@block_hash_key]
    end

    def loaded
      global_executor.loaded_values_by_block[@block_hash_key]
    end

最終的にBatchLoaderのインスタンスが複数作られ、それらは全て同じExecutorの@items_by_blockに対してBatchLoader#batchに渡されたブロックそのものをkeyとしてpost.user_idをためていく。 @loaded_values_by_blockは実際の値とuser.idを紐づけたものが入っていくことになる。

__executor_proxy.add(item: @item)についてまとめると

__executor_proxy.add(item: post.user_id)
 # ↓
 BatchLoader::ExecutorProxy.new(&@batch_block).items_by_block["#{`batch_blockのソースライン情報`}"] << post.user_id
 # items_by_block[@block_hash_key]はデフォルトで空のSetのインスタンスが入っている。

BatchLoader.for(post.user_id).batch(&block)は何度実行しても&blockのソースロケーションが同じであれば同一の items_by_block[batch_blockのソースライン情報]というSetにpost.user_id`をつめこんでいくことになる。

__singleton_class.class_eval { undef_method(:batch) }

最後の__singleton_class.class_eval { undef_method(:batch) }について。 BatchLoaderのインスタンスの特異クラスからbatchメソッドを消している。 これは同じインスタンスに対してbatchの次の二回目メソッドの呼び出しはbatch自身も含めて全てmethod_missingに飛ばしたいという意図があると思われる。

そしてこれがBatchLoaderクラスのmethod_missing

  def method_missing(method_name, *args, &block)
    __sync!.public_send(method_name, *args, &block)
  end

実際にmethod_missingが走る時(本当にUserのインスタンスそのものが必要になった時) については次章で解説する。

ここまででexecutor.items_by_block[#{BatchLoader#batchに渡したブロック}"]post.user_idが複数積み込まれたことになる。

batchしたものをどうやって本当のオブジェクトにしているのか

ここから実際にオブジェクトをロードする過程に入る。

実際にlazyなオブジェクトが使用される時、ということはなにかしらメソッドが呼び出される時であり、その時にBatchLoaderのインスタンスでは method_missingが呼び出される。

  def method_missing(method_name, *args, &block)
    __sync!.public_send(method_name, *args, &block)
  end

__sync!は概ね__syncのラッパー。 BatchLoaderクラス内では多くのメソッドが__というprefixが付いているが、実際のオブジェクトのメソッドと極力かぶらないためだろう。

  def __sync!
    loaded_value = __sync

    if @replace_methods
      __replace_with!(loaded_value)
    else
      loaded_value
    end
  end
  def __sync
    return @loaded_value if @synced

    __ensure_batched
    @loaded_value = __executor_proxy.loaded_value(item: @item)

    if @cache
      @synced = true
    else
      __purge_cache
    end

    @loaded_value
  end

__ensure_batchedをみると

  def __ensure_batched
    return if __executor_proxy.value_loaded?(item: @item)

    items = __executor_proxy.list_items
    loader = __loader
    args = {default_value: @default_value, cache: @cache, replace_methods: @replace_methods, key: @key}
    @batch_block.call(items, loader, args)
    items.each do |item|
      next if __executor_proxy.value_loaded?(item: item)
      loader.call(item, @default_value)
    en
    __executor_proxy.delete(items: items)
  end

最初にloaderを作っている。

  def __loader
    mutex = Mutex.new
    -> (item, value = (no_value = true; nil), &block) do
      if no_value && !block
        raise ArgumentError, "Please pass a value or a block"
      elsif block && !no_value
        raise ArgumentError, "Please pass a value or a block, not both"
      end

      mutex.synchronize do
        next_value = block ? block.call(__executor_proxy.loaded_value(item: item)) : value]
        __executor_proxy.load(item: item, value: next_value)
      end
    end
  end

lamdaオブジェクト。「(item, value xor block)を引数にとって実際にvalueをloadするlamda」がloaderである。

__ensure_batchedでは次に@batch_block.call(items, loader, args)が実行される。 最初のload_userで書いたbatchに渡しているブロックが@batch_block。loaderには先程のlamdaオブジェクトが渡される。

def load_user(post)
  BatchLoader.for(post.user_id).batch do |user_ids, loader|
    User.where(id: user_ids).each { |user| loader.call(user.id, user) }
  end
end

loaderというlamdaに渡しているのはuser.idと実際のvalueであるuser。 この2つの引数がExecutorProxyを通してExecutorの@loaded_values_by_block[batch_blockのソースライン情報]というハッシュに { user.id: user } という形で追加されていくことになる。
ここで__syncに戻ると
@loaded_value = __executor_proxy.loaded_value(item: @item)
これはBatchLoaderのインスタンスがもつそれぞれの@item( = post.user_id) を使ってExecuterの@loaded_values_by_block[batch_blockのソースライン情報][@item]としてハッシュから値をとりだしている処理。
post.user_idと先程loadした{ user.id: user }user.idは等しいはずなのでここで無事にBatchLoaderのインスタンスごとに正しいuserを引っ張ってくることができる。
以降は別のBatchLoaderインスタンスでは@loaded_values_by_block[batch_blockのソースライン情報]というハッシュが中身付きでメモリに存在するため、再度User.where(id: user_ids).each { |user| loader.call(user.id, user) }という行為をする必要がなくなる。これにてBatch-Loading完了となる。

Gatsby.jsとTypeScriptでミニブログを作った

Scalaをそこそこやって型いいじゃん!とすこしづつ思いはじめてきたのでTypeScriptとReactで遊ぶがてらGatsby.jsでブログを書いてみた。

tallmanlog.netlify.app

なんのOGPもなくて寂しい。 ソースコードはこれ github.com

TypeScript

とりあえずTypeScript deep diveでundefind使えと書いてあったのでundefind使ったりよちよち歩きでやっている。とはいえよちよちでもIDEはしっかりと補完してくれるしESLintはしっかり指摘してくれるので型の恩恵にはあずかれてる気がする。

React

公式のチュートリアルをみながら色々やったのだけど、Hookというのが直感的だし記述量も少ないしで楽しかった。コンポーネントディドマウントとかよりだいぶわかりやすくなった気がする。
今回のブログだとトップページのLOLのランクステータスをfetchしてるところでHookを使っている。 このブログのコードを社のフロントの人にみせたらfetchしてる間もなにか表示した方がいいという金言をもらったので is Loading..をfetch中に表示するようにした。
バックエンドだと非同期ジョブとかDBのロックとかいかにも非同期非同期してるところしか非同期処理を意識しないので、フロントでhttpレスポンスが返ってくるのを待っている間とかの処理を考えるのは脳に新鮮な刺激で楽しい。

クライアントにとってのGraphQL

Gatsbyは静的データをGraphQLで扱うことができる。 とってくるクエリごとに名前をつけて型を作れるため、サンプルレスポンスといちいちにらめっこしたりする必要がないのが体験がいい。 nullableはできるだけ避けたいとか、空文字はjs界だとfalseだったりとかも面白かった。 業務では逆に僕がGraphQLサーバーを書く側なのだけれど、Gatsby.jsの経験を通してよりよいスキーマを定義できればいいな。

ブログの内容の予定

本当にチラ裏みたいなことを書いていくと思う。まだまだブログとしても寂しいしソースコードも記事もちょこちょこいじっていくぞ。

ScalaとRubyで関数合成してみる

N予備校Scala応用編を受講したのでRubyと比較して色々やってみたくなった。

まえおき

ScalaRubyと似てる気がする

Rubyは関数(というか処理をまとめたProcかMethodのインスタンス)も他のオブジェクトと同等だし、Scalaも同様で関数の返り値や引数を関数にしたりできる。

関数合成してみる

例えば文字列 "true"と”false"をパースする関数を書くためにtruePaser、falsePaserそれぞれを定義して関数合成したいとする。 Rubyなら

class ParseResult
  attr_accessor :value
end

class Success < ParseResult
  def initialize(value)
    @value = value
  end
end

class Failure < ParseResult
  def initialize; end
end


def true_parser(input)
  if input == "true"
    Success.new(true)
  else
    Failure.new
  end
end

def false_parser(input)
  if input == "false"
    Success.new(false)
  else
    Failure.new
  end
end

def select(left, right)
  Proc.new do |input|
    if (success = left.call(input)).class == Success
      success
    else
      right.call(input)
    end
  end
end

true_parser_method = method(:true_parser)
false_parser_method = method(:false_parser)

puts select(true_parser_method, false_parser_method).call('true').value         # true
puts select(true_parser_method, false_parser_method).call('false').value          # false
puts select(true_parser_method, false_parser_method).call('hogehoge').value # (何も出力されない)

合成は問題なくできるが結局selectが何をするProcを返すのかわかりにくい。

ここで型があるScalaだと

object Main extends App {
  sealed trait ParseResult[+T] {
    def value :Option[T]
  }

  case class Success[+T](in_value: T) extends ParseResult[T] {
    def value: Option[T] = Option(in_value)
  }

  case object Failure extends ParseResult[Nothing] {
    def value: Option[Nothing] = None
  }

  type Parser[+T] = String => ParseResult[T]

  def trueParser: Parser[Boolean] = input =>
    if (input == "true") {
      Success(true)
    } else {
      Failure
    }

  def falseParser: Parser[Boolean] = input =>
    if (input == "false") {
      Success(false)
    } else {
      Failure
    }

  def select[T, U >: T](left: => Parser[T], right: => Parser[U]): Parser[U] = input => {
    left(input) match {
      case success@Success(_) => success
      case Failure => right(input)
    }
  }

  println(select(trueParser, falseParser)("true").value) // Success(true)
  println(select(trueParser, falseParser)("false").value) // Success(false)
  println(select(trueParser, falseParser)("hogehoge").value) // None
}

型があると、関数合成をする関数を定義する時点で引数に取りたい関数のパラメーターを表現でき、合成関数の返り値に何を引数として渡したら良いかわかりやすい。 後Rust同様Option型が便利。
対して、Rubyは型がないことで明らかに記述量へるし多分僕の書いた以上に研ぎ澄ますこともできると思う。あとobject Main extends Appみたいなのがいらない。

まとめ

関数型言語として関数の集まりを書いていくなら型があったほうが扱いやすいのでは?という気持ち。Lispは型ないけど。 入出力に対してルールがある程度形式張って表現されていたほうが関数を組み合わせやすい気がする。

N予備校が無料だったのでプログラミングコースをちょっとやってみた

N予備校が世の中のもろもろで無料だったのでプログラミングコースを少し受講してみた。

www.nnn.ed.nico

良かったところ

教材の質が尋常じゃない

本来有償のものなので具体的なリンクは貼れないのだけど、教材一つ一つの質がとんでもなく高い。 「とにかくコードを書かせて体験してもらう」というのが徹底されている。例えば、CSRF対策やセッションハイジャックに対して、再現アプリケーションが公開され具体的な動作を確認でき、対策方法の具体的な内容、実際にコードでどうやって対策するかまで全て解説があった。更にGitHubから問題をフォークしてPRを送るという課題まである。驚くべきはこれらのセキリュティに対しての記事が「プログラミング入門」という「プログラミングとはコンピュターに対して命令を与えるものです」という内容から始まるコースの第三章に掲載されている内容であるということ。この章をやるだけで巷のプログラミングスクール以上の知識がみにつくのではないかと思う。
Scalaについて学ぶコースで「Scala基礎コース」があるのだけれどforとかwhileの説明で転置インデックスやBM法にまで話がすすんでいたり、最短経路問題やbit探索などScalaを通してCSの勉強がきっちり行われている感じだった。実際にアプリケーションを書いてそれをリファクタリングしていく章まである。これらは全て「基礎コース」であり「応用コース」は別にある。「応用コース」では参照透過性とかストリームなんかも扱っていて、Scala以外にも応用がきく概念を習得できると思う。

動画つき

僕はみてないんだけど授業動画もあるようだ。なんかルルーシュのコスプレしてる人が写ってた。

おやと思ったところ

コース間の動線

コースとコースの関連がコース最初の手引きぐらいでしかわからなかった。Scala基礎コースのページで次へを踏んだらScala応用コースに飛ばしてほしい…。僕が見つけてないだけでまとめページみたいなのがあるのかもしれない。

継続率

これは不満というか不安な部分なんだけど最初のプログラミング入門コースでは何百とあった練習問題のPRがScala基礎コースではかなり少なくなっていた。ある程度プログラミング経験のある僕ですら良く出来てるなぁと思うくらいのなので「プログラミング初めて!」みたいな人はなかなかついていくのが難しそう。

まとめ

個人的にはScala完全理解した(この完全理解は全然わかってないの意)ので良かった。これで月額1000円なのは安すぎる。僕自身プログラミングスクールからエンジニアに転職した身だが、スクールに通わなくてもこの教材をやれば仕事がないってことはないんじゃなかろうか。学生達がこれをバンバン進めているかと思うとすごい世の中である。Androidコースなんかもやってみようかな。

League of legendsでダイヤモンドに昇格した。サポートでsoloQを勝ちぬくには

League of legends(以下LOL)で自分語りしていきたい。大学入学から20代前半はずっとこのゲームをやっていた。最後にめっちゃためになるsoloQ tipsも書いたので読んで欲しい。
これがマイOP.GG。
jp.op.gg

シーズン4

f:id:sasa5740:20200308133243j:plain 最初は「簡単、強い」でチャンピオン検索したらmordekaiserが出てきたので200戦ぐらいスパムしてた。ちなみにずっとシルバーだった。 昔のmordeはEがAOEでプッシュとハラスが両立しやすく簡単だったのは間違いない。huehuehue。

シーズン5

f:id:sasa5740:20200308133732j:plain このシーズンで初めてプレシーズンでゴールドになり、シーズン中にプラチナになった。あまりにも嬉しかったことを覚えている。このころからサポートをやるようになった。Jannaがとにかく壊れておりピールという行為をするだけで勝てた。Topヘカリムも好きだったので使っていた記憶がある。ホームガードTP最強!

シーズン6

f:id:sasa5740:20200308151609j:plain スレッシュをやり始める。。NAのサーバーが移動してラグくなったので台湾サーバーでランクを回していた。台湾ではヘカリムとサポートを回してプラチナ2まで上がった。最初にソロQでダイヤ枠と当たったときは興奮したものだ。

シーズン7

f:id:sasa5740:20200308151906j:plain サポートというロールに嫌気が差して色々なチャンピオンを触っていた。そんなに残業があったりするような会社ではなかったのでランクの回数はこなすことができた。しかし、当然付け焼き刃のチャンピオンでは勝てず、シーズン後半にスレッシュで何とかプラチナに上げるような感じのシーズンだった。今見ると思ったよりルシアンを使っていた。なんで?

シーズン8

f:id:sasa5740:20200308155202j:plain 仕事が2年目になり忙しくはないもののメンタル的にキツくなっており、ゲーム内でも暴言を吐いたりAFKするようになった。おかげで勝率も下がり結局プラチナ止まり。

シーズン9

f:id:sasa5740:20200308155258j:plain ラカンわりと使っていた。電撃ラカンをどこかの動画で見て真似してスパムしていた。このシーズンもプラチナにはなった。
転職したのでメンタルは正常になったがゲーム内のメンタルは変わらずTOXICなプレイをし続けシーズン終わりに2週間バンされた。 このとき初めてチャットをミュートしたり暴言を無視することを覚え、何とか2019年中に名誉レベルを2に戻した。

シーズン10

f:id:sasa5740:20200308155337j:plain ひたすらスレッシュをスパムした。ロールも絶対にブレずにサポートだけやり続けて他のロールになったら昇格戦以外は全てドッジするようにした。暴言吐いてきた奴がいたらちゃんとミュートし、マイナスなチャットはしないようにした。そうすると勝率はドンドンあがりシーズン開始一週間でプラチナになり、そのまま勝率58%でダイヤモンドになった。涙が出るほど嬉しかった。勝率は変わらずD3になりD4ゲートキーパー化も回避した。
このままならダイヤ上位も十分なれるしワンチャンマスターにもなれるんじゃないかと思っている。

サポートとしてsoloQで勝つために

少なくとも日本鯖のダイア以下では、

  • メンタルを保つ。このゲームはメンタルが六割であり連勝しているときはなるだけランクを回すべきだし連敗したら大人しく寝たほうがいい。
  • 暴言をはかない。すねない。不貞腐れない。炊いてもそれを画面には出さない。これらの行為は勝率を上げることになにも貢献しないのでする意味がない。ムカついたやつがいたらレポート画面で罵詈雑言思いっきり書いてレポートすれば良い。
  • jgとmidを常にみる。soloQは人数差ゲームであり、人数差を作るのはだいたいmidかjg、相手のjgとmidの位置を常に考え4マンダイブ喰らいそうなら予め下がってランタンを準備するべし(そういう意味でもスレッシュはOP)。味方のjgが相手と川で争いそうならすぐに寄らなくてはならない。
  • ぶれない。絶対に連敗が重なる時期が来るがそこでmidしたりjgしたくなってもやってはならない。どうせ対面には手も足も出ない。
  • ワードを三個おいたらさっさとリコールする。ワードを三個おいたらサポートはファームする必要がない。サポートの4割はワード貯蔵庫。ピンクワードは絶対に二個買え。
  • レーンでは距離感を意識する。例えば相手はソラカならQの射程ギリギリで常にステップすること。相手はスキルを外しやすくなりCDというスキが生まれる。フック系に対してもこちらがフックを当てるのではなく如何に相手のフックを躱すかを意識したほうがいい。こうすると相手もこちらのガンクに気づきにくくなる。
  • 相手がCSを取るタイミングでハラスする。あたり前田のクラッカー。
  • フラッシュはできるかぎりメモる。サポートが一番暇なのでメモるのはサポートの仕事。対面だけでなくTOPやMIDのフラッシュもできるかぎりメモる。チャットでタイムスタンプをオンにしてフラッシュしたことだけメモしておき、リコール後に計算するでも良い。これで味方がガンクにくる確率が上がる。

半分自分に言い聞かせているところがあるがこんなところだろう。

LOLを六年やって

このゲームははっきり言ってあまり人に進められない。かんたんにメンタルを壊しにくるし敷居もめっちゃ高い。しかし、様々な操作感のキャラクターを同じ条件から少しづつ上回っていき相手を叩きのめす感覚は他のゲームではなかなか味わえない。二週間に一度のパッチでメタが変わるので飽きることもない。世界で一番遊ばれていのにはそれなりに理由があるし、いわゆるesportsのイベントも世界的に盛んである。日本でLJLほど成功してるesportsイベントはないんじゃなかろうか。
このゲームで知り合った人や物事で色々な意味で人生が変わったとも言える。エンジニアになったのもこのゲームの影響である。

今後

エンジニアでLOLやってる人の知り合いがほしい。明確にLOLやっていると知っている人はひよこ大佐氏くらいである。RubyistであまりPCゲーをやっている人を知らない。みんな、LOLやろう!!!!!!!!!!!!!!!!!!!!