Rubyと違いのあるCrystalの文字列の注意点

最初に

Crystal と Ruby はよく似た言語だが、様々な点で違いがある。

Ruby から Crystal の文字列を扱うときの注意点を書いてみる。

Crystalには文字がある

Ruby には文字列という概念しかないが、Crystal には文字と文字列という概念がある。

C言語などを学習したことがある人にとっては馴染み深いだろうが、
文字と文字列で区別するとき、

  • 文字 …… 1文字で'で囲む。
  • 文字列 …… 0文字以上で"で囲む。

という違いがある。

Ruby の文字列をシングルクォートで書いてると、Crystal に移植するとき、"に変更する必要があって面倒。

Crystalの文字列はイミュータブル

Ruby の文字列は、ミュータブルなもので、破壊的メソッドで破壊的な変更をすることができる。
しかし、Crystalの文字列は、イミュータブルで、破壊的メソッドはない。

イミュータブルにすると速くなることがあったり、コードとしての安全性が増す等のメリットがあるようだ。
なお、PythonJavaScript などの言語で、文字列はイミュータブルものである。

Ruby も昔は、Ruby 3.0では文字列はイミュータブルなものに変更しようという動きがあったが、
結局、後方互換性に大きな問題があるということでなくなった。 なお、後方互換性のない大きな変更をしたところで、大きなメリットがないという批判もあった。

具体的にできないこと

str = "Hello!"
p str[0]  # => 'H'
p str[2..3]  # => "ll"

Crystal は[]で文字や文字列をとることができるが、 Crystalの文字列はイミュータブルなので[]=の形で文字列の中の文字を変更することができない。

Crystalの文字と文字列の結合

文字と文字列は、足すことができる。

p "Hello" + '!'  # => "Hello!"
p '!' + "Hello"  # => "!Hello"

しかし、文字と文字は足すことが、できない。

p '!' + '?'
# Error: no overload matches 'Char#+' with type Char
# 
# Overloads are:
#  - Char#+(str : String)
#  - Char#+(other : Int)

このエラー文を見てわかるが、文字にはInt型の数値を足すことができる。

p 'b' + 1 # => 'c'
p 'b' - 1 # => 'a'

これは、Ruby にはない機能だ。競プロとかだと、ちょっと便利かもしれない。

Crystalの文字と文字列の比較

p 'a' == "a" # => false
p 'a' == 97  # => false
p 'a'.ord       # => 97

Crystal の文字と文字列は足すことができたが、同じだろうと思うものでも比較をするとfalseを返してくる。
このあたりは非自明で、注意ポイントだと捉えている。

Crystal の文字は、倍にできない。

RubyでもCrystalでも、文字列は倍などにできる。

p "a" * 2  # => "aa"

しかし、Crystalの文字に対して、*を使えないので注意が必要。

'a' * 2
# error in line 1
# Error: undefined method '*' for Char

実行しようとすれば、定義されてないので、Charに対して*は未定義というエラーで怒られる。

String#chars

RubyString#charsは、文字列から文字の配列を作るだけでなく、ブロックで文字を回すこともできる。

しかし、CrystalのString#charsは、文字の配列を作るだけで、ブロックを取ることはできない。

"str".chars{ |c| p c }
# Error: 'String#chars' is not expected to be invoked with a block, but a block was given

Ruby でも、 Crystal でも、文字列の文字を回したくなったら、each_charを使おう。

"str".each_char{ |c| p c }

Crystal の全体的な方針としてエイリアスは作らない方針があり、ブロックをとって回すメソッドはeach_を接頭辞(prefix)でつけると考えてよい。

なお、Crystal のcharseach_charも、要素は文字列Stgringではなく、文字Charとなることに注意。

p typeof("str".chars) # => Array(Char)

String#bytes

RubyString#bytesは、文字列からバイトの配列を作るだけでなく、ブロックでバイトを回すことができる。

こちらもchars同様、Crystal ではbytesでブロックをとって回すことはできない。
ブロックをとって回すときは、each_byteを使う。
Ruby でも、charsbytesでブロックをとって回すのは少しお行儀が悪いので、each_chareach_byteを使いましょうという話。

また、Crystal の返す配列の要素は、Int32などではなく、Int8型である点に注意。

Crystalは、三単現

文字列に限った話ではないが、Crystal は三単現(三人称・単数形・現在形)で統一する方向性である。

Ruby でのString#start_with?String#end_with?は、CrystalではString#starts_with?String#ends_with?である。

JavaScript でも同名の三単現のメソッドがあるので、JavaScript使いは馴染みやすそうである。

最後に

ザーッと Ruby から Crystal の文字列を扱うときの注意点を書いてみたが、いかがでしたでしょうか?

【Heroku】Error: vue-loader requires @vue/compiler-sfc to be present in the dependency tree.【Rails&yarn】

最初に

git push heroku mainとコマンドを打って、herokuにpushした。 そうすると、次のようなエラーがでた。

Error: vue-loader requires @vue/compiler-sfc to be present in the dependency tree.

このエラーをググって出てきた解決策だと、解決しなかったのでブログに書くことにした。

巷の解決策

「バージョンを変更するといい」みたいな解決策がヒットしたが、これだとうまくいかなかった。

実際のエラーの解決

"@vue/compiler-sfc"が必要らしい。 package.jsonを見ると、何故か"devDependencies"の中に記載されていた。 devはdevelopment(開発)の略で、開発環境だけで使われる想定なのだろう。 これを"dependenciesの方に移してやることにした。

{
  "name": "application-name",
  "private": true,
  "dependencies": {
    "vue": "^3.0.7",
    "vue-loader": "16.0.0-rc.0",
    "vue-template-compiler": "^2.6.12",
    "@vue/compiler-sfc": "^3.0.11",
    "vuex": "^3.6.2"
  },
  "version": "0.1.0",
  "devDependencies": {
    "webpack-dev-server": "^3.11.2"
  }
}

それから、再びyarnでインストールし直すところから。 yarn upgradeして、git add .&git commit -m "@vue/compiler-sfcを本番環境に"として、再びgit push heroku main。 これでエラーはでなくなった。めでたし、めでたし。

改めて見てみると

package.jsonを見慣れてなくて、dependency treeが具体的に何を指してるのかピンとこなかったけれど、 解決して改めて見てみると、エラー文はそのまんまの解決策。

【Ruby】多次元配列の定数倍高速化(EDPC J - Sushi)

最初に 

EDPC J - Sushiという競プロの問題をRubyで解いてました。

この問題自体は、大学受験数学の難しい方の典型にありそうな問題の延長で、 確率の漸化式を作り式を整理してメモ化再帰の形で動的計画法に乗せると解けます。

詳しい解説は、kyopro_frienedsさんのEDPC解説記事(F~L)を読むといいでしょう。

しかし、Ruby 2.7だと、時間がだいぶギリギリです。 kyopro_friendsさんの解説どおりの解法をとっても、定数倍高速化をしないとTLEになります。 式を整理して定数倍高速化をして、ようやく1900ms台に乗せることができます。 そのせいか、RubyでACされてる方は、kuma_rbさんという方しかいませんでした。

多次元配列の要素数のちょっとした工夫で1950ms前後から1750ms前後と0.2秒ほど速くなったので、定数倍高速化の参考にのせます。

多次元配列の高速化

多次元配列を作るさいに、添字の順番が特に関係ないのであれば、 多次元配列の内側よりも外側の要素数が小さくなるようにして、全体の配列数を減らした方がいいです。

n < mとして、m個の要素の配列をn個作る方が、n個の要素の配列をm個作るよりも、速いです。

生成の比較

require 'benchmark'
n = 10**2
m = 10**4
Benchmark.bm(12) do |r|
  x = r.report("a few arrays"){ a = Array.new(n){ [0] * m } }
  y = r.report("many arrays "){ b = Array.new(m){ [0] * n } }
end

#                   user     system      total        real
# a few arrays   0.000397   0.004267   0.004664 (  0.004749)
# many arrays    0.004898   0.004679   0.009577 (  0.009607)

ローカルで配列の生成を計測した結果で、明らかに差がありました。

配列を作るのは時間のかかる操作で、全体で配列を作る個数が減る方が速いのは当然なのかもしれません。

参照の比較

さらにアクセス時間でも検証しましたが、配列数の少ない多次元配列の方がちょっぴり速そうでした。

require 'benchmark'
n = 10**2
m = 10**4
a = Array.new(n){ [0] * m } 
b = Array.new(m){ [0] * n }

Benchmark.bm(12) do |r|
  r.report("a few arrays"){ n.times{ |i| m.times{ |j| a[i][j] } } }
  r.report("many arrays "){ n.times{ |i| m.times{ |j| b[j][i] } } }
  r.report("a few arrays"){ n.times{ |i| ai = a[i]; m.times{ |j| ai[j] } } }
  r.report("many arrays "){ m.times{ |j| bj = b[j]; n.times{ |i| bj[i] } } }
end

#                   user     system      total        real
# a few arrays   0.053819   0.000000   0.053819 (  0.053898)
# many arrays    0.089143   0.000000   0.089143 (  0.089156)
# a few arrays   0.045093   0.000000   0.045093 (  0.045101)
# many arrays    0.046487   0.000000   0.046487 (  0.046492)

その他

ハッシュでは速くならなかった

ハッシュを用いても、今回の問題では速くはなりませんでした。

  • 多次元配列の代わりに多次元ハッシュを使うと、TLEしました。
  • 多次元ハッシュの代わりに、数値を組み合わせて1つの数値にすることでギリギリAC。 具体的には、[a][b][c]の代わりに、[(a * 301 + b) * 301 + c]という形。 1999msなので本当にギリギリです。

今回の問題でハッシュは有効ではなかったです。 しかし、キーのパターンが少ないケースでは有効と思われるので、一概に決めつけずハッシュが有効なパターンも試してみるといい気がします。

EDPC J - Sushiのための高速化

ブログタイトルから離れて、EDPC J - Sushiの定数倍高足化について。

再帰関数内の分母・分子

再帰関数が呼び出される回数が多いところで、この再帰関数内の定数倍高速化させるかどうかでRuby 2.7で通せる・通せないが変わってくるようです。 分母・分子で同じ値で割っていたらこの部分はコードで計算しないようにしたり、定義して1度しか使わない変数は定義しないようにする必要があったり、細かい部分を頑張る必要があるようです。

もっと速い別の解法

kuma_rbさんという方が1900ms台の解法以外に1350ms前後のもっと速いコードを通していて、根本的にもっと高速化させる方法があるようです。 ちょっと見てみたのですが、異なる解法で難しそうでした。

最後に

このあたりのEDPCの問題は、Ruby 2.7だと厳しいですね。 ちょっとした工夫で、ちょっと速くなることを知れたので良かったです。

インド映画『ダンガル きっと、強くなる』をNetflixで見た

アーミル・カーン主演の映画を見ようと思い、

Netflixにあった『ダンガル きっと、つよくなる』を見ました。

原題は『Dangal』。意味は「レスリング」らしいです。

日本語訳の副題「きっと、つよくなる」は、アーミル・カーンの別映画『きっと、うまくいく』(英題『3 Idiots』) を真似したのでしょう。アーミル・カーンが主演という共通点はありますが、物語の繋がりは特にないです。

 

 

話のあらすじは、国の支援がなく金にならず世界を諦めたレスラー国内王者が、娘に夢を託して世界王者に育てあげてていく、というものです。

最初は、果たせたなかった夢を娘に託すというより、娘にやらせるという感じで、アーミル・カーンが昭和の頑固親父みたいだなと思いました。親父の考えが絶対みたいな。特に、長い髪を男の子みたいに短く切られるシーンなど、可愛そうでした。

ただ、物語の中で、"出荷"のように14歳で知らない男と結婚した少女と対比させることで、娘が結婚相手を選べるようになればいいと考えてる父親の考えがクッキリし、娘達も強制から積極的に練習をしたいという考えを持つに至り、そこからは比較的安心して見ることが出来ました。

女の子にレスリングをさせることに反対や嘲笑してた周囲も、結果がでるにつれみんな喜んだり称賛し始めていて、やっぱり新しいことを始める人は始めるとき本当に大変なんだろうな、と感じました。

どこか不器用に感じる父との親子愛を感じさせるスポーツ成長物語、気になった方は見てみてはいかがしょう。

蛇足ですが、ところで、娘は4人いるはずだけど、三女・四女はどうしてるんだろう、と少しだけ気になりました。Wikipedia(英語版)で調べて見ると、三女もレスラーで、映画の公開年2016年にCommonwealth Wrestling Championshipで金メダルを取っているようです。