modalsoul’s blog

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

LeetCode 18. 4Sum

LeetCode Problem No.18

No.17 is here

modalsoul.hatenablog.com

18. 4Sum

Given an array nums of n integers, return an array of all the unique quadruplets [nums[a], nums[b], nums[c], nums[d]] such that:

  • 0 <= a, b, c, d < n
  • a, b, c, and d are distinct.
  • nums[a] + nums[b] + nums[c] + nums[d] == target

You may return the answer in any order.

Example 1:

Input: nums = [1,0,-1,0,-2,2], target = 0

Output: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

Example 2:

Input: nums = [2,2,2,2,2], target = 8

Output: 2,2,2,2

Code 1

object Solution {
    def fourSum(nums: Array[Int], target: Int): List[List[Int]] = {
        nums.sorted.combinations(4).flatMap(x => if(x.sum == target) Some(x.toList) else None).toList
    }
}

Methods

Make all combinations and check sum.

Results

Memory Limit Exceeded 241 / 294 testcases passed

Code 2

object Solution {
  def fourSum(nums: Array[Int], target: Int): List[List[Int]] = {
    val sorted = nums.sorted
    val length = nums.length
    var result = List.empty[List[Int]]

    for (
      i <- 0 until length - 3
      if !(i > 0 && sorted(i - 1) == sorted(i)) &&
         (sorted(i).toLong + sorted(length - 1) + sorted(length - 2) + sorted(length - 3) >= target) &&
         (sorted(i).toLong + sorted(i + 1) + sorted(i + 2) + sorted(i + 3) <= target)
    ) {
      val a = sorted(i).toLong

      for (
        j <- i + 1 until length - 2
        if !(j > i + 1 && sorted(j - 1) == sorted(j)) &&
           (a + sorted(j) + sorted(length - 1) + sorted(length - 2) >= target) &&
           (a + sorted(j) + sorted(j + 1) + sorted(j + 2) <= target)
      ) {
        val b = sorted(j).toLong
        var left = j + 1
        var right = length - 1

        while (left < right) {
          val c = sorted(left).toLong
          val d = sorted(right).toLong
          val sum = a + b + c + d

          if (sum == target) {
            result = List(a.toInt, b.toInt, c.toInt, d.toInt) +: result
            left += 1
            right -= 1
            while (left < right && sorted(left) == c) left += 1
            while (left < right && sorted(right) == d) right -= 1
          } else if (sum < target) {
            left += 1
          } else {
            right -= 1
          }
        }
      }
    }

    result
  }
}

Methods

  • Sort given array.
  • Fix two elements (a, b)
  • Find remaining two elements (c, d)
  • Skip equal values

Results

Runtime 32ms Beats100.00%

Memory 61.61MB Beats28.57%

Code 3

import scala.util.control.Breaks._

object Solution {
  def fourSum(nums: Array[Int], target: Int): List[List[Int]] = {
    val sorted = nums.sorted
    val length = nums.length
    var result = List.empty[List[Int]]

    breakable {
      for(i <- 0 until length-3) {
        if(i > 0 && sorted(i-1) == sorted(i)) {
          
        } else {
          val i_max = sorted(i).toLong + sorted(length-1) + sorted(length-2) + sorted(length-3)
          val i_min = sorted(i).toLong + sorted(i+1) + sorted(i+2) + sorted(i+3)
          if(i_min > target) break()
          if(i_max < target) {

          } else {
            val a = sorted(i).toLong
            breakable {
              for(j <- i+1 until length-2) {
                if(j > i+1 && sorted(j-1) == sorted(j)) {

                } else {
                  val j_max = a + sorted(j) + sorted(length-1) + sorted(length-2)
                  val j_min = a + sorted(j) + sorted(j+1) + sorted(j+2)
                  if(j_min > target) break()
                  if(j_max < target) {

                  } else {
                    val b = sorted(j).toLong

                    var left = j+1
                    var right = length-1

                    while(left < right) {
                      val c = sorted(left).toLong
                      val d = sorted(right).toLong
                      val sum = a+b+c+d
                      if(sum == target) {
                        result = List(a.toInt, b.toInt, c.toInt, d.toInt) +: result
                        left = left + 1
                        right = right - 1
                        while(left < right && sorted(left) == c) left = left + 1
                        while(left < right && sorted(right) == d) right = right - 1
                      } else if(sum < target) {
                          left = left + 1
                      } else {
                          right = right - 1
                      }
                    }
                  }
                }
              }  
            }
          }
        }
      }
    }
    result
  }
}

Methods

  • Use breakable instead of for-guard

Results

Runtime 25ms Beats100.00%

Memory 61.97MB Beats21.43%

Macに接続したAirPods Proの音量が勝手に大きくなる問題の対処

tl;dr

パーソナライズされた音量をオフにしよう


仕事でMBPにAirPods Pro(gen2)を使っているのだけど、いつ頃からかGoogle MeetやZoom中に勝手に音量が大きくなり爆音になる事象が発生するようになった。

最初は音量キーやショートカットの誤操作と思ったけど、そうではなさそう。

類似の事例を探してたけど、meetやzoomに起因していそうな事例は見つからなかった。

AirPods Proの機能「パーソナライズされた音量」をオフにして様子を見たところ、再現しなくなったので、これが原因で確定っぽそう。

aws_cloudwatch_event_ruleのstateでデフォルト値が反映されないパターン

tl;dr

  • aws_cloudwatch_event_ruleのstate引数を"DISABLED"にした後、設定から削除してもルールは"DISABLED"のまま
  • 設定を削除した場合、管理が放棄されるのみでデフォルト値は適用されない

事象

aws_cloudwatch_event_ruleリソースでCloudWatch Event Ruleを管理する際、以下の状況に遭遇した。

  1. 初期設定で、ルールの状態を明示的に無効化するため、state = "DISABLED"と記述し、terraform applyを実行した。

    resource "aws_cloudwatch_event_rule" "example" {
      name        = "my-event-rule"
      description = "My example event rule"
      event_pattern = jsonencode({
        source = ["aws.s3"]
      })
      state = "DISABLED" # ここでDISABLEDに設定
    }
    
  2. state引数のデフォルト値は"ENABLED"であるため、ルールを有効化する意図でstate引数を削除した。

    resource "aws_cloudwatch_event_rule" "example" {
      name        = "my-event-rule"
      description = "My example event rule"
      event_pattern = jsonencode({
        source = ["aws.s3"]
      })
      // state引数を削除
    }
    
  3. この状態でterraform applyを実行したが、ルールは無効("DISABLED")のままだった。


原因

Terraformが一度管理した引数を設定ファイルから削除した場合の挙動にある。

  • デフォルト値の適用タイミング: Terraformの引数におけるデフォルト値は、その引数を設定ファイルに明示的に記述しなかった場合にのみ適用される。
  • 管理の放棄: 一度state = "DISABLED"と明示的に記述してterraform applyを実行すると、Terraformはaws_cloudwatch_event_rulestate属性を管理対象として認識する。その後、設定ファイルからstate引数を削除した場合、Terraformは「もうこのstate属性はTerraformの設定で管理しない」と判断し、その属性に対する変更操作を行わない。
  • 既存状態の維持: 結果として、AWS上に存在するCloudWatch Event Ruleのstateは、以前に"DISABLED"に設定された状態のまま維持される。Terraformは、設定ファイルに記述されていない属性については、AWS上の既存の状態を変更しようとしないため、デフォルト値に戻ることはない。

これは、Terraformが「設定ファイルに記述された状態」にAWSリソースを収束させようとする性質から生じる挙動だ。設定ファイルに存在しないものは、Terraformの管理外となるため、既存の状態が温存される。


解決方法

state引数のデフォルト値が"ENABLED"であっても、DISABLEDからENABLEDへ戻したい場合は、明示的にstate = "ENABLED"と記述し、terraform applyを実行する必要があった。

resource "aws_cloudwatch_event_rule" "example" {
  name        = "my-event-rule"
  description = "My example event rule"
  event_pattern = jsonencode({
    source = ["aws.s3"]
  })
  state = "ENABLED" # 明示的にENABLEDに設定
}

詳解

Terraformのでリソースを定義する際、Optionalな引数(記述しなくてもよい引数)やComputedな引数(Terraformの実行中にAWSから値が返される引数)の扱いに注意が必要。

aws_cloudwatch_event_ruleにおけるstate引数では、

  • 定義時: ドキュメントの記載上、stateOptionalであり、デフォルト値は"ENABLED"
    • 初回デプロイでstateを省略した場合 → ENABLED
    • 初回デプロイでstate = "DISABLED"と記述した場合 → DISABLED
  • 更新時: 一度Terraform Stateファイルにstate = "DISABLED"という情報が記録されると、Terraformは次回以降のterraform planterraform applyで、この値を管理対象として比較する。
    • 設定ファイルからstateを削除 Terraformは設定ファイルにstateの記述がないため、stateに関する変更は「計画」されない。Terraform Stateファイルに記録されているstateの値と、AWS上の実際のstateの値が合致しているため、「変更なし」と判断される。結果として、AWS上のルールはDISABLEDのまま。
    • 設定ファイルをstate = "ENABLED"に変更 Terraformは設定ファイル上のstate = "ENABLED"と、Terraform Stateファイルに記録されているstate = "DISABLED"を比較し、差分を検出する。この差分を解消するため、AWS上のルールをENABLEDに更新する「計画」を立て、apply時に実行する。

Terraformは「最終的にどういう状態になってほしいか」を宣言するツールであり、その宣言から外れたものは管理対象外となる。デフォルト値は「宣言を省略した場合の便宜的な値」であり、一度明示的に宣言したものを後で削除しても、Terraformは「以前の宣言状態に戻れ」とは指示しない。

「デフォルト値」と「管理対象からの離脱」の概念の理解が浅かった。

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