Murayama blog.

プログラミング教育なブログ

超速習 - はじめてのPHPプログラミング

最速?でPHPプログラミングの基礎を学ぶためのガイドです。できるだけ短いコードでプログラミングの基礎となる変数や配列、制御構文、関数を紹介してみました。30分くらいで一通り紹介するイメージで。

開発環境としてはMacを想定しています。最近のMacはデフォルトでPHPがインストールされているので、AtomVSCodeのようなテキストエディタとターミナル*1を起動すればすぐにPHPの学習を始めることができます。*2

サンプルプログラム

ここでは以下のサンプルプログラムを取り上げます。最速で。

  1. データの出力
  2. 変数
  3. 配列(添字配列)
  4. 制御構文(for文)
  5. 制御構文(if文)
  6. 配列(連想配列
  7. 関数
  8. まとめ

プログラミング初学者にとっては配列や関数など躓きやすいポイントがいくつかありますが、上から順番に学習を進めていけばどこが苦手かチェックできると思います。

1 データの出力(sample1.php

次のプログラムは画面(ターミナル)にHello PHPと出力するプログラムです。ファイル名はsample1.phpとして任意のフォルダに保存します。

以降のサンプルプログラムはMac上で/Users/murayama/Desktop/php-basicフォルダに保存したものとします。

<?php
echo "Hello PHP";
?>

PHPで画面にデータを出力するにはecho命令を使います。上記のように記述すれば画面にHello PHPと出力されます。

次にターミナル(Windowsの場合はコマンドプロンプト)を開きます。カレントディレクトリ(カレントフォルダ)を変更するために以下のコマンドを入力します。

$ cd /Users/murayama/Desktop/php-basic

ターミナル上で現在作業している(開いている)ディレクトリをカレントディレクトリと呼びます。cdコマンドによってカレントディレクトリを変更することができます。ここではカレントディレクトリを/Users/murayama/Desktop/php-basicに変更しています。

注意:自分のMacに合わせて上記のパス/Users/murayama/Desktop/php-basicは置き換えてください。

それでは作成したプログラムを実行してみましょう。ターミナル上でphpコマンドを入力します。

$ php sample1.php
Hello PHP

上記のようにHello PHPと出力されればOKです。

PHPプログラムの終端となる閉じタグ?>は省略可能です。以降は?>を省略してコードを記載します。

2 変数(sample2.php

続いて変数を扱うプログラムについて見てみましょう。変数とはプログラム上でデータを扱う仕組みです。次のプログラムをsample2.phpという名前で保存します。

<?php
$name = "Andy";
echo "Hello ";
echo $name;

ここでは変数$nameを定義しています。また変数$nameの中に"Andy"という文字列データを代入しています。PHPの文字列データは""(ダブルクォーテーション)あるいは''(シングルクォーテーション)で囲む必要があります。変数に格納したデータはecho命令で出力できます。

PHPの変数は先頭に$マークが付きます。

それでは作成したプログラムを実行してみましょう。ターミナル上でphpコマンドを入力します。

$ php sample2.php
Hello Andy

上記のようにHello Andyと出力されればOKです。

さて、もう一つ変数を扱うサンプルプログラムを考えてみましょう。先ほどのプログラムはHello Andyと出力しましたが、もう一人、登場人物として"Betty"を追加してみましょう。先ほどのプログラムsample2.phpを修正します。

<?php
$name = "Andy";
$name2 = "Betty";

echo "Hello ";
echo $name;
echo "Hello ";
echo $name2;

変数が$name$name2の2つになりました。変数$nameには"Andy"$name2には"Betty"がそれぞれ代入されています。

それでは作成したプログラムを実行してみましょう。ターミナル上でphpコマンドを入力します。

$ php sample2.php
Hello AndyHello Betty

上記のように画面にHello AndyHello Bettyと出力されればOKです。ところでもう一人名前の出力を追加する場合はどうでしょうか。"Andy""Betty""Carol"のように名前を追加していくと、変数の数も多くなってしまいます。

3 配列(添字配列)(sample3.php

続いて配列を扱うプログラムについて見てみましょう。配列は変数の一種で、関係性のある複数のデータをまとめて管理する仕組みです。たとえば先ほどの"Andy""Betty""Carol"のような名前を表すデータは配列で管理すると簡単になります。次のプログラムをsample3.phpという名前で保存します。

<?php
$names = ["Andy", "Betty", "Carol"];

echo "Hello ";
echo $names[0];
echo "Hello ";
echo $names[1];
echo "Hello ";
echo $names[2];

変数$namesには"Andy""Betty""Carol"と3つのデータが代入されています。このように複数のデータをまとめて管理する仕組みを配列(添字配列)と呼びます。配列は前から順番に要素番号が割り振られます。また要素番号の先頭は1ではなく0から始まる点に注意しておきましょう。

変数名が$namesと複数形になっている点も注目してください。配列のような複数のデータを表現する変数は名前の付け方を工夫すると読みやすくなります。

それでは作成したプログラムを実行してみましょう。ターミナル上でphpコマンドを入力します。

$ php sample3.php
Hello AndyHello BettyHello Carol

上記のように出力されればOKです。

4 制御構文(for文)(sample4.php

配列のような集合データはfor文などの繰り返し構造を使えば簡単に出力できます。次のプログラムをsample4.phpという名前保存します。

<?php
$names = ["Andy", "Betty", "Carol"];

for ($i = 0; $i < 3; $i++) {
    echo "Hello ";
    echo $names[$i];
}

ここで繰り返し構造であるfor文は変数$iの値が0から3まで(計3回)処理を繰り返します。

for文で扱う変数$iはカウンター変数などと呼ばれます。変数$iのiはincrement(増える)という単語の頭文字を意味しています。for ($i = 0; $i < 3; $i++)とすると、変数$iの初期値は0となり、$i < 3の条件が成立する間、処理(forの後の{})を繰り返します。繰り返しが1回終了するごとに$i++が実行されます。$i++$iの値を1増やすという処理なので、繰り返しの都度、変数$iの値が1増えることになります。

それでは作成したプログラムを実行してみましょう。ターミナル上でphpコマンドを入力します。

$ php sample4.php
Hello AndyHello BettyHello Carol

上記のように出力されればOKです。

5 制御構文(if文)(sample5.php

さきほどのプログラムを少し修正してみましょう。ここではif文を使って"Andy""Betty""Carol" 3人の名前の中から"Andy"以外の名前を出力するように修正してみましょう。次のプログラムをsample5.phpという名前保存します。

<?php
$names = ["Andy", "Betty", "Carol"];

for ($i = 0; $i < 3; $i++) {
    if ($names[$i] != "Andy") {
        echo "Hello ";
        echo $names[$i];
    }
}

for文の中で、if文を使って変数($names[$i])の値が"Andy"でないか確認しています。

if文で利用している演算子!=は左辺と右辺が等しくない場合に真(True)となります。等しいかどうかを比較する場合は==を使います。!=====といった演算子も大切ですが、もう少しあとで勉強しましょう。

それでは作成したプログラムを実行してみましょう。ターミナル上でphpコマンドを入力します。

$ php sample5.php
Hello BettyHello Carol

上記のように出力されればOKです。

6 配列(連想配列)(sample6.php

少し複雑なプログラムも見てみましょう。ここでは"Andy""Betty""Carol" 3人のデータに対して"Andy"20歳、"Betty"19歳、"Carol"21歳のように、年齢(age)データも定義します。また出力の条件も変更して、年齢が20歳以上であればHelloと出力するように修正してみましょう。次のプログラムをsample6.phpという名前保存します。

<?php
$students = [
  ["name" => "Andy", "age" => 20],
  ["name" => "Betty", "age" => 19],
  ["name" => "Carol", "age" => 21]
];

for ($i = 0; $i < 3; $i++) {
    if ($students[$i]["age"] >= 20) {
        echo "Hello ";
        echo $students[$i]["name"];
    }
}

連想配列はダブルアロー演算子=>を使って定義します。通常の配列(添字配列)は前から順番に要素番号が割り振られるのに対して、連想配列は要素番号ではなく、キー("name""age"のような文字列)を割り振ります。

またfor文の中で、if文を使って変数$names[$i]["age"]の値が20以上かどうか確認しています。

それでは作成したプログラムを実行してみましょう。ターミナル上でphpコマンドを入力します。

$ php sample6.php
Hello AndyHello Carol

上記のように出力されればOKです。

7 関数(sample7.php

続いて本講座では関数について取り上げます。関数を使うサンプルプログラムを見てみましょう。次のプログラムはstrtoupper関数を使って"Andy""Betty""Carol"3人の名前をアルファベット大文字で出力します。次のプログラムをsample7.phpという名前保存します。

<?php
$names = ["Andy", "Betty", "Carol"];

for ($i = 0; $i < 3; $i++) {
    echo "Hello ";
    echo strtoupper($names[$i]);
}

for文の繰り返し処理の中でstrtoupper関数を利用しています。strtoupper関数は引数$names[$i]に受け取った値を大文字に変換する関数です。

引数とは関数の受け取るデータのことです。また関数の返却するデータは戻り値(返り値)などと呼びます。

それでは作成したプログラムを実行してみましょう。ターミナル上でphpコマンドを入力します。

$ php sample7.php
Hello ANDYHello BETTYHello CAROL

上記のように出力されればOKです。

アルファベット小文字に変換するにはstrtolower関数を使います。

PHPにはstrtoupper以外にも様々な関数が用意されています。次のサンプルプログラムはmkdir関数を使ってディレクトリ(フォルダ)を作成するプログラムです。さきほどのプログラムsample7.phpを次のように修正してみましょう。

<?php
$names = ["Andy", "Betty", "Carol"];

for ($i = 0; $i < 3; $i++) {
    mkdir($names[$i]);
}

for文の繰り返し処理の中でmkdir関数を利用しています。mkdir関数は引数$names[$i]に受け取った値を使ってディレクトリを作成します。

それでは作成したプログラムを実行してみましょう。ターミナル上でphpコマンドを入力します。

$ php sample7.php

このプログラムは実行結果をターミナルに表示しませんが、Finderやlsコマンド(Windowsの場合はExplorerやdirコマンド)で生成されたディレクトリを確認できるでしょう。

$ ls
Andy    Betty    Carol

上記のように生成されたディレクトリを確認できればOKです。

8 まとめ(sample8.php

それではこれまでのまとめとして、配列(連想配列)や制御構造(for文やif文)、それから関数(strtoupper)を使うプログラムを作成してみましょう。次のプログラムをsample8.phpという名前保存します。

<?php
$students = [
  ["name" => "Andy", "age" => 20],
  ["name" => "Betty", "age" => 19],
  ["name" => "Carol", "age" => 21]
];

for ($i = 0; $i < 3; $i++) {
    if ($students[$i]["age"] >= 20) {
        echo "Hello ";
        echo strtoupper($students[$i]["name"]);
    }
}

それでは作成したプログラムを実行してみましょう。ターミナル上でphpコマンドを入力します。

$ php sample8.php
Hello ANDYHello CAROL

上記のように出力されればOKです。

おわりに

以上、駆け足でPHPプログラミングについて紹介しました。

  1. データの出力
  2. 変数
  3. 配列(添字配列)
  4. 制御構文(for文)
  5. 制御構文(if文)
  6. 配列(連想配列
  7. 関数

わずかなプログラムでしたが、これだけでもプログラミングの基礎的な概念は網羅できています。ここで紹介したコードの読み書きができればPHPerとしての道は開けると思います。

*1:ターミナルはSpotlightでTerminalと入力すると起動します。

*2:ブラウザでPHPプログラムを開発するには https://repl.it/ などが便利です。repl.itで実行する場合はPHPの閉じタグ?>が要るみたいなので注意してください。

30minくらいで学ぶVue.jsとVuex

カウンターアプリケーションの開発を通じてVue.jsによるプログラミングとVuexによる状態管理を学びます。

f:id:yamasahi:20190213141451p:plain

ボタンを押したら数字が増えていくアプリケーションです。

Agenda

  • Part 1 Vueアプリケーションの開発(10min)
  • Part 2 Vuexを活用したVueアプリケーションの開発(10min)
  • Part 3 非同期処理(Actions)の実装(10min)

Part 1 Vueアプリケーションの開発(10min)

プロジェクトの作成

vue-cliを使ってプロジェクトの雛形を作成します。

vue create counter-app

vue-cliの設定はデフォルトを使います。途中でVuexを追加しますが手動で追加するものとします。

プロジェクトが作成できたらディレクトリを移動してサーバを起動してみましょう。ここではyarnを使って起動しています(インストールの設定によってはnpmでも大丈夫です)。

cd counter-app
yarn serve

次のような画面を確認できればOKです。

f:id:yamasahi:20190213141744p:plain

Vueコンポーネント(MyCounter.vue)の作成

vue-cliで作成したプロジェクトの開発では主にsrcディレクトリ下のファイルを編集することになります。

  • src/
    • assets/
      • 画像ファイルなどのアセット
    • components/
      • App.vueからロードされる画面の部品となるVueファイル
    • App.vue
      • main.jsからロードされるVueファイル
    • main.js
      • 起動ファイル

src/components/Mycounter.vueファイルを新規作成します。

<template>
    <div>
        Count: {{ count }}
        <button @click="onclick">Increment</button>
    </div>
</template>

<script>
export default {
    data () {
        return {
            count: 1
        }
    },
    methods: {
        onclick () {
            this.count++
        }
    }
}
</script>

続いてsrc/App.vueを修正します。

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
-    <HelloWorld msg="Welcome to Your Vue.js App"/>
+    <MyCounter />
  </div>
</template>

<script>
-import HelloWorld from './components/HelloWorld.vue'
+import MyCounter from '@/components/MyCounter.vue'

export default {
  name: 'app',
  components: {
-    HelloWorld
+    MyCounter
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

ブラウザでサーバ(localhost:8080)にアクセスすると次のような画面が表示されるでしょう。

Part 2 Vuexを活用したVueアプリケーションの開発(10min)

Vuexのインストール

つづいてVuexをインストールします。

VuexはVue.jsアプリケーションのための状態管理パターン+ライブラリです。 https://vuex.vuejs.org/ja/

$ yarn add vuex --save

ここではMyComponent.vueの状態(dataプロパティ)をVuex上で管理するように修正していきます。

Storeの作成

Vuex上で状態を管理するためのStoreファイルを作成します。src/store/index.jsを作成し、以下のように実装します。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment (state) {
            state.count++
        },
    },
})

export default store

VuexのStoreには以下の主に4つを記述します。

  • state・・・状態データ
  • mutations・・・状態を更新する処理
  • getters・・・状態を取得する処理
  • actions・・・非同期処理(mutationsを呼び出す)

gettersとactionsについては後述します。

Vueコンポーネント(MyCounter.vue)の修正

src/components/MyCounter.vueを次のように修正します。

 <template>
     <div>
         Count: {{ count }}
         <button @click="onclick">Increment</button>
     </div>
 </template>

 <script>
 export default {
-    data () {
-        return {
-            count: 1
-        }
-    },
+    computed: {
+        count() {
+            return this.$store.state.count;
+        }
+    },
     methods: {
         onclick () {
-            this.count++
+            this.$store.commit('increment')
         }
     }
 }
 </script>

VueコンポーネントからStoreにアクセスするにはthis.$storeと記述します。this.$store.state.countで状態(state)にアクセスし、状態を更新するmutationsを実行するにはcommitメソッドを使います。

ブラウザでサーバ(localhost:8080)にアクセスして正しく動作することを確認しておいてください。

mapGetters、mapMutationsによるリファクタリング

Vuexには以下のヘルパーメソッドが用意されています。

  • mapState
  • mapMutations
  • mapGetters
  • mapActions

上記のヘルパーメソッドを使うとVueコンポーネントの記述がシンプルになります。

ここではmapGettersとmapMutationsを利用するように修正してみます。

まずはsrc/store/index.jsを修正します。Storeファイルにgettersを追加します。

 import Vue from 'vue'
 import Vuex from 'vuex'

 Vue.use(Vuex)

 const store = new Vuex.Store({
     state: {
         count: 0
     },
+    getters: {
+        count: state => state.count
+    },
     mutations: {
         increment (state) {
             state.count++
         },
     },
 })

 export default store

ここでgettersは単純にstateを返却していますが、gettersはStoreの状態を算出(フィルタリングなど)する用途に使えます。

次にsrc/componetns/MyCounter.vueを修正します。

<template>
    <div>
        Count: {{ count }}
-        <button @click="onclick">Increment</button>
+        <button @click="increment">Increment</button>
    </div>
</template>

<script>
+import { mapGetters, mapMutations } from 'vuex'
+
export default {
    computed: {
-        count() {
-            return this.$store.state.count;
-        }
+        ...mapGetters(['count'])
    },
    methods: {
-        onclick () {
-            this.$store.commit('increment')
-        }
+        ...mapMutations(['increment'])
    }
}
</script>

mapMutationsやmapGettersは戻り値にオブジェクトを返すので、オブジェクトスプレッド演算子を合わせて利用します。また引数には利用したい処理を定数の配列で渡すようにします。

ブラウザでサーバ(localhost:8080)にアクセスして正しく動作することを確認しておいてください。

Part 3 非同期処理(Actions)の実装(10min)

Storeの修正

続いて非同期処理を実装してみます。ここではボタンを押してから2秒後にインクリメントするIncrement Asyncボタンを実装します。

まずはsrc/store/index.jsを修正します。

 import Vue from 'vue'
 import Vuex from 'vuex'

 Vue.use(Vuex)

 const store = new Vuex.Store({
     state: {
         count: 0
     },
     getters: {
         count: state => state.count
     },
     mutations: {
         increment (state) {
             state.count++
         },
     },
+    actions: {
+        incrementAsync ({ commit }) {
+            setTimeout(() => commit('increment'), 2000)
+        }
+    }
 })

 export default store

新たにactionsを定義しています。actionsではmutationsをcommitするようにします。

actionsはWeb APIの呼び出しなどに適しています。

Vueコンポーネント(MyCounter.vue)の修正

続いてsrc/components/MyCounter.vueを修正します。

<template>
    <div>
        Count: {{ count }}
        <button @click="increment">Increment</button>
+        <button @click="incrementAsync">Increment Async</button>
    </div>
</template>

<script>
import { mapGetters, mapMutations } from 'vuex'

export default {
    computed: {
        ...mapGetters(['count'])
    },
    methods: {
+        incrementAsync () {
+            this.$store.dispatch({
+                type: 'incrementAsync'
+            })
+        },
        ...mapMutations(['increment'])
    }
}
</script>

新たにincrementAsyncメソッドを追加しています。incrementAsyncメソッドではStoreに対してdispachメソッドを呼ぶことでactionsを呼び出しています。

ブラウザでサーバ(localhost:8080)にアクセスして正しく動作することを確認しておいてください。

mapActionsによるリファクタリング

続いてmapActionsを使ってリファクタリングしてみましょう。src/components/MyCounter.vueを修正します。

<template>
    <div>
        Count: {{ count }}
        <button @click="increment">Increment</button>
        <button @click="incrementAsync">Increment Async</button>
    </div>
</template>

<script>
-import { mapGetters, mapMutations } from 'vuex'
+import { mapGetters, mapMutations, mapActions } from 'vuex'

export default {
    computed: {
        ...mapGetters(['count'])
    },
    methods: {
-        incrementAsync () {
-            this.$store.dispatch({
-                type: 'incrementAsync'
-            })
-        },
+        ...mapActions(['incrementAsync']),
        ...mapMutations(['increment'])
    }
}
</script>

ブラウザでサーバ(localhost:8080)にアクセスして正しく動作することを確認しておいてください。

まとめ

Vuexを使ってVue.jsアプリケーションの状態管理を試してみました。Vuexの詳細については公式のマニュアルを読めば大体わかります。

https://vuex.vuejs.org/ja/

以降はVue Routerを理解して、Nuxt.jsの学習を進めていくと良いでしょう。

勢いでMac買っちゃった人のためのWebプログラミング開発環境構築

同級生(昭和54年生まれ)の友達が最近プログラミングに興味を持っていて「Mac買おうかなー」と迷っていたのでまとめました。プログラミング楽しいよ。

私とプログラミング

2019年1月からMacBook Pro(15)に乗り換えました。以前の13インチに比べて5cmくらい?画面が大きくなり、しばらく満足していたのですが1週間も経つともっと画面広かったらなーと思うようになりました。しばらくコワーキングで仕事しているのでデュアルディスプレイでないのが少しさびしい。

これからはしばらくFirebaseを中心にWebアプリケーションをいくつか作る予定です。フロントエンドはVue.jsを中心にJavaScriptをしっかりやろうと思っています。あとたまにPHPのコードも書きます。そんなわけで必要なツールはエディターとターミナルとブラウザがあれば十分です。というわけで以下のアプリをインストールしました。

エディタ(VS Code

エディタはこれまでAtomを使っていましたが(今も使っていますが)VS Codeに乗り換えました。

code.visualstudio.com

デフォルトでも機能が十分ですがVimプラグインなどを入れてプログラマーっぽくしています。他にはGitLensとかGit系の拡張を入れたり、FirebaseとかVueとかのJSまわりのプラグインをいくつかインストールしました。

WakaTime

VS Codeにはプログラミングの時間を計測してくれるWakaTimeプラグインもインストールしてみました。

wakatime.com

ちなみに私の最近のプログラミング時間はこんなかんじ。

https://wakatime.com/@murayama333

DAILY AVERAGE 2 hrs 56 mins

最近だと毎日3Hくらいコーディングの時間を確保できているみたい。5Hくらいいきたい。これからプログラミング始める人にもおすすめのプラグインです。

Dash

JavaScriptとかAPIよくわかっていないのでDash入れてみました。

kapeli.com

ショートカットキーが安定していないけど、cmd + shift + spaceとかでAPIをひけるようにしています。あとスニペット機能も優秀みたいだけどまだあまり試していない。

ターミナル(iTerm2)

続いてターミナル編。デフォルトのターミナルでも良いような気がしますがプログラマーっぽくありたいのでiTerm2をインストールしました。

www.iterm2.com

CUIベースでコード書くのもカッコいいのでVimでいくべきか迷うのですが、Vim素人なので今はVS Codeでコードを書くようにしています。そうするとエディタとiTerm2の切り替えが頻繁に発生するのでiTerm2のホットキー設定を有効にしています。Alt + SpaceでいつでもiTtem2が表示されるので気持ち良いです。ctrlキー連打で開くのもありみたいです。

blog.mah-lab.com

Homebrew

Macだとお約束のパッケージ管理ツールHomebrewをインストールします。

brew.sh

インストールは上記のページを参考に。インストールが完了するとbrewコマンドでパッケージを追加できるようになります。以下はwgetコマンドをインストールする例です。

$ brew install wget

zsh

デフォルトのシェルはzshに変更しています。複数のターミナル間でコマンドのヒストリを共有できるのが有り難いです。たしかzshbrewでインストールしたような気がします。

$ brew install zsh

tmux

iTermの中ではtmuxをインストールしています。

$ brew install tmux

tmuxを使えば、iTermの画面表示を分割したりタブ移動したり便利です。プレフィックスキーはこれまでctrl + tを使っていましたが、今回からはctrl + spaceに変更しました。余談ですがVimのleaderもspaceキーに変更していてspace駆動で操作できるように設定しています。

以下のサイトを参考にしています。

qiita.com

qiita.com

tmuxのステータスバーにバッテリーとかWi-Fiとかいろんな情報を表示すると楽しいです。

peco

シェルではctrl + rでコマンドの履歴を検索したいのでpecoを入れておきました。brewでインストールできます。

$ brew install peco

以下のサイトを参考にしています。

qiita.com

ctrl + x でcdコマンドを補完できるのが地味に便利です。

Vim

やっぱりVimの設定も楽しいです。LIGさんの設定がシンプルで良かったです。

liginc.co.jp

上記のページでも紹介がありましたが、少し古い記事ですが「Vimの生産性を高める12の方法」も面白かったです。

postd.cc

ブラウザ(Chrome

とりあえずChromeを入れました。あんまりカスタマイズはしていないです。はてなブックマーク拡張は入れています。あとはVueの開発用の拡張とか。

ユーティリティ系

ウィンドウのリサイズにはSpectacleがちょうどいいです。

www.spectacleapp.com

あとはVS Codeでctrl + m での改行が上手く効いてくれないのでキーのリマップもやろうか迷い中。Karabinerのお世話になるかも。

pqrs.org

画面を広く使いたい

ここまでベタなアプリの設定ばかりだったので。ライフハック?的なやつも。

これからしばらくMac1台で仕事をするので画面を広く効率的に使いたいところです。なので、まずMacの「システム環境設定」で以下のとおり変更します。

  • Dockを隠す
  • メニューバーを隠す

そうすると画面が少し広くなります。

次にiTerm2からタイトルバーを消します。iTerm2の環境設定Profile=>WindowにあるStyleで No Title Barを選びます。

f:id:yamasahi:20190207124258p:plain

タイトルバーが消えて1cmくらいだけでも画面が広くなると嬉しいです。

調べてみたらVS CodeAtomもタイトルバーをプラグインで消すことができました。

marketplace.visualstudio.com

VS Codeの拡張は再起動時に警告が出るけどとりあえず無視しています。

以上でこんなかんじになります。

f:id:yamasahi:20190207124348p:plain

ちなみに画面を広く使って集中していると「今何時?」となることが多いので、ターミナルとかエディタのステータスバーに時刻を表示するようにしています。

フリーランス始めました。

近況です。元気にやっています。

  • 税務署に行った。開業届(青色申告)を提出した。
  • 税務署はこわいイメージがあったけど書類を出すのは1分で終わった。
  • フリーランス始めました。
  • MacBook Proを買った。
  • 大きいサイズ(15インチ)にしたら快適だった。
  • お名前.comでドメインを取った。
  • お名前.comからの案内メールが多すぎ。
  • GSuiteを申し込んだ。
  • https://gsuite.google.co.jp/intl/ja/
  • Googleあればどこでも仕事できそうな気がした。
  • Freeeを申し込んだ。
  • https://www.freee.co.jp/
  • 会計処理だけでなく見積もり書とか、書類関連も作ってくれた。
  • Freeeがあればぼくでも仕事ができそうな気がした。
  • 印鑑も買った。
  • ほとんどWebやし印鑑とか意味あるのかなーと思いつつも購入した。
  • ビジネスにおけるネクタイみたいなもの、と聞いて納得。
  • ちなみに印鑑屋さんは減ってるらしい。
  • 名刺はまだない。
  • コワーキングに通い出した。
  • 月額1万円くらい。ほどよく空いている。
  • 家からコワーキングまで片道3kmを歩いてる。
  • まだ痩せていない。
  • むしろ肥えた。
  • フリーランスの税金について勉強をした。
  • 雰囲気わかってきた。
  • この本のおかげ
  • https://www.amazon.co.jp/dp/4801400604
  • 口座も開設した。
  • デビットカード
  • 領収書(レシート)をもらうようになった。
  • 案の定、財布がパンパンになった。
  • Windowsマシンも必要だと気づいてThinkPadを買った。
  • MacよりThinkPadで仕事している人の方がかっこいい説も浮上。
  • スマホの充電ができていないことが多い。
  • スマホも買い換えようか迷う。
  • なんでも経費に見える。
  • 平日の昼間に映画:ボヘミアン・ラプソディを観た。
  • この10年で観た映画の中で1番良かった。
  • フレディ・マーキュリーのファンに。
  • Spotifyは経費扱いになるのだろうか。
  • ちなみに映画を見たのは10年ぶり。
  • 学校を訪問させて頂いた。
  • 勉強する空間は良い。
  • 年齢とか関係なくいろいろ学び直したい。
  • お世話になった人に再会できた。
  • ヒントも頂いた。
  • これからもお世話になります。
  • 前の職場を訪問した。
  • みんな元気そうだった。
  • いうても2週間ぶり。
  • 今はまだ懐かしさとかない。
  • Webで知り合った社会人の方にプログラミングを教えている。
  • Webで知り合った大学生にもプログラミングを教えている。
  • お二人ともリピートしてくれている。
  • ありがたい。
  • 動画教材づくりにUdemyを始めようかと思っている。
  • 教えてもらったCamtasiaをインストールして動画づくりを始めている。
  • テキスト教材づくりも今までのやり方を見直している。
  • MarkdownよりAsciidocが良いみたい。
  • https://qiita.com/xmeta/items/de667a8b8a0f982e123a
  • 電子書籍づくりをGitHubで管理している人もいる。
  • https://azu.github.io/slide/individual/
  • すごく参考になる。
  • Firebaseでアプリを作り始めた。
  • GoogleのCodelabsのチュートリアルがすごく充実している。
  • https://codelabs.developers.google.com/
  • 今は作る時間を最優先にしたい。
  • とかいいつつ、こんなブログを書いている。
  • ベンチャー企業の方とお会いすることが増えた。
  • 志、技術、マーケティング。全部大事だと思う。
  • 組織づくりに興味が湧いてきた。
  • OKRの本(ジョン・ドーア本)が良かった。
  • https://www.amazon.co.jp/dp/4532322405
  • 去年読めなかったサピエンス全史も読み終えた。
  • コロンブスの新大陸発見からいろいろ繋がっているように思う。
  • 「文系でプログラマーになったけど色々失敗して3年半で会社を辞めた話」を読んだ。
  • https://note.mu/denkigai/n/nafff6bd87802
  • 「プログラミングを覚えてから何をすればいいかわからなかった」
  • と感じている人は確かに多いかも。
  • 例えとしてのSTRIDERの話も面白かった。
  • 最近noteって流行ってるように思う。
  • 飛行機に乗った。
  • 1時間のフライトも想像力が邪魔をして生きた心地がしなかった。
  • 登壇した。
  • 仕事している感じがした。
  • パフォーマーだ」
  • ブログも書いた。

JavaScript中級トレーニング10問(String編)

この前の記事(JavaScript 30-seconds-of-code String編)をベースにJavaScript問題集を作ってみました。

murayama.hatenablog.com

問題は全部で10問。正規表現をゴリゴリ使うのはなるべく避けてみました。個人的にはヒント見ずに(ググらずに)1時間で全部解けたらマスタークラスだと思います。

  1. isLowerCase
  2. reverseString
  3. truncateString
  4. mapString
  5. mask
  6. compactWhitespace
  7. palindrome
  8. capitalize
  9. byteSize
  10. pluralize

開発環境はブラウザとエディタで十分ですがCodeSandboxも便利です。

codesandbox.io

CodeSandboxをVanillaで起動してもらうとJavaScriptコードの勉強にちょうど良いです。

f:id:yamasahi:20190116085210p:plain

それでははりきってどうぞ。


1 isLowerCase

文字列が小文字か検証します。次の実行結果となるようにisLowerCase関数を定義してください。

isLowerCase('abc'); // true
isLowerCase('a3@$'); // true
isLowerCase('Ab4'); // false

isLowerCase関数を実装して、console.log(isLowerCase('abc')) でtrueが出力されればOKです。

ヒント

String.prototype.toLowerCase()を使って、与えられた文字列を小文字に変換し、元の文字列と等しいか比較します。

const isLowerCase = str => str === str.t__________();

2 reverseString

文字列を反転します。次の実行結果となるようにreverseString関数を定義してください。

reverseString('foobar'); // 'raboof'

ヒント スプレッドオペレータ(...)とArray.prototype.reverse()を使って文字列内の文字の並びを逆順にしString.prototype.join('')によって文字を連結して文字列とします。

const reverseString = str => [...str].r______().j___('');

3 truncateString

指定したサイズで文字列を切り詰めます。次の実行結果となるようにtruncateString関数を定義してください。

truncateString('boomerang', 7); // 'boom...'

ヒント 文字列のサイズの上限を指定します。切り詰められた文字列の後部に'...'を連結して返します。

const truncateString = (str, num) =>
  str.l_____ > num ? str.s____(0, num > 3 ? num - 3 : num) + '...' : str;

4 mapString

文字列に含まれる個々の文字に対して、指定されたコールバック関数を適用して、新たな文字列を生成します。次の実行結果となるようにmapString関数を定義してください。

mapString('lorem ipsum', c => c.toUpperCase()); // 'LOREM IPSUM'

ヒント String.prototype.split('')とArray.prototype.map()を使って、コールバック関数(引数のfn)を文字列内の個々の文字に対して適用します。それからArray.prototype.join('')を使って文字配列を文字列として再結合します。コールバック関数は3つの引数(現在の文字、現在の文字のインデックス、mapString関数の引数に指定された文字列)を受け取ります。

const mapString = (str, fn) =>
  str
    .s____('')
    .m__((c, i) => f_(c, i, str))
    .j___('');

5 mask

文字列を指定されたマスク文字で置き換えます。ただし、文字列の後部については、引数のnumに指定された文字数分はマスクしません。次の実行結果となるようにmask関数を定義してください。

mask(1234567890); // '******7890'
mask(1234567890, 3); // '*******890'
mask(1234567890, -4, '$'); // '$$$$567890'

ヒント String.prototype.slice()を使って、アンマスク(マスクしない)する文字列を取り出し、String.prototype.padStart()で、元の文字列の長さ分のマスク文字を追加します。第2引数のnumが省略された場合、デフォルトで4文字のアンマスク文字を確保します。またnumに負の値が指定された場合は、文字列の先頭部分をアンマスクします。第3引数のmaskが省略された場合、デフォルトのマスク文字に*を使います。

const mask = (cc, num = 4, mask = '*') => `${cc}`.s____(-num).p_______(`${cc}`.l_____, mask);

6 compactWhitespace

連続するホワイトスペース文字(スペース、タブ、改ページ、改行)をホワイトスペース文字1文字に置き換えます。次の実行結果となるようにcompactWhitespace関数を定義してください。

compactWhitespace('Lorem    Ipsum'); // 'Lorem Ipsum'
compactWhitespace('Lorem \n Ipsum'); // 'Lorem Ipsum'

ヒント 正規表現とString.prototype.replace()を使うことで、2文字以上のホワイトスペース文字を1文字に置き換えます。

const compactWhitespace = str => str.r______(/\s{2,}/g, ' ');

7 palindrome

与えられた文字列が回文になっているか検証します。回文の場合、true、そうでない場合、falseを返します。次の実行結果となるようにpalindrome関数を定義してください。

palindrome('taco cat'); // true

ヒント String.prototype.toLowerCase()とString.prototype.replace()を使って非アルファベット文字を除去して小文字に統一し、それからスプレッドオペレータ(...)を使って文字列を文字の配列に変換し、Array.prototype.reverse()、String.prototype.join('')を使って生成した文字列と、元の文字列(非アルファベット文字を除去して小文字に統一したもの)を比較します。

const palindrome = str => {
  const s = str.t__________().r______(/[\W_]/g, '');
  return s === [...s].r______().j___('');
};

8 capitalize

文字列の先頭文字を大文字に変換します。次の実行結果となるようにcapitalize関数を定義してください。

capitalize('fooBar'); // 'FooBar'
capitalize('fooBar', true); // 'Foobar'

ヒント 配列の分割代入とString.prototype.toUpperCase()を使って先頭の文字を大文字にします。 ...restには先頭文字を除く文字の配列が格納されるのでArray.prototype.join('')を使って再び文字列に復元しています。引数のlowerRestが指定されなかった場合は残りの文字列(先頭文字を除く)をそのまま使います。lowerRestにtrueが指定された場合は残りの文字列を小文字に置き換えます。

const capitalize = ([first, ...rest], lowerRest = false) =>
  first.t__________() + (lowerRest ? rest.j___('').t__________() : rest.j___(''));

9 byteSize

文字列の長さをbytesで返します。次の実行結果となるようにbyteSize関数を定義してください。

byteSize('😀'); // 4
byteSize('Hello World'); // 11

ヒント 文字列をBlobオブジェクトに変換してsizeプロパティを参照します。

const byteSize = str => new B___([str]).s___;

10 pluralize

入力された数値によって、基準となる文字列を単数系、あるいは複数形にして返します。第1引数にオブジェクトが指定された場合、関数によって返却されるクロージャを返します。これは"s"による単純な複数形の変換だけでなく、指定されたディクショナリに含まれる複数形単語を返却します。次の実行結果となるようにpluralize関数を定義してください。

pluralize(0, 'apple'); // 'apples'
pluralize(1, 'apple'); // 'apple'
pluralize(2, 'apple'); // 'apples'
pluralize(2, 'person', 'people'); // 'people'

const PLURALS = {
  person: 'people',
  radius: 'radii'
};
const autoPluralize = pluralize(PLURALS);
autoPluralize(2, 'person'); // 'people'

ヒント numが-1か1の場合、単数系の単語を返却します。numがそれ以外の値の場合、複数形の単語を返却します。第3引数のpluralが省略された場合、デフォルトで第2引数のwordに"s"を連結した文字列を複数形の単語として処理するので、必要に応じてカスタマイズすることができます。第1引数のvalがオブジェクトの場合、複数形の単語を格納したディクショナリを保持したクロージャを返却します。

const pluralize = (val, word, plural = word + 's') => {
  const _pluralize = (num, word, plural = word + 's') =>
    [1, -1].i_______(Number(num)) ? word : plural;
  if (typeof val === 'object') return (num, word) => __________(num, word, val[word]);
  return __________(val, word, plural);
};

答え

お疲れ様でした。答えはこちらの記事を参考に。

murayama.hatenablog.com

書籍:Measure What Mattersを読んでいる

たまには普段書かないことをブログに書いてみます。本日は目標管理、OKRについてです。

私は仕事のやる気とかモチベーションとか、そういうのであまり悩んだことのないタイプです。どんな仕事も手をつけ始めたら面白いし学ぶことは多くあると考えています。蟹座のO型です。

一方で組織として、仕事のやる気やモチベーションを考えたときに、先輩や後輩、同僚が仕事に対して悩んでいるのはたくさん見てきたように思います。過去を振り返ると、上司としての自分は周囲のメンバーを前向きにさせるような発言・行動を取れていたかというと、んー、、微妙かもしれません。

最近はソフトウェア開発の現場で「開発チームとして生産性を高めるにはどうしたら良いか」について考えるようになりました。アジャイル開発、スクラムのような考え方・取り組みには以前から関心はありましたが、今はチームとしての仕事へのやりがい・モチベーションを高める工夫として「OKR」というものに興味を持っています。

本の紹介

Measure What Mattersという本を読んでいます。

まだ半分しか読んでいないですが面白いです。ブログに書こうと思うくらいです。スタートアップの事例としてremind.com(https://www.remind.com/)やnuna.com(https://www.nuna.com/)の事例はとてもわかりやすく、またnuna(社名)の由来にも感動しました。

あとGoogleの話や、ITの有名な人たちがたくさん出てくるので面白いです。名言を連発するアンディ・グローブさんがかっこいいです。

OKRとは

OKR(Objectives and key results)は、目標(Objectives)とその主要な結果(Key Results)を定義し、追跡するためのフレームワークです。"目標(O)"は何を達成すべきかを表現し、"主要な結果(KR)"は目標をどのように達成するかをモニタリングする基準になります。

従来の目標管理は「何を」に焦点をあてるものでしたが、OKRでは「どのように」という具体的な指標を補完することで追跡可能な目標管理を実現します。

また一般的に、OKRは組織においてオープンに共有し誰もが閲覧できる状態とし、1つの目標に対して、3つ,4つ程度の主要な結果(Key Results)を定義するようにします。また目標は実現可能でかつ野心的なものが良いとされていて、四半期に一度くらいのペースで振り返りを行うことを推奨しています。

目標の立て方については書籍「Measure What Matters」の中でも以下のような解説がありました。

「第1に困難な目標の方が、楽な目標より、パフォーマンスを高めるために有効である。第2に、具体性のある困難な目標のほうが、曖昧な文言で書かれた目標より、アウトプットの水準が高くなる。」

具体的にどのように書けばよい?

OKRはシンプルなツールなので、私のような単純な人間(蟹座のO型)はすぐに実践したくなります。そうすると「具体的にどのように目標や指標を記述すればよいのか?」という点に興味がいきます。そこが落とし穴のように思っています。

目標のもたらす失敗

OKRの話から離れて、目標管理について考察してみます。

会社のような組織の中で、年に一度あるいは半年に一度、目標を立てる、といった取り組みはよくあるケースだと思います。それらの目標管理が上手く機能している組織もあるのでしょうが、そうでない組織の方が多いのではないでしょうか。

目標について注意しないといけない点もいくつかあります。

  • 目標の形骸化
    • 立てた目標を放置して、忘れてしまう問題
  • 恣意的な目標変更
    • 組織の目標がころころと変わってしまう問題
  • 暴走する目標
    • 目標達成を優先するあまり、品質を無視してしまう問題

「目標なんて要らないよ」派の人はだいたいこれらの意見に同意するのではないでしょうか。管理できていない目標管理はマイナスに作用することも理解しておかなければいけません。

それで、OKRは具体的にどのように書けばよい?

OKRは目標管理ツールです。

現場に導入しようとすると、どうしても具体的な目標や評価指標の書き方にばかり目がいってしまいます(具体的な書き方は「OKR 書き方」でググればわかります)。

それよりもOKRを導入する目的は何か、そこを理解しておかないとOKRを導入しても失敗してしまうでしょう。

実際、書籍「Measure What Matters」の中でも多くの失敗談が語られています。「OKRは1度失敗したら辞める」というケースも多いようですがそれはアンチパターンで、OKR導入の目的を理解して繰り返し実践していくことで精度を高めていく必要があります。

組織におけるOKR導入の目的

目標は進むべき方向のことです。

話がそれますが、以前見たテレビ番組で林修先生がこんなイラストで説明していたのを覚えています。正しい目標と正しい努力、のような話だったと思います。

f:id:yamasahi:20190109173203p:plain

目標を理解して努力しないと意味がないよ、というお話です。

f:id:yamasahi:20190109173236p:plain

組織だとこんな感じでしょうか。

f:id:yamasahi:20190109173910p:plain

(なんか図がでかくてすみません)

これも完全に余談ですが、漫画キングダムでは飛信隊の旗を立てるシーンが印象的です。目標と旗ってなんか似てるような気がしています。

組織における目標はチームメンバーを鼓舞し、パフォーマンスを改善するものであるべきです。もう一度「Measure What Matters」の中から引用しておきます。

「第1に困難な目標の方が、楽な目標より、パフォーマンスを高めるために有効である。第2に、具体性のある困難な目標のほうが、曖昧な文言で書かれた目標より、アウトプットの水準が高くなる。」

組織にOKRを導入する目的、それはチーム全員が目標を理解し、同じ方向を向いて仕事を進めていくことです。またOKR(目標と主要な評価指標)によって、組織の中で具体的に議論が行われ、協力関係が生まれ、仕事へのモチベーションを高めるいくことが理想です。

そのためにはOKRを補完する仕組みとしてCFR(Conversation, Feedback, Recognition)という考え方もあるります。対話、フィードバック、承認、いずれも組織において大事な言葉です。OKRのようなツールを導入したからといって、すぐに現場が改善されるわけではありません。

今後の話

本が面白かったので勢いで書いてみました。今も読み進めていますが、OKRの導入には以下の4つのキーワードを理解する必要があります。

OKRについて、目標管理や組織におけるモチベーションについて、もう少し勉強を継続してみようと思います。

JavaScript 30-seconds-of-code String編

お仕事で若手エンジニアのみなさんから「エンジニアならブログを書きますよねぇ」という話になり、ネタ探しにGitHubのExploreを覗いていたら30-seconds-of-codeというリポジトリを見つけました。

https://github.com/30-seconds/30-seconds-of-code/tree/master/snippets

30秒で学べるJavaScriptコードスニペット集って感じでしょうか。配列や文字列、関数オブジェクトなどいくつかのカテゴリーにコードがまとめらています。

  • 🔌 Adapter
  • 📚 Array
  • 🌐 Browser
  • ⏱️ Date
  • 🎛️ Function
  • ➗ Math
  • 📦 Node
  • 🗃️ Object
  • 📜 String
  • 📃 Type
  • 🔧 Utility

まずは入りやすそうなStringを見てみました。

String

けっこうな数のコードがあります。いずれも関数として定義します。

  • CSVToArray
  • CSVToJSON
  • README
  • URLJoin
  • byteSize
  • capitalize
  • capitalizeEveryWord
  • compactWhitespace
  • decapitalize
  • escapeHTML
  • escapeRegExp
  • fromCamelCase
  • indentString
  • isAbsoluteURL
  • isAnagram
  • isLowerCase
  • isUpperCase
  • mapString
  • mask
  • pad
  • palindrome
  • pluralize
  • removeNonASCII
  • reverseString
  • sortCharactersInString
  • splitLines
  • stringPermutations
  • stripHTMLTags
  • toCamelCase
  • toKebabCase
  • toSnakeCase
  • toTitleCase
  • truncateString
  • unescapeHTML
  • words

余談ですが、この辺の関数の名前から処理を想像できるのも大事ですよね。padとかmapStringとかtoKebabCaseとか。ケバブ

中にはクイズみたいな面白いものもあります。たとえばpalindrome関数。palindromeは日本語だと"回文"という意味で、文字列を反対から読み上げても同じかどうか検証します。サンプルコードはこんなかんじです。

const palindrome = str => {
  const s = str.toLowerCase().replace(/[\W_]/g, '');
  return s === [...s].reverse().join('');
};
palindrome('taco cat'); // true

String.prototypeにはreverse関数がないので、いったん文字の配列に変換してからreverseしてjoinで文字列として再結合しています。コードをよく見ると、

[...s]

おっとなにこれ、JS詳しくないマンだと拒否反応がでますが、...はスプレッドオペレータというもので文字列に対して使うとs.split("")と同様の結果となります。

s = "taco cat"
"taco cat"
s.split("")
(8) ["t", "a", "c", "o", " ", "c", "a", "t"]
[...s]
(8) ["t", "a", "c", "o", " ", "c", "a", "t"]

他にもキャメルケース変換、スネークケース変換、ケバブケース変換(、、ケバブ?)など、面白い関数がたくさんあります。あと正規表現もバンバン出てくるので、真面目にやれば正規表現の勉強になると思います。あとプログラミング習い始めの人、配列とか関数とかオブジェクトとか理解していた人にはちょうど良いお題のように感じました。まずは無心になって写経するだけでも勉強になると思います。あと技術者のスキル測るのにも使えるんじゃない、、と思ったり。

というわけで原文の英語の方が読みやすい気もしますが、面白そうなので日本語にまとめてみました。

CSVToArray

CSV文字列を2次元配列に置き換えます。

第3引数のomitFirstRowtrueの場合Array.prototype.slice()Array.prototype.indexOf('\n')を使って先頭行を削除します。それからString.prototype.split('\n')を使って個々の行を配列に変換します。第2引数のdelimiterが省略された場合はデフォルトの区切り文字として,を使います。また第3引数が省略された場合はCSV文字列の先頭行(タイトル行)を処理対象として含みます。

const CSVToArray = (data, delimiter = ',', omitFirstRow = false) =>
  data
    .slice(omitFirstRow ? data.indexOf('\n') + 1 : 0)
    .split('\n')
    .map(v => v.split(delimiter));
CSVToArray('a,b\nc,d'); // [['a','b'],['c','d']];
CSVToArray('a;b\nc;d', ';'); // [['a','b'],['c','d']];
CSVToArray('col1,col2\na,b\nc,d', ',', true); // [['a','b'],['c','d']];

CSVToJSON

CSV文字列をオブジェクトの2次元配列に置き換えます。文字列の1行目はタイトル行として処理します。

Array.prototype.slice()Array.prototype.indexOf('\n')String.prototype.split(delimiter)を使って1行目(タイトル行)を取得します。それからString.prototype.split('\n')を使って行ごとの文字列配列を作り、Array.prototype.map()String.prototype.split(delimiter)を使って、個々の行の中の値を取り出します。Array.prototype.reduce()を使って個々の行の値を格納したオブジェクトを作ります。これらのオブジェクトはタイトル行をパースした際のキーを持ちます。第2引数を省略した場合は、デフォルトの区切り文字として, を使います。

const CSVToJSON = (data, delimiter = ',') => {
  const titles = data.slice(0, data.indexOf('\n')).split(delimiter);
  return data
    .slice(data.indexOf('\n') + 1)
    .split('\n')
    .map(v => {
      const values = v.split(delimiter);
      return titles.reduce((obj, title, index) => ((obj[title] = values[index]), obj), {});
    });
};
CSVToJSON('col1,col2\na,b\nc,d'); // [{'col1': 'a', 'col2': 'b'}, {'col1': 'c', 'col2': 'd'}];
CSVToJSON('col1;col2\na;b\nc;d', ';'); // [{'col1': 'a', 'col2': 'b'}, {'col1': 'c', 'col2': 'd'}];

URLJoin

URLのブロックを結合して、妥当なURLに置き換えます。

String.prototype.join('/')を使ってURLのブロックを結合し、一連のString.prototype.replace()と様々な正規表現使って妥当なURL(ダブルスラッシュの排除、プロトコルの適切なスラッシュの追加、パラメータの前のスラッシュの削除、2つ目以降のパラメータを&による結合)に置き換えます。

const URLJoin = (...args) =>
  args
    .join('/')
    .replace(/[\/]+/g, '/')
    .replace(/^(.+):\//, '$1://')
    .replace(/^file:/, 'file:/')
    .replace(/\/(\?|&|#[^!])/g, '$1')
    .replace(/\?/g, '&')
    .replace('&', '?');
URLJoin('http://www.google.com', 'a', '/b/cd', '?foo=123', '?bar=foo'); // 'http://www.google.com/a/b/cd?foo=123&bar=foo'

byteSize

文字列の長さをbytesで返します。

文字列をBlobオブジェクトに変換してsizeプロパティを参照します。

const byteSize = str => new Blob([str]).size;
byteSize('😀'); // 4
byteSize('Hello World'); // 11

capitalize

文字列の先頭文字を大文字に変換します。

配列の分割代入とString.prototype.toUpperCase()を使って先頭の文字を大文字にします。 ...restには先頭文字を除く文字の配列が格納されるのでArray.prototype.join('')を使って再び文字列に復元しています。引数のlowerRestが指定されなかった場合は残りの文字列(先頭文字を除く)をそのまま使います。lowerRestにtrueが指定された場合は残りの文字列を小文字に置き換えます。

const capitalize = ([first, ...rest], lowerRest = false) =>
  first.toUpperCase() + (lowerRest ? rest.join('').toLowerCase() : rest.join(''));
capitalize('fooBar'); // 'FooBar'
capitalize('fooBar', true); // 'Foobar'

capitalizeEveryWord

文字列内の単語ごとの先頭文字を大文字に変換します。

各単語の先頭文字をString.prototype.replace()でマッチさせ、String.prototype.toUpperCase()で大文字に変換します。

const capitalizeEveryWord = str => str.replace(/\b[a-z]/g, char => char.toUpperCase());
capitalizeEveryWord('hello world!'); // 'Hello World!'

compactWhitespace

連続するホワイトスペース文字(スペース、タブ、改ページ、改行)をホワイトスペース文字1文字に置き換えます。

正規表現String.prototype.replace()を使うことで、2文字以上のホワイトスペース文字を1文字に置き換えます。

const compactWhitespace = str => str.replace(/\s{2,}/g, ' ');
compactWhitespace('Lorem    Ipsum'); // 'Lorem Ipsum'
compactWhitespace('Lorem \n Ipsum'); // 'Lorem Ipsum'

decapitalize

文字列の先頭文字を小文字に変換します。

配列の分割代入とString.prototype.toLowerCase()を使って先頭の文字を大文字にします。 ...restには先頭文字を除く文字の配列が格納されるのでArray.prototype.join('')を使って再び文字列に復元しています。引数のupperRestが指定されなかった場合は残りの文字列(先頭文字を除く)をそのまま使います。upperRestにtrueが指定された場合は残りの文字列を大文字に置き換えます。

const decapitalize = ([first, ...rest], upperRest = false) =>
  first.toLowerCase() + (upperRest ? rest.join('').toUpperCase() : rest.join(''));
decapitalize('FooBar'); // 'fooBar'
decapitalize('FooBar', true); // 'fOOBAR'

escapeHTML

HTMLで利用可能な文字列(HTML特殊文字)にエスケープします。

String.prototype.replace()正規表現を使って、マッチした文字をエスケープします。このときコールバック関数を使ってディクショナリ(オブジェクト)によって管理された特殊文字に変換します。

const escapeHTML = str =>
  str.replace(
    /[&<>'"]/g,
    tag =>
      ({
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        "'": '&#39;',
        '"': '&quot;'
      }[tag] || tag)
  );
escapeHTML('<a href="#">Me & you</a>'); // '&lt;a href=&quot;#&quot;&gt;Me &amp; you&lt;/a&gt;'

escapeRegExp

正規表現で利用可能な文字列にエスケープします。

String.prototype.replace()を使って特殊文字エスケープします。

Use String.prototype.replace() to escape special characters.
const escapeRegExp = str => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
escapeRegExp('(test)'); // \\(test\\)

fromCamelCase

キャメルケースの文字列を変換します。

String.prototype.replace()を使って、キャメルケースを単語の並びに変換します。単語の並びはアンダースコア、ハイフン、スペースなどで補完します。

const fromCamelCase = (str, separator = '_') =>
  str
    .replace(/([a-z\d])([A-Z])/g, '$1' + separator + '$2')
    .replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + separator + '$2')
    .toLowerCase();
fromCamelCase('someDatabaseFieldName', ' '); // 'some database field name'
fromCamelCase('someLabelThatNeedsToBeCamelized', '-'); // 'some-label-that-needs-to-be-camelized'
fromCamelCase('someJavascriptProperty', '_'); // 'some_javascript_property'

indentString

文字列内の各行をインデントします。

String.prototype.replace正規表現を使って、指定されたインデント幅分、各行の先頭に指定された文字を追加します。第3引数indentが省略された場合デフォルトのインデント文字として' '(半角スペース)を使います。

const indentString = (str, count, indent = ' ') => str.replace(/^/gm, indent.repeat(count));
indentString('Lorem\nIpsum', 2); // '  Lorem\n  Ipsum'
indentString('Lorem\nIpsum', 2, '_'); // '__Lorem\n__Ipsum'

isAbsoluteURL

与えられた文字列が絶対URLの場合にtrue、そうでない場合にはfalseを返します。

正規表現を使って、与えられた文字列が絶対URLか検証します。

const isAbsoluteURL = str => /^[a-z][a-z0-9+.-]*:/.test(str);
isAbsoluteURL('https://google.com'); // true
isAbsoluteURL('ftp://www.myserver.net'); // true
isAbsoluteURL('/foo/bar'); // false

isAnagram

文字列が他の文字列(大文字小文字は無視し、スペース、句読点、特殊文字は除去した文字列)のアナグラム(文字配列の並び替え)であるか検証します。

String.prototype.toLowerCase()と、適切な正規表現を適用したString.prototype.replace()を使って、不要な文字を除去し、 String.prototype.split('')Array.prototype.sort()Array.prototype.join('')を使ってノーマライズ(文字の並びを整理)するnormalize関数を定義します。引数の2つの文字列にnormalize関数を適用して、文字列の並びが等しいか検証します。

const isAnagram = (str1, str2) => {
  const normalize = str =>
    str
      .toLowerCase()
      .replace(/[^a-z0-9]/gi, '')
      .split('')
      .sort()
      .join('');
  return normalize(str1) === normalize(str2);
};
isAnagram('iceman', 'cinema'); // true

isLowerCase

文字列が小文字か検証します。

String.prototype.toLowerCase()を使って、与えられた文字列を小文字に変換し、元の文字列と等しいか比較します。

const isLowerCase = str => str === str.toLowerCase();
isLowerCase('abc'); // true
isLowerCase('a3@$'); // true
isLowerCase('Ab4'); // false

isUpperCase

文字列が大文字か検証します。

String.prototype.toUpperCase()を使って、与えられた文字列を大文字に変換し、元の文字列と等しいか比較します。

Convert the given string to upper case, using String.prototype.toUpperCase() and compare it to the original.

const isUpperCase = str => str === str.toUpperCase();
isUpperCase('ABC'); // true
isLowerCase('A3@$'); // true
isLowerCase('aB4'); // false

mapString

文字列に含まれる個々の文字に対して、指定されたコールバック関数を適用して、新たな文字列を生成します。

String.prototype.split('')Array.prototype.map()を使って、コールバック関数(引数のfn)を文字列内の個々の文字に対して適用します。それからArray.prototype.join('')を使って文字配列を文字列として再結合します。コールバック関数は3つの引数(現在の文字、現在の文字のインデックス、mapString関数の引数に指定された文字列)を受け取ります。

const mapString = (str, fn) =>
  str
    .split('')
    .map((c, i) => fn(c, i, str))
    .join('');
mapString('lorem ipsum', c => c.toUpperCase()); // 'LOREM IPSUM'

mask

文字列を指定されたマスク文字で置き換えます。ただし、文字列の後部については、引数のnumに指定された文字数分はマスクしません。

String.prototype.slice()を使って、アンマスク(マスクしない)する文字列を取り出し、String.prototype.padStart()で、元の文字列の長さ分のマスク文字を追加します。第2引数のnumが省略された場合、デフォルトで4文字のアンマスク文字を確保します。またnumに負の値が指定された場合は、文字列の先頭部分をアンマスクします。第3引数のmaskが省略された場合、デフォルトのマスク文字に*を使います。

const mask = (cc, num = 4, mask = '*') => `${cc}`.slice(-num).padStart(`${cc}`.length, mask);
mask(1234567890); // '******7890'
mask(1234567890, 3); // '*******890'
mask(1234567890, -4, '$'); // '$$$$567890'

pad

対象の文字列が、指定された文字数を下回る場合に、文字列の両側に指定された文字を連結します。

String.prototype.padStart()String.protorype.padEnd()を使って与えられた文字列の両側に文字を連結します。第3引数が省略された場合、半角スペースがデフォルトの文字として連結されます。

const pad = (str, length, char = ' ') =>
  str.padStart((str.length + length) / 2, char).padEnd(length, char);
pad('cat', 8); // '  cat   '
pad(String(42), 6, '0'); // '004200'
pad('foobar', 3); // 'foobar'

palindrome

与えられた文字列が回文になっているか検証します。回文の場合、true、そうでない場合、falseを返します。

String.prototype.toLowerCase()String.prototype.replace()を使って非アルファベット文字を除去して小文字に統一し、それからスプレッドオペレータ(...)を使って文字列を文字の配列に変換し、Array.prototype.reverse()String.prototype.join('')を使って生成した文字列と、元の文字列(非アルファベット文字を除去して小文字に統一したもの)を比較します。

const palindrome = str => {
  const s = str.toLowerCase().replace(/[\W_]/g, '');
  return s === [...s].reverse().join('');
};
palindrome('taco cat'); // true

pluralize

入力された数値によって、基準となる文字列を単数系、あるいは複数形にして返します。第1引数にオブジェクトが指定された場合、関数によって返却されるクロージャを返します。これは"s"による単純な複数形の変換だけでなく、指定されたディクショナリに含まれる複数形単語を返却します。

numが-1か1の場合、単数系の単語を返却します。numがそれ以外の値の場合、複数形の単語を返却します。第3引数のpluralが省略された場合、デフォルトで第2引数のwordに"s"を連結した文字列を複数形の単語として処理するので、必要に応じてカスタマイズすることができます。第1引数のvalがオブジェクトの場合、複数形の単語を格納したディクショナリを保持したクロージャを返却します。

const pluralize = (val, word, plural = word + 's') => {
  const _pluralize = (num, word, plural = word + 's') =>
    [1, -1].includes(Number(num)) ? word : plural;
  if (typeof val === 'object') return (num, word) => _pluralize(num, word, val[word]);
  return _pluralize(val, word, plural);
};
pluralize(0, 'apple'); // 'apples'
pluralize(1, 'apple'); // 'apple'
pluralize(2, 'apple'); // 'apples'
pluralize(2, 'person', 'people'); // 'people'

const PLURALS = {
  person: 'people',
  radius: 'radii'
};
const autoPluralize = pluralize(PLURALS);
autoPluralize(2, 'person'); // 'people'

removeNonASCII

印刷できないASCII文字を除去します。

正規表現を使って印刷できないASCII文字を除去します。

const removeNonASCII = str => str.replace(/[^\x20-\x7E]/g, '');
removeNonASCII('äÄçÇéÉêlorem-ipsumöÖÐþúÚ'); // 'lorem-ipsum'

reverseString

文字列を反転します。 Reverses a string.

スプレッドオペレータ(...)とArray.prototype.reverse()を使って文字列内の文字の並びを逆順にしString.prototype.join('')によって文字を連結して文字列とします。

const reverseString = str => [...str].reverse().join('');
reverseString('foobar'); // 'raboof'

sortCharactersInString

文字列内の文字をアルファベット順にソートします。

スプレッドオペレータ(...)とArray.prototype.sort()String.prototype.localeCompare()を使って文字列内の文字をソートし、String.prototype.join('')を使って再結合します。

const sortCharactersInString = str => [...str].sort((a, b) => a.localeCompare(b)).join('');
sortCharactersInString('cabbage'); // 'aabbceg'

splitLines

複数行の文字列を行の配列に分割します。

String.prototype.split()正規表現を使って行を検出し、配列を生成します。

const splitLines = str => str.split(/\r?\n/);
splitLines('This\nis a\nmultiline\nstring.\n'); // ['This', 'is a', 'multiline', 'string.' , '']

stringPermutations

⚠️ 警告:この関数は文字数が増えると処理時間が指数関数的に増加します。8-10文字を超えると、ブラウザはすべての異なる組み合わせを試みるのでハングするでしょう。

文字列のすべての置換(並び替えた文字列)を生成します(重複を含みます)。

再帰を使います。与えられた文字列の各文字について、残りの文字すべての部分置換を生成します。Use Array.prototype.map()を使って文字と部分置換を連結し、Array.prototype.reduce()によって配列の中のすべての置換を連結します。処理が再帰しますが、文字列が2文字か1文字の場合をベースケースとしています。

const stringPermutations = str => {
  if (str.length <= 2) return str.length === 2 ? [str, str[1] + str[0]] : [str];
  return str
    .split('')
    .reduce(
      (acc, letter, i) =>
        acc.concat(stringPermutations(str.slice(0, i) + str.slice(i + 1)).map(val => letter + val)),
      []
    );
};
stringPermutations('abc'); // ['abc','acb','bac','bca','cab','cba']

stripHTMLTags

文字列からHTML/XMLタグを除去します。

正規表現を使って、文字列からHTML/XMLタグを除去します。

const stripHTMLTags = str => str.replace(/<[^>]*>/g, '');
stripHTMLTags('<p><em>lorem</em> <strong>ipsum</strong></p>'); // 'lorem ipsum'

toCamelCase

文字列をキャメルケースに変換します。

正規表現を使って文字列を単語に分解し、個々の単語の先頭文字を大文字にして結合します。

const toCamelCase = str => {
  let s =
    str &&
    str
      .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
      .map(x => x.slice(0, 1).toUpperCase() + x.slice(1).toLowerCase())
      .join('');
  return s.slice(0, 1).toLowerCase() + s.slice(1);
};
toCamelCase('some_database_field_name'); // 'someDatabaseFieldName'
toCamelCase('Some label that needs to be camelized'); // 'someLabelThatNeedsToBeCamelized'
toCamelCase('some-javascript-property'); // 'someJavascriptProperty'
toCamelCase('some-mixed_string with spaces_underscores-and-hyphens'); // 'someMixedStringWithSpacesUnderscoresAndHyphens'

toKebabCase

文字列をケバブケースに変換します。

正規表現を使って文字列を単語に分解し、個々の単語を-(ハイフン)をセパレータとして結合します。

const toKebabCase = str =>
  str &&
  str
    .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
    .map(x => x.toLowerCase())
    .join('-');
toKebabCase('camelCase'); // 'camel-case'
toKebabCase('some text'); // 'some-text'
toKebabCase('some-mixed_string With spaces_underscores-and-hyphens'); // 'some-mixed-string-with-spaces-underscores-and-hyphens'
toKebabCase('AllThe-small Things'); // "all-the-small-things"
toKebabCase('IAmListeningToFMWhileLoadingDifferentURLOnMyBrowserAndAlsoEditingSomeXMLAndHTML'); // "i-am-listening-to-fm-while-loading-different-url-on-my-browser-and-also-editing-xml-and-html"

toSnakeCase

文字列をスネークケースに変換します。

正規表現を使って文字列を単語に分解し、個々の単語を_(アンダースコア)をセパレータとして結合します。

const toSnakeCase = str =>
  str &&
  str
    .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
    .map(x => x.toLowerCase())
    .join('_');
toSnakeCase('camelCase'); // 'camel_case'
toSnakeCase('some text'); // 'some_text'
toSnakeCase('some-mixed_string With spaces_underscores-and-hyphens'); // 'some_mixed_string_with_spaces_underscores_and_hyphens'
toSnakeCase('AllThe-small Things'); // "all_the_smal_things"
toSnakeCase('IAmListeningToFMWhileLoadingDifferentURLOnMyBrowserAndAlsoEditingSomeXMLAndHTML'); // "i_am_listening_to_fm_while_loading_different_url_on_my_browser_and_also_editing_some_xml_and_html"

toTitleCase

文字列をタイトルケースに変換します。

正規表現を使って文字列を単語に分解し、個々の単語の先頭文字を大文字に変換して半角スペースで結合します。

const toTitleCase = str =>
  str
    .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
    .map(x => x.charAt(0).toUpperCase() + x.slice(1))
    .join(' ');
toTitleCase('some_database_field_name'); // 'Some Database Field Name'
toTitleCase('Some label that needs to be title-cased'); // 'Some Label That Needs To Be Title Cased'
toTitleCase('some-package-name'); // 'Some Package Name'
toTitleCase('some-mixed_string with spaces_underscores-and-hyphens'); // 'Some Mixed String With Spaces Underscores And Hyphens'

truncateString

指定したサイズで文字列を切り詰めます。

文字列のサイズの上限を指定します。切り詰められた文字列の後部に'...'を連結して返します。

const truncateString = (str, num) =>
  str.length > num ? str.slice(0, num > 3 ? num - 3 : num) + '...' : str;
truncateString('boomerang', 7); // 'boom...'

unescapeHTML

HTML特殊文字をアンエスケープします。

正規表現String.prototype.replace()正規表現を使って、マッチした文字をアンエスケープします。このときコールバック関数を使ってディクショナリ(オブジェクト)によって管理されたアンエスケープ文字に変換します。

const unescapeHTML = str =>
  str.replace(
    /&amp;|&lt;|&gt;|&#39;|&quot;/g,
    tag =>
      ({
        '&amp;': '&',
        '&lt;': '<',
        '&gt;': '>',
        '&#39;': "'",
        '&quot;': '"'
      }[tag] || tag)
  );
unescapeHTML('&lt;a href=&quot;#&quot;&gt;Me &amp; you&lt;/a&gt;'); // '<a href="#">Me & you</a>'

words

文字列を単語の配列に変換します。

指定された正規表現パターン(デフォルトでは非アルファベット文字)とString.prototype.split()を使って、単語文字列の配列に変換します。またArray.prototype.filter()によって空の文字列を除去しています。第2引数が省略された場合はデフォルトの正規表現パターンを使います。

const words = (str, pattern = /[^a-zA-Z-]+/) => str.split(pattern).filter(Boolean);
words('I love javaScript!!'); // ["I", "love", "javaScript"]
words('python, javaScript & coffee'); // ["python", "javaScript", "coffee"]

まとめ

久しぶりのブログ面白かったです。String以外の他のもやってみようと思いました。不定期でGitHubに追記していきます。

https://github.com/murayama333/30-seconds-of-code-ja