ねぎとろ放浪記

ねぎとろ放浪記

個人的備忘録です。勉強したことをまとめていきます。

chrome.storageの使い方

Chrome拡張の開発中にローカルストレージの使い方で詰まったのでメモ。

公式でドキュメントでわかりにくかった操作をまとめた。

任意のキーで値を保存する

任意のキーで保存するためには {[key]: value} のようにする必要がある。

const key = "YOUR_KEY"
chrome.storage.local.set({ [key]: value }).then(() => {
  console.log("Value is set to " + value);
});

全てのキーを取得する

null を指定するとストレージの全ての要素を取得できる。

const keys = await chrome.storage.local.get(null).then((result) => {
    return Object.keys(result);
});

参考

【React Native】WebViewでのログの出し方

React NativeのWebViewでのログの出力方法のメモ。

injectedJavaScriptonMessage を使う。

ボタンをクリックした時にログを表示するサンプル。
console.log()window.ReactNativeWebView.postMessage() も表示できる。

index.tsx

const debugging = `
    const consoleLog = (type, log) => window.ReactNativeWebView.postMessage(JSON.stringify({'type': 'Console', 'data': {'type': type, 'log': log}}));
    console = {
        log: (log) => consoleLog('log', log),
        debug: (log) => consoleLog('debug', log),
        info: (log) => consoleLog('info', log),
        warn: (log) => consoleLog('warn', log),
        error: (log) => consoleLog('error', log),
    };
`;

const onMessage = payload => {
    let dataPayload;
    try {
        dataPayload = JSON.parse(JSON.stringify(payload.nativeEvent.data));
    } catch (e) {}

    if (dataPayload) {
        if (dataPayload.type === 'Console') {
        console.info(`[Console] ${JSON.stringify(dataPayload.data)}`);
        } else {
        console.log(dataPayload);
        }
    }
};

return (
    <View>
        <WebView
            source={{uri: 'file:///android_asset/index.html'}}
            injectedJavaScript={debugging}
            onMessage={onMessage}
        />
    </View>
    );


index.html

<html>
    <head>
        <title>Sample</title>
    </head>
    <body>
        <h1>This is WebView sample</h1>
        <button id="button1">Button1</button>
        <button id="button2">Button2</button>
        <script type="text/javascript" src="init.js"></script>
    </body>
</html>


init.js

function send_log() {
  console.log('This is log');
}

const button1 = document.getElementById('button1');
button1.addEventListener('click', send_log, false);

function send_log2() {
  window.ReactNativeWebView.postMessage('Hello');
}

const button2 = document.getElementById('button2');
button2.addEventListener('click', send_log2, false);


参考

https://stackoverflow.com/questions/53560744/javascript-console-log-in-a-react-native-webview

【Spring Data JPA】テーブル結合を含むクエリの結果を受け取る

INNER JOIN などのテーブル結合を含むクエリの結果の受け取り方で詰まったのでメモ。

@SqlRusltSetMappingを使用するか、Objectの配列を目的の型に変換する方法がある。

例として、ItemsテーブルとPricesテーブルを結合し、ItemごとのPriceの結果を受け取る。

テーブルの定義

CREATE TABLE items(
id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(16) NOT NULL
);

CREATE TABLE prices(
id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
item_id BIGINT,
price BIGINT NOT NULL
);


方法1 - SqlResultMappingを使う

@SqlRusltSetMappingを使用することで受け取れる。

ただしこの方法は、テーブルに対応しないEntityを作成するので、混乱を招く可能性がある。

結果を受け取るためのクラス(Entity)

@Entity
@SqlResultSetMapping(
        name="ItemPricesResult",
        classes = {
                @ConstructorResult(
                        targetClass = ItemPrices.class,
                        columns = {
                                @ColumnResult(name="id", type=Long.class),
                                @ColumnResult(name="name", type=String.class),
                                @ColumnResult(name="price", type=Long.class)
                        }
                )
        }
)
@NamedNativeQuery(
        name="ItemPricesResult",
        query = "SELECT items.id, items.name, prices.price " +
                "FROM items " +
                "INNER JOIN prices ON (items.id = prices.item_id)",
        resultSetMapping = "ItemPricesResult"
)
public class ItemPrices {

    public ItemPrices(Long id, String name, Long price){
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @Id
    private Long id;

    private String name;

    private Long price;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getPrice() {
        return price;
    }

    public void setPrice(Long price) {
        this.price = price;
    }
}

@Idは必須。 コンストラクタを定義する必要がある。

Service

@Service
public class ItemService {

    @PersistenceContext
    EntityManager entityManager;

    public List<ItemPrices> getItemPrices(){
        return entityManager.createNamedQuery("ItemPricesResult").getResultList();
    }
}

repositoryを介さずに呼び出せる。

方法2 - Objectの配列として受け取り変換

新しいDtoを作成する。

@Data
@AllArgsConstructor
public class ItemPriceDto{

    private Integer itemId;
    private String name;
    private Integer price;

    public ItemPriceDto(Object[] objects) {
        this(
                ((BigInteger) objects[0]).intValue(),
                (String) objects[1],
                ((BigInteger) objects[2]).intValue()
                );
    }
}

Repository

public interface ItemRepository extends JpaRepository<Item, Integer> {

    @Query(
            value ="SELECT items.id as item_id, items.name, prices.price " +
                    "FROM items " +
                    "INNER JOIN prices ON (items.id = prices.item_id)",
            nativeQuery = true
    )
    List<Object[]> findItemPricesRaw();

    default List<ItemPriceDto> findItemPrices(){
        return findItemPricesRaw()
                .stream()
                .map(ItemPriceDto::new)
                .collect(Collectors.toList());
    }
}

findItemPriceRawはObjectの配列を返す。

これをItemPriceDtoのリストに変換する。


戻り値が1つのときは、objectの0番目の値を指定してコンストラクタに渡す。

public interface ItemRepository extends JpaRepository<Item, Integer> {

    @Query(
            value ="SELECT items.id as item_id, items.name, prices.price " +
                    "FROM items " +
                    "INNER JOIN prices ON (items.id = prices.item_id) " +
                    "WHERE items.id = :itemId",
            nativeQuery = true
    )
    Object[] findSingleItemPriceRaw(Integer itemId);

    default ItemPriceDto findSingleItemPrice(Integer itemId){
        Object[] objects = findSingleItemPriceRaw(itemId);
        ItemPriceDto itemPriceDto = new ItemPriceDto((Object[]) objects[0]); // objectsの0番目の値を指定
        return itemPriceDto;
    }
}

参考

6.3. データベースアクセス(JPA編) — TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.3.1.RELEASE documentation

2.3.2. ネイティブクエリのマッピング JBoss Enterprise Application Platform 5 | Red Hat Customer Portal

Javaの標準入力

諸事情によりJavaで競プロをすることになったので、標準入力の方法をまとめます。
初心者なのでとりあえずScannerでの受け取り。

1行に1つの文字または数字

最も基本となる標準入力方法。

Scanner sc = new Scanner(System.in);
int n = sc.nextInt();

型にあわせて

  • nextLong()
  • next()

を使う。

1行に複数の数字(配列へ)

11 234 55など。

Scanner sc = new Scanner(System.in);
int[] a = new int[n];
Arrays.setAll(a, i -> sc.nextInt());


nextInt()をfor文でまわすだけの方法もある。

Scanner sc = new Scanner(System.in);
int[] numbers = new int[n];
for(int i=0; i<n; i++){
    numbers[i]=sc.nextInt();
}


1行に複数の文字列(配列へ)

foo bar hogeなど。

Scanner sc = new Scanner(System.in);
String[] words = sc.nextLine().split(" ");


1つの文字列を1文字ずつ配列へ

oo..xoxxx.oなどを受け取る。

Scanner sc = new Scanner(System.in);
char[] s = sc.next().toCharArray();


複数行の文字列(地図など)

###..##などで表現される地図を受け取る。

Scanner sc = new Scanner(System.in);
char[][] map = new char[r][c];
for (int row = 0; row < r; row++) {
    map[row] = sc.next().toCharArray();
}

【レビュー】HUAWEI Band 4 Pro を買いました

ランニングの距離やペース記録用に買いました。

HUAWEI Band 4 Proの赤です。 他には黒とピンクがあります。


これよりも低価格のスマートバンドとして、XiaomiのMiスマートバンド4HUAWEIHUAWEI Band 4があります。

スマホを持たずランニングのペースを記録したかったので、GPS内蔵のHUAWEI Band 4 Proを選びました。

価格は約7000円でした。
GPSのないモデルと比較すると3000円ほど高くなっています。
GPS内蔵ではApple Watchがありますが、いきなり数万出す勇気がなく、こちらを選んだ次第です。

目次

スペック

公式のHPによるとスペックは以下の通りです。


f:id:neginegitoro:20210601205412p:plain
スペック

Androidスマホでは音楽の再生と停止ができます。

特徴としてはGPSが内蔵されていることですね。
これによってスマホを持たずともランニングの記録ができます。

その他の機能は一般的なスマートバンドと変わらないと思います。


外観

内容物は本体と充電器と説明書、保証書でした。
色はAmazonの写真の通り綺麗な赤で満足です。

f:id:neginegitoro:20210601205444j:plain
内容物


10円玉と比較したときのサイズです。
他のスマートバンドと差はないかと。

f:id:neginegitoro:20210601205512j:plain
サイズの比較


一つの残念ポイントは充電器ですね。
クレードルにmicro USBを接続する仕様になっています。
この接続がType-Cだったらよかったんだけど...

f:id:neginegitoro:20210601205541j:plain
充電器


ペアリング

HUAWEI HEALTHというアプリを使ってペアリングします。

ペアリングに当たってはHUAWEI IDを取得する必要があります。
何かと話題の企業なので抵抗ありましたが、しかたなく登録。
連絡先を同期してって言ってきますが、許可しなくてもOKでした。

ペアリングは一瞬でできましたが、スマートバンドのアップデートに1時間かかりました。

使用感

つけ心地は普通の腕時計と同じです。
バンドの長さが17段階で調節できます。腕の太い人も細い人も使えそうです。

バッテリーのもちは72時間(GPS使用1時間) でした。
GPSを使用時間を踏まえても公式のスペックよりは短いですね。
ただ、明るさの調節や画面を操作する回数を減らせば、もう少し持ちそうです。

以下それぞれの機能の使用感です。

ランニング

記録できるのは、

  • ランニング時間
  • 1kmあたりのペース
  • 平均速度
  • 歩数
  • 平均歩幅
  • 経路
  • 心拍数
  • 消費カロリー

などの情報です。
ランニング中は1kmごとに通知してくれたり、ペースや心拍数を表示してくれます。

ランニング後には経路やペースの詳細をアプリで確認することができます。

f:id:neginegitoro:20210601211303p:plain
ランニングの記録とペースの詳細


スマートバンド単体でのGPSの精度もかなり良く、走った経路を正確に取れていました。誤差は概ね20m程度です。

さまざまな情報がわかり、モチベーションが上がるので楽しいです。

睡眠

睡眠時間を判断し、自動的に記録してくれます。 眠りの深さから睡眠を3段階に分割し、それぞれ何時間だったかを分析してくれます。

f:id:neginegitoro:20210601205711p:plain:w300
睡眠スコア

このように深い睡眠、浅い睡眠、レム睡眠がそれぞれ何分だったかがわかります。自分は深い睡眠がほとんどないようです。

ただ、睡眠時間のアプリへの同期に3分ほどかかるのが少し残念です。
できれば起床直後にデータを確認したいです。

まとめ

以上、HUAWEI Band 4 Proのレビューでした!

今回はランニングと睡眠の記録しか使用しませんでしたが、他のスポーツの記録にも対応しています。

スマートバンドデビューにちょうど良い価格と機能です。
気になっている人は買ってみてはいかがでしょうか。

物体検出の評価指標(APとmAP)

AP、mAPの計算方法のまとめです。

Precision, Recallなどの定義は前回の記事を参考にしてください。

目次

APとは

Average Precisionとも呼ばれます。
Precision-Recall曲線の下の部分の面積がAPとなります。

例えば、「自転車」を検出した結果が以下のようになったとします(Confidence Score順に並んでいます)。

f:id:neginegitoro:20210529180211p:plain
「自転車」を検出したときの例


これを縦軸にPrecision、横軸にRecallをとってプロットすると、以下のような図になります。
((Precision, Recall) = (1,0), (0,1)を追加)

f:id:neginegitoro:20210529180313p:plain
PR曲線

この図の線の下の部分の面積がAPとなります。

mAPとは

Mean Average Precisionとも呼ばれます。

モデルが検出できる各クラスに対するAPの平均です。

クラス数をC、クラスiのAPをAPiとすると、mAPは次の式で表せます。


mAP = \frac{1}{C}\sum_{i=1}^{C}AP_i 

全部のクラスに対するAPの平均です。

最後に

以上、簡単ですがAP、mAPのまとめでした。

間違いやご指摘あればコメントにて教えていただきたいです。

Pythonの多次元リストのソートについて

Pythonの多次元リストをソートする時にいつも忘れるのでメモ。

TL;DR

多次元リストを二次元目の値でソートする方法
key=lambda x:x[1] とします。

l = [[111, "さしすせそ"],
    [222, "あいうえお"],
    [333, "たちつてと"],
    [444, "かきくけこ"]]

sorted_l = sorted(l, key=lambda x:x[1])
print(sorted_l)  

# 実行結果 [[222, 'あいうえお'], [444, 'かきくけこ'], [111, 'さしすせそ'], [333, 'たちつてと']]

2次元目の値でソートできました。

なにをやっているのか

sorted()の2つめの引数に関数を設定することができます。
ソートするときに関数を適用した値をソートします。

上の例ではラムダ式を適用した結果をソートしているのです。


絶対値でソートするときは以下のようにします。

l = [2,-3,4,1]

sorted_l = sorted(l, key=abs)
print(sorted_l)  # [1, 2, -3, 4]

絶対値の昇順でソートできました。

なお、返ってくるリストには 関数適用前の値 が入ります。
この例であれば、-3が入っていることがわかります。

ついでに

降順にソートするときは reverse=True とします。

l = [2,-3,4,1]

sorted_l = sorted(l, key=abs, reverse=True)
print(sorted_l)  # [4, -3, 2, 1]