dak ブログ

python、rubyなどのプログラミング、MySQL、サーバーの設定などの備忘録。レゴの写真も。

TypeScript で avif ファイルを jpeg に変換

2024-03-22 23:52:24 | Node.js
TypeScript で avif ファイルを jpeg に変換する方法のメモ。
■sharp のインストール
npm install sharp
■プログラム
import { readFileSync, writeFileSync } from 'fs';
import sharp from 'sharp';

(async () => {
  const inFile = process.argv[2];
  const outFile = process.argv[3];

  await sharp(inFile).jpeg().toFile(outFile);
})();

heic 形式の画像ファイルを jpeg に変換

2024-03-22 00:14:37 | Node.js
heic 形式の画像ファイルを jpeg に変換する方法のメモ。

■heic-convert のインストール
npm install heic-convert
npm install --save-dev @types/heic-convert

■プログラム
import { readFileSync, writeFileSync } from 'fs'
import convert from 'heic-convert'

(async () => {
const inFile = process.argv[2];
const outFile = process.argv[3];

const inBuf = readFileSync(inFile);
const outBuf = await convert({
buffer: inBuf,
format: 'PNG',
quality: 1,
});
const outArr = new Uint8Array(outBuf);
writeFileSync(outFile, outArr);
})();

上記のプログラムで heic を jpeg に変換します。
$ ts-node heic_to_heic.ts img.heic img.png

■heic 形式のファイルのマジックナンバーを確認
$ cat img.heic | od -tx1z | head -1
0000000 00 00 00 1c 66 74 79 70 68 65 69 63 00 00 00 00 >....ftypheic....<

マジックナンバーは5バイト目以降が ftypheic のため、HEIC であることが確認できます。

■jpeg 形式のファイルのマジックナンバーを確認
$ cat img.jpg | od -tx1z | head -1
0000000 ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 01  >......JFIF......<
jpeg のマジックナンバーは 0xff 0xd8 のため、jpeg に変換されていることがわかります。

TypeScript で TTL つきキャッシュを実装

2024-02-23 15:02:11 | Node.js
TypeScript での TTL つきキャッシュの実装例です。
※2024/02/26 にバグを修正

オブジェクトの登録時刻がキャッシュ作成時に指定した TTL を過ぎると、キャッシュを参照した際に undefined を返却します。

キャッシュの実装で利用している list の実装は以下にあります。
Typescript での双方向連結リスト
■キャッシュの実装
/**
 *
 * Cache with TTL
 *
 */

import { Node, List } from '../list/lib/list';
import { SimpleCache } from 'SimpleCache';


export class TtlCache {
  private _max_size: number;
  private _list: List = new List();
  private _hash: any = {};
  private _ttl_msec: number;
  
  public constructor(max_size: number, ttl_msec: number) {
    if (max_size < 1) max_size = 1;
    this._max_size = max_size;
    this._ttl_msec = ttl_msec;
  }
  
  public get(key: string) {
    super.get(key);
    // obj = [key, obj, time];
    const now = (new Date()).getTime();
    const node = this._hash[key];
    if (node === undefined) return undefined;
    
    if (node.obj[2] + this._ttl_msec < now) {
      // timeout
      this._list.remove_node(node);
      delete this._hash[key];
      return undefined;
    }

    this._list.remove(node);
    this._list.push(node);
    return node.obj[1];
  }
  

  public set(key: string, obj: any) {
    // obj = [key, obj, time];
    // update obj if exists
    const now = (new Date()).getTime();
    let node = this._hash[key];
    if (node === undefined) {
      const node = new Node([key, obj, now]);
      this.push(node);
      
      if (this._list.length > this._max_size) {
	this._list.shift_node();
	delete this._hash[key];
      }
    }
    else {
      node.obj = [key, obj, now];
      this.remove_node(node);
      this.push_node(node);
    }
    return obj;
  }
}

■実行例
import { setTimeout } from 'timers/promises';
import { TtlCache } from './TtlCache';


async function test_set() {
  const cache = new TtlCache(8, 1000);
  
  const num = 10;
  for (let i = 0; i < num; i++) {
    const key = `key_${i}`;
    const value = `value_${i}`;
    cache.set(key, value);
    const time = (new Date()).getTime();
    console.log(`${time} ${key}: ${value}`);
  }

  console.log('');
  await setTimeout(500);

  for (let i = 0; i < num; i++) {
    await setTimeout(100);
    const key = `key_${i}`;
    const value = cache.get(key);
    const time = (new Date()).getTime();
    console.log(`${time} ${key}: ${value}`);
  }
  
  return 0;
}

async function main() {
  let errs = 0;
  errs += await test_set();
  
  return 0;
}

main();

■実行結果
1708956725261 key_0: value_0
1708956725262 key_1: value_1
1708956725262 key_2: value_2
1708956725262 key_3: value_3
1708956725262 key_4: value_4
1708956725262 key_5: value_5
1708956725262 key_6: value_6
1708956725262 key_7: value_7
1708956725262 key_8: value_8
1708956725262 key_9: value_9

1708956725870 key_0: undefined
1708956725973 key_1: undefined
1708956726076 key_2: value_2
1708956726179 key_3: value_3
1708956726280 key_4: undefined
1708956726384 key_5: undefined
1708956726492 key_6: undefined
1708956726595 key_7: undefined
1708956726699 key_8: undefined
1708956726806 key_9: undefined


TypeScript でシンプルなキャッシュの実装

2024-02-23 14:45:46 | Node.js
TypeScript で最大オブジェクト数までオブジェクトを格納するシンプルなキャッシュの実装例です。
※2024/02/25 バグを修正しました

キー:オブジェクト をリストで管理し、直近でアクセスされた キー:オブジェクト をリストの末尾に移動させます。
格納する キー:オブジェクト が最大数に達すると、リストの先頭の キー:オブジェクト を削除して、新しい キー:オブジェクト を登録します。
オブジェクトを取得する際、リストの末尾に該当の キー:オブジェクト を移動させ、アクセス頻度が高いキーがキャッシュに残りやすくしています。

list の実装は以下にあります。
Typescript での双方向連結リスト
■キャッシュ
/**
 *
 * Simple Cache
 *
 */

import { Node, List } from '../list/lib/list';


export class SimpleCache {
  private _size: number = 0;
  private _max_size: number;
  private _list: List = new List();
  private _hash: any = {};
  
  public constructor(max_size: number) {
    if (max_size < 1) max_size = 1;
    this._max_size = max_size;
  }
  
  public get(key: string) {
    const node = this._hash[key];
    if (node === undefined) return undefined;
    
    this._list.remove_node(node);
    this._list.push(node);
    return node.obj;
  }
  
  public set(key: string, obj: any) {
    let node = this._hash[key];
    if (node === undefined) {
      // update obj if exists
      node = new Node([key, obj]);
      this._hash[key] = node;
      this._list.push_node(node);
      this._size += 1;
    }
    else {
      // set (key, obj)
      this._list.remove_node(node);
      this._list.push_node(node);
    }
      
    // remove head if size is max
    if (this._size > this._max_size) {
      const [old_key, old_obj] = this._list.shift();
      delete this._hash[old_key];
      this._size -= 1;
    }
    
    return obj;
  }
}

■実行例
import { SimpleCache } from './SimpleCache';

function test_set() {
  const cache = new SimpleCache(3);
  const num = 10;
  
  console.log('set');
  for (let i = 0; i < num; i++) {
    const key = `key_${i}`;
    const value = `value_${i}`;
    cache.set(key, value);
    console.log(`${key}: ${value}`);
  }

  console.log('get');
  for (let i = 0; i < num; i++) {
    const key = `key_${i}`;
    const value = cache.get(key);
    console.log(`${key}: ${value}`);
  }

  return 0;
}


function main() {
  let errs = 0;
  errs += test_set();
  
  return 0;
}

main();

■実行結果
set
key_0: value_0
key_1: value_1
key_2: value_2
key_3: value_3
key_4: value_4
key_5: value_5
key_6: value_6
key_7: value_7
key_8: value_8
key_9: value_9
get
key_0: undefined
key_1: undefined
key_2: undefined
key_3: undefined
key_4: undefined
key_5: undefined
key_6: undefined
key_7: key_7,value_7
key_8: key_8,value_8
key_9: key_9,value_9


Typescript で sleep

2024-01-30 23:36:23 | Node.js
TypeScript で sleep する方法のメモ。
■プログラム
import { setTimeout } from 'timers/promises';

(async () => {
  console.log('before wait');
  await setTimeout(3000);
  console.log('after wait');
})();

■実行結果
before wait
after wait


TypeScript で WordPress に記事を登録・更新

2024-01-24 00:10:04 | Node.js
TypeScript で WordPress に記事を登録する方法のメモ。
Advanced Custom Fields(ACF)で作成したフィールドは fields でフィールドの値を指定します。

■登録
import WPAPI from 'wpapi';

async function create(article) {
  // オレオレ証明書を許可
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

  const wpapi = new WPAPI({
    endpoint: 'https://{ドメイン}/wordpress/wp-json',
    username: '{ユーザID}',
    password: 'uuuu vvvv wwww xxxx yyyy zzzz', // アプリケーションパスワード
  });

  const res = await wpapi.posts().create(article);
  return res;
}

(async () => {
  const article = {
    title: 'タイトル',
    fields: {
      main: '本文',
      ...
    },
  };
  const res = await create(article);
  console.log(res);
})();

レスポンス
{
  id: 1,
  ...
  status: '',
  type: 'post',
  ...
  title: {
    raw: 'タイトル',
    rendered: 'タイトル'
  },
  ...
  acf: {
    main: '本文',
    ...
  },
  ...
}

■更新
import WPAPI from 'wpapi';

async function update(id, article) {
  // オレオレ証明書を許可
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

  const wpapi = new WPAPI({
    endpoint: 'https://{ドメイン}/wordpress/wp-json',
    username: '{ユーザID}',
    password: 'uuuu vvvv wwww xxxx yyyy zzzz', // アプリケーションパスワード
  });

  const resCreate = await wpapi.posts().id(id).update(article);
  return resCreate;
}

(async () => {
  const article = {
    // status: draft / publish
    status: 'publish',
    title: '新タイトル',
    fields: {
      main_text: '新本文',
      ...
    },
  };
  const res = await update(1, article);
  console.log(res);
})();


TypeScript で WordPress API を使って記事データを取得

2024-01-17 23:24:05 | Node.js
TypeScript で WordPress API を使って記事データを取得する方法のメモ。

■wpapi のインストール
npm install wpapi
npm install @type/wpapi

■プログラム
import WPAPI from 'wpapi';

async function getPost(id_num) {
  const wpapi = new WPAPI({
    endpoint: 'http://xxx.yyy.zzz/wordpress/wp-json',
    username: '{user}',
    password: '{password}',
  });

  // 指定ID
  const res = await wpapi.posts().id(100);
  return res;
}

(async () => {
  const res = await getPost(100);
  console.log(res);
})();

■実行
$ ts-node test_get_article1.ts
{
  id: 100,
  date: '2024-01-15T17:34:22',
  date_gmt: '2024-01-15T08:34:22',
  guid: { rendered: 'http://xxx.yyy.zzz/wordpress/?p=100' },
  modified: '2024-01-15T17:34:32',
  modified_gmt: '2024-01-15T08:34:32',
  slug: '...',
  status: '1',
  type: 'post',
  link: 'http://xxx.yyy.zzz/wordpress/archives/100',
  title: { rendered: 'タイトル タイトル タイトル' },
  ...
}



ts-node でエラーがでる場合の設定

2024-01-17 23:12:23 | Node.js
ts-node でエラーがでる場合の package.json、tsconfig.json の設定のメモ。
■package.json
{
  "type": "module",
  "dependencies": {
  },
  "devDependencies": {
  }
}

■tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "esModuleInterop": true
  },
  "ts-node": {
    "esm": true
  }
}


Typescript で Cloud SQL(MySQL) のデータを取得する方法

2023-07-12 23:56:27 | Node.js
Typescript で Cloud SQL(MySQL) のデータを取得する方法のメモ。

■インストール
npm install promise-mysql
npm --save-dev install @types/mysql

■プログラム
import * as mysql from 'promise-mysql';

(async () => {
  const pool = await mysql.createPool({
    //socketPath: config.socket_path,
    host: {HOST},
    user: {USER},
    password: [PASSWORD},
    database: {DATABASE},
    connectionLimit: 10,
    timezone: '+0900',
  });

  const sql = "select * from {table} where id = ?";
  const parameters = [{ID1}];

  const rows = await pool.query(sql, parameters);
  for (const row of rows) {
    console.log(row);
  }
})();


Typescript で Cloud Storage 上の json ファイルを読み込む方法

2023-07-11 20:04:39 | Node.js
Typescript で Cloud Storage 上の json ファイルを読み込む方法のメモ。
以下の json ファイルをダウンロードするプログラム例です。
gs://cloud-storage-test-bucket/data/test.json

■インストール
npm install @google-cloud/storage

■プログラム
import storage from '@google-cloud/storage';

(async () => {
  const bucketName = 'cloud-storage-test-bucket';
  const fileName = 'data/test.json';

  const storage = new Storage();
  const response = await storage.bucket(bucketName).file(fileName).download();
  
  const obj = JSON.parse(response.toString());
  console.log(JSON.stringify(obj));
})();


Typescript での遺伝的アルゴリズムの実装例

2023-04-30 14:43:44 | Node.js
Typescript での遺伝的アルゴリズムの実装例

Typescript での遺伝的アルゴリズムの実装例です。
・個体クラス: BaseIndividual
・GAクラス: BaseGeneticAlgorithm
これらのライブラリを継承して、巡回セールスマン問題に適用してみます。

■遺伝的アルゴリズムのライブラリ(BaseGeneticAlgorithm)
/**
 *
 * Base Genentic Algorithms
 *
 */
/**
 * config
 * timeout_msec: Number    # timeout in msec
 * max_population: Number  #
 * fitness_min: Boolean    # true: asc, false: desc
 * max_population: Number  # 
 * 
 */


export class BaseIndividual {
  public fitness: number | null;
  public genes: any | null;
  public encoded: string | null;
  
  
  public constructor() {
  }

  
  public encode(): string | null {
    this.encoded = null;
    return this.encoded;
  }
}



export class BaseGeneticAlgorithm {
  public config: any;
  public population: Array<BaseIndividual> = [];
  public fitness_map = {};

  
  public constructor(config: any) {
    this.config = config;
  }


  public add_population(new_pop: Array<BaseIndividual>) {
    for (let ind of new_pop) {
      ind.encode();
      this.fitness(ind);
      this.population.push(ind);
    }
  }

  
  public select1() {
    const i = Math.floor(Math.random() * this.population.length);
    return this.population[i];
  }


  public select2() {
    const i = Math.floor(Math.random() * this.population.length);
    let j = Math.floor(Math.random() * this.population.length);
    if (i == j) j = (j + i) % this.population.length;
    return [this.population[i], this.population[j]];
  }
  
  
  public fitness(ind: BaseIndividual): number {
    return 0;
  }
  
  
  public crossover(ind1: BaseIndividual, ind2: BaseIndividual):
  Array<BaseIndividual> {
    const new_ind1 = new BaseIndividual()
    new_ind1.encode();
    this.fitness(new_ind1);
    
    const new_ind2 = new BaseIndividual();
    new_ind2.encode();
    this.fitness(new_ind2);
    
    return [new_ind1, new_ind2];
  }
  
  
  public mutate(ind: BaseIndividual): BaseIndividual {
    const new_ind = new BaseIndividual();
    this.fitness(new_ind);
    new_ind.encode();
    return new_ind;
  }


  public append(new_pop: Array<BaseIndividual>, new_pop_map: any, ind: BaseIndividual) {
    if (ind.encoded === null) {
      new_pop.push(ind);
      return true;
    }
    
    if (ind.encoded in new_pop_map) return false;

    new_pop.push(ind);
    new_pop_map[ind.encoded] = true;
    return true;
  }
  
  
  public evolve1() {
    const c = this.config
    const pop = this.population;
    const pop_map = {};
    let new_pop = [];

    for (let ind of pop) {
      if (ind.encoded === null) continue;
      pop_map[ind.encoded] = true;
    }
    
    // crossover
    const num_crossover = c.max_population * c.rate_num_crossover;
    for (let i = 0; i < num_crossover; i++) {
      let inds = this.select2();
      let new_inds = this.crossover(inds[0], inds[1]);
      this.append(new_pop, pop_map, new_inds[0]);
      this.append(new_pop, pop_map, new_inds[1]);
    }
    
    // mutate
    const num_mutate = c.max_population * c.rate_num_mutate;
    for (let i = 0; i < num_mutate; i++) {
      let ind = this.select1();
      let new_ind = this.mutate(ind);
      this.append(new_pop, pop_map, new_ind);
    }
    
    // fitness
    new_pop = new_pop.concat(pop);
    if (c.fitness_order_by_asc) {
      new_pop = new_pop.sort((a, b) => {
	if (a.fitness < b.fitness) return -1;
	else if (a.fitness > b.fitness) return 1;
	else return 0;
      });
    }
    else {
      //console.log(`order: desc`);
      new_pop = new_pop.sort((a, b) => {
	if (a.fitness > b.fitness) return -1;
	else if (a.fitness < b.fitness) return 1;
	else return 0;
      });
    }
    new_pop = new_pop.slice(0, c.max_population);
    this.population = new_pop;
  }
  
  
  public evolve() {
    const c = this.config;
    const start_time = (new Date()).getTime();

    for (let i = 0; i < c.max_iteration; i++) {
      this.evolve1();
      let cur_time = (new Date()).getTime();
      if (cur_time - start_time > c.timeout_msec) break;
    }
  }
}

■巡回セールスマン問題への適用(tsp1.ts)
/**
 *
 * Travelling Salesman Problem
 *
 * 遺伝子は未訪問の都市
 * genes[0]: 1番目の都市のインデックス(0 ~ N-1)
 * genes[1]: 2番目の都市のインデックス(0 ~ N-2)
 * genes[n-1]: N番目の都市のインデックス(0)
 *
 */


import { List } from '../../../list/lib/list';
import { BaseIndividual, BaseGeneticAlgorithm } from '../lib/base_genetic_algorithm';


class TspIndividual extends BaseIndividual {
  public num_cities: number;
  public genes: Array<number>;
  
  
  public constructor(num_cities) {
    super();

    this.num_cities = num_cities;
    this.genes = Array(this.num_cities);
    for (let i = 0; i < num_cities; i++) {
      this.genes[i] = 0;
    }
  }


  public static cities(num_cities: number): List {
    const cities = new List();
    for (let i = 0; i < num_cities; i++) {
      cities.push(String.fromCharCode('A'.charCodeAt(0) + i));
    }
    
    return cities;
  }
  
  
  public random(): TspIndividual {
    const cities = TspIndividual.cities(this.num_cities);
    
    for (let i = 0; i < this.num_cities; i++) {
      let c = Math.floor(Math.random() * cities.length);
      this.genes[i] = c;
      cities.remove(c);
    }
    
    return this;
  }
  
  
  public encode() {
    this.encoded = this.genes.map(x => `${x}`).join('_');
    return this.encoded;
  }
  
  
  public route() {
    const cities = TspIndividual.cities(this.num_cities);
    const city_ids = Array(this.num_cities);
    
    for (let i = 0; i < this.num_cities; i++) {
      let c = this.genes[i];
      let city_id = cities.remove(c);
      city_ids[i] = city_id;
      console.log(city_id);
    }
    
    return city_ids;
  }
}


class TspGA extends BaseGeneticAlgorithm {
  public static NUM_CITIES = 5;
  public start_pos: any;
  public cities: any;

  
  public constructor(config: any, start_pos: any) {
    super(config);
    
    this.start_pos = start_pos;
    
    this.cities = {
      A: {x: 10, y: 10},
      B: {x: 20, y: 20},
      C: {x: 30, y: 30},
      D: {x: 40, y: 40},
      E: {x: 50, y: 50},
    };
  }
  
  
  public fitness(ind: TspIndividual): number {
    if (ind.encoded in this.fitness_map) {
      ind.fitness = this.fitness_map[ind.encoded];
      if (ind.fitness !== null) return ind.fitness;
    }
    
    const cities = TspIndividual.cities(TspGA.NUM_CITIES);
    ind.fitness = 0;
    let pos = this.start_pos;
    for (let i = 0; i < TspGA.NUM_CITIES; i++) {
      let cid = cities.remove(ind.genes[i]);
      let next = this.cities[cid];
      let dist = Math.abs(next.x - pos.x) + Math.abs(next.y - pos.y);
      ind.fitness += dist;
      pos = next;
    }
    //console.log(ind);
    return ind.fitness;
  }
  
  
  public crossover(ind1: TspIndividual, ind2: TspIndividual):
  Array<TspIndividual> {
    const c = this.config;
    const new_ind1 = new TspIndividual(TspGA.NUM_CITIES);
    const new_ind2 = new TspIndividual(TspGA.NUM_CITIES);
    
    for (let i = 0; i < TspGA.NUM_CITIES; i++) {
      if (Math.random() > c.rate_crossover) {
	new_ind1.genes[i] = ind1.genes[i];
	new_ind2.genes[i] = ind2.genes[i];
      }
      else {
	new_ind1.genes[i] = ind2.genes[i];
	new_ind2.genes[i] = ind1.genes[i];
      }
    }

    new_ind1.encode();
    this.fitness(new_ind1);
    
    new_ind2.encode();
    this.fitness(new_ind2);
    
    return [new_ind1, new_ind2];
  }
  
  
  public mutate(ind: TspIndividual): TspIndividual {
    const c = this.config;
    const new_ind = new TspIndividual(TspGA.NUM_CITIES);
    for (let i = 0; i < TspGA.NUM_CITIES; i++) {
      new_ind.genes[i] = ind.genes[i];
      if (Math.random() > c.rate_mutate) continue;
      
      new_ind.genes[i]
	= Math.floor(Math.random() * (TspGA.NUM_CITIES - i));
    }
    
    new_ind.encode();
    this.fitness(new_ind);

    return new_ind;
  }
}


(() => {
  const config = {
    max_cities: 10,
    
    max_population: 10,
    
    rate_num_crossover: 0.5,
    rate_crossover: 0.2,
    
    rate_num_mutate: 0.5,
    rate_mutate: 0.5,

    fitness_order_by_asc: true,
    max_iteration: 20,
    timeout_msec: 30 * 1000,
  };

  const start_pos = {x: 0, y: 0};
  const ga = new TspGA(config, start_pos);
  
  const init_pop: Array<TspIndividual> = [];
  for (let i = 0; i 

■実行結果
$ ts-node tsp1.ts
TspIndividual {
  num_cities: 5,
  genes: [ 1, 1, 0, 1, 0 ],
  encoded: '1_1_0_1_0',
  fitness: 100
}
TspIndividual {
  num_cities: 5,
  genes: [ 1, 1, 0, 0, 0 ],
  encoded: '1_1_0_0_0',
  fitness: 120
}
TspIndividual {
  num_cities: 5,
  genes: [ 1, 0, 0, 1, 0 ],
  encoded: '1_0_0_1_0',
  fitness: 140
}
TspIndividual {
  num_cities: 5,
  genes: [ 2, 1, 0, 1, 0 ],
  encoded: '2_1_0_1_0',
  fitness: 140
}
TspIndividual {
  num_cities: 5,
  genes: [ 1, 1, 2, 0, 0 ],
  encoded: '1_1_2_0_0',
  fitness: 140
}
TspIndividual {
  num_cities: 5,
  genes: [ 1, 1, 1, 1, 0 ],
  encoded: '1_1_1_1_0',
  fitness: 140
}
TspIndividual {
  num_cities: 5,
  genes: [ 1, 1, 2, 1, 0 ],
  encoded: '1_1_2_1_0',
  fitness: 140
}
TspIndividual {
  num_cities: 5,
  genes: [ 1, 1, 1, 0, 0 ],
  encoded: '1_1_1_0_0',
  fitness: 160
}
TspIndividual {
  num_cities: 5,
  genes: [ 1, 0, 1, 1, 0 ],
  encoded: '1_0_1_1_0',
  fitness: 160
}
TspIndividual {
  num_cities: 5,
  genes: [ 1, 0, 2, 1, 0 ],
  encoded: '1_0_2_1_0',
  fitness: 160
}


Typescript での双方向連結リスト

2023-04-30 13:44:49 | Node.js
Typescript での双方向連結リストの実装例です。
※2024/02/25 にバグを修正

■プログラム(list.ts)
/**
 *
 * bidirectional linked list
 *
 */


export class Node {
  public obj: any;
  public prev: Node | null = null;
  public next: Node | null = null;
  
  
  constructor(obj: any) {
    this.obj = obj;
  }
}


export class List {
  public length: number = 0;
  public head: Node | null = null;
  public tail: Node | null = null;
  
  
  constructor(arr?: Array<any>) {
    if (arr === undefined) return;
    
    for (let obj of arr) {
      this.push(obj);
    }
  }


  /**
   * add node to tail
   */
  public push_node(node: Node) {
    if (this.head === null) {
      this.head = node;
    }
    
    if (this.tail !== null) {
      this.tail.next = node;
      node.prev = this.tail;
    }

    this.tail = node;
    this.length += 1;
    
    return this;
  }


  /**
   * add new obj to tail
   */
  public push(obj: any) {
    const node = new Node(obj);
    return this.push_node(node);
  }
  

  /**
   * add node to head
   */
  public unshift_node(node: Node) {
    if (this.head === null) {
      this.head = this.tail = node;
    }
    else {
      const head = this.head;
      head.prev = node;
      node.next = head;
      this.head = node;
    }
    
    this.length += 1;
    
    return this;
  }

  /**
   * add new obj to head
   */
  public unshift(obj: any) {
    const node = new Node(obj);
    return this.unshift_node(node);
  }
  

  /**
   * remove tail node
   */
  public pop_node() {
    if (this.tail === null) return null;
    
    const node = this.tail;
    this.tail = node.prev;
    if (node.prev === null) this.head = null;

    node.prev = null;
    node.next = null;
    this.length -= 1;
    
    return node;
  }


  /**
   * remove tail
   */
  public pop() {
    const node = this.pop_node();
    return node.obj;
  }
  
  
  /**
   * remove head node
   */
  public shift_node() {
    if (this.head === null) return null;
    
    // head -> node <-> next
    const node = this.head;
    this.head = node.next;
    if (this.head !== null) this.head.prev = null;
    if (node.next === null) this.tail = null;
    
    node.prev = null;
    node.next = null;
    this.length -= 1;
    
    return node;
  }
  
  
  /**
   * remove head
   */
  public shift() {
    const node = this.shift_node();
    return node.obj;
  }
  
  
  /**
   * return nth node
   */
  public nth_node(n: number) {
    if (n < 0) return null;
    if (n >= this.length) return null;
    
    let node = this.head;
    if (node === null) return null;
    
    for (let i = 0; i < n; i++) {
      if (node.next === null) return null;
      node = node.next;
    }

    if (node === null) return null;
    
    return node;
  }


  /**
   * return nth obj
   */
  public nth(n: number) {
    const node = this.nth_node(n)
    return node.obj;
  }


  /**
   * remove node
   */
  public remove_node(node: Node) {
    if (node === null) return null; // for tsc
    
    if (node.prev === null) {
      this.head = node.next;
    }
    else {
      node.prev.next = node.next;
    }
    
    if (node.next === null) {
      this.tail = node.prev;
    }
    else {
      node.next.prev = node.prev;
    }
    
    this.length -= 1;
    return node.obj;
  }
  

  /**
   * remove nth obj
   */
  public remove(n: number) {
    if (n < 0) return null;
    if (n >= this.length) return null;

    let node = this.head;
    if (node === null) return null; // for tsc
    for (let i = 0; i < n; i++) {
      if (node === null) return null; // for tsc
      node = node.next;
    }

    return this.remove_node(node);
  }


  public slice(from: number, to?: number) {
    if (from < 0) return null;
    if (from >= this.length) return null;
    if (to === undefined) to = this.length;
    if (to < 0) return null;
    if (to > this.length) return null;

    // skip head
    let node = this.head;
    if (node === null) return null; // for tsc
    for (let i = 0; i < from; i++) {
      if (node === null) return null; // for tsc
      node = node.next;
    }
    if (node === null) return null; // for tsc
    const ret_head = node;
    const head_tail = node.prev;
    
    // slice
    for (let i = from; i < to; i++) {
      if (node === null) return null; // for tsc
      node = node.next;
    }
    if (node === null) return null; // for tsc
    const ret_tail = node.prev;
    const tail_head = node;
    
    // this
    if (head_tail === null) {
      this.head = tail_head;
    }
    else {
      head_tail.next = tail_head;
    }
    
    if (tail_head === null) {
      this.tail = head_tail;
    }
    else {
      tail_head.prev = head_tail;
    }
    this.length -= (to - from);
    
    // ret
    const ret = new List();
    if (ret_head !== null) {
      ret.head = ret_head;
      ret_head.prev = null;
    }
    
    if (ret_tail !== null) {
      ret.tail = ret_tail;
      ret_tail.next = null;
    }
    
    ret.length = to - from;
    
    return ret;
  }
  
  
  public copy() {
    const new_list = new List();
    
    let node = this.head;
    while (node !== null) {
      new_list.push(node.obj);
      node = node.next;
    }
    new_list.length = this.length;
    
    return new_list;
  }


  /**
   * insert list at idx (0 - length)
   */
  public insert(idx: number, list: List) {
    if (idx < 0) return null;
    if (idx > this.length) return null;
    if (list === null) return null;
    if (list.head === null) return this;
    if (list.tail === null) return this;
    
    const new_list = list.copy();
    if (new_list.head === null) return this;
    if (new_list.tail === null) return this;
    
    if (this.head === null) this.head = new_list.head;
    if (this.tail === null) this.tail = new_list.tail;
    if (this.length === 0) {
      this.length = new_list.length;
      return this;
    }
    
    if (idx === 0) {
      const head: Node | null = this.head;
      new_list.tail.next = this.head;
      this.head = new_list.head;
      new_list.tail.next = head;
      this.length += new_list.length;
    }
    else if (idx === this.length) {
      const tail: Node | null = this.tail;
      new_list.head.prev = tail;
      tail.next = new_list.head;
      this.tail = new_list.tail;
      this.length += new_list.length;
    }
    else {
      // skip
      let node: Node | null = this.head;
      for (let i = 0; i < idx; i++) {
	if (node === null) return null;
	node = node.next;
      }
      
      // insert
      if (node === null) return null;
      const prev = node.prev;
      if (prev !== null) prev.next = new_list.head;
      new_list.head.prev = prev;
      new_list.tail.next = node;
      this.length += new_list.length;
    }
    
    return this;
  }
  
  
  public to_array() {
    const arr = new Array(this.length);
    let node = this.head;
    for (let i = 0; i < this.length; i++) {
      if (node === null) return null;
      arr[i] = node.obj;
      node = node.next;
    }
    
    return arr;
  }
}

■動作確認
import { List } from '../lib/list';

(() => {
  // push
  const list1 = new List(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n']);
  list1.push('o');
  console.log(`push('o')`);
  console.log(list1.to_array());
  console.log(list1.length);
  if (list1.head == null) {
    console.log('error: list1.head is null');
    return;
  }
  console.log(`head: obj: ${list1.head.obj}, prev: ${list1.head.prev}, next: ${list1.head.next}`);
  
  // pop
  let obj;
  obj = list1.pop();
  console.log(`pop()`);
  console.log(obj);
  console.log(list1.to_array());
  console.log(list1.length);
  if (list1.head == null) {
    console.log('error: list1.head is null');
    return;
  }
  console.log(`head: obj: ${list1.head.obj}, prev: ${list1.head.prev}, next: ${list1.head.next}`);

  // unshift
  list1.unshift('-');
  console.log(`unshift('-')`);
  console.log(list1.to_array());
  console.log(list1.length);
  if (list1.head == null) {
    console.log('error: list1.head is null');
    return;
  }
  console.log(`head: obj: ${list1.head.obj}, prev: ${list1.head.prev}, next: ${list1.head.next}`);

  // shift
  obj = list1.shift();
  console.log(`shift()`);
  console.log(obj);
  console.log(list1.to_array());
  console.log(list1.length);
  if (list1.head == null) {
    console.log('error: list1.head is null');
    return;
  }
  console.log(`head: obj: ${list1.head.obj}, prev: ${list1.head.prev}, next: ${list1.head.next}`);

  // nth
  obj = list1.nth(2);
  console.log(`nth(2)`);
  console.log(obj);
  console.log(list1.to_array());

  // remove_node
  let node;
  node = list1.head;
  if (node == null) {
    console.log('error: list1.head is null');
    return;
  }
  console.log(`head: obj: ${node.obj}, prev: ${node.prev}, next: ${node.next}`);
  obj = list1.remove_node(node);
  console.log(`remove_node(head)`);
  console.log(obj);
  console.log(list1.to_array());

  node = list1.head;
  if (node == null) {
    console.log('error: list1.head.next is null');
    return;
  }
  node = node.next;
  if (node == null) {
    console.log('error: list1.head.next is null');
    return;
  }
  list1.remove_node(node);
  console.log(`remove_node(head.next)`);
  console.log(list1.to_array());

  node = list1.tail;
  if (node == null) {
    console.log('error: list1.tail is null');
    return;
  }
  list1.remove_node(node);
  console.log(`remove_node(tail)`);
  console.log(list1.to_array());
  
  // remove
  obj = list1.remove(2);
  console.log(`remove(2)`);
  console.log(obj);
  console.log(list1.to_array());

  // slice
  obj = list1.slice(1, 4);
  console.log(`slice(1, 4)`);
  if (obj !== null) console.log(obj.to_array());
  console.log(list1.to_array());

  // copy
  obj = list1.copy();
  console.log(`copy()`);
  if (obj !== null) console.log(obj.to_array());
  console.log(list1.to_array());
  
  // insert
  const list2 = new List(['x', 'y', 'z']);
  obj = list1.insert(1, list2);
  console.log(`insert(1, ['x', 'y', 'z'])`);
  if (obj !== null) console.log(obj.to_array());
  console.log(list1.length);
  
  obj = list1.insert(0, new List(['-']));
  console.log(`insert(0, ['-'])`);
  if (obj !== null) console.log(obj.to_array());
  console.log(list1.length);

  obj = list1.insert(8, new List(['--']));
  console.log(`insert(8, ['--'])`);
  if (obj !== null) console.log(obj.to_array());
  console.log(list1.length);

})();

■実行例
push('o')
[
  'a', 'b', 'c', 'd',
  'e', 'f', 'g', 'h',
  'i', 'j', 'k', 'l',
  'm', 'n', 'o'
]
15
head: obj: a, prev: null, next: [object Object]
pop()
o
[
  'a', 'b', 'c', 'd',
  'e', 'f', 'g', 'h',
  'i', 'j', 'k', 'l',
  'm', 'n'
]
14
head: obj: a, prev: null, next: [object Object]
unshift('-')
[
  '-', 'a', 'b', 'c',
  'd', 'e', 'f', 'g',
  'h', 'i', 'j', 'k',
  'l', 'm', 'n'
]
15
head: obj: -, prev: null, next: [object Object]
shift()
-
[
  'a', 'b', 'c', 'd',
  'e', 'f', 'g', 'h',
  'i', 'j', 'k', 'l',
  'm', 'n'
]
14
head: obj: a, prev: null, next: [object Object]
nth(2)
c
[
  'a', 'b', 'c', 'd',
  'e', 'f', 'g', 'h',
  'i', 'j', 'k', 'l',
  'm', 'n'
]
head: obj: a, prev: null, next: [object Object]
remove_node(head)
a
[
  'b', 'c', 'd', 'e',
  'f', 'g', 'h', 'i',
  'j', 'k', 'l', 'm',
  'n'
]
remove_node(head.next)
[
  'b', 'd', 'e', 'f',
  'g', 'h', 'i', 'j',
  'k', 'l', 'm', 'n'
]
remove_node(tail)
[
  'b', 'd', 'e', 'f',
  'g', 'h', 'i', 'j',
  'k', 'l', 'm'
]
remove(2)
e
[
  'b', 'd', 'f', 'g',
  'h', 'i', 'j', 'k',
  'l', 'm'
]
slice(1, 4)
[ 'd', 'f', 'g' ]
[
  'b', 'h', 'i',
  'j', 'k', 'l',
  'm'
]
copy()
[
  'b', 'h', 'i',
  'j', 'k', 'l',
  'm'
]
[
  'b', 'h', 'i',
  'j', 'k', 'l',
  'm'
]
insert(1, ['x', 'y', 'z'])
[
  'b', 'x', 'y', 'z',
  'h', 'i', 'j', 'k',
  'l', 'm'
]
10
insert(0, ['-'])
[
  '-', 'b', 'x', 'y',
  'z', 'h', 'i', 'j',
  'k', 'l', 'm'
]
11
insert(8, ['--'])
[
  '-',  'b', 'x', 'y',
  'z',  'h', 'i', 'j',
  '--', 'k', 'l', 'm'
]
12


TypeScript での json-server を利用したモックサーバ

2023-03-18 14:38:57 | Node.js
TypeScript で json-server を利用したモックサーバを作成する方法のメモ。

■インストール
npm install json-server
npm install --save-dev @types/json-server

■db.json
{
  "path1": [
    {"id": "1_1", "key": "key1_1", "value": "value1_1"},
    {"key": "key1_2", "value": "value1_2"}
  ],
  "path2":
    {"key": "key2_1", "value": "value2_1"}
}

■モックサーバ(test_server.ts)
import * as JsonServer from 'json-server';

const server = JsonServer.create();
const router = JsonServer.router('db.json');
const middles = JsonServer.defaults();

server.use(middles);
server.use(router);

server.listen(3000, () => {
  console.log('json-server starts');
});

■モックサーバ実行
$ ts-node test_server.ts

■モックサーバの動作確認
$ curl http://localhost:3000/path1
[
  {
    "id": "1_1",
    "key": "key1_1",
    "value": "value1_1"
  },
  {
    "key": "key1_2",
    "value": "value1_2"
  }
]


Typescript で sharp による画像の各ピクセルの色を変更

2023-01-08 14:07:21 | Node.js
Typescript で画像処理モジュールの sharp を使って、画像の各ピクセルの色を変更する方法のメモ。

■インストール
npm install sharp
npm i --save @types/sharp


■プログラム
.raw().toBuffer() で画像の各ピクセルのデータを参照できるようにした後で、各ピクセルの色を補色に変更し、変更後のデータで画像を生成してファイルに保存しています。
import * as fs from 'fs';
import sharp from 'sharp';

function change_color(data: any) {
  const size = data.length;

  for (let idx = 0; idx < size; idx += 4) {
    let r = data[idx];
    let g = data[idx+1];
    let b = data[idx+2];

    // 補色に変更
    let max_min = Math.max(r, g, b) + Math.min(r, g, b);
    data[idx] = max_min - r;
    data[idx+1] = max_min - g;
    data[idx+2] = max_min - b;
  }
}

(async () => {
  const img_in_path: string = 'test1.png';
  const img_out_path: string = 'conv1.png';

  const img =
    await await sharp(img_in_path)
      .raw()
      .toBuffer({resolveWithObject: true});

  change_color(img.data);

  await sharp(new Uint8ClampedArray(img.data.buffer),
              {raw: {width: img.info.width,
                     height: img.info.height,
                     channels: img.info.channels}})
    .toFile(img_out_path);
})();


TypeScript での catch と finally の処理順

2022-09-09 00:24:09 | Node.js
TypeScript で例外が発生した場合に catch と finally がどのような順に実行されるかを調べてみました。
以下のプログラムでは、func1() から func2() を呼び出し、func2() からはさらに func3() を呼び出します。
func3() 内で throw した場合に、catch と finally がどのような順で呼び出されるかを確認します。

■プログラム
function func1() {
  try {
    console.log("func1: start");
    func2();
  }
  catch (e) {
    console.log("func1: catch");
  }
  finally {
    console.log("func1: finally");
  }

  console.log("func1: end");
}

function func2() {
  try {
    console.log("func2: start");
    func3();
  }
  catch (e) {
    console.log("func2: catch");
    throw e;
  }
  finally {
    console.log("func2: finally");
  }

  console.log("func2: end");
}

function func3() {
  try {
    console.log("func3: start");
    throw 'func3';
  }
  catch (e) {
    console.log("func3: catch");
    throw e;
  }
  finally {
    console.log("func3: finally");
  }

  console.log("func3: end");
}

func1();

■実行結果
func1: start
func2: start
func3: start
func3: catch
func3: finally
func2: catch
func2: finally
func1: catch
func1: finally
func1: end


実行結果をみると、finally が定義されている場合には catch が実行された後に finally が呼び出されていますが、catch 内で throw した場合には呼び出し元の処理に戻る前に finally が実行されています。