Monoxer と組版: より良いコンテンツの表示を目指して

モノグサで Software Engineer として働いている杉江です。

Monoxer では、記憶対象が問題としてアプリ上で出題されます。その際に表示される問題文などのコンテンツが見やすく表示されることは重要です。今回は、一般的に知られているコンテンツ作成方式の種類を紹介し、また Monoxer での組版についてもお話しします。

コンテンツ作成方式の種類

電子書籍やブログ記事などのデジタルコンテンツは、デバイス上で文字や図表の集まりとして表示されます。文字や図表を画面上に配置する工程は「組版」と呼ばれます。

コンテンツ作成方式は大きく分けて「リフロー型」と「フィックス型」の 2 種類があります。組版の工程をどの段階で行うかが異なります。

リフロー型は、表示するデバイスの画面サイズや設定に合わせて、テキストやレイアウトが流動的に表示される方式です。この場合、コンテンツは文字列として保持されていて、閲覧するたびにデバイス側でレンダリングしてコンテンツを表示します。

フィックス型は、表示するデバイスの画面サイズや設定に関わらず、文字や図表などのレイアウトが固定される方式です。この場合、コンテンツは画像や PDF などの形で保持されていて、デバイス側では組版処理は行わずに単に表示だけを行います。

Monoxer と組版

従来の Monoxer における組版と、その問題点

これまでの Monoxer はリフロー型のみに対応しており、アプリ側でコンテンツのレイアウトを決めて表示していました。

なお、コンテンツには 1 枚の画像を「挿絵」として挿入でき、挿絵は常にコンテンツの最上部に、決められたサイズに収まるよう配置されます。図を使って表す必要がある要素が複数ある場合は、それらを 1 枚の画像に収めたうえで、「図の(ア)は○○であり〜」のように、各要素にラベルをつけて指し示す必要がありました。

例として連立方程式が含まれたコンテンツを考えてみます。連立方程式の部分を正確に表記するには、数式を挿絵にする必要があり、その際は以下のようにそれぞれの式に(ア)と(イ)というラベルをつけて指し示すことでコンテンツを作成できます。

数学のコンテンツ制作を検討していく中で、この仕様ではレイアウトの自由度が低く、表現上で限界が出るのではないかという懸念が生まれました。仕様に関する主な問題点は次のとおりです。

  • コンテンツに必要な図表を、1 枚の画像に収めることが難しい場合がある

    • 数学では図表を多用する単元もあります。このような単元で、問題を解くために必要な画像群を 1 枚に全て収めようとすると無理が出てくる場合があります。
  • 文と文の間に画像を挿入できず、学習体験として望ましくない状況になることがある

    • 画像は最上部に表示され、文と文の間に画像を挿入できません。先ほどの連立方程式の例では、(ア)や(イ)に対応する式を見るときに、コンテンツの最上部にある画像に再び注目する必要があるため、視線が不自然に上下してしまいます。もしも(ア)や(イ)が書かれている場所に式がそのまま書かれていれば、上から下へ流れにそって、より自然にコンテンツを読めるようになる可能性があります。

また、これらの問題は数学だけでなく、他の教科や分野でも起こりうる問題ではないかと考えました。

課題解決に向けた方針検討

これらの課題を解決するために、リフロー型の方式を維持してアプリ側を改修する方針と、フィックス型の方式にも対応する方針の 2 つを検討しました。

Monoxer には、今後もさまざまな教科や分野のコンテンツが搭載される可能性があります。そのため、レイアウトに関するコンテンツ作成者からの要望も多岐にわたると予想されます。現状の仕組みで対応できないコンテンツに直面するたびに、必要な条件を洗い出してアプリ側を改修し、リフロー型の方式のみで対応し続けることは工数的・技術的に困難であると考えました。

一方でフィックス型の方式では、コンテンツは後述するように画像であり、レイアウトに関する要望は画像側(コンテンツ側)で対処すればよくなります。アプリ側では、コンテンツである画像を適切に表示できれば十分です。レイアウトに関する個々の要望をコンテンツ作成者側に移譲できることが決め手となり、フィックス型の方式に対応する方針を採用しました。

フィックス型の導入

今回導入した方式では、コンテンツは画像として保持されており、コンテンツが縦に長い場合は縦にスクロールすることで全体を閲覧できます。

画像がそのままコンテンツになるため、コンテンツ作成者は文面を自由に作成できます。たとえば先ほどの連立方程式の問題であれば、「次の連立方程式を」から「答えは x = 3, y = 2 である。」まで、問題文全体が 1 枚の画像になるように画像を作成します。この方式であれば、文と文の間に数式を挿入できるため、上から下へ流れにそってコンテンツを読めるようになりました。

Monoxer がフィックス型のコンテンツを導入してよかった点は、主に以下が挙げられます。

  • 複雑なレイアウトにも柔軟に対応できる

    • コンテンツ全体が画像であるため、画像さえ用意すればどのようなレイアウトでも表現可能です。
    • もちろん、文と文の間に図表を入れたり、複数の図表を含めたりすることができます。
  • コンテンツのレイアウトに関する個々の要望を、アプリ側ではなくコンテンツ側で対処できる

    • 先に述べたように、リフロー型のみで対処するとアプリ開発がボトルネックになるうえ、個々の要望に対応しきれなくなることが予想されます。フィックス型に対応したことで、レイアウトの複雑さをコンテンツ側で対処できるようになったため、このような懸念を払拭できた点がよかったと考えています。
  • 組版に関する既存の資産を活用できる

    • 出版社やライターなどのコンテンツ作成者は、組版に関するツールやスキルをすでにお持ちであると考えています。また、組版結果を画像形式で出力することは一般的に行われています。今回導入した方式では、画像がそのままコンテンツになるため、Monoxer に搭載するコンテンツを作成するときに、組版に関するツールやスキルを活用しやすくなります。

今後の課題

今回の改修によって、まだすべてのコンテンツがカバーできたわけではありません。たとえば、縦書きのコンテンツが入力されたときは、縦方向のスクロールだけでは対応できないかもしれません。教科や分野に応じてより良い学習体験を実現できるように、今後もさまざまな方針を検討していきたいです。

まとめ

コンテンツの作成方式にはリフロー型とフィックス型があり、それぞれ異なる強みを持っています。 従来の Monoxer ではリフロー型にのみ対応しており、また画像は決まった位置にしか挿入できませんでした。フィックス型にも対応できるように改修したことで、文中に図表を挿入したり、複数の画像を挿入したりなど、より柔軟にコンテンツのレイアウトを決められるようになりました。

モノグサ株式会社では一緒に働く仲間を募集しています。
少しでも興味を持ってくださった方は、ぜひお話しましょう!
モノグサ株式会社の採用サイトです。モノグサは「記憶定着」をサポートする学習サービス「Monoxer」を運営する会社です。

careers.monoxer.com

JaSST `24 Tokyo参加レポート

こんにちは!モノグサでQAEをしている甲斐と申します。 2024年3月14日(木)、3月15日(金)に開催されたJaSST'24 Tokyoに弊社のQAEとTest Automation Engineerの6名で参加してきましたので、その感想をレポートします。

JaSSTとは

JaSSTはソフトウェアテスト技術振興協会(ASTER)が開催する、テスト技術力の向上と普及を目的としたソフトウェアテストシンポジウムです。2020年からはしばらくの間オンラインのみの開催でしたが、今年(2024年)からオンラインと現地でのハイブリッド開催となりました。 私は初日を現地で、2日目をオンラインで参加しました。現地の基調講演では会場の8割前後が埋まるくらい盛況でした。現地には現地の、オンラインにはオンラインの良さがあったので、ハイブリッド方式の開催がとてもよかったです。 現地で実際に講演者の声や空気を感じるのもよかったのですが、オンラインの特性を活用して例えばDiscordでわからない用語についてURLを貼ってくださる方がいたり、社内のSlackでその瞬間刺さった言葉の感想や各個人が課題に感じていて改善したいと思っていることを意見交換できたり、プロアクティブに講演を理解できる経験ができてよかったです。

社内Slackの様子

メイン会場の後ろにはスポンサーのブースがあり、あまりITのイメージがなかったChocozapさんがスポンサーとしてブースをされていてびっくりしました。私はアンケートに記入してロゴ入りグッズをいただきました、ありがとうございます!

タオルはけっこうな頻度で使わせていただいてます!

講演の感想

初めてJaSSTに参加したのですが、B3講演「QAエンジニアの〇〇UP!キャリア解剖で見える今どきの成長軸とは?」で言及されていた、「現場で得られるものは断片で、シンポジウムや勉強で断片を体系化する」という話がとても印象に残りました。 普段仕事の中であまり意識しておらず、自分のキャリアアップとはテストをうまくできることに終始していましたが、テストの作業はソフトウェアやサービスの先にいる顧客に満足のいく品質で届けるためという俯瞰的な視点が抜けていたなと感じています。

他にも社内ではこんな感想が出ました:

  • 両日オンライン参加だったが、社内slack実況もdiscordでのチャットも盛り上がっていたので楽しく過ごせた
  • 登壇者のトレンドとして、QAの存在価値(そもそも、なぜQAが必要なのか)をきちんと(どのような指標が満たされれば達成されるのか)定義しよう、という論調が多かった
  • 今回は全体的にQAは品質をリードする存在であると言う内容の話が多かった気がする
  • 勉強ではなく知識を得て使え、というお話がありましたがまずは知識を得る勉強が必要だと思いました。
  • プロセス改善を地道に進める話や品質の考え方のセッションが多くあり参考になった。

JaSST 2024 Tokyoの参加は、私たちモノグサのQAチームにとってとても有益な経験になりました。個人としてもチームとしても自分たちの仕事に対する視野が広がり、これから取り組んでいきたい課題に対する理解も深まったと感じています。次回も是非参加させていただきたいと思います!

JaSSTに参加された方は、各講演の録画が公開されてますのであらためて視聴してみてもいいかなと思います。JaSSTからの案内メールか、公式Discordの「総合案内」にURLがあります(URLは転載禁止のため案内だけにしております。)

Monoxer Intern Report #18_Webクライアントにおける数式入力手段の拡充

自己紹介

 2024 年春にモノグサで SWE としてインターンに参加しました、宮崎と申します。2024 年 3 月現在、慶應義塾大学理工学部の 1 年生です。

参加を決めた理由

 もともと春休みに大きな予定がなく、何かしらの春季インターンに参加することは考えていました。そんなとき、AtCoderJobs でモノグサの募集を見て、実際のサービスの開発に携われることに魅力を感じたこと、また AtCoder Beginner Contest や ICPC のスポンサーなどを通して会社について認知していたことから、応募を決めました。

取り組んだこと

 主に管理者用 Web クライアントでの Book 編集機能に関するタスクを担当しました。その中でもメインタスクとして、数式の入力手段の拡充に取り組みました。

メインタスクについて

 Monoxer では数学の問題を出題することができますが、そのためには自ずと数式を入力する機能が必要になります。Monoxer では MathML と簡易数式という 2 種類の数式入力手段をサポートしており、前者はマークアップ言語であり、後者は AsciiMath をベースとした記法となっています。

図1. MathML と簡易数式を記述する例

 図 1 からわかるように、MathML は文法が明確に定まっておりプログラム上では扱いやすい一方、タイプ量が多いことや人にとって読みづらいことからユーザにとって負担が大きいです。それに対して、簡易数式は直感的な記述ができるため、簡易数式を使える場面を増やすことが目標となっています。

 現状の Web クライアントにはとある問題があり、それは図 2 のように簡易数式で入力しても、一度保存して再度編集しようとすると MathML に変換されてしまうことです。

図2. 簡易数式が MathML に変換される例

 これは仕様であり、入力された数式のデータはすべて MathML に変換された上でデータベースに保存されていることに起因します。

 この問題を解消すべく、私は解答の入力欄に MathML と簡易数式を相互に変換するボタンを配置し、ユーザの操作により切り替えられるようにしました。

図3. MathML と簡易数式を切り替えるラジオボタン

実装について

 ここで、簡易数式 → MathML の変換は既にあるものを流用することができましたが、その逆である MathML → 簡易数式の変換は新たに実装する必要がありました。

 MathML はマークアップ言語のため木構造が成り立っており、深さ優先探索 (DFS) により容易に探索することができます。そこで私は「自らを根とする部分木に対する変換結果」を返す関数をつくり、再帰的に変換する実装としました。

図4. MathML を深さ優先探索により探索する例

 また、React を用いた UI の実装も担当しました。

インターンの感想

 今回参加したインターンシップではとても多くのことを学びました。その中でもいちばん大きなものが、チーム開発の流れです。たとえば製品に 1 つの変更を加えるだけでも、最初の要件定義や実装後のコードレビューなど、多くの段階を踏む必要があることを実感しました。さらに保守性を高めるため、読みやすいコードを書くことやテストを作成することなども初めての経験であり、重要な学びとなりました。

 コーディングの面では、TypeScript や React が完全に未経験の状態だったため、一から調べながら実装していましたが、そのような状態からこのような成果を出せたのは大きな成長であったと感じます。

 そして、働く環境はとても良かったです。ボードゲームの文化が端的な例ですが、それ以外にもあらゆる場面で伸び伸びと働ける雰囲気がありました。また、我々インターン生の考えも社内で真剣に扱ってもらえたことが嬉しく、印象に残っています。

 その反面大変だったこととしては、必要な知識を自分で手にいれる必要があったことが挙げられます。たとえば React は、公式のチュートリアルなども活用して習得していきました。もっとも、そのおかげでスキルをよりしっかりと身につけられたとも考えています。

 最後のまとめとなりますが、初めてのインターンであり製品の開発経験もなかった私がこのような成果を出せたことはとても貴重な経験であり、参加して良かったと感じています。私を支えてくださった社員の方々に深く感謝します。

Monoxer Intern Report #17_CSV Data Export機能の追加と改善

自己紹介

モノグサの春インターンに2ヶ月間参加した中尾です。春から立命館大学の学部3年生です。普段は技術書や技術記事を読んだりしています。今回は遠方からの参加ということで、最初と最後に1週間強ほどずつ出社し、他はリモートでの勤務となっていました。

参加を決めた理由

春のインターン生をAtCoderJobsで募集しているというポストをXでみた友人に教えてもらって存在を知りました。AtCoder自体は最近はやっていなかったのですが、半年ほど夢中で取り組んでいたので深い思い入れがありましたし、モノグサに関してもAtCoderをやっていた時に解説記事などでお世話になったけんちょんさんが入社したというポストを見たことがあったので、存在は知っていました。当時ちょうどインターンに参加したいと思って色々探していた私にはまさに渡りに船で、すぐに応募を決めました。モノグサのブログやインタビュー記事、ネットニュースなどを調べるにつれ、参加したいという思いは強まっていきました。そこで実際に面接を受けた結果、選考を通過することができ、参加することを決めました。

取り組んだこと

組織の管理者がユーザを管理するためのWeb管理ツールにある、CSV Data Export機能の追加や改善に取り組みました。 CSV Data Export機能とは、ユーザに関する情報をCSVファイル形式で出力できる機能です。ダウンロードしたCSVファイルをExcelに取り込んで、データを分析するために使用されます。現在は、一部の組織向けに限定で公開している機能です。 私が取り組んだ課題は主に3つあります。

記憶度の出力項目に所属クラス情報を追加する

課題背景

CSV Data Export機能に、ユーザの記憶度を出力できるものがあります。記憶度とは、学習するBookに対して、どのくらい記憶しているのかを表す指標です。組織の管理者は、情報が欲しいBookを選択して、組織に所属する各ユーザの選択されたBookに対する記憶度をCSVファイルとして出力することができます。 今まで、この機能に対して、各ユーザの「スクールID」「スクール名」「クラスID」「クラス名」などの所属クラスの情報が付属していませんでした。なので、所属クラスの情報を必要とする分析を行うためには、お客様自身で、出力したCSVファイルとユーザの所属クラスの情報を組み合わせる必要がありました。 また、「学習回数の出力」などの他の機能にはこれらのクラス情報が付属していたため、記憶度と他の機能の出力項目の違いをお客様に説明する必要もありました。 よって、この課題を解決することで、お客様の手間も省け、ビジネスチームがお客様に出力項目の違いを説明する必要もなくなります。

やったこと

フロントエンドの実装

「出力するCSVデータの項目」に所属クラス情報を追加しました。

記憶度の出力予約画面

バックエンドの実装

ユーザの「組織メンバーID」から所属クラス情報が取得できるMapを作成し、それを使って出力項目に追加していきました。

結果

実装箇所としてはあまり多くないのですが、ビジネスとしてはインパクトがあると考えられます。 記憶度の出力項目に所属クラスの情報が追加されたことで、お客様自身で所属クラスの情報を付与する手間がなくなりました。また、ビジネスチームも他の機能との出力項目の違いをお客様に説明する必要がなくなりました。

管理者週間アクティブ率を出力できるようにする

課題背景

管理者週間アクティブ率とは、1週間でWeb管理ツールを使用した管理者の割合を表すものです。塾現場では管理者を管理したいというニーズが高いため、この機能を追加することになりました。この機能は、Monoxerを導入した組織が、出力されたデータを基に管理者がきちんとWeb管理ツールを利用しているかを確認し、利用を促したりするために利用されます。

やったこと

フロントエンドの実装

「管理者アクティブ率の出力」というボタンを設置し、そこから出力予約画面に飛べるようにしました。

出力予約画面への遷移ボタン

出力予約画面では、集計する期間や集計対象を選択して、出力予約が行えるようにしました。Monoxerでは組織階層の下にスクール階層というものがあり、組織AにはスクールA, スクールB, スクールCがあるというようになっています。よって、集計対象としてスクールを選択することで、スクール単位での管理者のアクティブ率というのも出力できるようにしました。

出力予約画面

ちなみになぜ「出力」ではなく「出力予約」なのかというと、データの出力に時間がかかるからです。先ほど紹介した記憶度の出力だと、長い場合2時間半ほどかかるので、出力予約という形にして、CSVファイルが準備できたらダウンロードできるようにしています。

バックエンドの実装

こちらは2ステップあります。

  1. 出力予約リクエストの処理

    出力予約画面で選択されたパラメータを含むリクエストが飛んでくるので、それを処理します。任意の出力予約を処理するハンドラがあるので、そこに今回の機能の処理を追加する形になります。集計期間が意図された値か、集計対象が存在しているかなどをチェックして、OKであれば予約をDBに格納します。なぜDBに格納するかというと、CSVファイルの生成はバッチ処理で行う仕組みになっているためです。

  2. CSVファイルの生成

    こちらでは管理者のアクティブ率を計算するため必要なデータをDBから取得して、出力するデータを算出し、CSVファイルを生成します。 まず、管理者週間アクティブ率を計算するためには「組織の管理者一覧」と「対象期間の管理者のアクセスログ」が必要になるので、それらをDBから取得します。 次に、集計期間を1週間ごとに区切って、各期間でアクセスしていた管理者の数をカウントし、アクティブ率を計算します。 このようにして、「集計開始日」「集計終了日」「アクティブ率」をCSVデータとして出力します。

    以上が組織単位での管理者アクティブ率の出力方法です。スクール単位だともう少し処理が複雑になりますが、流れとしては同じです。

結果

管理者週間アクティブ率が出力できるようになったことで、Monoxerの利用実態についてデータに基づいた分析や利用の促進が可能になり、お客様の「組織の管理者を管理したい」というニーズを満たすことができました。

追加の実装箇所

実は、記憶度の出力項目に所属クラスの情報を追加する課題が終わった後に、大手のお客様が記憶度を出力しようとするとエラーになってしまう問題が発生しました。調査の結果、私が実装した箇所に不備があったことが発覚し、それを修正することで問題は解決しました。レビューはしっかりと行っていたのですが、それでも不備が入り込んでしまったことの原因として、CSVファイルを生成する関数にテストが存在しないことがありました。これらの関数は複雑な処理をしているため、レビューだけでバグを無くすのは困難です。よって、今回の管理者週間アクティブ率を出力する関数にはテストを追加することにしました。結果として不備が2つ見つかり、そのうちの1つは実際にアクティブ率の計算結果に影響を及ぼす可能性のあるものでした。このように、早期に不備に対応することができ、他のCSVファイル生成関数にもテストを追加する足がかりを作ることができたのは、地味ではありますが今回のインターンの中でも重要な成果であると思っています。

出力項目に属性情報を追加できるようにする

課題背景

属性情報とは、「学年」「出席番号」「校舎」「選択科目」など、組織で個別に設定できるユーザに関する情報のことです。現状では、これらをCSVファイルの項目に追加することができません。よって、これらの情報を用いて分析を行いたい場合は、お客様自身でCSVファイルとアカウント情報を組み合わせる必要があります。このような作業は二度手間です。また、そもそもCSVファイルを分析ではなく表のように扱いたい小規模な組織のお客様にとっては、完全に手作業で属性情報を付与していくことによってミスをしてしまう危険性もあります。 よって、この課題を解決することで、お客様の苦しみを解決することができます。

やったこと

Design Docの執筆

機能の要件を満たす上で、どの方針にどんなメリット、デメリットがあるのかを記述し、どれをなぜ採用したのか。不安要素は何で、それは実際に問題となるのか。などを記述しました。 例えば、属性選択のコンポーネントのデザインで以下の3つが良い方針として浮かび上がりました。

  • 画面スクロール
    • メリット
      • ページに比べて操作が楽
    • デメリット
      • 既存の基盤コンポーネントがなく、新たに実装する必要がある
      • デザインを工夫しないと、スクロールできることがわかりづらい
      • Web管理ツールのターゲットによっては、必ずしもスクロールが使いやすいデザインであるとは限らない

スクロールでの選択画面

  • ページネーション
    • メリット
      • 既存の基盤コンポーネントが使用できる
      • 操作方法がわかりやすい
      • デザインとして見栄えが良い
    • デメリット
      • ページ遷移の操作がやや面倒
      • 表示件数を増やすと、縦に伸びて画面を占有してしまう
      • 属性の追加は必須項目でないのに、画面の占有率が高く、存在感が大きい

ページネーションでの選択画面

  • Modal&ページネーション
    • メリット
      • 既存の基盤コンポーネントが使用できる
      • 操作方法がわかりやすい
      • 表示件数を増やしても予約画面には影響がない
    • デメリット
      • ページ遷移の操作がやや面倒
      • デザインとして単純なページネーションに比べて、見栄えが悪い

Modal&ページネーションでの選択画面

このように、それぞれの方法のメリットデメリットを検討し、結果としてModal&ページネーションを採用しました。特に単純なページネーションではなくModalを採用した理由としては、「属性の追加は必須項目でないのに、画面の占有率が高く、存在感が大きい」がクリティカルでした。 最初私はスクロールを推していましたが、「Web管理ツールのターゲットによっては、必ずしもスクロールが良いデザインであるとは限らない」というのは考えられておらず、人によって良いデザインは異なるかもしれないというのは私にとって深い学びになりました。

他にも、出力項目に属性を追加する必要があるのですが、無制限に追加できるようにするとデザインが崩れてしまう恐れがあります。よって、以下の2つの案を検討しました。

  • 選択数に制限を持たせる
    • メリット
      • 何が追加されるのか明確にわかる
    • デメリット
      • 制限を持たせることによって、お客様が不便に思うケースが生じる可能性がある
  • 「選択された属性情報」などの表現で代替
    • メリット
      • 選択数に制限がない
    • デメリット
      • 何が追加されるのかが明確に表現できない

これに関してはビジネス側とも連携をとって、どのくらいの選択数が必要かを確認しました。すると、5つあれば十分との回答がありました。また、選択数に制限を持たせることでCSVファイルのサイズも抑えられるので、この方針に決定しました。このように、ビジネスの都合だったり、付随する問題についても考慮して設計を行うのは面白いなと思いました。

フロントエンドの実装

予約画面で追加する属性を選択できるようにしました。 選択し直す際に選択している項目が上にくるようにしたり、検索ボタンをなくして入力するだけで動的に検索結果が表示されるようにするなど、お客様が使いやすいよう工夫しました。

出力予約画面での追加する属性の選択ボタン

追加する属性の選択Modal

バックエンドの実装

管理者週間アクティブ率の出力と流れとしては同じです。

  1. 出力予約リクエストの処理

    選択された属性の数が制限を超えていないか、選択された属性が本当にその組織に存在するのかなどをチェックします。OKであれば予約をDBに格納します。

  2. CSVファイルの生成

    選択された属性の情報、組織のメンバーに対する属性の値をDBから取得します。ユーザの「組織のメンバーID」から属性の値の配列が取得できるMapを作成し、それを使って出力項目に追加していきました。

結果

属性情報を出力項目に追加できるようになり、お客様が手作業で属性情報をCSVに付与する必要がなくなりました。これにより、簡単に属性情報を必要とする分析が可能になり、お客様の苦しみが解決されました!

インターンの感想

この2ヶ月間で、本当に様々なことを学ばせていただきました。開発の観点では、Design Docを書いて、実装をして、テストを書いて、レビューをいただいて修正するという開発の一連のプロセスを経験することができました。その経験を通して、レビューやテストの大切さ、設計をする上では様々なことを考慮しなければならないこと、どのようなコードを書くべきかなどを学ぶことができました。経験の観点では、実際に2ヶ月間チームの一員として働くことで、会社の雰囲気や仕事上のコミュニケーション、プロダクトに対してどのように人が関わっているかなど、仕事に対する具体的なイメージを持つことができました。また、開発をしていると設計や実装において意見がぶつかることがありますが、その時にきちんと自分の意見を伝え、相手の意図を汲み取って尊重し、お互いが気持ちよく合意を取れるように議論をするというのがとても大切だと思いましたし、そのような環境が構築できているモノグサは良い会社だなと思います。 このインターンを通して、エンジニアとしてとても成長できたと感じています。この経験を活かして、今後も成長できるように精進していきます。このような機会を提供してくださったモノグサの皆様に深く感謝を申し上げます。2ヶ月間ありがとうございました!

Monoxer Intern Report #16_文章分割確認ツールの開発

自己紹介

こんにちは。モノグサのソフトウェアエンジニアのインターンに参加した堤歩斗です。普段は東京都立大学でコンピュータサイエンスの学習をしています。モノグサでは1ヶ月半ほどインターンをしていました。 この記事ではインターンを振り返りモノグサで取り組んだことやインターン中のあれこれについて紹介したいと思います。

参加を決めた理由

モノグサを知ったのはICPC2022アジア横浜地区大会のスポンサーブースでお話を聞いたときでした。その他にもAtCoder上でモノグサプログラミングコンテストを開催していたり、CodeQUEENのスポンサーを務めるなど、モノグサは競技プログラミングに理解のある会社なのだなという印象を持っていました。 私は比較的長い期間で本格的な開発に携われるインターンに行ってみたいと思っていました。 そんな中、モノグサの春インターンを見つけました。モノグサのインターンは期間が1ヶ月以上と希望にマッチしており、大学の春休み期間に取り組むのにちょうど良いと思いました。さらに、応募の決め手となったのはAtCoderJobsでの要求ランクがC(AtCoderのレートが緑)でちょうど応募可能だったためです。競技プログラミングで得た機会を生かしてみようと思い応募しました。

取り組んだこと

色々なことに取り組みました。最初はiOSとAndroidのアプリ開発に触れました。そして最後にはwebフロントエンドからバックエンドの開発まで、幅広くフルスタックでの開発を体験できました。そんな中から今回はインターン中にメインで取り組んだテーマについて紹介します。

Monoxerでは、「文章記憶機能」を提供しています。文章記憶機能というのは以下のような機能で、

  • 試験勉強に必要な文章や歴史的に有名な詩、業務に必要なマニュアルやトークスクリプトなど、様々なシーンで記憶が必要な文章が存在しています。
  • モノグサでは、この「ミラーの法則」を参考にして、文章をチャンク(情報のかたまり)に分割したうえで、チャンクごとの記憶を促すための出題する機能を開発いたしました。

引用元:文章をチャンクに分割して記憶する「文章記憶機能」をリリース | Monoxer・解いて憶える記憶アプリ

今回私は文章記憶機能における問題作成時のチェックプロセスを自動化するツールの開発を行いました。

文章記憶機能では、覚えたい元の文章を分割することで問題を作成します。 今回自動化したいチェックプロセスというのは、元の文章を分割して出来た短い文章のブロック(図1のL1に相当)同士で類似したものがないかをチェックするというものです。

図1 文章記憶コンテンツの構造

なぜ、このようなチェックをする必要があったのでしょうか?それは類似したものがあった場合に問題の選択肢にそれらが同時に出現することがあるからです。(図2 似たような選択肢が出現するスクリーンショット)

図2 似たような選択肢が出現するスクリーンショット

このスクリーンショットでは「当社で確認し、」と「当社で確認した」という選択肢が同時に出現しています。この問題の正解は「当社で確認した」なのですが、「当社で確認し、」を選択しても良いように思えます。 このように似たような選択肢が出現すると、文章の内容を十分に記憶出来ているのに誤答と判定されてしまうという問題が発生します。

今回のケースではスクリーンショット中の「当社で確認し、」と「当社で確認した」のような表記揺れや文末の表現の違いがある文章を検出するだけでなく、「迅速に」と「素早く」といったような言い換え表現も検出したいという背景がありました。(図3 検出したい文章の例)

図3 検出したい文章の例

そこで、このチェックを自動で行うために、SentenceTransformerという自然言語処理ライブラリを用いました。

SentenceTransformerは、文などのテキストデータをベクトル表現に変換するためのライブラリです。SentenceTransformerのモデルは、文を入力として受け取り、固定長のベクトルにエンコードします。このベクトル表現を用いることで、文間の類似度を計算することが可能になります。すなわち、SentenceTransformerを用いると文レベルでの意味論的な情報を捉えることが出来ます。 *1

実際には単純にSentenceTransformerで文章の最小単位(図1のL1に相当)同士の類似度を計算するだけではなく、その文章を誤って選んだ場合にどうなるか?という部分を加味して判定するようにしました。具体的には、「正解の選択肢を選んだ場合に完成する元の文章」と「類似した誤答選択肢を選んだ場合に完成する類似した文章」の二つの類似度を計算することで、誤答選択肢として適切かどうかをより正確に判定できるようになりました。

また、このツールの使用者はエンジニアではなく社内のコンテンツを作成する方々になります。そのため、普段のコンテンツ作成で使用しているGoogle スプレッドシートからGUIでの操作が出来るようにしました。そこで、以下の2つを開発しました。

  • 類似度の計算処理等を実行するバックエンドAPI
  • APIを叩き、結果を視覚的に表示するGoogle スプレッドシートのアドオン

また、SentenceTransformerを使用するには、Pythonで提供されているライブラリを利用する必要があります。そのため、Pythonの実行環境が必要です。そこで今回はGCPのCloud Runへ新たにデプロイすることになりました。

Cloud Runはコンテナを直接実行できるマネージドな実行環境で、リクエストベースでの自動スケーリングが行われます。すなわち、リクエストがあった場合にのみ動作し、しばらくリクエストが無い場合にはスケールダウンされるということです。今回作成したいツールはコンスタントにリクエストがあるものではなく、コンテンツ作成の作業時という限られたタイミングでのみ使用できれば良いので、Cloud Runは最適な選択肢でした。さらに、Cloud Runではコンテナを直接実行できるので、実行環境の新規構築としては比較的簡単です。 *2

今回はインフラチームの方と連携しながら、必要な知識を学びつつ、Cloud Runのデプロイにかかるプロセスを習得しました。この過程で、Cloud Runの仕組み、コンテナ技術、GCPの基本操作など、クラウドインフラに関する幅広い知識が得られました。

また、SentenceTransformerを使用するに当たり、パフォーマンスと精度についても課題がありました。デプロイにおいては、これら二つの要素を最適化することが重要な挑戦となりました。応答時間と消費するリソースの改善を精度を犠牲にすることなく、実現する必要があります。そのために、モデルの軽量化やキャッシュの利用などを施しました。このプロセスを通じて、パフォーマンスと精度の間のバランスを取ることの難しさを学びました。

インターンの感想

今回のインターンでは本当に幅広い領域を扱わせていただきました。「どんな文章を検出するべきなのか」というプロダクト理解から、「デプロイやシステム構成」といったインフラ領域、さらにバックエンドとフロントエンドの一通りの実装などに取り組みました。その中で自分で考えた内容に対して様々な方面からレビューをいただき、一つの機能を完成させていくという一連のプロセスが体験できました。主体性を持って幅広い領域の開発を経験できたことは非常に興味深かったです。

さらに、本格的なコードレビューを受けてプロダクトに自身のPullRequestをマージするという経験は学生にはなかなか得られるものではなく、様々な学びがありました。また、PullRequestだけでなく、Design Docという開発にまつわる文書のレビューも社内から広く受けられるモノグサの文化に感心しました。

そして、モノグサでは非常に働きやすい環境が整っているなとも感じました。インターンの最初に支給されるPCのキーボード配列の希望調査があったときから、インターンを終える今日まで、社員のことを第一に考えていることを随所に感じました。

このインターン体験を通じて、幅広いスキルを身につけることができました。また、実務でのコードレビューやプロダクト開発の一端を担うなど、貴重な経験をさせていただき、成長できたと自負しています。技術的なスキルだけでなく、チームで働く重要性や、プロダクト開発の過程における細かな配慮を学ぶ機会もありました。最後に、この機会を提供してくださったモノグサの皆様に心から感謝を申し上げます。ありがとうございました。

Monoxer Intern Report #15_タブレットUIのレスポンシブ化

自己紹介

夏からモノグサでソフトウェアエンジニアインターンをしていました、筑波大学情報科学類一年の甲賀です。

参加を決めた理由

AtCoderのコンテストで会社の存在を知り、AtCoder Jobsの求人から応募しました。 X(Twitter)などで所属を公表している競技プログラマの社員の方がいることや、ハッカソン形式などではなく実務に近い形式で参加できるという点に魅力を感じ参加を決めました。

取り組んだこと

主にアプリUIのレスポンシブ化に取り組みました。 Monoxerのアプリは主に教育現場で使われていることもあり、利用者の過半数がタブレット(特にiPad)ユーザーです。しかし、当時のアプリのUIはタブレットでの表示に最適化されておらず、レイアウトが崩れてしまうことがありました。

私は主に、

  • ホーム画面
  • キーボード入力形式の問題の画面
  • 選択肢形式の問題の画面

の三つの画面についてUIの改善に取り組みました。

iOS版アプリのUIはstoryboardというファイルで管理されています。主にこのファイルにレイアウトについての制約を追加し、必要に応じてコードにも変更を加えていくといった形で開発を進めていきました。デザイナーの方とデザインを相談しながら、最終的には次のようなUIとなりました。

画面にしてみてみると単純ですが、映っているUIの他にも細かい機能や考慮する点が多く、アプリ開発の経験がなかったこともあり、思いの外大変で苦戦しました。 例として、上記のりんごの画像のように問題画像の横幅の最大長を制限する変更をする際、storyboardでは各制約にpriorityを設定でき、矛盾する制約がある場合などにはpriorityの高い制約が優先されます。そのため、単純に「画像の幅を⚪︎px以下にする」というような制約を追加しただけでは、すでにある他の制約が優先されてしまいうまく反映されないといったことが起こります。そのため、矛盾している他の制約を探してpriorityを下げる、もしくは矛盾しないよう制約の条件を変更する、などといった変更が必要でした。

また、メインで取り組んだタスクの他にも、様々な領域で細かいバグ修正といった開発をしました。結果的にはこれらのメインタスク以外のタスクがインターン期間の開発の1/3ほどを占めていた気もします。 Web、サーバー、アプリといった様々な領域で、触れた言語としてはSwift、Kotlin、Scalaと、幅広い分野に新しく触れることができ、とても勉強になりました。

インターンの感想

私は、このインターンに参加するまでは個人開発の経験しかなかったため、自分の書いたコードをちゃんとレビューしていただき、より良い設計方針を提案していただいたくなど、今後の開発やエンジニアとしてのキャリアに役立つような多くの経験を得ることができました。 また、多くの利用者の方の満足度に関わるインパクトの大きいタスクに取り組ませていただいたことが、開発のモチベーションにもつながった気がします。

メンターの方だけでなく、デザイナーの方や他のエンジニア、インターン生など色々な方と関わることができ、 ランチやボドゲなどにも誘っていただき楽しくインターン期間を終えることができました。ありがとうございました!

PG BATTLE 2023に参加して飛び賞をゲットした話

ソフトウェアエンジニアの@tobisatisより、競プロ部の活動報告をお届けします。

2023年10月21日に開催されたPG BATTLE 2023に参加しました。PG BATTLEは、同じ会社内や学校内の3人を1チームとして出場する競技プログラミングの団体戦です。2021年の大会から数えて3度目の参加となりました。

チーム結成

部員数が増えたこともあり、今回は6人で2チームを編成できました。アンケートに回答し、参加意欲の高い順、参加意欲が同じなら提出が早い順で、上から3の倍数人を集めるという手順でメンバーを決めました。チーム名とAtCoder(日本最大の競技プログラミングのサービス)での各メンバーの最高レベル(色)は次の通りです。

  • チーム1「モノグサでいこう」
  • チーム2「Monoxer Blues」

結果

両チームとも240点を記録し、企業の部184位中「モノグサでいこう」が14位、「Monoxer Blues」が20位でした。企業の部では130位までの10位ごと13チームにスポンサー賞が与えられ、「Monoxer Blues」が「国際ソフトウェア賞」を獲得しました。

「Monoxer Blues」、左からscrblbug、tamura2004、konayuki

国際ソフトウェア株式会社様、ありがとうございました!部員一同、この場にて御礼申し上げます。

問題の感想

問題と詳しい解説は、PG Battleの公式サイトに掲載されています。

ましゅまろ

「モノグサでいこう」のましゅまろで参加しました。言語はC#です。

  • 1問目、コイントス

(1/2)Nを求めます。Nが小さいので、1.0をN回2で割ることで十分小さい誤差の値を得られます。

  • 2問目、微分

x^で文字列を分割して数値を得て、問題文にある通りに計算をすればよいです。オーバーフローに注意しましょう。

  • 3問目、2進数と10進数

BigIntegerを使ってBを2進数から10進数に変換し、適当にゼロ埋めを行って文字列で比較しました。組み込みの文字列比較で正しい答えを得られるのですが、1度しか提出できない緊張感のため、本番では上の文字から1文字ずつ比較しました。

  • 4問目、ダンス

「L番目からR番目の人をすべて取り除く操作の数」という小問題を再帰的に解いていくことで求まります。小問題は、左端の人を誰とペアにするかを決めていくことでより小さい問題に帰着させます。状態数O(N2)、遷移O(N)よりO(N3)で解けます。やはり緊張しましたが、入出力例の4が大きいケースだったので、ある程度安心して提出できました。

本番提出コードの一部を掲載します。(NとSの入力、ライブラリを省略)

var v = new bool[n + 1, n + 1];
var f = new ModInt[n + 1, n + 1];
ModInt F(int x, int y)
{
    if (x >= y)
    {
        return 1;
    }
    if (v[x, y]) return f[x, y];
    ModInt res = 0;
    for (var i = x + 1; i < y; i += 2)
    {
        if (s[x] == s[i]) continue;
        var l1 = (i - x - 1) / 2;
        var l2 = (y - 1 - i) / 2;
        var w = F(x + 1, i) * F(i + 1, y);
        var h = (l2 + 1).H(l1 + 1);
        res += w * h;
    }
    v[x, y] = true;
    f[x, y] = res;
    return res;
}
var ans = F(0, n);
return ans;

終わりに

競プロ部の普段の活動としては、主にコンテスト後の感想や考察の共有をslackにて行っています。

興味を持っていただけましたら、ぜひ過去の記事もあわせてご覧ください!