たにしきんぐダム

プログラミングやったりゲームしてます

JSで CodePoint 数えたい

ここで一句

JSで文字列を16bit単位ではなくUnicode Code Point単位で数える方法はいくつかあるが、結局2017年5月時点で(IE11のようなブラウザも含めて)ほとんどの環境で動作する方法はどれなんだろう。調べたのでまとめておきます、ご指摘あればどしどし(ง ‘-’ )ง

参考

Unicode コードポイント

Unicode では全ての文字にID(コードポイント)(0 ~ 0x10FFFF)をふっている。 コードポイントを表す時は U+{16進数} と書く。

UTF-16 では U+0000 ~ U+FFFF までのコードポイントは 16bitの1符号単位で表現されて U+10000 以降のコードポイント(𩸽のコードポイントなど)は、16bitだけでは表現できないため後述するサロゲートペアで表現される。

サロゲートペア

UTF-16 で16bitだけでは表現できないコードポイントを表現するために、一部の文字(U+10000 以降のコードポイントに対応する文字)は 16bit x 2 の 32bit で表現する。これらの文字(表現)をサロゲートペアと呼ぶ。

サロゲートコードポイント

サロゲートペアを構成する 16bit の値の範囲は U+D800 ~ U+DFFF であり、これらをサロゲートコードポイントと呼ぶ(このサロゲートコードポイントには文字が割り当てられていない)。さらに

と呼び、上位サロゲートと下位サロゲートの組み合わせでサロゲートペアを表現する。

lengthプロパティ

JavaScriptstr.lengthUnicodeコードポイント単位での文字列の長さではなく、UTF-16 符号単位の長さを返す。

例えば「𩸽」はサロゲートペアであるため、length は 2 となってしまう。

"𩸽".length; // 2

「𩸽」のようなサロゲートペアも1としてカウントしたい。そのためにはコードポイント単位での長さを調べれば良い。

どうやってコードポイント単位の長さを調べるか

いかれたメンバーを紹介するぜ!

for of

ES2015で追加された for ... of... を使うとコードポイント単位で繰り返しが可能

for (let c of '𩸽定食') console.log(c)
// 𩸽
// 定
// 食

しかし現在(2017年5月)IE11で未対応 https://kangax.github.io/compat-table/es6/#test-for..of_loops

スプレッド演算子

分割代入時の分割もコードポイントが意識されている模様。 スプレッド演算子を使えばシュッとコードポイント単位で文字列を配列に変形できる。

[...'𩸽定食']
// ['𩸽', '定', '食']

これも現在(2017年5月)IE11未対応 [https://kangax.github.io/compat-table/es6/#test-spread(…)operator]

RegExp unicode flag

ES2015より unicode flag が導入された、このフラグを使うとパターンをコードポイントの羅列として扱ってくれる。

'𩸽定食'.match(/./ug);
// ['𩸽', '定', '食']

残念ながら unicode flag も現在(2017年5月)IE11未対応 https://kangax.github.io/compat-table/es6/#test-RegExp_y_and_u_flags

for文で頑張る

文字列を16bit単位でループし、サロゲートペアは1としてカウントする。

function stringLength(str) {
  let count = 0;
  for (let i = 0; i < str.length; i++) {
    count++;
    // i番目の 16bit が得られる
    const code = str.charCodeAt(i);
    if (0xD800 <= code && code <= 0xDBFF) {
      // i番目の 16bit が上位サロゲートなら
      // 次の 16bit (下位サロゲート) はスキップ
      i++;
    }
  }
  return count;
}

これならIE11でも動く

正規表現(Unicode Sequence)

サロゲートペアを非サロゲートペア文字に変換してlengthをとる

function stringLength(str) {
  // サロゲートペアを _ にreplace
  return str.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length;
}

stringLength('𩸽定食'); // 3

配列に変換したい場合

function stringToArray(str) {
  return str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF]/g) || [];
}

stringToArray('𩸽定食');
// ['𩸽', '定', '食']

まとめ

以上だ!

随時更新していきたい

株式会社はてなに入社しました

こんにちは、id:tanishiking24 といいます。 2017年4月より株式会社はてなでWebアプリケーションエンジニアとして働くことになりました。京都オフィス勤務です。

これまでは京都大学工学部情報学科という所に通っていました。 他の企業とも迷ったのですが、はてなのサービスが好きで馴染みがあったり、勉強会やSNSでの社員さんの印象が良かったり、給与が良かったりのあれこれではてなが最高そうだなと思い、お世話になることにしました。頑張るぞ

(初日の朝の自己紹介で「バリバリバリューを出していきたい」みたいなことを言ったらフレッシュ感がないと言われてしまい失敗したなと反省しています、これからフレッシュさを取り戻していきたい。)

Coursera の Algorithms on Strings 受けました

Cousera の Algorithms on Strings を受講していて、平日にお昼ご飯食べながらビデオを見たり休日とかに課題をやったりしていたのですが先日完走しました!(講義は4週分なのですが忙しかったり難しかったりで2ヶ月くらいかかってしまった)
お金を払うと課題提出システムみたいなのが使えて提出したプログラムの時間/空間計算量を教えてくれるらしいけど無料でも授業ビデオと資料にはアクセスできた
めちゃくちゃ良かったのでみんなも受講しましょう

www.coursera.org

きっかけはアルバイト先で開催されていたのに参加させてもらったのと、 ISUCON6予選で高速な文字列マッチングアルゴリズムが全く分からず悔しい思いをしたから(正規表現の更新・キャッシュをうまく頑張れば十分な点数は獲得できたみたいですが...)でした。
学んで終わりじゃ多分忘れるから何とか応用とかできたら良い

授業ビデオは例を交えて教えてくれたり定理を証明してくれたりクイズが挟まったり(前半の)先生のテンションが異常に高かったりして楽しかったし分かりやすかったので受講しましょう

文句を言うならWeek4は先生がひたすらスライド数枚で定理と証明を延々と喋ったあとアルゴリズムはこれだよってスライドが何枚か出てきただけだったのはめちゃくちゃ分かりにくかったのでもっと例が欲しかった。Couseraのフォームから意見を送ったりした、評価-1したりした...
(授業ビデオに対して気軽にLike/Dislikeしたり意見を送信できるシステムがあるのは凄く良いですね)


以下は授業メモを清書したもの、ブログ書くつもりで授業受けると他の人も読むのでしっかり整理する必要があって理解が深まるという知見が!
この記事頑張りすぎたので次からはもう少し手を抜きたい

  • ちまちま書いてたらめちゃくちゃ長くなってしまったので読むとしたら辞書的に使うのが良さそう
  • この記事にはプログラムは掲載していません
  • この記事では詳細なアルゴリズムなどは書いていません(調べれば/受講すれば出てくる)、アルゴリズム・データ構造の具体例くらいの気持ち
  • この記事を読んで各アルゴリズムの雰囲気を掴んでもらって、詳しく知りたい場合はCoursera受けてみてください
  • Week1
    • BruteForce
    • Trie
    • Suffix Trie
    • Suffix Tree
  • Week2
  • Week3
    • ShiftingPatterns
    • PrefixFunction
    • Knuth–Morris–Pratt algorithm
  • Week4
    • Construct Suffix Array (Manber & Myers)
    • Construct LCP Array
      • kasai’s Algorithm
    • Suffix Array + LCP Array -> Suffix Tree
  • おわりに
続きを読む

JS知識ほぼ0からTypeScript入門してる

この記事は CAMPHOR- Advent Calendar 2016 23日目の記事です。
JS知識ほぼ0は言い過ぎかもしれないが、いわゆるモダンJSというものには縁遠く、つい最近まで jQuery をブラウザからぽちぽちダウンロードして適当に ajax とか使う人生を送ってまいりました。(当然フレームワークとか使ったことない)

まさしくこの記事みたいな状況

kikuchi1201.hateblo.jp

最近 TypeScript を書く機会があって、開発環境は用意されてるのでなんとなく書けるけど、エコシステムとかいろいろ全くわかってなくてこのまま旧石器時代然としたJavaScriptを書いていてはまずい気がすると思って勉強することにしました。

目標は TypeScript を使ってこんなんを作る、テストも書こうね(こんなのに何をテストするんだ)
この記事では主に環境構築~DOM操作のテストまでをやっていてTypeScriptの文法みたいな話はしません

https://gyazo.com/ac3e0137e71e2ef475c685185fdcd06f

  • npm とか node のバージョンを管理
  • npm でパッケージ管理をする
    • プロジェクトごとにパッケージ管理
  • TypeScript
    • 開発環境
  • tsconfig.json
  • 外部モジュールをTypeScriptから使う
  • 実際にTypeScriptプログラムを書く
  • CommonJS
  • browserify
  • gulp
  • テスト
    • 環境構築
    • HTML断片を読んでDOM操作のテスト
  • npm script からテストなどを呼び出す
  • 感想
  • 参考
続きを読む

WartRemover で Scala を静的解析

この記事は CAMPHOR- Advent Calendar 2016 7日目の記事です。

WartRemoverScala のASTレベルの静的解析ツールで、WartRemover に組み込まれているパターンに加えて、自分で定義したパターンをビルド時に検出することができます。
これを使えばscalacはエラーや警告を出さないけど検出してほしいコーディング規約などをビルド時に検出することができるようになって便利。

github.com

もとから組み込まれてるパターンはGitHubのREADMEに詳しく書かれています。

使ってみる

詳しくは GitHubREADME を参照。

project/plugins.sbt に以下を追記

addSbtPlugin("org.wartremover" % "sbt-wartremover" % wartremoverVersion)

build.sbt に以下を追記

wartremoverErrors ++= Warts.all

これだけでビルド時にビルド対象のscalaプログラムを見て、すべてのパターンにマッチするASTを探索し、マッチしたパターンをエラーとして出力してくれるようになります、簡単ですね。

上の例だとすべてのプログラムに対して、すべてのパターンを検出しますが

  • 特定ファイルは検出対象から外す
  • 一部のパターンのみ/一部のパターンを除いてすべてのパターンを検出
  • あるパターンはエラー、あるパターンは警告を出すようにする

などのことが build.sbt に記述するだけでシュッとできるようになります。
詳しい設定方法はREADMEを(ry

カスタム Wart を作ってみる

ここ を見ると wart rule を自分で追加できるようです。

今回は HOGE_HOGE みたいな全部大文字とアンスコのみの変数名を見つけてみます。
Scala の定数は 公式のscala code styleUpperCamel だよって言われているのですが、僕はよくミスって LIMIT みたいな定数名を作ってしまうし、scalac も scalastyle も warning を出さないからです。(もしかして僕の設定が足りてないだけ?)(まあそこは本題じゃないし)

完成したものはこちら

github.com

WartRemoverはscalaのASTとのマッチングによって検出を行うので、まずは val HOGE = 1 みたいなプログラムがどういう AST になるのか確認しましょう。

scala> import scala.reflect.runtime.universe
import scala.reflect.runtime.universe

scala> universe.showRaw(universe.reify { val HOGE = 1 }.tree)
res1: String = Block(
List(ValDef(Modifiers(), TermName("HOGE"), TypeTree(), Literal(Constant(1)))),
Literal(Constant(())))

なんとなく ValDef(Modifiers(), TermName(), TypeTree(), Litenarl()) にマッチさせてやれば良いことがわかりますね。
(Modifiers には final private みたいな修飾子 が入ります。TypeTree はよく分からない...)

ちなみにScalaのリフレクションを使った構文木へのアクセスは公式ドキュメントが詳しい

概要

よっしゃ、それでは ValDef(Modifiers(), TermName(), TypeTree(), Litenarl()) のうち TermName が全部大文字かアンスコだけな構文木にマッチしてくれるwartを書いていきましょう!
ディレクトリ構成はこんな感じ、LargeValueName.scala は wartルールを記述するプログラム、wartsは別プロジェクトとして管理しています。

.
├── build.sbt
├── project
│   ├── build.properties
│   └── plugins.sbt
├── src/main/scala/Main.scala
└── warts
    └── src/main/scala/LargeValueName.scala

LargeValueName.scala

自分で wart を作るときはだいたい以下のような必要があります。

  • wart object は WartTraverser を継承
  • def apply(u: WartUniverse): u.Traverser のみをメソッドとして持ち
    • WartUniversereflect.api.Universe を内部にもつ
  • apply が返す Traversertraverse(tree: Tree): Unit を override
    • 引数として与えられたASTと、検出したいパターンとのマッチングさせる
package warts

import org.wartremover.{WartTraverser, WartUniverse}

object LargeValueName extends WartTraverser {
  def apply(u: WartUniverse): u.Traverser = {
    import u.universe._

    new Traverser {
      override def traverse(tree: Tree) {
        tree match {
          case t @ ValDef(_, TermName(s), _, _)
            if s.trim.matches("[A-Z_]+") =>
            u.error(tree.pos, s"Value name $s should be upper/lower camel case")
            // 他のwartもチェックするかもしれないので super.traverse を呼ぶ
            super.traverse(tree)
          case _ =>
            super.traverse(tree)
        }
      }
    }
  }
}

build.sbt

それじゃあこれを build.sbt に登録してみます、今回は LargeValueName.scala は sbt のマルチプロジェクトを使ってメインプロジェクトとは別で管理します。

  • wartremoverClasspaths にカスタム wart の classpath を追加
  • wartremoverWarnings += Wart.custom("your.own.wart")

build.sbt はこんな感じ、

val wartremoverVersion = "1.2.1"
val customWartProjectName = "MyWarts"

lazy val commonSettings = Seq(
  scalaVersion := "2.11.8"
)

lazy val root = (project in file(".")).
  settings(commonSettings: _*).
  dependsOn(warts).
  settings(
    name := "wartremoverPlayground",
    version := "1.0",
    // warts project の jar ファイルが欲しい
    // wartremoverClasspaths += 
    //   "file://" + baseDirectory.value + "/warts/target/scala-2.11/mywarts_2.11-1.0.jar",
    wartremoverClasspaths ++= {
       // lazy val warts = ... settings( exportJars := true) と
       // dependsOn(warts) により dependencyClasspath in Compile に上記のjarが追加されてるはず
      (dependencyClasspath in Compile).value.files
        .find(_.name.contains(customWartProjectName.toLowerCase()))
        .map(_.toURI.toString)
        .toList
    },
    wartremoverWarnings += Wart.custom("warts.LargeValueName")
  )

lazy val warts = (project in file("warts")).
  settings(commonSettings: _*).
  settings(
    name := customWartProjectName,
    version := "1.0",
    exportJars := true,
    libraryDependencies ++= Seq(
      "org.wartremover" %% "wartremover" % wartremoverVersion
    )
  )

warts project に移動して jar ファイルをいちいち生成して、頑張ってjarへのpathを渡しても動くのですが大変、自動化したすぎます。

wartremoverClasspaths += "file://" + baseDirectory.value + "/warts/target/scala-2.11/mywarts_2.11-1.0.jar"

この部分で、wartsプロジェクトの生成したjarファイルを見つけています。

wartremoverClasspaths ++= {
  (dependencyClasspath in Compile).value.files
     .find(_.name.contains(customWartProjectName.toLowerCase()))
     .map(_.toURI.toString)
     .toList
}

動かしてみる

Main.scala

package playground

object Main {
  val LIMIT = 1
}

これで sbt compile すると

[warn] /path/to/Main.scala:4: Value name LIMIT should be upper/lower camel case
[warn]   val LIMIT = 1

おお、警告出してくれた!
これを使って、簡単にパターンとして記述できるコーディング規約などは静的解析で検出してくれると良いですね!

明日は id:ryota-ka の「 Vim script でジェネレータを作ったり、遅延評価してみる」です。

参考