モノグサのメンバーでチーム戦の競技プログラミングコンテストに出場しました。

f:id:corp_monoxer:20211224103150p:plain モノグサ社員3名(AtCoder黄・青・青)でチームを組んでKUPC(京都大学プログラミングコンテスト)という競技プログラミングに出場しました。 KUPCはAtCoderのプラットフォームを使った京都大学の学生有志が主催するコンテストであり、通常のAtCoderとは異なり、チームでの参加が許可されています。(コンテストページ

コンテスト中はDiscordを使ってチャットや音声を介して議論したり、それぞれのPCでコーディングしたりしてリモートで参加していました。 今回はE問題を二人で考えたのが面白い体験だったので、それをシェアしようと思います。

目次

問題概要

【原文:KUPC 2021 E

f:id:corp_monoxer:20211215185336p:plain 図1のような、無向単純連結グラフGがある。 グラフの辺は全て相異なる番号(1〜M)が振られており、辺には赤い辺と青い辺がある。 赤い辺はGの全域木を為している。 グラフの辺に1〜Mのインデックスをアサインした時に、赤い辺の集合が最小全域木になるように、かつインデックスをグラフの番号順で並べた時に辞書順で最小になるようなインデックスのアサイン方法を求めよ。

例えば図1の場合は以下のようなインデックスアサインが解です。 f:id:corp_monoxer:20211220152758p:plain

考察の流れ

yujidx7「E問題、どんな感じですか」

ryoryoryo111「そうですね、辞書順なんで可能な限り前の方の辺に小さいインデックスを決定することを考えてます」

f:id:corp_monoxer:20211220153411p:plain

ryoryoryo111「いまこの図の状況で番号1の辺にできるだけ小さいインデックスを決定するために、先に頂点1と頂点2の赤い全域木上での経路のインデックスの総和がそれより小さい必要があります。 具体的には頂点1~LCAと頂点2〜LCA上の赤い辺である番号2と3と4の辺のより大きいインデックスを番号1の辺に決定する必要があります」

ryoryoryo111「だから番号2,3,4のインデックスの和を得るために、パスクエリが必要なんですけど、」

yujidx7「総和はいらないんじゃないんですか?まだ振ってないインデックスの最小の値を番号2-4の辺に決定して、その後番号1の辺にインデックスをアサインすれば十分です」

ryoryoryo111「そうですか?うーん。。」

ryoryoryo111「あー、確かに総和じゃないですね」

補足1

総和じゃないが、正しいです。 f:id:corp_monoxer:20211220154304p:plain

クラスカル法で最小木を構成することを考えると、上の図のような状況の場合、最小全域木の構築にはコスト5の辺は不要です。経路上のもっとも大きいコストより、わずかでも青い辺のコストが大きければ、青い辺が最小全域木に含まれることはありません。 議論のおかげで誤解が解けて、いい方向に動いてきました。一人で考えてると見当違いの方向にいってしまっているというのはあることです。

補足2

今回グラフの問題であるため、 GRAPH × GRAPH:競技プログラミングにおけるグラフ可視化サイト を使い、具体的なグラフを描画してDiscordで画面共有しながら、議論しました。

yujidx7「そうなると、全域木上でのインデックスが未決定な複数の辺に対してどのようにインデックスを決定していくかですね」

ryoryoryo111「これはでも、辞書順最小を構築したいので、番号が前の方の赤い辺から、未使用の小さいインデックスをアサインしていく形になるでしょう」

f:id:corp_monoxer:20211220154551p:plain

yujidx7「それが良さそうですね」

ryoryoryo111「あとは経路上の辺のうち、インデックス未決定の辺が列挙できればいいです」

yujidx7「未決定の辺を列挙する必要ってありますか?」

f:id:corp_monoxer:20211220154645p:plain

ryoryoryo111「例えば上の図で番号2のインデックスを決定する時に、番号3・4・6のインデックスをより小さくする必要がありますが、番号4は既に番号1の青い辺のインデックスを決定するときにインデックスが決定されているため、以後考慮する必要がありません。」

yujidx7「確かにそうですね」

yujidx7「あとはLCAまでの経路中に存在する未アサインの辺を列挙できれば、解けますね」

yujidx7「そういうデータ構造ってありませんか?」

ryoryoryo111「うーん」

==

補足3

全ての辺に対して全域木上での経路を列挙して、インデックスアサイン済みかどうか判定するということもできますが、同じ辺が何度も列挙されるため、計算量的に大きすぎます。ということは二人ともわかっているため、うまく高速化できる方法を検討しています。

==

結構筋が良さそうな方向に議論が進んだのですが、ここで止まってしまいました。別の可能性も探ってみます。

ryoryoryo111「全然別の発想とか考えてみます?」

yujidx7「後ろから考えるとかどうですか?」

ryoryoryo111「面白いですね。しかし、辞書順の場合、後ろの方の要素に貪欲に大きいインデックスをふれても、結果的に前の方の要素に最小のインデックスをふれなくなってしまうことがあるので、難しそうです」

yujidx7「確かにそうですね」

いくつもアイデアを出して、お互いで検証して、駄目だったら捨てるというサイクルを繰り返しました。

==

ryoryoryo111「UFT(Union Find Tree)でアサイン済みのエッジを管理できないですかね?」

yujidx7「UFT考えましたけど、どんな感じですか?」

ryoryoryo111「すでにインデックスをアサインした辺をUFTでuniteします。」 f:id:corp_monoxer:20211220155440p:plain

ryoryoryo111「さらに、毎回の更新で、各unionのうちもっとも根に近いnodeを管理する感じです」

f:id:corp_monoxer:20211220155546p:plain

ryoryoryo111「経路を列挙する場合にはどんどん根に近づいていって、LCAよりも浅いnodeについたら、探索を打ち切ります」

yujidx7「あー」

yujidx7「一度アサインした辺をスキップできるから計算量的にも間に合いそうですね」

yujidx7「いけそうです」

こんな感じでお互いの発想を出し、確認し、固めることができました。

感想

青〜黄程度の競技プログラマーが集まったので、LCAやUFTといった複数のアルゴリズムが絡む複雑な問題でも少ない言葉でもアイデアを交換できたり、お互いのアイデアを検証することでスピーディーに検討を進めることができました。

私一人では解けない問題だったと思います。

仕事でも競技でも、チームとして一つの難問を解くというのは素晴らしい体験だと思います。 お互いの考える過程がわかるのも参考になります。 また、KUPC2021は有志コンですが、大きなトラブルもなく、問題文もわかりやすく良質で、解説も丁寧でした。ぜひ時間があるときには参加されると良いのではないでしょうか。 モノグサ競プロ部では一緒にチームでコンテストに出てくれる社員を募集中です。

おまけ

yujidx7「実装どうします?」

ryoryoryo111「うーん、もしyujiさんがやりたいならお任せしますが(実装量あって大変そう…)」

yujidx7「うーん(実装量あってやりたくない)」

ryoryoryo111「じゃあ、やりますね」

結局、20分ほどかけてなんとか実装しましたが、サンプルの入出力があいません。

ryoryoryo111「サンプル合ってなくて、どこかバグってそうなので、Discordに実装貼りました。」

Tobisatis「ryoryoryo111さんが実装貼ってますね」

yujidx7「んーまあ、ryoryoryo111さんが頑張るでしょう」

Tobisatis「他の問題解いてましょうか」

現実は非情です。見捨てられました。(チームワークとは...)

おまけのおまけ

後々聞いてみると、私のコードがRustで読めなかったとのことでした。(さらに、別の問題をACしてくれました。)

どうやらC++, C#, Rustと全員が異なる言語で参戦していたらしいです。

モノグサ株式会社では多様なバックグラウンドや得意言語を持ったエンジニアが活躍しています! 興味を持っていただけたら、ぜひ話を聞きに来てください!

アルファベット手書き機能をリリースするまでにやったこと

f:id:corp_monoxer:20211215171111p:plain こんにちは。インターンの伊藤です。 今回は、先日リリースしたアルファベット手書き機能について、開発経緯や工夫した点などを紹介できればと思います。

背景

今回のリリースで、私が担当したのは、「アルファベットの手書き問題に対応する」という点でした。

従来のアプリでも、アルファベットの手書き問題を出題すること自体は可能でしたが、平仮名や漢字の学習と同じように、十字の背景での表示となっていました。

一般的に、アルファベットを書く際は、以下のような4本線に沿って練習するのが一般的です。そのため、「ひらがなや漢字の時は従来と同じ十字線を、アルファベットの時は4本線を表示する」という内容に取り組みました。

4本線の表示

手書き時の背景は、画像を読み込んだりしているわけではなく、アプリ内で描画を行なっています。4本線も、同じようにアプリ内で描画の処理を追加しました。

一般的な4本線は、上から3本目が他の線よりも強調されている(これを「baseline」というそうです)ため、今回の実装では、この線を実線、他の線を点線で描画することにしました。

文字の表示位置の変更

従来の表示方法では、文字が真ん中に大きく表示されていました。そのため、単に背景を差し替えるだけでは、以下のように不自然な見た目になってしまいます。そこで、文字の表示位置についても対応しました。 f:id:corp_monoxer:20211206143724j:plain 文字の形状の情報は、svgファイルという形で保存されています。アプリの内部では、svgファイルの情報を読み取って、表示する位置の座標を計算していますが、今回はこの計算を行なっている部分ではなく、svgファイル自体を変更することにしました。(アプリの処理が重くならないようにするため。)

svgファイルの変更は、プログラムで自動化しました(大文字と小文字を合わせるとアルファベットは全部で52文字あります。その52個のファイル全てを手作業で変更するとなると非常に大変だからです)。

具体的には、4本線の位置に合わせて、アルファベットを拡大・縮小しなくてはいけません。この時の変換行列の値を4本線の位置から計算し、svgファイルの形に直して出力する、と言った処理を記述しました。(例えば、小文字のaであれば、一番上が1本目の線、一番下が3本目の線の位置に来るように倍率を設定しないといけません。) f:id:corp_monoxer:20211206143931p:plain

形状の微調整

svgファイルは、元々フォントから作成されていますが、手書きの文字とは若干異なる部分があり、そのまま掲載するのでは少し違和感がある、という問題点がありました。そこで、デザイナーの方とも打ち合わせしながら、文字の形状を微調整しました。 f:id:corp_monoxer:20211206144118p:plain f:id:corp_monoxer:20211206144145p:plain

工夫した点

今回のリリースでは、「現場で利用した時に、違和感なく使えること」を重視して開発していました。具体的には、新指導要領や、学校で用いられている標準の教科書の内容に沿うように開発しました。「4本線の間隔がどのように開いているか」や「アルファベットはどの書き順が正しいとされているか」などは、さまざまな流派が存在する一方で、指導の内容と統一されていないと学習時に混乱してしまうなどの問題が発生することが考えらるためです。

モノグサ株式会社では一緒に働く仲間を募集しています。

少しでも興味を持っていただけた方はぜひお話しましょう!

careers.monoxer.com

サービス障害とその対策についてのTech talk

f:id:corp_monoxer:20211119154643p:plain

自己紹介

皆様はじめまして。ソフトウエアエンジニアの加藤です。

今回はサービスの障害についてとそれに対する弊社での取り組みについて書いていきたいと思います。

不具合の原因について

利用者から「サービスを利用しようとしたけどログインできない。」と言われた場合、原因として考えられることは一体何でしょうか?

例えば、ログインの画面が表示されないといったことやIDとパスワードが入力できたとしても、アプリケーションの処理に問題があってログインできないかもしれません。このようにサービス側に何らかの問題があるケースが主に考えられます。

一方、IDとパスワードの組み合わせが違っていたやインターネットに接続されていないといったような利用者やその環境が起因のケースが考えられます。 この場合は、利用者側の環境で問題があることに気づいてもらって解決してもらうか、 必要があればパスワードリセットのような解決するための機能が必要になるでしょう。 f:id:corp_monoxer:20211126131805p:plain

ここではサービスが原因の不具合(サービスの障害)について詳しく話を進めていきたいと思います。

サービスの障害が発生する原因

それではサービスの障害がなぜ発生するのでしょうか? その主な原因はサービスを変化させるときに発生するものです。

サービスを変化させるというのは、以下のようなことが該当します。

・新しい機能をリリースする

・サービスの内部で利用しているソフトウェアを変更する

・使わなくなった機能を取り除く

・DBのデータ操作やアプリケーションに関するコマンドの実行

このときに何らかのミスをすると不具合が発生する場合があります。

また、障害発生の原因は他にも以下のようなものがあります。

・ハードウェアの破損

・利用しているサービスの停止

・想定以上のアクセスによって処理が詰まってしまう

前者については、Webアプリやスマホアプリのような頻度高く更新が行われる開発では根本的な解決は難しいと考えられます。

後者については、適切なインフラ設計やパフォーマンスの改善などで一定数発生を防ぐことができます。

ではサービスの障害を減らすことや、起きたときに影響を小さくするためにはどのようなことをすればよいのでしょうか?

サービスの障害の予防・抑制

ここからは障害の予防の観点と障害への対応という観点から話をします。

障害の予防のためには、開発時にミスを起こりにくくし、リリースまでにミスを検知できる状態にすることが大事になります。 具体的には以下のような方法が考えられます。

QAや受け入れテストの強化

・受け入れテストを開発プロセスに組み込んだり、専属のQAエンジニアを設けるなどが考えられます。

CIを使った自動テストやリントチェックの導入する

・コンパイルエラーを始めとしたケアレスミスを防ぐ

・自動テストで期待と異なる処理が発生している箇所を特定する

クリティカルな操作の制限

・例えば、サーバの停止ボタンやルート権限などを誰もが利用できる状況をなくすといったことです。セキュリティの観点からも重要ですが、内部の人間が間違って押してしまったやコマンド実行できたなどの危険を事前に減らすことができます。

オートスケーリングの導入

・1台あたりのサーバに対する負荷が増えたら、自動でその負荷を減らすように新しいサーバを追加する機能があります。これにより負荷にによるサービス不全を防ぐことが可能になります。

続いて障害への対応についてです。 具体的には障害が起きた際にその影響を最小限に抑えることについてです。

障害の影響は復旧までの時間と対象の機能を利用するユーザー数が大きいほど大きくなります。 影響を小さくするには「障害を早く発見すること」と「発見した障害に早く対処すること」が重要になります。

障害を発見する方法

主に自動で検知するケースと利用ユーザーから連絡が来るケースがあります。 自動で検知できる仕組みがあると、問題に気づくのがより早くなります。

異常な状態のスパイクを検知

・エラーログが通常よりも増えている ・CPUの負荷がしきい値を超えている

顧客からの連絡

・問い合わせフォーム

・CSへの連絡

発見した障害への対処

発見した障害を解決するには、障害の原因特定と原因への対処が必要です。 原因を特定するためには具体的な状況を知る必要があります。

エラーログの情報

・どのような処理でエラーが発生したかわかる。 ・ログのカスタマイズ次第では対象のユーザーなどの詳細な情報も知ることが可能。

発見したユーザーからの情報

・使用デバイスやバージョン、時刻、再現方法など。

このようにユーザーやログから情報を集めて、原因の特定を行います。原因の特定ができたら複数の部署が連携して対応をします。

・ソフトウェアエンジニアが原因を特定し、問題を解決する。

・必要に応じてですが障害の状況を外部へ発信します。

弊社での取り組み

ここまでは一般的なサービスの障害とそれに対する対策について説明してきました。 最後に弊社でどのようなことに取り組んでいるかについて簡単にお話をしたいと思います。

自動テスト・自動Lintチェック

・github上でリモートのブランチを更新したタイミングで実行されるようになっており、チェックをが通らないとリリースブランチにはマージできない仕組みになっています。 f:id:corp_monoxer:20211124091020p:plain

異常の検知

・CPU利用率などがしきい値を超えたらslack上にアラートが出るようにしています。 f:id:corp_monoxer:20211124091110p:plain

ログの整備

・GCPのログやCrashlyticsを利用して、エラーログや処理が遅いリクエストを閲覧できるようにしています。

最後に

今回はサービスの障害の原因や対策について簡単に説明をさせていただきました。

サービスの障害への対策はプロダクトの価値を高めることに直接的には繋がらないですが、 サービスが問題なく安定して稼働することは利用者にとって重要なことです。 さらには従業員にとっても安心して通常の業務に集中できるようになるなど大事なことになります。

この記事を読みサービスの障害について少しでも知識が増え、関心を持っていただければ幸いです。 最後まで読んでいただきありがとうございます。

モノグサ株式会社では一緒に働く仲間を募集しています。

少しでも興味を持っていただけた方はぜひお話しましょう!

careers.monoxer.com

競プロ部をつくって PG Battle 2021 に参加しました

f:id:corp_monoxer:20211119123428p:plain モノグサ株式会社のソフトウェアエンジニア、Tobisatis (@tobisatis)です。 今回は、当社の部活のひとつである競技プログラミング部(通称、競プロ部)より、活動報告をお届けします。

競プロ部は、2021年8月に設立された部活です。競技プログラミングが趣味の社員が順調に増えてきた頃を見計らって、4人以上のメンバーが集まりそうだと確信を得てすぐに設立宣言をしました(当社では4人以上の部員を集めることで部活を設立できます)。競プロ部のSlackチャンネルには、現在12人が参加しています。

f:id:corp_monoxer:20211119121033p:plain
競プロ部のSlackチャンネルの様子

モノグサ競技プログラミング部として初めての大会参加として、2021年10月23日開催の「PG BATTLE 2021」に出場しました。PG BATTLEは、同じ会社内や学校内の3人を1チームとして出場する競技プログラミングの団体戦です。学生向けと比べて社会人向けの団体戦は開催される数が少ないため、会社で参加できる貴重な機会となりました。

チーム結成

競プロ部設立直後の集会で話し合いを行い、1チームが結成されました。出場する3人のメンバーは、AtCoder(日本最大の競技プログラミングのサービス)でのレベルがそれぞれ黄色、黄色、青色です。当日に解く問題は、普段のコンテストの成績などを考慮して決められました。 チーム名はメンバーで話し合い、「モノグサで行こう」に決まりました。

問題と考察

それぞれのメンバーが大会中に行った考察を一部紹介します。 解法などを含みますので、問題を自力で解きたい方は解いてからご覧ください。 問題は公式サイトから確認できます。 products.sint.co.jp

・ましゅまろ2問目

次の考察で正解となります。 f:id:corp_monoxer:20211119135728p:plain

・ましゅまろ3問目

範囲内のすべてのマスを使って目的地に行く場合の数から、封鎖されたマスを通って目的地にいく場合の数を引いて解くことができました。封鎖されたマスが範囲外であるパターンにも気づいて正解できました。

・ましゅまろ4問目

数列の数え上げ問題で、尺取り法を使って解くことができました。与えられたテストケースが少ないこともあり場合分けが正しいか不安でしたが、テストケースを自作することで正解できました。

・せんべい2問目

最大値最小値を求める問題で、二分探索によって解くことができました。バグを解消するのに時間がかかりましたが、丁寧に作成したサンプルケースの成果もあり正解できました。

・せんべい4問目

UnionFindを使った貪欲法などを考えていましたが、十分に早い方法を時間内に思いつけませんでした。

・かつおぶし1問目

ある整数の階乗の桁数を求める問題で、対数などを用いて解くことができました。数学の関数を使いやすいPythonに切り替えて解いたところ、正解できました。

・かつおぶし3問目

imos法のような考え方の実装に挑みましたが、時間内に解ききれませんでした。

結果と振り返り

12問中8問を正解し、全体で19位でした。 出場したメンバーはチーム結成以降、AtCoder(日本最大の競技プログラミングのサービス)でほぼ毎週開催されるコンテストに出場して、結果や考察の共有を社内のSlackで行っていました。本番はAtCoderのコンテストとの形式の違いなどで慣れない点もあったようですが、日頃の練習の成果もあり、大変良い結果を残してくれたと私は感じています。

終わりに

会社のおやつ会だけでなく独自のおやつ会を開催するなど、競技プログラミング部ではあらゆるメンバーが楽しめるような環境を整えています。 モノグサ競技プログラミング部に興味のある方、ぜひ一度お話ししてみませんか?

モノグサ株式会社では一緒に働く仲間を募集しています。

少しでも興味を持っていただけた方はぜひお話しましょう!

careers.monoxer.com

みんなで一緒に!5分でCSSアニメーション

f:id:corp_monoxer:20211108175648p:plain

こんにちは、モノグサ開発チームのliminです。今回は誰でもCSSアニメーションを作成できるように実例で説明したいと思います。

そもそもCSSとは?

一言で言うと、「web要素(HTML tag)の見た目(styles)を指定できる言語」です。 ブラウザはHTML tagsをそれぞれデフォルトの形で表示させます。そして、CSSはそのデフォルトのstylesをオーバーライトできます。 さらに、CSSは静的stylesを指定できた上、動けるstyles、つまり「CSSアニメーション」も作成できます。

f:id:corp_monoxer:20211108141352g:plain
例:画像を動かすように

実例1:実例を参考しながら始めましょう!

まず、こちらで一番目の実例から見てみましょう。

アニメーションは以下の真ん中の一行のみで指定できます。ちなみに、一行目はターゲットのHTML要素を指定するCSS selectorsです。

f:id:corp_monoxer:20211108141544p:plain

animation: の後ろに4つのパラメーターがあり、以下のものになります。

  • animation-name
  • animation-duration  
  • animation-timing-function
  • animation-iteration-count  

animation-name 事前に定義した keyframes の名前を指定するフィールドです。

keyframes @keyframesとは一回のアニメーションの中でどんな動きをするかを指定できる属性です。 f:id:corp_monoxer:20211108142113p:plain 0%は開始時点での状態、そして100%は終了時点の状態です。0%, 100%の指定は必須です。それ以外はいくら「X%」を増やして状態を指定しても良いのです。 今回の実例は0%と100%で指定しかないため、途中の1%, 2%, ...99%などの状態が「線形補間」で自動生成されます。 f:id:corp_monoxer:20211108142155p:plain ちなみに、transformは回転以外、他の行為(移動、拡大)も指定できます。詳細はこちらで確認してください。

animation-duration アニメーションを1回回す時間を指定するフィールドです。こちらは1秒の場合「1s」です。

animation-timing-function 少し複雑なパラメーターですが、簡単に言うと「1回のアニメーションでどのように進行するのか」を設定できるフィールドです。「どのように進行する」はcubic-bezier関数で決められます。 基本的には以下のどちらかを設定すれば大丈夫です。 f:id:corp_monoxer:20211108144215p:plain

cubic-beizer関数はどのようにアニメーションに影響するのか、以下のリンクでご自由にお試しください。

https://cubic-bezier.com/ https://cubic-bezier.com/

animation-iteration-count デフォルトは1回で止まってしまいますが、infiniteで設定すれば無限ループができます。

実例2:遅延時間の応用

以下はこちらの実例で説明します。

今回のアニメーションは簡単。scaleを使って、「拡大⇨戻す」を繰り返すだけです。

f:id:corp_monoxer:20211108150054g:plain

animation-delay アニメーションの開始時間を遅らせる属性です。実例のように5つの要素が全部同じアニメーションでも、それぞれ違う遅延時間を指定すれば...

f:id:corp_monoxer:20211108151210g:plain

完全にローティングっぽい感じになりますね。 つまり、animation-durationanimation-delayをちゃんと合わせて調整すれば、簡単でいい感じなアニメーションが作れます。

f:id:corp_monoxer:20211108150439p:plain

f:id:corp_monoxer:20211108150251p:plain

他の属性変化も入れてみよう! 実例2のanimation-nameを変更してみよう!

f:id:corp_monoxer:20211108150936p:plain

f:id:corp_monoxer:20211108151009g:plain

f:id:corp_monoxer:20211108150950p:plain

f:id:corp_monoxer:20211108151029g:plain

本来のscale以外、背景色(background)、透明度(opacity)などの属性で指定すると全然違うアニメーションになります。

おまけ:自ら考えて試してみよう

f:id:corp_monoxer:20211108151110g:plain

できた方、おめでとうございます!上記で紹介した内容をしっかりと把握していただけたら、この動画のようなアニメーションを自分で作れるはずです。試してみましょう!

MonoxerはCSSアニメーションを使っていますか?

f:id:corp_monoxer:20211108151336g:plain 一番わかりやすいところは、ローディングのアニメーションです。これはreact-spinnersというライブラリのCSSアニメーションを使っています。今後はもっと多くの自作的なアニメーションを導入していくかもしれません。

以上です。最後までお読みいただき、ありがとうございました!

モノグサ株式会社では一緒に働く仲間を募集しています。

少しでも興味を持っていただけた方はぜひお話しましょう!

careers.monoxer.com

Monoxer Intern Report #4_API設計と追加(yokozeki)

自己紹介

こんにちは、モノグサでソフトウェアエンジニアのインターンをさせていただいた横関です。 お茶の水女子大学の4年生で、プログラミング言語の基礎理論を扱っている研究室に所属しています。

参加した理由

AtCoderJobsというサイトで夏季インターンを探していたところ、モノグサのインターンに興味を持ち、応募しました。興味を持ったきっかけは、記憶にまつわる課題をソフトウェアの力で解決しているところが創造的で素敵な会社だと思ったことでした。また、期間が長く勉強になることが多そうだったことも魅力的でした。 参加の最終的な決め手は、面接の時の雰囲気が終始穏やかだったことです。

取り組んだこと

今回のインターンでは、Monoxer APIという外部APIにいくつかのAPIを追加しました。インターンの前半はアカウント管理のためのAPIの追加をしていました。後半は主テーマとして、振り分けルールという便利な機能のAPIを新しく作っていました。

Monoxer APIとは
Monoxerはただの学習アプリではなく、塾・学校・会社など、記憶させたいものがある組織に向けて記憶のプラットホームを提供しています。学習者に何を記憶させたいかは、学習者の属性(所属クラスや志望校など)によって変わります。そのためMonoxerには、学籍番号などのID、〇年〇組といった所属など、色々な情報を登録できます。 クラス変更や学習者の追加などが発生した場合、その情報をMonoxerに反映したいときがあります。その反映は管理画面から行うことが出来るのですが、組織の規模が大きい場合は、管理画面からではなく、その組織のシステムから直接行うと便利です。

Monoxer APIは、そのような場面で使われていて、Monoxerサーバーと外部システムとの間のインターフェースとなっています。 f:id:corp_monoxer:20211007120035p:plain

振り分けルールAPIの追加
Monoxerにはクラスという単位があり、クラスごとに学習者に記憶させたいものを指定できます。振り分けルールとは、アカウントをクラスに自動で振り分けたいときに適用されるルールです。アカウントの情報とクラスの情報を組み合わせて、あらゆるパターンでルールを作ることができ、組織の仕組みに柔軟に対応できるようになっています。

インターン前半で取り組んだアカウント管理APIの方はすでにいくつかのAPIが存在していたのですが、後半の振り分けルールAPIの方は既存のAPIがありませんでした。そのため、JSONの構造や、「どのようなAPIを揃えて振り分けルールの管理を可能にするか」という点から考えました。

API設計
どのようなAPIを揃えるかという部分では、分かりやすく使いやすいAPIにするためにきれいな設計を心がける必要がありました。 Monoxer APIは、RESTの原則に基づいてAPI設計されています。リソース指向アーキテクチャについて勉強してみると、統一的で分かりやすく、おもしろいと思いました。

f:id:corp_monoxer:20211007120131p:plain

得た学び

実際の製品のコードを読んだり書いたりする体験ができて、今まで勉強できていなかった部分(読みやすいコードを書く・APIの設計・データベース周辺)が勉強できました。レビューがとても機能していて、社員の方から、可読性に関するレビューをたくさんいただきました。 Scalaという言語を初めて使ったのですが、Scalaについても色々なことが分かるようになりました。

社員さんとのやりとり

初日にメンターさんから1:1の頻度はどのくらいが良いか聞かれ、小まめに見てもらえるとありがたいと答えたところ、毎日1:1を設定していただけることになりました。心強かったです。
レビューやミーティングではどの社員さんも言葉遣いが柔らかく、働きやすかったです。 またコロナ禍で控えめだったのですが、ボードゲームや、社員の方々・他のインターン生との会話も楽しかったです。

さいごに

エンジニアとして働いたことが無かったので、インターンには勉強になることがたくさんありました。モノグサのみなさま、2ヶ月間ありがとうございました。

モノグサ株式会社では一緒に働く仲間を募集しています。

少しでも興味を持っていただけた方はぜひお話しましょう!

careers.monoxer.com

Monoxer Intern Report #3_手書き文字評価の改善(chikai)

自己紹介

モノグサ社にてソフトウェアエンジニアインターンとして参加した伊藤 誓です。 現在は東京大学大学院に所属しており、VR環境におけるユーザーインターフェースについての研究を行なっています。

なぜモノグサのインターンに参加したのか

夏期インターンを探している最中に、AtCoderJobsというサイトに乗っている掲載を見て応募しました。参加の決め手になったのは、自社開発を行なっている会社で、幅広い技術領域を取り扱っており新しい技術経験が出来ると感じたことや、「記憶」に対して科学的観点からアプローチしていくというアプリのコンセプトが興味を引いたことなどでした。

取り組んだこと

今回のインターンでは、手書き文字評価周りの改善に取り組みました。その中でも、大きく分けると3つのことに取り組みました。

アルファベット対応
元々、アルファベットの手書き文字評価を行うのには以下のような課題がありました。

  • 十字線の上に表示されていて不自然
  • フォントの形が不自然
  • 1画ずつ分けて書く必要がある

内部で実装されている言語IDやテキストの内容から「アルファベットの問題である」と判断された場合には、十字線ではなく四本線の表示を行いました。

フォントの形に関しては、デザイナーさんとも打ち合わせしつつ、svgファイルの変更を行いました。また、背景に表示する4本線と合うように、svgファイルを編集する処理を行いました。52個もあるファイルに対して編集をするのは面倒なので、ファイルの情報を自動で編集するスクリプトを記述しました。

f:id:corp_monoxer:20211007114724p:plain

繋げ書きに関しては、内部のアルゴリズムの根幹に関わる部分なので、まず既存のライブラリの中身を理解する必要がありました。その後、アルファベットにも対応できるように、「複数回の繋げ書き」「離れたストロークを補間する処理」などを実装しました。

アルゴリズムのテストツールの開発
2つ目に、アルゴリズムのテストツールの開発に着手しました。手書き文字評価では、正解の文字と、ユーザの書いた文字を照らし合わせて正解/不正解を判定しますが、評価の精度が低いとユーザ体験の質の低下に繋がってしまいます。そのため、出来る限り精度を上げる必要があります。しかし、アプリに直接組み込まれている状態でアルゴリズムの精度を評価するのは非常に大変です。そこで、一連の評価/デバッグの流れを簡単にするためのテストツールを開発しました。

アルゴリズムに変更を加えた後、コマンドを1つ叩けば、データベースから作成したデータセットに対してアルゴリズムが実行され、変更による寄与の大きいケースをまとめて出力してくれます。さらに、実行時間やスコアの構成要素などのログも閲覧可能にすることで、より詳細なデバッグが可能になっています。

f:id:corp_monoxer:20211007114830p:plain f:id:corp_monoxer:20211007114840p:plain

アルゴリズムの改善
3つ目に取り組んだこととしては、手書き評価アルゴリズムの改善です。自分で開発したテストツールを用いて、上手くいかないケースを探しつつ、改善に取り組んでいきました。

自分は競技プログラミングを趣味の一つとしていますが、感覚としてはヒューリスティックコンテストで出題される問題に非常に近かったです。一方で、普段のコンテストと違っていると感じた点として、入力ケースのほとんどが暗黙の制約を満たしている場合があったり、最悪計算量が悪いアルゴリズムでもほとんどの入力ケースでは上手くいくため事実上問題ない場合などがあったりして、非常に面白かったです。

学び

自分にとっては、他の人と共同開発しながらプログラムを書くという経験があまりなかったので、その部分は非常に勉強になりました。Pull Requestの出し方や、コードレビューへの応答など、円滑なコミュニケーションを取る上では気をつけるべきことがたくさんありました。

また、自分のコードが本番のアプリケーションに統合される経験も新鮮で、「どのようにして出来るだけ安全なコードを書くか?」といったことを考えるのも新しい経験でした。今までは、強い型付けのある言語のメリットが実感できていませんでしたが、実行時エラーの許されない環境で Kotlin を書いていると、型推論をしてくれる有り難さが身に染みた気がします。

社員さんとの関係

コロナ禍でのインターンでしたが、出社して働くかリモートワークするかが比較的自由に選べる環境でした。私は、週に2回出社して残りはリモートという感じでした。リモートの場合でも、1日に1回はメンターさんとの 1on1 ミーティングが用意されたので、特に困るといったことはありませんでした。

相談したいことがあった場合でも、基本的にメンターさんに Slack を送れば即座に反応してくれるので、助かりました。他にも、Jira などを用いてプロジェクトが管理されており、他部署の社員の方も含めて柔軟な議論をすることができました。社員全員がプロダクトの品質向上に対して真摯に向き合っている様子が伝わってくるので、働いているだけで創造性が刺激される素敵な環境でした。

モノグサ株式会社では一緒に働く仲間を募集しています。

少しでも興味を持っていただけた方はぜひお話しましょう!

careers.monoxer.com