modalsoul’s blog

これは“失敗”と呼べるかもしれないが、ぼくは“学習体験”と呼びたい

direnvで.envrcに設定した環境変数が読み込まれない場合

tl;dr

  • シェル設定ファイルのdirenv hookの設定を確認
    • ex). .bashrc.zshrcを確認

事象

  • direnvをインストール
  • .envrcファイルを作成
  • .envrcファイルに環境変数を記述
  • 設定した環境変数が読み込まれない
  • direnv allow しても同様に読み込まれない

原因

シェル設定ファイルにhookする設定の追加が漏れていた。

解決方法

手元の環境はzshなので、.zshrcにeval "$(direnv hook zsh)"を追加することで、direnvが正常に機能するようになった。

詳解

direnv hookはdirenvをシェルに統合するためのコマンドで、各shに対する初期化スクリプトのコードを出力する。このコードが実行されることで、direnvが自動的に動作し、環境変数のロード・アンロードを行う。

zshの場合のdirenv hookの出力

_direnv_hook() {
  trap -- '' SIGINT
  eval "$("/Users/hoge/.asdf/installs/direnv/2.34.0/bin/direnv" export zsh)"
  trap - SIGINT
}
typeset -ag precmd_functions
if (( ! ${precmd_functions[(I)_direnv_hook]} )); then
  precmd_functions=(_direnv_hook $precmd_functions)
fi
typeset -ag chpwd_functions
if (( ! ${chpwd_functions[(I)_direnv_hook]} )); then
  chpwd_functions=(_direnv_hook $chpwd_functions)
fi

_direnv_hook関数

direnvを呼び出し、環境変数を更新する。

1行目のtrapでSIGINTを無視し、処理中に中断されないようにしている。 direnv export zsh.envrcを読み込み、3行目のtrapでSIGINTに対する処理をデフォルトに戻している。

precmd_functions

precmd_functions_direnv_hookが含まれない場合、precmd_functionsに追加。 こうすることで、プロンプトが表示される直前に_direnv_hookが実行される。

chpwd_functions

chpwd_functions_direnv_hookが含まれない場合、chpwd_functionsに追加。 こうすることで、カレントディレクトリが変更された場合に_direnv_hookが実行される。

Warpのショートカットが反応しない問題

MacのターミナルをiTerm2からWarpに移行したところ、タイトルの通りショートカットが反応しない問題が発生した。

tl;dr

  • これは既知のバグ
  • 回避方法は無い

事象

  • Windowの表示・非表示のショートカットを設定
  • 設定したショートカットでWindowの表示・非表示ができることを確認
  • ショートカット機能しないことを確認

Issue

github.com

入力ソースが英語以外だとhotkeyが反応しないらしい

issueが上げられてから1年以上経っているが、未だに修正されていないし、回避方法も無いようだ

Pythonのcsvモジュールの挙動とRFC4180

出力されたCSVファイルでカンマズレが発生する事象の調査をしたときの小ネタな話

事象

CSVファイルでカンマ「,」を含むデータがカンマの位置で分割され、かつ、カンマが増殖していた

ex.)

山田,太郎

が、

,山田,,太郎,

に変換されていた

原因

原因自体はとても単純で以下のような誤設定だった

csv.writer(delimiter=',', quotechar=',', quoting=csv.QUOTE_MINIMAL)

本来は「"」が設定されるはずのquotecharが「,」になっていたため、delimiterである「,」を含むフィールドが「,」でクオートされてしまい、文字列の先頭・末尾に「,」が付加されていた。

しかし、ここでもう一つ謎なのが、文字列中に含まれた「,」が「,,」になっていたこと。

csvモジュールのドキュメントを読んで行くと、以下のような仕様が記述されていました。

Dialect.doublequote

以下引用

フィールド内に現れた quotechar のインスタンスで、クオートではないその文字自身でなければならない文字をどのようにクオートするかを制御します。 True の場合、この文字は二重化されます。 False の場合、 escapechar は quotechar の前に置かれます。デフォルトでは True です。

doublequoteのデフォルトはTrueなのでデフォルトの挙動ではquotecharquotecharエスケープされるということ。

ちなみに doublequoteFalseに設定するとescapechar(デフォルトは「\」)エスケープされる。

ダブルクオートでエスケープするという慣れ親しんだエスケープと異なる挙動で混乱しますが、これにより文字列中に含まれた「,」がquotecharである「,」でエスケープされ「,,」になっていたことがわかりました。

気になったのでさらにCSVの仕様について調べると、以下の記述を見つけました。

RFC4180

2. Definition of the CSV Formatより以下引用

7. If double-quotes are used to enclose fields, then a double-quote appearing inside a field must be escaped by preceding it with another double quote. For example:

"aaa","b""bb","ccc"

ダブルクオートで囲まれるフィールドの中にダブルクオートが含まれる場合、ダブルクオートはダブルクオートでエスケープされる、となっていました。

ダブルクオートでのエスケープはCSVの御作法だったようです。


もろもろ話をまとめると、

クオート文字が「,」に設定されていたため、「,」を含むフィールドが「,」で囲まれ、かつ、フィールド内の「,」がクオート文字の「,」でエスケープされ「,,」になった

ということで、quotecharを「"」にすることで、期待通りの挙動になりました。(quotecharのデフォルトは「"」なので、quotechar指定を省略してもOK)

Amazon EventBridgeのcron expressionで最終日(月末/週末)指定がValidationExceptionになる問題

事象

毎月14日および月末日2日前の1:00 のようなたルールを登録する場合、ValidationExceptionでエラーとなる

> aws events put-rule --schedule-expression "cron(0 1 14,L-2 * ? *)" --name samplerule1

An error occurred (ValidationException) when calling the PutRule operation: Parameter ScheduleExpression is not valid.

原因

末日(週末日や月末日)を表すLワイルドカードは、同じくDay-of-monthフィールドに指定している,(カンマ)と併用が不可

ちなみに-(ダッシュ)とも併用できないことを確認

> aws events put-rule --schedule-expression "cron(0 1 10-L-2 * ? *)" --name samplerule1

An error occurred (ValidationException) when calling the PutRule operation: Parameter ScheduleExpression is not valid.

対策

単一のルールでは登録できないので、ルールを分割することで対応した

> aws events put-rule --schedule-expression "cron(0 1 14 * ? *)" --name samplerule1

> aws events put-rule --schedule-expression "cron(0 1 L-2 * ? *)" --name samplerule2

LeetCode 17. Letter Combinations of a Phone Number

LeetCode Problem No.17

No.16 is here

modalsoul.hatenablog.com

17. Letter Combinations of a Phone Number

Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. Return the answer in any order.

A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.

Example 1

Input: digits = "23"
Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]

Example 2

Input: digits = ""
Output: []

Example 3

Input: digits = "2"
Output: ["a","b","c"]

Constraints

0 <= digits.length <= 4
digits[i] is a digit in the range ['2', '9'].

Code

object Solution {
    val letters = List(
        List("a", "b", "c"),
        List("d", "e", "f"),
        List("g", "h", "i"),
        List("j", "k", "l"),
        List("m", "n", "o"),
        List("p", "q", "r", "s"),
        List("t", "u", "v"),
        List("w", "x", "y", "z")
    )
    def letterCombinations(digits: String): List[String] = {
        if(digits.isEmpty) return List.empty[String]
        digits.map(_.asDigit).map(x => letters(x-2)).foldLeft(List(""))(
            (z, n) =>
                if(z.isEmpty) n
                else z.flatMap{ x => n.map(y => x + y)}
        )
    }
}

Methods

  • Define letters as list.
  • Convert digits to number list and convert to list of letters item.
  • (((N1 x N2) x N3)... x Nn)
    • use foldLeft
  • String => List[Int] => List[List[String]] => List[String]

Results

Runtime: 476 ms, faster than 96.70% of Scala online submissions for Letter Combinations of a Phone Number.

Memory Usage: 52.8 MB, less than 94.51% of Scala online submissions for Letter Combinations of a Phone Number.

Use reduceLeft

def letterCombinations(digits: String): List[String] = {
  if(digits.isEmpty) return List.empty[String]
  digits.map(_.asDigit).map(d => chars(d-2)).reduceLeft(
    (z, n) => z.flatMap{ x => n.map(y => x + y)}
  )
}

LeetCode 16. 3Sum Closest

LeetCode Problem No.16

No.15 is here

modalsoul.hatenablog.com

16. 3Sum Closest

Given an integer array nums of length n and an integer target, find three integers in nums such that the sum is closest to target.

Return the sum of the three integers.

You may assume that each input would have exactly one solution.

Example 1

Input: nums = [-1,2,1,-4], target = 1
Output: 2
Explanation: The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).

Example 2

Input: nums = [0,0,0], target = 1
Output: 0

Code

object Solution {
  import scala.math.abs
  def threeSumClosest(nums: Array[Int], target: Int):Int = {
    val sorted = nums.sorted
    var result = Option.empty[Int]
    var i = 0
    while(i < nums.length-2) {
      val head = sorted(i)
      var left = i+1
      var right = nums.length-1
      while(left < right) {
        val sum = head+sorted(left)+sorted(right)
        if(sum == target) return target
        if(result.isEmpty || abs(target - sum) < abs(target - result.get)) result = Some(sum)
        if(sum < target) left = left+1
        else right = right-1
      }
      i = i+1
    }
    result.get
  }
}

Methods

This problem is similar to No. 15. modalsoul.hatenablog.com

  • Sort given array.
  • Take a variable(i) from array in order, and make sub-array(like tail)
    • Left starts with head of sub-array.
    • Right starts with last of sub-array.
    • If sum of triplets equals to target, fin.
    • If absolute value of sum of triplets is
      • under current result
        • Shift left
      • over current result
        • Shift right

Results

Runtime: 885 ms, faster than 70.83% of Scala online submissions for 3Sum Closest. Memory Usage: 68.7 MB, less than 62.50% of Scala online submissions for 3Sum Closest.

LeetCode 15. 3Sum

LeetCode Problem No.15

No.14 is here

modalsoul.hatenablog.com

15. 3Sum

Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.

Notice that the solution set must not contain duplicate triplets.

Example 1

Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]

Example 2

Input: nums = []
Output: []

Example 3

Input: nums = [0]
Output: []

Code 1

object Solution {
    def threeSum(nums: Array[Int]): List[List[Int]] = {
        nums.combinations(3).flatMap(x => if(x.sum == 0) Some(x.toList) else None).toList
    }
}

Methods

  • Make all combinations and check sum.

Results

315 / 318 test cases passed. Status: Memory Limit Exceeded

Code 2

object Solution {
  def threeSum(nums: Array[Int]): List[List[Int]] = {
    val sorted = nums.sorted
    var result = List.empty[List[Int]]
    for(i <- 0 until nums.length-2) {
      val head = sorted(i)
      var left = i+1
      var right = nums.length-1
      if(i == 0 || sorted(i-1) < sorted(i)) {
        while(left < right) {
          if(head+sorted(left)+sorted(right) == 0) {
            result = result :+ List(head, sorted(left), sorted(right))
            left = left+1
            right = right-1
            while(left < right && sorted(right+1) == sorted(right)) {
              right = right-1
            }
          } else if(head+sorted(left)+sorted(right) < 0) left = left+1
          else right = right-1
        }
      }
    }
    result
  }
}

Methods

This problem is similar to No. 11.

modalsoul.hatenablog.com

  • Sort given array.
  • Take a variable(i) from array in order, and make sub-array(like tail)
    • Skip while i and i-1 are equivalent.
    • Left starts with head of sub-array.
    • Right starts with last of sub-array.
    • If sum of triplets is
      • 0
        • Shift left and right.
        • Skip right while same value.
      • under 0
        • Shift left.
      • over 0
        • Shift right.

Results

Runtime: 2654 ms, faster than 51.47% of Scala online submissions for 3Sum. Memory Usage: 733.3 MB, less than 6.62% of Scala online submissions for 3Sum.