シェルワンライナー #10

Markdownファイルの見出し書式を変更するコマンドについて

Markdownファイルの見出し書式を変更するために、以下のコマンドを使用することができます。

cat headings.md | sed -r 's/^## +(.)/\1\n---/' | sed -r 's/^# +(.)/\1\n===/'

コマンドの解説

このコマンドは、headings.mdというMarkdownファイルの見出し(ヘッダー)の書式を変更しています。具体的には、次のような処理を行っています。

  1. cat headings.mdheadings.mdファイルの内容を表示します。
  2. sed -r 's/^## +(.*)/\1\n---/'sedコマンドを使って、二つ目の見出し(##で始まる行)を変更します。具体的には、##の後に1つ以上のスペースと任意の文字列が続く行(^## +(.*))を、その文字列に改行と---を続けたものに置き換えます(\1\n---)。
  3. sed -r 's/^# +(.*)/\1\n===/':同様に、sedコマンドを使って、一つ目の見出し(#で始まる行)を変更します。具体的には、#の後に1つ以上のスペースと任意の文字列が続く行(^# +(.*))を、その文字列に改行と===を続けたものに置き換えます(\1\n===)。

正規表現の解説

キャプチャグループ

\1は、正規表現でキャプチャした最初のグループにマッチした文字列を表します。ここでは、正規表現の中で括弧(.*)を使ってキャプチャグループを作成しています。

括弧(.*)を使ってキャプチャグループを作成し、マッチした部分を後で参照できるようになります。.は任意の1文字にマッチし、*は直前の文字が0回以上繰り返されることを意味します。従って、.*は任意の長さの文字列(1文字以上)にマッチします。

一つ以上のスペース

+正規表現において、直前の文字やグループが1回以上繰り返されることを意味します。今回の例では、###の後に1つ以上のスペースがあることを想定しています。そのため、+を使って1回以上のスペースをフィルタリングしています。

このコマンドを実行すると、headings.mdファイルの内容は次のように変更されます。


別件

Q1

下記要件を満たす処理をc言語で記述してください。

要件

  • ハンドルを回すとインクリメントされるカウンタがある。
  • ハンドルを逆方向に回すとデクリメントされる。
  • カウンタの取りうる値は0-255。

  • 1ms周期タスク上で実行する処理を考える。

    • その処理では、毎周期カウンタ値を取得する
    • 前回周期でのカウンタ取得値と、現在のカウンタ取得値を比較する
    • 比較した結果、カウンタが増えている場合、モードをONにする。
    • 比較した結果、カウンタが減っている場合、モードをOFFにする。

A1

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

// カウンタの値を格納する変数
static uint8_t counter = 0;

// ハンドルを回すとインクリメントされる関数
void handle_turn_clockwise() {
    if (counter < 255) {
        counter++;
    }
}

// ハンドルを逆方向に回すとデクリメントされる関数
void handle_turn_counterclockwise() {
    if (counter > 0) {
        counter--;
    }
}

// カウンタ値を取得する関数
uint8_t get_counter_value() {
    return counter;
}

// 1ms周期タスク
void one_ms_task() {
    static uint8_t prev_counter_value = 0;
    bool mode = false;

    uint8_t current_counter_value = get_counter_value();

    if (current_counter_value > prev_counter_value) {
        mode = true; // モードをONにする
    } else if (current_counter_value < prev_counter_value) {
        mode = false; // モードをOFFにする
    }

    prev_counter_value = current_counter_value;

    // ここでモードを使用して何らかの処理を行う
    // ...
}

int main() {
    // ハンドルを回す操作や逆方向に回す操作をシミュレート
    handle_turn_clockwise();
    handle_turn_clockwise();
    handle_turn_counterclockwise();

    // 1ms周期タスクを呼び出す(実際のアプリケーションではタイマー割り込みなどで実行する)
    one_ms_task();

    return 0;
}

このコード例では、カウンタの値をインクリメント・デクリメントする関数と、カウンタ値を取得する関数が定義されています。また、1ms周期タスク内で、前回のカウンタ値と現在のカウンタ値を比較し、モードをON/OFFに変更しています。実際のアプリケーションでは、1ms周期タスクはタイマー割り込みなどを使用して実行する必要があります。

Q2

追加要件

  • ハンドルを順方向に回すと、カウンタがmax値(255)までいったとき、カウンタ値は255→0に変わる。

    • このときカウンタが減ったためハンドルが逆方向に回されたと認識してしまう。
    • 実際にはハンドルを順方向に回している。
    • この問題を解決したい。
  • ハンドルを逆方向に回すと、カウンタがmin値(0)までいったとき、カウンタ値は0→255に変わる。

    • このときカウンタが増えたためハンドルが順方向に回されたと認識してしまう。
    • 実際にはハンドルを逆方向に回している。
    • この問題を解決したい。
  • 1カウンタ値は必ずしも1周期で1ずつインクリメントorデクリメントされるのではない。

    • 例えば252→ 4になったり、252→0になったりする。
    • この場合にも対応できるようにしたい。

A2

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

// カウンタの値を格納する変数
static uint8_t counter = 0;

// ハンドルを回すとインクリメントされる関数
void handle_turn_clockwise() {
    counter++;
}

// ハンドルを逆方向に回すとデクリメントされる関数
void handle_turn_counterclockwise() {
    counter--;
}

// カウンタ値を取得する関数
uint8_t get_counter_value() {
    return counter;
}

// 1ms周期タスク
void one_ms_task() {
    static uint8_t prev_counter_value = 0;
    bool mode = false;

    uint8_t current_counter_value = get_counter_value();
    int16_t diff = (int16_t)current_counter_value - (int16_t)prev_counter_value;

    // 周期内に複数インクリメント/デクリメントされた場合の誤差を許容する範囲
    int16_t threshold = 127;

    if (diff > 0 && diff <= threshold) {
        mode = true; // モードをONにする
    } else if (diff < 0 && diff >= -threshold) {
        mode = false; // モードをOFFにする
    } else if (diff > threshold) {
        mode = false; // カウンタがmin値(0)からmax値(255)に戻った場合
    } else if (diff < -threshold) {
        mode = true; // カウンタがmax値(255)からmin値(0)に戻った場合
    }

    prev_counter_value = current_counter_value;

    // ここでモードを使用して何らかの処理を行う
    // ...
}

int main() {
    // ハンドルを回す操作や逆方向に回す操作をシミュレート
    handle_turn_clockwise();
    handle_turn_clockwise();
    handle_turn_counterclockwise();

    // 1ms周期タスクを呼び出す(実際のアプリケーションではタイマー割り込みなどで実行する)
    one_ms_task();

    return 0;
}
  • このコードでは、1ms周期タスク内で前回のカウンタ値と現在のカウンタ値の差(diff)を計算しています。
  • カウンタ値の差が正で閾値(threshold)以下の場合、モードをONに設定し、カウンタ値の差が負で閾値以上の場合、モードをOFFに設定します。
  • また、カウンタ値の差が閾値より大きい場合や閾値より小さい場合は、カウンタがmax値(255)からmin値(0)に戻った場合やmin値(0)からmax値(255)に戻った場合として、それぞれモードを適切に設定します。

  • このようにすることで、1周期でカウンタ値が複数インクリメント/デクリメントされる場合にも対応できます。

  • ただし、閾値は適切に設定する必要があります。
  • 閾値が大きすぎると、誤差が大きくなりますし、閾値が小さすぎると、カウンタ値が複数インクリメント/デクリメントされた場合に正しくモードを設定できません。
  • 実際のアプリケーションでは、閾値を適切に設定することが重要です。

Q3

今回は基本的には4ずつインクリメント or 4ずつデクリメントされます。 どのようなしきい値が適切でしょうか。

A3

基本的にカウンタが4ずつインクリメントまたはデクリメントされる場合、しきい値はカウンタの変化範囲を考慮して設定する必要があります。ここで、最大の変化量は4であり、1周期内でカウンタが複数のインクリメント/デクリメントを行うことを許容する範囲を示すため、しきい値を4よりも大きく設定することが適切です。

例えば、しきい値を8と設定すると、1周期内で最大2回のインクリメント/デクリメントが許容されることになります。このように、しきい値を適切に設定することで、カウンタが4ずつインクリメント/デクリメントされる場合でも、正しくモードを判断できます。

以下のコード例では、しきい値を8に設定しています。

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

// カウンタの値を格納する変数
static uint8_t counter = 0;

// ハンドルを回すとインクリメントされる関数
void handle_turn_clockwise() {
    counter += 4;
}

// ハンドルを逆方向に回すとデクリメントされる関数
void handle_turn_counterclockwise() {
    counter -= 4;
}

// カウンタ値を取得する関数
uint8_t get_counter_value() {
    return counter;
}

// 1ms周期タスク
void one_ms_task() {
    static uint8_t prev_counter_value = 0;
    bool mode = false;

    uint8_t current_counter_value = get_counter_value();
    int16_t diff = (int16_t)current_counter_value - (int16_t)prev_counter_value;

    // 周期内に複数インクリメント/デクリメントされた場合の誤差を許容する範囲
    int16_t threshold = 8;

    if (diff > 0 && diff <= threshold) {
        mode = true; // モードをONにする
    } else if (diff < 0 && diff >= -threshold) {
        mode = false; // モードをOFFにする
    } else if (diff > threshold) {
        mode = false; // カウンタがmin値(0)からmax値(255)に戻った場合
    } else if (diff < -threshold) {
        mode = true; // カウンタがmax値(255)からmin値(0)に戻った場合
    }

    prev_counter_value = current_counter_value;

    // ここでモードを使用して何らかの処理を行う
    // ...
}

int main() {
    // ハンドルを回す操作や逆方向に回す操作をシミュレート
    handle_turn_clockwise();
    handle_turn_clockwise();
    handle_turn_counterclockwise();

    // 1ms周期タスクを呼び出す(実際のアプリケーションではタイマー割り込みなどで実行する)
    one_ms_task();

    return 0;
}

このコードでは、閾値が8に設定されているため、カウンタが4ずつインクリメント/デクリメントされる場合でも正しくモードを判断できます。ただし、閾値を適切に設定することが重要です。カウンタの変化範囲やアプリケーションの要件に応じて、適切な閾値を選択してください。