技術と日常。

日々の気が付いたこと・気になったことを残しておきます。

[Scala]無限ループより再帰を使うのがScalaらしいコードらしい

Scalaで無限ループを書くとエラー

投資商品等で、スタートの金額、利率、目標金額を与えて、何年後に到達するかを計算する関数をループで書きたいとします
※本当は対数計算のほうが筋が良いと思います

Javaの感覚で以下のように書くと、コンパイル時にエラーが出ます

def calc(start: Int, rate: Double, goal: Int): Int = {
  var years = 0
  var current: Double = start
  while(true) {
    if (current >= goal) {
      return years
    }
    years += 1
    current = current * rate
  }
}

>
Found:    Unit
Required: Int
  while(true) {

これは、Intを返さないといけないところ、scalaは最後の文を自動的に戻り値として認識するゆえに、最後の文であるwhileが値を返さないためUnitと認識されてしまっているために起こるエラーのようです

無限ループを成立させるには

到達することはないものの、最後に例外を投げてあげると成立します。

def calc(start: Int, rate: Double, goal: Int): Int = {
  var years = 0
  var current: Double = start
  while(true) {
    if (current >= goal) {
      return years
    }
    years += 1
    current = current * rate
  }

  // この文を追加するとコンパイルが通る
  throw new RuntimeException("ここには来ない");
}

積極的に再帰を使う

本題なのですが、他言語を普段書かれている方だと、再帰はStackOverflowを起こす可能性があることから、普段は再帰よりもループを選ばれると思います
ところが、Scalaでは、 末尾再帰の関数であれば、コンパイル時にループに書き換え、最適化をしてくれます
末尾再帰とは、 自身のシンプルな再帰呼び出しが、最後のステップになっているパターン のことです

例えば、これは末尾再帰です

def foo(): Int = {
  foo()
}

これは、末尾再帰ではありません
自身の呼び出しに+ 1と、余計なことをしてしまっているためです

def bar(): Int = {
  bar() + 1
}

そして、@tailrecアノテーションを付与することで、末尾再帰ではない、最適化がかからない関数に関しては、エラーを発生させることが可能です
まとめると、今回のケースだと、以下のように書き換えが可能です

import scala.annotation.tailrec

@tailrec
def calc(current: Double, rate: Double, goal: Int, years: Int): Int = {
  if (current >= goal) {
    years
  } else {
    calc(current * rate, rate, goal, years + 1)
  }
}

参考

Scala while(true) type mismatch? Infinite loop in scala? - Stack Overflow

末尾再帰 - Wikipedia

Scalaで再帰関数を使う際に覚えておくと役に立つこと - Qiita

[Scala]早期returnをしない方がScalaらしいコードらしい

タイトルの件、Javaだと「もし~~なら~~、それ以外なら~~を返す」という事をしたいときに、早期returnにて実装することがあります。

public String hoge(boolean isFoo) {
    if (isFoo) {
        return "FOO!";
    }
    return "BAR!";
}

Scalaでも同じように実装できますが

def hoge(isFoo: Boolean): String = {
  if (isFoo) {
    return "FOO!"
  }
  "BAR!"
}

Scalaのドキュメントにある通り、returnは必要なければ好まれないようです。

メソッド本体にある最後の式はメソッドの戻り値になります。(Scalaにはreturnキーワードはありますが、めったに使われません。)

言語特性を活かして、以下のように書けます。

def hoge(isFoo: Boolean): String = {
  if (isFoo) {
    "FOO!"
  } else {
    "BAR!"
  }
}

Scalaの場合ifが「式」なので、関数の最後にあるifが丸ごと戻り値となるためです。
Java三項演算子と同じ考え方ですね。

参考

[AWS Summit Tokyo 2023]ノベルティをもらい過ぎてしまったので紹介と反省

本日、AWS Summit Tokyoに行ってきました。
質の高いセッションと、それから得られるモチベーションとともに、営業スマイルに負けていっぱいお話を聞いて、たくさんのノベルティをもらってきてしまいました……。
スカスカで行ったリュックサックが、帰るころにはパンパンでした。

折角いろいろ頂いたので、宣伝をさせて頂ければと思います。
「こんなにいろいろ貰えちゃうなら、勉強がてら行ってみようかな!」という誰かの よこしまな モチベーションになれば幸いです。

ただ、反省、そして注意点なのですが、段々とノベルティをもらうことが楽しくなってきてしまい、目的を見失いそうになるので、ご注意ください(なにぶん豪華なものも多いので……)。

以下、左からです。
タイミングだったり、抽選だったり、時間によっては配り終わっていることもあるかもしれません。ご注意ください。


NHN テコラス株式会社

アイレット株式会社

Dynatrace合同会社


日本テラデータ株式会社(かばんフック)

dataris(ARアドバンストテクノロジ株式会社)

New Relic株式会社(Tシャツ 抽選)


レッドハッド株式会社

CircleCI Japan合同会社

TIS株式会社(蒸気でアイマスク)

C-Chorus(NHN テコラス株式会社/PCクリーナー)


サイオステクノロジー株式会社

Mackerel(株式会社はてな)


AWS公式

KDDI株式会社

トレンドマイクロ株式会社

どこかのチロルチョコ(すみません……)

[Java]文字列をreplaceAllで大文字/小文字化(\u, \l)する方法

文字列をreplaceAllで大文字/小文字化(\u, \l)する方法

そもそもPatternクラスで非対応

この記事にたどり着かれた方は、以下のように書いて「大文字小文字化しようとしたけれどできなかった!」という方も恐らく含まれているのではないかと思います。

// FOO barにしたいが……
"foo bar".replaceAll("(foo)", "\\u$1")

> Task :run
ufoo bar

これは、String#replaceAllにて内部的に呼び出されているPatternが、\u, \lに対応していないためです。

Perl 5との比較
Patternエンジンは、Perl 5と同じく、順序付けされた代替に対する従来のNFAベースのマッチングを実行します。

このクラスでサポートされていないPerl構文
...
プリプロセス演算\l \u、\L、および\U。

Pattern (Java SE 17 & JDK 17)

Java9以降なら解決策がある

Java9から、MatcherreplaceAll(Function<MatchResult,String> replacer)が追加されました。
こちらを使用することで、マッチした部分を大文字、小文字化することが可能です。

Pattern.compile("(foo)")
        .matcher("foo bar")
        .replaceAll(result -> result.group(1).toUpperCase());

> Task :run
FOO bar

参考

Use Java and RegEx to convert casing in a string - Stack Overflow

[Java]StreamのgroupingByで、元の順序を保つ方法

groupingByを普通に書くと

例えば、あるStringのリストがあり、それぞれの個数を数えるケースを考えます。
普通に書くと以下のようになるかと思います。

Map<String, Long> collected = List.of("d", "c", "b", "a", "d", "c", "b", "d", "c", "d")
        .stream()
        .collect(Collectors.groupingBy(
                Function.identity(),
                Collectors.counting()
        ));
System.out.println(collected);

> Task :run
{a=1, b=2, c=3, d=4}

この場合、groupingByの結果は、Listの順序は保証されません。
これは、戻り値として使用されるMap自体が、順序が保証されていないためです。

LinkedHashMapを使う

そこで、引数を3つ取るgroupingByを使用します

public static <T, K, D, A, M extends Map<K, D>>
Collector<T,?,M> groupingBy(
        Function<? super T,? extends K> classifier,
        Supplier<M> mapFactory,
        Collector<? super T,A,D> downstream
)

第2引数に順序が保証されるLinkedHashMapSupplierを渡すことで、順序が保たれます。

Map<String, Long> collected = List.of("d", "c", "b", "a", "d", "c", "b", "d", "c", "d")
        .stream()
        .collect(Collectors.groupingBy(
                Function.identity(),
                LinkedHashMap::new,
                Collectors.counting()
        ));
System.out.println(collected);

> Task :run
{d=4, c=3, b=2, a=1}

2引数のgroupingByの実装

参考までに、2引数のgroupingByの実装を見ると、内部では3引数のgroupingByに、順序の保たれないHashMapを渡していることがわかります。

public static <T, K, A, D>
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                      Collector<? super T, A, D> downstream) {
    return groupingBy(classifier, HashMap::new, downstream);
}

参考

Collectors (Java SE 17 & JDK 17)

java - Stream doesn't preserve the order after grouping - Stack Overflow

[放送大学]2022年度の受講科目と振り返り

2022度の受講科目と試験結果

1学期

履修年度 履修学期 科目コード 科目名 評語 合否
2022 1学期 1170040 情報学へのとびら(’22) 合格
2022 1学期 1140094 運動と健康(’22) 合格
2022 1学期 1150030 日本語リテラシー(’21) 合格
2022 1学期 1570269 データベース(’17) 合格
2022 1学期 1570374 映像コンテンツの制作技術(’20) 合格
2022 1学期 1920014 色と形を探究する(’17) 合格
2022 1学期 1519174 今日のメンタルヘルス(’19) 合格
2022 1学期 1940015 音を追究する(’16) 合格

2学期

履修年度 履修学期 科目コード 科目名 評語 合否
2022 2学期 1140051 問題解決の進め方(’19) 合格
2022 2学期 1750054 日常生活のデジタルメディア(’22) 合格
2022 2学期 1570285 コンピュータとソフトウェア(’18) 合格
2022 2学期 1570358 Webのしくみと応用(’19) 合格
2022 2学期 1950029 AIシステムと人・社会との関係(’20) 合格
2022 2学期 1710036 健康と社会(’17) 合格
2022 2学期 1710125 健康への力の探究(’19) 合格
2022 2学期 1710150 生活経済学(’20) 合格

印象・思い出に残った科目

1150030 日本語リテラシー(’21)

「日本語の助詞の働きを意識して読めているか」ということを、改めて考えさせられました。
特に第7回の「は」と「が」の違いについての考察が一番印象に残っています。
放送授業の終盤で出てきた、伊豆の踊子の翻訳の話は「本当に日本語が読めてますか?」ということを突き付けられた気持ちになり、ショックを受けました。是非印刷教材だけではなく、放送授業も見てほしいです。

1570269 データベース(’17)

レベル感としては、IPAデータベーススペシャリスト試験レベルのようです。実際に章末の演習問題は、その過去問から出題をされています。
RDBMSを扱うエンジニアなら、第一正規形が良くないことは知っていると思いますが、9章の第1正規形が更新時異常を引き起こす話は、その不都合を3パターンに分けて解説しており、改めて勉強になりました。
また、8章の選択、射影、結合などの演算の厳密な定義の話も興味深かったです。

1570374 映像コンテンツの制作技術(’20)

普段よくみる映像作品が、裏ではどう作られるのか、という内容です。
長時間の映像を短い尺ににカットするときに、どう映像をつなげば不自然ではないのか、という話が12章で学べるのですが、これを読む前と読んだあとで、映像作品を見てるときに「お、あの技法使ってるのか~」なんて考えたりするようになり、見方が少し変わった気がします。

1950029 AIシステムと人・社会との関係(’20)

AIの歴史から、AIを使った結果良かったこと悪かったことの実例と、最後は人間はAIとどう付き合っていけば、という話が学べます。
AIを使う上での倫理についての話が、14, 15章に載っています。
ChatGPT等の登場により、急激にシンギュラリティが現実味を帯びてきましたが、人間には、それを使う上での高い倫理観と責任が求められるようになると感じています。
これから、AI抜きでの生活というのはおそらく不可能なので、総合科目として、技術者にもそうでない人にも、広くいろいろな人にお勧めできます。

1750054 日常生活のデジタルメディア(’22)

この科目は内容ではなく、単位認定試験が記憶に残っています。
放送大学の単位認定試験は以下の3つのパターンがあると思っています。

  1. 印刷教材をちゃんと勉強すれば解ける
  2. 印刷教材だけではなく、放送授業まで視聴し理解する必要がある
    • このパターンの場合、印刷教材と放送授業の内容が違う
  3. 印刷教材・放送授業の両方を勉強しても厳しい
    • そもそも授業の内容が難しかったり、授業でやっていない問題が出たり

この科目は2のパターンでした、特に2022年1学期の過去問は、試験問題の大半が放送授業の内容(しかも結構細かいところ)から出されており、非常に焦りました。
実際にその試験の平均点は63.7点とかなり低く、当時のTwitterでも、難しかったという投稿が上がっていました。

放送授業を視聴しながらスクショやノートを取り試験に臨み、実際には1学期ほどは難しくなかった(と個人的には感じた)ものの、やはり放送授業の細かい点から出題され、一筋縄ではいかないと感じた科目でした。
ちなみに2022年1学期は、科目改定後初めての試験であり、試験を作る側としても、科目を頑張って作ったという想いが試験にあふれてしまったのかな……、なんて感じました。
内容としては良い科目でしたので、特に腰を据えて学習できる方にはおすすめです。

終わりに

この記事が、単位選択の際の何らかのヒントになれば幸いです。

[放送大学]放送授業のスクリーンショットが真っ黒になるときに読むページ

放送授業のスクリーンショットが真っ黒になってしまう時には

皆様、先日は単位認定試験お疲れさまでした。

さて表題の件、試験前に放送授業を見ながら、大事そうなところをメモ代わりにスクリーンショットにて撮っていたのですが、ある時まで撮れていたのが、ちょっと休憩をはさんだ後、急にできなくなってしまいました。

調べたところ、ハードウェア アクセラレーションに起因するもののようです。
例えばChromeの場合、 設定 > システム > ハードウェア アクセラレーションが使用可能な場合は使用する よりチェックを外すと、私の場合はスクリーンショットが撮れるようになりました。

ただ、この設定を外すと、YouTubeなど他の動画サイトを視聴の際に、PCが重くなることが考えられます。
そういう時は、また設定を有効にしてみてください。

参考

【PC画面録画】画面が黒くて録画できない時の対処方法 - LAPTOPRENEUR~ひざのうえライフ