Yuulis.log

Yuulis.log

トンネルを抜けるとそこは参照エラーであった。

【Flet v0.26.0】Fletで作ったアプリケーションをGithub Actionsによって自動でビルド & Releaseとして公開したい

はじめに

以前、 PythonマルチプラットフォームGUI アプリケーションを作ることができるフレームワーク「Flet」について紹介した。

その際、アプリケーションのビルドに関しては手元の Python 仮想環境でコマンドを打って行っていた。

今回はこの作業を Github Actions で自動化したいと思う。

想定する作業の流れ

  1. VScode 上でアプリケーションのコードを書く。
  2. VScode から Git 拡張機能等を利用してローカルに commit & リモートに push
  3. リリース段階に至ったら、該当する commit に Tag を作成する。
  4. tag の作成をトリガーとして、 Github Actions が実行される。この Actions では、以下の処理が行われる。
    1. Release を draft 状態で作成する。
    2. 任意のプラットフォームでビルドを行う。
    3. 作成された実行可能ファイル等(これらはbuild内に格納されている)をまとめて圧縮する。
    4. 圧縮されたファイル群を Release に添付する。
  5. draft 状態の Release が問題なければ手動で公開する。

Workflow の作成

以下の記事を参考に、Github Actions のWorkflow を書いていく。 Workflow の名前は build-flet-cross.ymlとした。

zenn.dev

ただし、 Flet v0.26.0 ではビルド時のコマンドが一部変更されているので、そのまま用いることはできない。

そこで、以下のリポジトリも参照する。

github.com

Workflow のトリガーと環境変数の作成

name: build-flet-cross

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:

env:
  BUILD_NUMBER: 1
  BUILD_VERSION: 0.1.0
  PYTHON_VERSION: 3.13.1
  FLUTTER_VERSION: 3.24.3

on以下で、この Workflow が実行されるトリガーを設定している。今回は、

  • push: > tags: > - "v*" : 「v」から始まる名前の Tag を作成したときに実行する
  • workflow_dispatch : Github の Actions タブから手動で Workflow を実行できるようにする

の2つのトリガーを設定した。


また、環境変数として次の4つを作成した。

  • BUILD_NUMBER : 内部バージョン番号として使用される識別子。各ビルドには、以前のビルドと区別するための一意の識別子が必要で、数字が大きいほど新しいビルドであることを示す。
  • BUILD_VERSION : ユーザーに表示されるバージョン番号。
  • PYTHON_VERSION : 使用する Python のバージョン。
  • FLUTTER_VERSION : ビルドにおいて使用する Flutter SDK のバージョン。

Release の作成

jobs:
  create-release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
      - name: "Create Release"
        run: gh release create ${{github.ref_name}} --draft --verify-tag --notes "Release ${{github.ref_name}}"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Github CLI を使って、コマンドラインから Release を draft 状態で作成する。

マルチプラットフォームビルド

jobs:
  cross-build:
    needs: create-release
    strategy:
      matrix:
        include:
          - runs-on: macos-latest
            binary-name: ${{ vars.APP_NAME }}_macos.tar.gz
            target-platform: macos
          - runs-on: windows-latest
            binary-name: ${{ vars.APP_NAME }}_windows.zip
            target-platform: windows
    runs-on: ${{ matrix.runs-on }}
    permissions:
      contents: write

matrix strategy を利用して、複数のプラットフォームでのビルドを同時並行で処理している。今回は WindowsMac OS でのビルドを行っている。

vars.APP_NAMEには、アプリの名前を環境変数にして代入している。これは、リポジトリの「Settings」から「Security」タブの「Secrets and variables」>「Actions」から設定可能。

Python と Flutter SDK のインストール

jobs:
  cross-build:
    steps:
      - name: Checkout Codes
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}

      - name: Set environment variable for UTF-8
        run: echo "PYTHONUTF8=1" >> $env:GITHUB_ENV

      - name: Install Python Dependencies
        run: |
          python -m pip install --upgrade pip  
          pip install .

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
          channel: 'stable'

PYTHON_VERSIONで指定した Python バージョンを、 actions/setup-pythonを利用してインストールする。

github.com

また、

echo "PYTHONUTF8=1" >> $env:GITHUB_ENV

で、文字コードUTF-8 に設定する。これをしないと、 Windows での Flet インストール時にエラーが出る。

そして、pyproject.tomlを利用して Python の依存パッケージをインストールした後、actions/flutter-actionで Flutter SDK をインストールする。

github.com

Windows でのビルド

jobs:
  cross-build:
    steps:
      - name: Windows Build
        if: runner.os == 'Windows'
        run: |
          chcp 65001
          flutter config --no-analytics
          flet build windows --verbose --no-rich-output --build-number=${{ env.BUILD_NUMBER }} --build-version=${{ env.BUILD_VERSION }}
          Compress-Archive -Path ./build/windows/* -DestinationPath ./build/windows/${{ matrix.binary-name }}
      - name: Upload Windows Artifact
        if: runner.os == 'Windows'
        uses: actions/upload-artifact@v4.3.4
        with:
          name: windows-build-artifact
          path: build/windows
          if-no-files-found: error
          overwrite: false

flet build windowsでパッケージ化した実行ファイル群は、build/windows以下に生成される。これを zip ファイル化し、 Artifact としてアップロードしている。

buildコマンドのオプションについては公式ドキュメントを参照。

Mac OS でのビルド

jobs:
  cross-build:
    steps:
      - name: MacOS Build
        if: runner.os == 'macos'
        run: |
          flutter config --no-analytics
          flet build macos --verbose --build-number=${{ env.BUILD_NUMBER }} --build-version=${{ env.BUILD_VERSION }}
          tar -zcvf ./build/macos/${{ matrix.binary-name }} -C ./build/macos .
      - name: Upload MacOS Artifact
        if: runner.os == 'macos'
        uses: actions/upload-artifact@v4.3.4
        with:
          name: macos-build-artifact
          path: build/macos
          if-no-files-found: error
          overwrite: false

やっていることは Windows の場合と同じ。

Release へのアップロード

jobs:
  cross-build:
    steps:
      - name: Upload Release
        if: runner.os == 'Windows'
        run: gh release upload ${{ github.ref_name }} ./build/windows/${{ matrix.binary-name }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Upload Release
        if: runner.os == 'macos'
        run: gh release upload ${{ github.ref_name }} ./build/macos/${{ matrix.binary-name }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

先ほど Artifact としてアップロードした実行ファイル群を、最初に作成した Release にアタッチする。


ここまでの内容を1つのまとめると以下のようになる。

name: build-flet-cross

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:

env:
  BUILD_NUMBER: 1
  BUILD_VERSION: 0.1.0
  PYTHON_VERSION: 3.13.1
  FLUTTER_VERSION: 3.24.3

jobs:
  create-release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
      - name: "Create Release"
        run: gh release create ${{github.ref_name}} --draft --verify-tag --notes "Release ${{github.ref_name}}"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  cross-build:
    needs: create-release
    strategy:
      matrix:
        include:
          # - runs-on: ubuntu-latest
          #   binary-name: ${{ vars.APP_NAME }}_linux.tar.gz
          #   target-platform: linux
          - runs-on: macos-latest
            binary-name: ${{ vars.APP_NAME }}_macos.tar.gz
            target-platform: macos
          - runs-on: windows-latest
            binary-name: ${{ vars.APP_NAME }}_windows.zip
            target-platform: windows
    runs-on: ${{ matrix.runs-on }}
    permissions:
      contents: write
    steps:
      - name: Checkout Codes
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}

      - name: Set environment variable for UTF-8
        run: echo "PYTHONUTF8=1" >> $env:GITHUB_ENV

      - name: Install Python Dependencies
        run: |
          python -m pip install --upgrade pip  
          pip install .

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
          channel: 'stable'

      - name: Windows Build
        if: runner.os == 'Windows'
        run: |
          chcp 65001
          flutter config --no-analytics
          flet build windows --verbose --no-rich-output --build-number=${{ env.BUILD_NUMBER }} --build-version=${{ env.BUILD_VERSION }}
          Compress-Archive -Path ./build/windows/* -DestinationPath ./build/windows/${{ matrix.binary-name }}
      - name: Upload Windows Artifact
        if: runner.os == 'Windows'
        uses: actions/upload-artifact@v4.3.4
        with:
          name: windows-build-artifact
          path: build/windows
          if-no-files-found: error
          overwrite: false

      - name: MacOS Build
        if: runner.os == 'macos'
        run: |
          flutter config --no-analytics
          flet build macos --verbose --build-number=${{ env.BUILD_NUMBER }} --build-version=${{ env.BUILD_VERSION }}
          tar -zcvf ./build/macos/${{ matrix.binary-name }} -C ./build/macos .
      - name: Upload MacOS Artifact
        if: runner.os == 'macos'
        uses: actions/upload-artifact@v4.3.4
        with:
          name: macos-build-artifact
          path: build/macos
          if-no-files-found: error
          overwrite: false

      # - name: Patch for linux build
      #   run: |
      #     flutter doctor
      #     sudo apt-get update -y
      #     sudo apt-get install -y ninja-build libgtk-3-dev
      #     flutter doctor
      # - name: Flet Build Linux
      #   run: |
      #     flutter config --no-analytics
      #     flet build linux --verbose --build-number=${{ env.BUILD_NUMBER }} --build-version=${{ env.BUILD_VERSION }}
      #     tar --warning=no-file-changed -zcvf ./build/linux/${{ matrix.binary-name }} -C ./build/linux .
      # - name: Upload Linux Artifact
      #   uses: actions/upload-artifact@v4.3.4
      #   with:
      #     name: linux-build-artifact
      #     path: build/linux
      #     if-no-files-found: error
      #     overwrite: false 
      
      - name: Upload Release
        if: runner.os == 'Windows'
        run: gh release upload ${{ github.ref_name }} ./build/windows/${{ matrix.binary-name }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Upload Release
        if: runner.os == 'macos'
        run: gh release upload ${{ github.ref_name }} ./build/macos/${{ matrix.binary-name }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      # - name: Upload Release
      #   if: runner.os == 'Linux'
      #   run: gh release upload ${{ github.ref_name }} ./build/linux/${{ matrix.binary-name }}
      #   env:
      #     GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Linux でのビルドも一応書いたのだが、うまく動かなかったのでコメントアウトしている。

Release の公開

Actions が正常に終了したら、 draft 状態の Release を公開する。

実行ファイル群が圧縮された状態で添付されている

終わりに

Github Actions を利用することで、ビルド作業で手を止めることなく開発に集中できるようになった。

コードを少し書き換えれば、WindowsMac OS だけでなく他のプラットフォームでも自動ビルドができそう。

【Flet v0.26.0】Python + FletでリッチなGUIアプリケーションを手軽に作ってみる ~tkinterはもう古い!?~

はじめに

突然だが、

Python の豊富なライブラリ群を生かして GUI アプリケーションを作りたい!

こう思ったことはないだろうか。


Python には、古くから tkinter という GUI ツールキットが標準ライブラリとして搭載されてきた。

しかし、使ってみたことがある人ならわかるだろうが、 tkinterGUI のデザインはあまりにもダサすぎると私は思うのだ。

どうせ自作で作るなら、 GoogleApple のアプリでつかわれているようなリッチなデザインの GUI を使いたい。

そんな欲望を満たしてくれるフレームワークがこの世には存在する。その名も Flet である。


ということで本記事では、 Pythonフレームワーク Flet に関する紹介と、開発環境構築からアプリケーションのビルドまでの流れを書くことにする。

なお、 Flet の詳しい機能や細かい処理の流れなどについては、この記事では詳しく扱わない。幸いなことに公式ドキュメントがある程度充実しているので、そちらを参照のこと。

また、開発環境構築に関連する内容は Windows を対象としているので悪しからず。

Flet とは何か?

Flet は、フロントエンドの知識なしに、 Python のみで Web やデスクトップ、モバイルといった様々なプラットフォームのアプリケーションを作成できるフレームワークである。

UI は Google が開発する Flutter をベースにした「Control」と呼ばれるコンポーネントを使用して構築される。 HTML や CSSJavascript といった他の言語の知識は一切必要ない。

主要なライブラリパッケージはfletのみで、 Python 環境上でpipコマンドを一発打つだけですぐに開発に移ることができる。


ただ、良い面もあれば悪い面もあるわけで。

まず、 Python という言語の特性上、大規模・複数人での開発に向いていないという点がある。また、実行速度もそれほど早いわけではないので、大量のデータをリアルタイムに処理したり、複雑な UI を組み合わせたりするようなアプリケーションの作成には向いていない。

さらに、フレームワークのバージョンがまだ0.xであり、正式版ではない。そのため、将来のバージョンで破壊的な変更が加えられる可能性がある。


これらのメリット・デメリットを見てみると、個人で小規模な作業時短ツールを Python でサクッと作りたいというときに採用するのが一番良さそうだ。

Flet の開発環境構築

ここからは、実際に Flet を触っていこう。

...とその前に、開発環境の構築をしていく。

Python 仮想環境の作成

今回は Python 自体のバージョン管理に pyenv を、ライブラリのバージョンの管理に venv を用いることにする。

基本的には以下の記事に沿って進めていくが、 venv 自体は Python にデフォルトで備わっているので、インストールするのは実質 pyenv のみ。

zenn.dev

pyenv-win のインストール

早速 pyenv をインストール... と言いたいところだが、 pyenv は Windows には対応していないため、代わりに pyenv-win を用いる必要がある。

> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine

「実行ポリシーの変更」について確認がなされるが、[Y] はい(Y)でよい。

  • Windows PowerShell を再起動したら、次のコマンドで pyenv-win をインストール。
> Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"; &"./install-pyenv-win.ps1"

以下のコマンドでインストール可能な Python バージョンが列挙されたら、 pyenv のインストールは成功。

> pyenv install --list


次に、 Python をインストールしていこう。 Flet v0.26.0 では Python 3.9 以上での動作を保証しているようなので、とりあえず現時点での最新バージョンを入れておけば問題ないだろう。この記事の執筆時点では 3.13.1 が最新であった。

  • 次のコマンドを実行。
> pyenv install 3.13.1

:: [Info] :: completed! 3.13.1と表示されたら成功。

> pyenv versions

を実行すると、インストール済みの Python のバージョンが一覧で表示される。

venv のインストール

続いて、 venv のインストールを行う。今回は、 Flet の公式ドキュメントに沿って、プロジェクトディレクトリの配下に.venvディレクトリを作成することにする。

  • まず、お好みの場所にプロジェクトディレクトリを作り(今回はflet-testとした)、その中に移動する。そして、以下のコマンドを実行する。
> python -m venv .venv

特に実行結果が表示されることはない。

  • 次のコマンドで仮想環境を起動させる。
> .venv\Scripts\activate

(.venv) >のような形で入力待ちになれば成功。以降のコマンドは全て仮想環境内で実行する。

  • このプロジェクトで使用する Python のバージョンを次のコマンドで指定する。
> pyenv local 3.13.1

次のコマンドを実行して、指定したバージョンが返ってくれば成功。

> pyenv version

Flet のインストール

さて、いよいよ本命の Flet をインストールしていく。

  • 次のコマンドを実行する。
> pip install flet[all]

以下のコマンドでバージョン情報が返ってくれば成功。

> flet --version

...これで Flet の開発環境の構築は完了。簡単すぎて拍子抜けしてしまった。

簡単なアプリケーションを作ってみる

環境構築が完了したので、簡単な Flet アプリケーションを作ってみよう。

  • 以下のコマンドで、Flet アプリケーションの雛形を作成する。
> flet create

ディレクトリ構成は以下のようになるはず。

flet-test
├ .venv (中身は省略)
├ src
│  ├ assets
│  │  └ icon.png
│  └ main.py
├ .gitignore
├ .python-version
├ pyproject.toml
└ README.md

ここからmain.pyを書き換えてアプリケーションを作っていくわけだが、実はこの時点で動くものはできている。

  • 以下のコマンドでアプリケーションを実行。
> flet run

Python スクリプトの名前はmain.pyでなくてもよいが、その場合は flet run <スクリプト名>で実行する必要がある。

右下のボタンで数字を増やせるカウンターアプリ

正直これだけだと味気ないが、公式ドキュメントには様々なチュートリアルが掲載されている。

色々な Control が用意されているので、 Python のライブラリと組み合わせれば、作ることができるアプリの幅はかなり広いと言えるだろう。

アプリケーションのビルド

先述した Flet の強みとして、「クロスプラットフォーム対応」というものがあった。

ということで、先ほど作ったカウンターアプリを配布すると仮定して、ビルド作業をしてみよう。

ここでは、 Web アプリケーションビルドWindows アプリケーションビルドの2通りの方法を紹介していく。

Web アプリケーションビルド

Web アプリケーションビルドは、どの OS からでも可能である。

  • 以下のコマンドを実行する。ただし、このコマンドでビルを行う際は、プロジェクトディレクトリの構成が先述の通りになっているかを確認すること。
> flet build web 

実行後は、何も問題がなければ以下のような表示が出るはず(各 OS での初回のビルド時は、 Flutter SDK のインストールが行われる)。

[15:33:46] Flutter 3.27.3 installed ✅
[15:33:49] Created Flutter bootstrap project from gh:flet-dev/flet-build-template with ref "0.26.0" ✅
[15:33:57] Packaged Python app ✅
[15:33:59] Customized app icons and splash images ✅
[15:34:02] Generated app icons ✅
[15:34:05] Generated splash screens ✅
[15:34:10] Built web app ✅
           Copied build to build\web directory ✅
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Successfully built your web app! 🥳 Find it in build\web directory. 📁                                                                                                     │
│ Run python -m http.server --directory build\web command to start dev web server with your app.                                                                              │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
  • 続いて、以下のコマンドを実行して Web アプリケーションを起動する。
> python -m http.server --directory build\web

その後、http://localhost:8000にアクセスしてみよう。

先ほどflet runを実行したときに現れたウィンドウと同じ内容が表示された。これでビルド作業は終了である。


ビルドした Web アプリケーションのホスティングについては、公式ドキュメント等を参照のこと。

Windows アプリケーションビルド

Windows のアプリケーションビルドを行う際は、開発者モードを有効にした上で、前提要件を満たしている必要がある。

「設定」の「開発者向け」カテゴリから、開発者モードを有効化

  • 以下のコマンドで、その前提要件を満たしているか確認する。
> flutter doctor

例えば、こんな表示になるはず。

Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.24.3, on Microsoft Windows [Version 10.0.22631.4890], locale ja-JP)
[√] Windows Version (Installed version of Windows is version 10 or higher)
[X] Android toolchain - develop for Android devices
    X Unable to locate Android SDK.
      Install Android Studio from: https://developer.android.com/studio/index.html
      On first launch it will assist you in installing the Android SDK components.
      (or visit https://flutter.dev/to/windows-android-setup for detailed instructions).
      If the Android SDK has been installed to a custom location, please use
      `flutter config --android-sdk` to update to that location.

[√] Chrome - develop for the web
[!] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.12.2)
    X Visual Studio is missing necessary components. Please re-run the Visual Studio installer for the "Desktop development with C++" workload, and include these components:  
        MSVC v142 - VS 2019 C++ x64/x86 build tools
         - If there are multiple build tool versions available, install the latest
        C++ CMake tools for Windows
        Windows 10 SDK
[!] Android Studio (not installed)
[√] VS Code (version 1.97.1)
[√] Connected device (3 available)
[√] Network resources

! Doctor found issues in 3 categories.

色々出てきたが、指示は丁寧なので書いてあることに従えばよい。

今回の例でいうと

ということなので、順に入れていく。

Android Studio のインストール

色々チェック項目が出てくるが、とりあえずデフォルトのままで進めて OK 。

  • インストールが終わったら、続けて Android Studio のセットアップを行う。

こちらも色々項目が存在するが、デフォルトのまま進めてしまおう。ここは結構時間がかかった。

さらに、 Android Studioコマンドラインツールをインストールする。

  • 上の画像の「More Actions」から「SDK Manager」を選択。
  • 「Languages & Frameworks」から「Android SDK」を選択し、「SDK Tools」タブを開き、「Android SDK Command-line Tools (latest)」にチェックマークを入れて、「Apply」を押す。


  • ここまで終わったら、一度flutter doctorを実行してみる。
[!] Android toolchain - develop for Android devices (Android SDK version 35.0.1)
    ! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses

Android toolchain の部分の表示が変わった。どうやらライセンスへの同意が必要らしい。

  • 言われている通りコマンドを実行する。
> flutter doctor --android-licenses

全て終わるとこのような表示が出る。

All SDK package licenses accepted

Visual StudioC++ デスクトップ開発関連のワークロードを追加する

続いて、 Visual StudioC++ デスクトップ開発関連のワークロードを追加しよう。なお、まだ Visual Studio がインストールされていない場合は、以下のリンクからインストールしよう。

visualstudio.microsoft.com

  • Visual Studio Installer を起動し、インストール済みのバージョンの「変更」を押す。

最新バージョンへの更新はしなくてもかまわない

  • 「ワークロード」タブから「デスクトップとモバイル」カテゴリにある「C++によるデスクトップ開発」コンポーネントを選択。右側にインストールされる内容が表示されるので、デフォルトのままインストールへ。
  • 終わったら、再度flutter doctorを実行。
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.24.3, on Microsoft Windows [Version 10.0.22631.4890], locale ja-JP)
[√] Windows Version (Installed version of Windows is version 10 or higher)
[√] Android toolchain - develop for Android devices (Android SDK version 35.0.1)
[√] Chrome - develop for the web
[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.12.2)
[√] Android Studio (version 2024.2)
[√] VS Code (version 1.97.1)
[√] Connected device (3 available)
[√] Network resources

• No issues found!

このような表示が出れば OK 。

ビルド

ようやく Windows ビルドの準備が整った。

  • 次のコマンドで Windows アプリケーションビルドを行う。
> flet build windows
[15:17:02] Flutter 3.27.3 installed ✅
[15:17:09] Created Flutter bootstrap project from gh:flet-dev/flet-build-template with ref "0.26.0" ✅
[15:17:26] Packaged Python app ✅
[15:17:29] Customized app icons and splash images ✅
[15:17:32] Generated app icons ✅
[15:19:05] Built Windows app ✅
[15:19:07] Copied build to build\windows directory ✅
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Successfully built your Windows app! 🥳 Find it in build\windows directory. 📁                                                                                             │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

実行ファイルはbuild\windows内にflet-test.exeとして格納されている。これを起動すると...

無事成功した。

おわりに

ということで今回は、 PythonGUI アプリケーションを手軽に作成できるフレームワーク「Flet」の紹介と、環境構築~ビルドまでの流れをまとめた。

このフレームワークはまだまだ開発段階にあり、コミュニティもホットである。今後の発展に期待したいところだ。

【AtCoder】ABC 385 D - Santa Claus 2 | 緑コーダーが解くAtCoder

atcoder.jp

実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: 1171

問題概要

2次元平面上の  N (X_1,Y_1),\cdots,(X_N,Y_N) に家が建っている。最初、点  (S_x,S_y) にサンタクロースがいて、列  (D_1,C_1),\cdots,(D_M,C_M) に従って以下の行動を行う。

  •  i=1,2,\cdots,M の順に以下のように移動する。なお、現在サンタクロースがいる点を  (x,y) とする。
    •  D_i =Uなら、 (x,y) から  (x,y+C_i) に直線で移動する。
    •  D_i =Dなら、 (x,y) から  (x,y-C_i) に直線で移動する。
    •  D_i =Lなら、 (x,y) から  (x-C_i,y) に直線で移動する。
    •  D_i =Rなら、[tex (x,y)] から  (x+C_i,y) に直線で移動する。

行動を終えたあとにサンタクロースがいる点と、行動により通過または到達した家の数を求めよ。ただし、同じ家を複数回通過または到達してもそれらは重複して数えない。

制約

  • 与えられる数値は全て整数。
  •  1 \leq N, M \leq 2\times 10^5
  •  -10^9 \leq X_i,Y_i \leq 10^9
  •  (X_i,Y_i) は相異なる。
  •  -10^9 \leq S_x,S_y \leq 10^9
  •  (S_x,S_y) に家は建っていない。
  •  1 \leq C_i \leq 10^9

考察

サンタクロースは各行動で軸平行な向きにしか移動しないので、家の位置もそれぞれの軸の位置でまとめて管理しておくことにする。具体的には、

  •  \mathrm{housesOnX}_X :=  x 座標が  X である家の  y 座標の集合
  •  \mathrm{housesOnY}_Y :=  y 座標が  Y である家の  x 座標の集合

というような、map<long long, set<long long>>型のデータ構造を用意する。

こうすることで、例えば  (X, Y) \rightarrow (X + dx, Y) \: (dx \gt 0) とサンタクロースが移動するとき、通過・到達した家の数は  \mathrm{housesOnY}_Y の中の  X 以上  X + dx 以下の要素数 ( k とする) に対応することになる。


ここで、mapvaluesetにしていることがポイントで、 このsetに対して  X をキーとして二分探索 (lower_bound) し、そこからイテレータを1個ずつずらしていくことで、  O(k \log N) 時間で  k 個の要素を列挙できる。

列挙した各要素はsetからeraseすることで、家の二重カウントを防ぐ。

以上の操作を各方向に対して行っていくことで、全体としては  O(N \log N) でこの問題を解くことができる。


以下、実装時の注意点を少しだけ書いておく。

まず、setに対して二分探索を行うときにstd::set::lower_boundを使うようにするということだ。

普段使っているstd::lower_boundsetに対して用いると、実行速度が極端に遅くなってしまう (1敗) 。詳しくは以下の記事を参照。

qiita.com


また、std::set::eraseが削除された次の要素を指すイテレータを返すという仕様があるため、通過した家をsetから除くときは

itr = houses_on_x[pos_x].erase(itr);

のように書いている。

cpprefjp.github.io

コード

#include <bits/stdc++.h>
using namespace std;

using ll = long long;

#define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++)

// ======================================== //

int main()
{
    int N, M;
    ll Sx, Sy;
    cin >> N >> M >> Sx >> Sy;
    map<ll, set<ll>> houses_on_x, houses_on_y;
    rep(i, 0, N)
    {
        ll X, Y;
        cin >> X >> Y;
        houses_on_x[X].insert(Y);
        houses_on_y[Y].insert(X);
    }

    ll pos_x = Sx, pos_y = Sy, ans = 0;
    rep(i, 0, M)
    {
        char D;
        ll C;
        cin >> D >> C;

        ll dx = 0, dy = 0;
        if (D == 'U')
            dy = C;
        else if (D == 'D')
            dy = -C;
        else if (D == 'L')
            dx = -C;
        else if (D == 'R')
            dx = C;

        if (dx == 0)
        {
            ll min_y = min(pos_y, pos_y + dy);
            ll max_y = max(pos_y, pos_y + dy);

            auto itr = houses_on_x[pos_x].lower_bound(min_y);
            while (itr != houses_on_x[pos_x].end() && *itr <= max_y)
            {
                ans++;
                houses_on_y[*itr].erase(pos_x);
                itr = houses_on_x[pos_x].erase(itr);
            }
        }
        else if (dy == 0)
        {
            ll min_x = min(pos_x, pos_x + dx);
            ll max_x = max(pos_x, pos_x + dx);

            auto itr = houses_on_y[pos_y].lower_bound(min_x);
            while (itr != houses_on_y[pos_y].end() && *itr <= max_x)
            {
                ans++;
                houses_on_x[*itr].erase(pos_y);
                itr = houses_on_y[pos_y].erase(itr);
            }
        }

        pos_x += dx;
        pos_y += dy;
    }

    cout << pos_x << " " << pos_y << " " << ans << endl;
}

atcoder.jp

実装時間: 30分


類題としては ABC370-D が挙げられるか?

【AtCoder】ABC 385 C - Illuminate Buildings | 緑コーダーが解くAtCoder

atcoder.jp

実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: 446

問題概要

 N 棟のビルが等間隔に一列に並んでおり、手前から  i 番目のビルの高さは  H_i である。次の条件をともに満たすようにいくつかのビルを選んで電飾で飾るとき、最大でいくつのビルを選ぶことができるか求めよ。なお、ちょうど1つのビルを選んだときは条件を満たすとみなす。

  • 選んだビルたちは高さが等しい。
  • 選んだビルたちは等間隔に並んでいる。

制約

    • 入力は全て整数。
  •  1 \leq N \leq 3000
  •  1 \leq H_i \leq 3000

考察

以下のような dp テーブル (dp<map<int, int>>) を用意して、動的計画法により解く。

  •  \mathrm{dp}_{i, d} :=手前から  i 番目のビルを含み、間隔が  d で高さが  H_i のビルを選んでいくときの最大選択数

遷移は以下のようになる。

  •  i = 1, 2, \cdots, N に対して、ビル  j = 1, 2, \cdots, i が含まれるグループを見ていく。 H_i \neq H_j の場合はビル  i は選べないのでスキップするとして、そうでないとき、
  •  \mathrm{dp}_{j} d = j - i が含まれるとき、 \mathrm{dp}_{j, d} のグループにビル  i を追加することができるので、  \mathrm{dp}_{i, d} = \mathrm{dp}_{j, d} + 1 と遷移する。
  • そうでないとき、ビル  i, j の2つで新たにグループを作ることになるので、  \mathrm{dp}_{j, d} = 2 と遷移する。
  • 上の2つの判定後、ans = max(ans, dp[j][d])で更新する。

コード

#include <bits/stdc++.h>
using namespace std;

#define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++)

template <typename T>
inline bool chmax(T &a, T b) { return ((a < b) ? (a = b, true) : (false)); }

// ======================================== //

int main()
{
    int N;
    cin >> N;
    vector<int> H(N);
    rep(i, 0, N) cin >> H[i];

    vector<map<int, int>> dp(N);
    int ans = 1;
    rep(i, 0, N)
    {
        rep(j, 0, i)
        {
            if (H[j] == H[i])
            {
                int d = i - j;
                if (dp[j].contains(d))
                {
                    dp[i][d] = dp[j][d] + 1;
                }
                else
                {
                    dp[i][d] = 2;
                }

                chmax(ans, dp[i][d]);
            }
        }
    }

    cout << ans << endl;
}

atcoder.jp

実装時間: 15分

【AtCoder】ABC 385 B - Santa Claus 1 | 緑コーダーが解くAtCoder

atcoder.jp

実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: 77

問題概要

 H \times W のマス目があり、上から  i 行目、左から  j 列目のマスをマス  (i,j) と表す。マス  (i,j) S_{i,j} =#のとき通行不可能、. のとき通行可能であり家が建っていない、@のとき通行可能であり家が建っていることを表す。

最初、マス  (X,Y) にサンタクロースがおり、文字列  T に従って以下の行動を行う。

  • 現在サンタクロースがいるマスを  (x,y) とする。  i = 1, 2, \cdots, |T| について、
    •  T_i =Uかつマス  (x-1,y) が通行可能ならマス  (x-1,y) に移動する。
    •  T_i =Dかつマス  (x+1,y) が通行可能ならマス  (x+1,y) に移動する。
    •  T_i =Lかつマス  (x,y-1) が通行可能ならマス  (x,y-1) に移動する。
    •  T_i =Rかつマス  (x,y+1) が通行可能ならマス  (x,y+1) に移動する。
    • それ以外の場合、マス  (x,y) に留まる。

行動を終えたあとにサンタクロースがいるマスと、行動により通過または到達した家の数を求めよ。ただし、同じ家を複数回通過または到達してもそれらは重複して数えない。

制約

  • 与えられる数値は全て整数。
  •  3 \leq H,W \leq 100
  •  1 \leq X \leq H
  •  1 \leq Y \leq W
  • 全ての  1 \leq i \leq H について  S_{i,1},S_{i,W} =#
  • 全ての  1 \leq j \leq W について  S_{1,j},S_{H,j} =#
  •  S_{X,Y}=.
  •  TU, D, L, Rのいずれかからなる長さ  1 以上  10^4 以下の文字列。

考察

基本的には、サンタクロースの動きをシミュレーションしていき、各行動毎に到着したマスが家かどうかを判定してカウントしていけばよい。

ただし、「同じ家を複数回通過または到達してもそれらは重複して数えない」とあるので、一度訪れた家のマスは@から.に置き換えるなどの処理が必要。家を破壊する物騒なサンタ...

コード

#include <bits/stdc++.h>
using namespace std;

#define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++)

// ======================================== //

int main()
{
    int H, W, X, Y;
    cin >> H >> W >> X >> Y;
    X--, Y--;
    vector<string> S(H);
    rep(i, 0, H) cin >> S[i];
    string T;
    cin >> T;

    int cnt = 0;
    rep(i, 0, T.size())
    {
        if (T[i] == 'U')
        {
            if (X == 0 || S[X - 1][Y] == '#')
                continue;
            X--;
        }
        else if (T[i] == 'D')
        {
            if (X == H - 1 || S[X + 1][Y] == '#')
                continue;
            X++;
        }
        else if (T[i] == 'L')
        {
            if (Y == 0 || S[X][Y - 1] == '#')
                continue;
            Y--;
        }
        else if (T[i] == 'R')
        {
            if (Y == W - 1 || S[X][Y + 1] == '#')
                continue;
            Y++;
        }

        if (S[X][Y] == '@')
        {
            cnt++;
            S[X][Y] = '.';
        }
    }

    cout << X + 1 << " " << Y + 1 << " " << cnt << endl;
}

atcoder.jp

実装時間: 5分