名もなき未知

エンジニアリングとか、日常とかそういうのをまとめる場所。アクセス解析のためGAを利用、Googleに情報を送信しています。商品紹介のためAmazonアフィリエイトを利用、Amazonに情報を送信しています。記事に関しては私が書いていない引用文を除いて自由にご利用ください。

自分の環境で WSL2 + Marp を使って pdf を出力するスクリプト

これの続き。

namonakimichi.hatenablog.com

書いたshell

環境変数渡したり、なんかもはや pdf ファイルは一定のディレクトリ内に格納されたほうが便利かなと思って下記を書いた。

英語は感覚で書いているのでめちゃくちゃな気がする。

#!/bin/sh

if [ $# -eq 0 ] || [ $# -gt 2 ]; then
    cat << EOS 
How to use:
    Single File Mode:
    ex) ./build_pdf.sh test-slide/slide.md
    
    Create pdf file in output dir.
    For example input, output pdf file name is slide.pdf

    Optinal mode:
    ex) ./build_pdf.sh test-slide/slide.md out.pdf
    Create pdf file in output dir, output file name is out.pdf
EOS
    exit 1
fi

OUTPUT_DIRNAME="./output/"
if [ ! -e "$OUTPUT_DIRNAME/" ]; then
    mkdir $OUTPUT_DIRNAME
    touch "$OUTPUT_DIRNAME/.keep"
    echo "Create Dir: $OUTPUT_DIRNAME"
fi

OUTPUT_FILENAME=$2
if [ -z "$OUTPUT_FILENAME" ]; then
    OUTPUT_FILENAME=${1##*/}
    OUTPUT_FILENAME=${OUTPUT_FILENAME%.*}.pdf
fi

CHROME_PATH=$(which google-chrome-stable) pnpx marp "$1" -o "$OUTPUT_DIRNAME$OUTPUT_FILENAME"

run script で解決してみる

ところで npm run の形で環境変数を渡す手段もありそうだ。

とりあえず pakage.json にこんな感じの記載をして、固定して渡す環境変数を定義してしまう。

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "marp": "CHROME_PATH=$(which google-chrome-stable) marp"
  },

これで pnpm run marp したら marp が動く( pnpm 試してるので npm じゃないんですが、コマンド体系的に似てて、この動作については一緒なので以後、 pnpm になっていますが、お気になさらず)。

で、引数を雑に渡すと失敗する。

❯ pnpm run marp test-slide/slide.md -o output.pdf
 ERROR   ERROR  Unknown option: 'o'
For help, run: pnpm help run

下記の記事を参考に、引数の渡し方を変えたらできた。 run script では必須の知識ではないだろうか…。

qiita.com

❯ pnpm run marp test-slide/slide.md -- -o output.pdf

> marp-backup@1.0.0 marp /home/yumechi/work/github.com/yumechi/marp-backup
> CHROME_PATH=$(which google-chrome-stable) marp "test-slide/slide.md" "-o" "output.pdf"

[  INFO ] Converting 1 markdown...
[  INFO ] test-slide/slide.md => output.pdf

一応公式のページ

docs.npmjs.com

感想

まず npm run する方向性で考えればよかったですねという反省。まあ久々に shell 書いて勉強になったのでよしとしましょう。

shell 、すぐ書き方なり典型的な処理を忘れてしまうんので、今年こそまとめようと思います…。

追記

今書いてるのは private でいろいろやらないといけないものなので見せられないのですが、時間ができたら自分用の slide 公開場としてテンプレートっぽいディレクトリを作ろうと思います。

WSL2 + Marp で pdf を何とか出力できるようにする

下記はメモ

前提の課題として、 Marp で pdf を出そうとしたのだが、こんな感じにエラーが出る。 html を

❯ pnpx marp test-slide/slide.md --pdf
[  INFO ] Converting 1 markdown...
[ ERROR ] Failed converting Markdown. (connect ECONNREFUSED 127.0.0.1:54872)

何のことやらという感じであるが、 marp-team/marp-cli: A CLI interface for Marp and Marpit based converters を見ると、pdf, pptx を出すために Chrome Google なりそれに類するブラウザが必要なようです。

で、ブラウザを表示するためには WSL2 上から chrome が起動できる必要があるが、これに関しては以前、「Xサーバーアプリが必要なので、VcXsrv を入れてみる。」あたりで設定している。今回もこう言った設定が必要となる。

namonakimichi.hatenablog.com

Google ChromeCUI ベースで入れる方法としては下記の方法を参考にした(他の記事も読んだが、追加するキーも方法も一緒だったので、まあ良いことにした)(ぱっと Google のサイトから探せなかったので、妥協した)。

qiita.com

このセットアップを行って VcXsrv を起動した状態で、 google-chrome を terminal からたたくと、Chrome が起動する。

最後に、下記の issue を参考に CHROME_PATH を指定し、起動する。

github.com

ちなみに絶対的なパス指定でなくても、 which google-chrome とかで行けそうな気がしたので、試したらできた。

❯ CHROME_PATH=$(which google-chrome-stable) pnpx marp test-slide/slide.md -o output.pdf
[  INFO ] Converting 1 markdown...
[  INFO ] test-slide/slide.md => output.pdf

というわけでOK。

ところで

なんかもはや Docker でやったほうが早かったのではないか、という説がある。

ABC195に参加しました

2年ぶりくらいにコンテストに参加しました。まあ気が向いたのと、たまたま時間が空いたので(ただ、しばらく今後も土日のこの時間予定が入ってて参加できなさそう)、参加してみました。

当日は4完でしたが、できたものを考えるともっと早く解けないといけないかなという感じですね…。

Fは無理そうでしたが、時間があればEは惜しいところまで行けたんじゃないかなと思います(解けるとは言っていない)

A問題

A - Health M Death

剰余を取ります。提出は Scala

import java.util.Scanner

object Main extends App {
  val sc = new Scanner(System.in)

  val m = sc.nextInt
  val h = sc.nextInt
  println(if(h % m == 0) "Yes" else "No")

}

B問題

B - Many Oranges

答え見たら分析方法が違ったのであれですが…。

みかんの重さで表現しうるのはN個選んだ時に AN ~ BN になるので、その範囲にないときは問答無用で UNSATISFIABLE にしました( AN~BN にしうる範囲では、起点を AN として、適当にミカンの重さを1gづつ調整できれば多分表現しうると考えたので)。

それ以外の場合は、まあ最小個数を math.floor(w.toDouble / a).toInt 、最大個数を math.ceil(w.toDouble / b).toInt として雑に出しました。

本当にこれでいいのかは不安だったので、かなり心配でした。想定解は N の全探索みたいですね。

提出は Scala

import java.util.Scanner

object Main extends App {
  val sc = new Scanner(System.in)

  val a = sc.nextInt
  val b = sc.nextInt
  val w = sc.nextInt * 1000

  def ok(a: Int, b: Int, w: Int): Boolean = {
    val d = b - a
    val t = w / a
    if(a * t <= w && w <= (a + d) * t) true
    else false
  }

  if(ok(a, b, w)) {
    val f = math.floor(w.toDouble / a).toInt
    val s = math.ceil(w.toDouble / b).toInt
    println(s"${s} ${f}")
  } else {
    println("UNSATISFIABLE")
  }
}

これ嘘解放じゃないか?大丈夫かな。

C問題

C - Comma

数え上げです。その数字以下の時は必ずコンマの数がこれだけあることが保証される、みたいな形になるので数え上げていきます。

解放では 1,000以上、1,000,000 以上みたいな形でうまく集計していましたが、僕は雑に 1,000 で割る、 1,000.000 で割る、を繰り返して一定の桁以上であれば全部買うと、そうでないならばそこまでの値をカウントする感じで書きました。

想定解がキレイなので、これは思いつきたかったな。提出はScala

import java.util.Scanner

object Main extends App {
  val sc = new Scanner(System.in)

  var n = sc.nextLong
  val t = Array.range(1, 6).map(i => Array(i, math.pow(10, 3 * i).toLong)).reverse
  val l = math.pow(10, 3).toLong
  var ans: Long = 0
  for((e, ii) <- t.zipWithIndex) {
    val i = e(0).toInt
    val v = e(1)
    val a: Long = n / v
    if(a >= l) {
      ans += (t(ii - 1)(1) - v) * i
    } else if(a > 0) {
      ans += (n - v + 1) * i
    }
  }
  println(ans)
}

D問題

D - Shipping Center

あらかじめ入れるものをソートしておいて、使える箱のリストを毎回生成して、貪欲に入れる形でやっていました。

配列あれこれするのがめんどくさかったので提出は Python。配列の長さが短めなので、雑に作り直しても間に合うと思ってやったら案の定大丈夫でした。あんまりきれいなコードではない。

from copy import deepcopy

def run():
    n, m, q = map(int, input().split())
    wv = [[0 for i in range(2)] for i in range(n)]
    for i in range(n):
        w, v = map(int, input().split())
        wv[i] = [w, v]
    wv.sort(key=lambda t: (t[0], -t[1]))
    x = [int(i) for i in input().split()]
    for i in range(q):
        r, l = map(int, input().split())
        qx = x[:r - 1] + x[l:]
        if(len(qx) == 0):
            print(0)
        else:
            qx.sort()
            qw = deepcopy(wv)
            ans = 0
            for j in qx:
                tans, ti = -1, -1
                for k in range(len(qw)):
                    if qw[k][0] > j:
                        break
                    if qw[k][1] > tans:
                        tans, ti = qw[k][1], k
                if tans >= 0:
                    ans += tans
                    qw.pop(ti)
                if len(qw) == 0:
                    break
            print(ans)

if __name__ == "__main__":
    run()

E問題

E - Lucky 7 Battle

ここからは復習。これは遷移をいい感じにDPに入れていって、最終結果から推定する問題でしたね。

後ろから見るのは思いついたんですが、時間切れだったのと、DPに保存すべき値がうまく思いつきませんでした。

なので解説見ながら書いた感じです。公式の解説読んで。提出は scala

import java.util.Scanner

object Main extends App {
  val sc = new Scanner(System.in)

  val n = sc.nextInt
  val s = sc.next
  val x = sc.next

  var dp = Array.fill(n + 1, 7)(false)
  dp(n)(0) = true
  for(i <- (0 until n).reverse) {
    val si = s(i) - '0'
    val xi = x(i)
    for(j <- 0 until 7) {
      val f1 = (10 * j) % 7
      val f2 = (10 * j + si) % 7
      dp(i)(j) = xi match {
        case 'A' => dp(i + 1)(f1) && dp(i + 1)(f2)
        case 'T' => dp(i + 1)(f1) || dp(i + 1)(f2)
      }
    }
  }
  println(if(dp(0)(0)) "Takahashi" else "Aoki")
}

F問題

F - Coprime Present

BitDPらしいですね。素数の範囲がせいぜい72までに限定されるので、それをいい感じに数え上げられればよさそうです。

なお、数え上げパートが全く思いつかなかったのでユーザー解説のほうを参考に書いていきました。ありがとうございます。

Coprime Present [パナソニックプログラミングコンテスト(AtCoder Beginner Contest 195) F] - はまやんはまやんはまやん

Scalaで書いたのですが、はまりポイントがあり

  • どうやら Array の引数として渡せるのは Int 型が想定らしい、なぜか ( 1 << 20 ) を変数に入れてから Array に突っ込むと動く(これ想定されている挙動か?怪しい気がするが)
  • DPを int[n + 1][1 << 20] の形で定義するとメモリが足りなくなり、REとなる(MLEではないらしい)。今回はせいぜい、一つ前のものを見る程度だったので、変数を2つ使って表現し、更新する形にした。

あと isPrime をもっと楽に描く方法があったような気がするんですが(Javaのパッケージか何かにあったような)、探し出せなかったので雑に素数を出すコードを書いています。こういうのはライブラリみたいに使いまわせる形で用意しておいてもよいかも。

import java.util.Scanner

object Main extends App {
  val sc = new Scanner(System.in)

  def isPrime(a: Int): Boolean = {
    for(i <- 2 to math.sqrt(a).toInt) {
      if(a % i == 0) return false
    }
    true
  }

  val a, b = sc.nextLong
  val d = (b - a + 1).toInt

  val primes = (2 to 72).toArray.filter(isPrime)
  val l = primes.length
  val primesMap = (a to b).toArray.map(i => {
    var r = 0
    for(j <- 0 until l) {
      if(i % primes(j) == 0) {
        r |= (1 << j)
      }
    }
    r
  })
  var dp = Array.ofDim[Int](1 << l)
  dp(0) = 1
  for(i <- a to b) {
    var dp2 = Array.ofDim[Int](1 << l)
    for(j <- 0 until (1 << l)) {
      val t = (i - a).toInt
      if((j & primesMap(t)) == 0) dp2(j | primesMap(t)) += dp(j)
      dp2(j) += dp(j)
    }
    dp = dp2
  }
  println(dp.sum)
}

感想

めちゃくちゃ久々に参加しましたが、やっぱりやってて思ったのは

  • 集中力落ちてない?(D問題の細かいロジックのバグを直すのに時間がかかった)
  • 瞬発力も落ちてない? とりあえず試す力が弱い(特に B 問題は、最初にこれ解ける?と思って怪しかったので飛ばしてC問題行って、考え直してたのでもったいなかった)
  • タイピングが遅い(はい)

という感じなので、衰えを感じつつも、久々に出たのにレート変動なかったのは意外と元のコーディング能力は落ちてないのかなと思います。

逆に言えばまだまだ伸びる可能性は十分にあるので、今後も参加できるときは参加したい…(が、土日の21時は結構裏側に予定あったりして難しい)ですね。

(あとはどちらかといえば厳密解出すアルゴリズムのコンテストのほうが好きなのかもしれない、ISUCONはなんかすきだけど、マラソンはいまだに手を出そうという気になれていないという課題感を最近思いました)。

過去問ちょいちょい解いて水色には戻したいですね(希望的観測)。