javascriptで行列の演算をする

class Matrix {
  constructor (ary) { this.matrix = ary }
  add (arg) { this.matrix = this.constructor.add (this.matrix, arg.matrix) }
  sub (arg) { this.matrix = this.constructor.sub (this.matrix, arg.matrix) }
  mult (arg) { this.matrix = this.constructor.mult (this.matrix, arg.matrix) }
  get transpose () { return this.constructor.transpose (this.matrix) }
  get inverse () { return this.constructor.inverse (this.matrix) }


  //行列の加算        
  static add (a, b) { return a.map((a,i)=> b[i].map((b,j)=> a[j]+b)) }
  //行列の減算
  static sub (a, b) { return a.map((a,i)=> b[i].map((b,j)=> a[j]-b)) }
  //行列の積
  static mult (a, b) { return a.map(a=> a.map((_, i)=> b.reduce((g,_,j)=> g + a[j]*b[j][i],0))) }
  //転置
  static transpose (a) { return a[0].map((_,i)=> a.map ((_,j)=> a[j][i]))}
  //単位行列
  static identity (n) { return [...Array(n)].fill(0).map((a,b,c,d=[...c])=>(d[b]=1,d)) }
  //逆行列
  static inverse (A) { return A.reduce((a,b,c,d,e=b[c],f=a[c])=>A.reduce((a,h,i,_,j=h[c],k=A[i],l=a[i])=>(c==i||A.forEach((_,m)=>(k[m]-=b[m]*j,l[m]-=f[m]*j)),a),(A.forEach((_,i)=>(b[i]/=e,f[i]/=e)),a)),this.identity(A.length))}
}

//_______________

let mt0 = [
  [ 1, 2, 0,-1],
  [-1, 1, 2, 0],
  [ 2, 0, 1, 1],
  [ 1,-2,-1, 1]
];
let rst = Matrix.inverse (mt0);

console.log(rst);



2*2

/*
| a b |
| c d |
*/
class Matrix22 {
  #matrix=new Array(4);
  constructor(A=1,B=0,C=0,D=1){this.#matrix=[A,B,C,D]}
  add(M){let[A,B,C,D]=this.#matrix,[a,b,c,d]=M.matrix;this.#matrix=[A+a,B+b,C+c,D+d];return this}//加算
  sub(M){let[A,B,C,D]=this.#matrix,[a,b,c,d]=M.matrix;this.#matrix=[A-a,B-b,C-c,D-d];return this}//減算
  scalarMultiple(n=1){let[A,B,C,D]=this.#matrix;this.#matrix=[A*n,B*n,C*n,D*n];return this}//スカラー倍
  multiply(M){let[A,B,C,D]=this.#matrix,[a,b,c,d]=M.matrix;this.#matrix=[A*a+B*c,A*b+B*d,C*a+D*c,C*b+D*d];return this}//乗算
  get matrix(){return this.#matrix}//内部の値を配列で返す
  get det(){let[A,B,C,D]=this.#matrix;return A*D-B*C}//行列式(determinant)
  get transpose(){let[A,B,C,D]=this.#matrix;return new this.constructor(A,C,B,D)}//転置
  get inverse(){let[A,B,C,D]=this.#matrix,e=1/this.det;return new this.constructor(D*e,-B*e,-C*e,A*e)}//逆数
}

/*
| a b c | 
| d e f | 
| g h i | 
*/
class Matrix33 {
  #matrix=new Array(9);
  constructor(a=1,b=0,c=0,d=0,e=1,f=0,g=0,h=0,i=1){this.#matrix=[a,b,c,d,e,f,g,h,i]}
  add(M){let[A,B,C,D,E,F,G,H,I]=this.#matrix,[a,b,c,d,e,f,g,h,i]=M.matrix;this.#matrix=[A+a,B+b,C+c,D+d,E+e,F+f,G+g,H+h,I+i];return this}//加算
  sub(M){let[A,B,C,D,E,F,G,H,I]=this.#matrix,[a,b,c,d,e,f,g,h,i]=M.matrix;this.#matrix=[A-a,B-b,C-c,D-d,E-e,F-f,G-g,H-h,I-i];return this}//減算
  scalarMultiple(n=1){let[A,B,C,D,E,F,G,H,I]=this.#matrix;this.#matrix=[A*n,B*n,C*n,D*n,E*n,F*n,G*n,H*n,I*n];return this}//スカラー倍
  multiply (M) {let[A,B,C,D,E,F,G,H,I]=this.#matrix,[a,b,c,d,e,f,g,h,i]=M.matrix;this.#matrix=[A*a+B*d+C*g,A*b+B*e+C*h,A*c+B*f+C*i,D*a+E*d+F*g,D*b+E*e+F*h,D*c+E*f+F*i,G*a+H*d+I*g,G*b+H*e+I*h,G*c+H*f+I*i];return this}//乗算
  get transpose(){let[A,B,C,D,E,F,G,H,I]=this.#matrix;return new this.constructor(A,D,G,B,E,H,C,F,I)}//転置
  get det(){let[A,B,C,D,E,F,G,H,I]=this.#matrix;return A*E*I-A*F*H+B*F*G-B*D*I+C*D*H-C*E*G}//行列式(determinant)
  get inverse(){let[A,B,C,D,E,F,G,H,I]=this.#matrix,e=1/this.det;return new this.constructor((E*I-F*H)*e,-(B*I-C*H)*e,(B*F-C*E)*e,-(D*I-F*G)*e,(A*I-C*G)*e,-(A*F-C*D)*e,(D*H-E*G)*e,-(A*H-B*G)*e,(A*E-B*D)*e)}//逆数
}

JavaScript で「順列」と「組合わせ」を計算する

//組合わせ c= 1:重複あり, 0:重複なし
function combinations (a = [ ], b = a.length, c = 1) {
  return 2 > b
  ? a.map(a =>[a])
  : a.flatMap ((d,e)=> combinations (a.slice (e+1-c), b-1, c).map (e=> [d,...e]));
}


//順列 c= 1:重複あり, 0:重複なし
function permutations (a =[ ], b = a.length, c = 1) {
  return 2 > b
  ? a.map (a => [a])
  : a.flatMap ((d,e,[...f]) => permutations ((f.splice (e,1-c), f), b - 1, c).map(a => [d, ...a]));
}



let ary = [1,2,3,4];
console.log(combinations(ary,3));
console.log(Permutations(ary,3,0));

1行で書く

//組合わせ c= 1:重複あり, 0:重複なし
const permutations=((F=(a=[],b=a.length,c=1)=>2>b?a.map(d=>[d]):a.flatMap((d,e,[...f])=> F((f.splice(e,1-c),f),b-1,c).map(a=>[d,...a])))=>F)();

//順列 c= 1:重複あり, 0:重複なし
const combinations=((f=(a=[],b=a.length,c=1)=>2>b?a.map(d=>[d]):a.flatMap((d,e)=>f(a.slice (e+1-c),b-1,c).map(a=>[d,...a])))=>f)();

「組合わせ」を返す関数

function mathematics (ary = [ ], n = ary.length) {
  if (1 === n) {
    return ary.map (a=> [a]);
  } else {
    return ary.flatMap ((a, i)=> mathematics (ary.slice (i), n -1).map (b=> [a,...b]))
  }
}

console.log (mathematics ([1,2,3],3));
/*
[1,1,1],
[1,1,2],
[1,1,3],
[1,2,2],
[1,2,3],
[1,3,3],
[2,2,2],
[2,2,3],
[3,3,3]
*/

やっぱり1行で済ます

const  mathematics=((f=(a=[],b=a.length)=>b==1?a.map(c=>[c]):a.flatMap((c,d)=>f(a.slice(d),b-1).map(d=>[c,...d])))=>f)();

で目的は

Javascript で、0000 から 9999 までの表を作りたい。 -HTML の <table- JavaScript | 教えて!goo

<!DOCTYPE html>
<title></title>
<meta charset="utf-8">
<style>
  td { text-align: right; width: 6em;}
</style>
<body>
<table border="1" id="T"></table>

<script>
const
  ary2tbody=(a,b=document.createElement('tbody'))=>a.reduce((b,a)=>(a.reduce((c,d)=>(c.insertCell().append(d||''),c),b.insertRow()),b),b),
  transpose=a=>a[0].map((_,i)=>a.map(b=>b[i])),
  mathematics=((f=(a=[],b=a.length)=>b==1?a.map(c=>[c]):a.flatMap((c,d)=>f(a.slice(d),b-1).map(d=>[c,...d])))=>f)(),
  sum=(a,b)=>a+b;


const A = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const B = [[0], ...mathematics (A,1), ...mathematics (A,2), ...mathematics (A,3), ...mathematics (A,4)];
const C = [...Array(10)].map (()=>[]);

B.forEach (a=> C[a.reduce (sum, 0) % 10].push (a ? a.join``.padStart (4,0) : ''));
ary2tbody (transpose(C), T);

</script>
    

canvas のツール 網目がうまく描けない(未完成)

<!DOCTYPE html>
<meta charset="UTF-8">
<title>HTML Canvas Library</title>
<style>
canvas {
  border: 2px gray ridge;
}
</style>
<body>
<canvas width="500" height="250"></canvas>
<canvas width="500" height="250"></canvas>
<canvas width="500" height="250"></canvas>
<canvas width="500" height="250"></canvas>
<canvas width="500" height="250"></canvas>

<script>

class Canvas {
  #col = 'black'; #view = [ ];

  constructor (canvas, option) {
    this.canvas   = canvas;
    this.option   = Object.assign ({ }, this.constructor.defOption, option);
    this.ctx      = canvas.getContext ('2d');
  }

  cls () {
    this.canvas.width = this.canvas.clientWidth;
    this.viewport (...this.#view);
    return this;
  }

  line (x, y, x1, y1) {
    let { ctx } = this;
    ctx.beginPath ();
      ctx.moveTo (x, y);
      ctx.lineTo (x1, y1);
    ctx.closePath ();
    ctx.stroke ();
    return this;
  }

  viewport (maxX = this.canvas.clientWidth, maxY = this.canvas.clientHeight, minX = 0, minY = 0, deg = 0) {
    const
      { canvas, ctx, option } = this,
      { sin, cos, trunc: int, abs, log10, PI: pi } = Math,
      digitNum = n=> int (log10 (n) + 1);//桁数を求める

    let
      w = canvas.clientWidth,
      h = canvas.clientHeight,
      dx = maxX - minX,
      dy = maxY - minY,
      uw = w / dx,
      uh = h / dy,
      ur = deg * (pi / 180);

    ctx.setTransform (
      uw * cos (ur),
      -sin (ur) * uh,
      -sin (ur) * uw ,
      -uh * cos (ur),
      -minX * uw,
      maxY * uh,
    );

    ctx.lineWidth = 1 / (uw + uh) * 1.;//線の太さは、縦横軸の平均から算出

    if (option.grid) {
      ctx.save ();
        //グリッドの間隔を2点の距離から算出する
        let
          mx = 10 ** (digitNum (uw)),
          my = 10 ** (digitNum (uh)),
          nx = mx / 10,
          ny = my / 10,
          sx = int ((minX - abs (dx)) / nx) * nx,
          ex = int ((maxX + abs (dx)) / nx) * nx,
          sy = int ((minY - abs (dy)) / ny) * ny,
          ey = int ((maxY + abs (dy)) / ny) * ny;

console.log(uw, uh,mx,my, sx,ex,sy,ey);

        this.color = 'silver';
//        for (let p = sx; p < ex; p += nx / 100) this.line (p, sy, p, ey);
//        for (let p = sy; p < ey; p += nx / 100) this.line (sx, p, ex, p);

        this.color = 'silver';
//        for (let p = sx; p < ex; p += nx / 10) this.line (p, sy, p, ey);
//        for (let p = sy; p < ey; p += nx / 10) this.line (sx, p, ex, p);

        this.color = 'gray';
        for (let p = sx; p < ex; p += nx) this.line (p, sy, p, ey);
        for (let p = sy; p < ey; p += ny) this.line (sx, p, ex, p);

        this.color = 'black';
        this.line (0, sy, 0, ey);
        this.line (sx, 0, ex, 0);

      ctx.restore ();
    }
    
    this.#view = [maxX, maxY, minX, minY, deg];
    return this;
  }


  set color (arg) {this.ctx.strokeStyle = this.#col = arg; }
  get color () { return this.#col; }


  static defOption = {
    grid: true,
  }

  static create (canvas = document.querySelector ('canvas'), option = { }) {
    return new this (canvas, option);
  }
}


</script>

ギアにペンを差し込んでグルグルまわるやつ

'''


HTML <a class="keyword" href="http://d.hatena.ne.jp/keyword/canvas">canvas</a>



















  1. -------- Option ---------









'''

<!DOCTYPE html>
<meta charset="UTF-8">
<title>HTML canvas </title>
<style>
ol li label { display: inline-block; width: 10em; padding: 0 1ex; }
ol li { margin: 2px; list-style: none; }
ol li label, ol li input { border: 2px silver ridge; border-radius: 5px; }
</style>
<body>
<form id="STATE">
  <ol>
    <li>
      <label>Aria</label>
      <input type="number" value="1200" step="50" min="50" max="10000">
    <li>
      <label>B:radius</label>
      <input type="number" value="352" min="0" max="5000">
    <li>
      <label>B:offset</label>
      <input type="number" value="0.52" min="0.01" max="1" step=".01">
    <li>
      <label>Line color</label>
      <select>
        <option value="rgba(0,0,0,.3)">Gray</option>
        <option value="rgba(255,0,0,.2)">Red</option>
        <option value="rgba(0,128,0,.2)">Green</option>
        <option value="rgba(0,0,255,.2)">Blue</option>
        <option value="rgba(128,0,255,.2)" selected>Violet</option>
      </select>
    <li>
      <label>Line speed</label>
      <select>
        <option value="1">Slow</option>
        <option value="5">Normal</option>
        <option value="25" selected>Early</option>
        <option value="100">High speed</option>
      </select>
    </li>
    <li>-------- Option ---------
    </li>
    <li>
      <label>C:radius</label>
      <input type="number" value="1" min="1" max="20">
    <li>
      <label>C:offset</label>
      <input type="number" value="0" min="0" max="1" step=".03" max="1">
  </ol>
</form>
<canvas width="1200" height="1200"></canvas>

<script>

//-----------------------------------------------

class Canvas {
  constructor (e, area, color = 'rgba(0,0,0,.5)') {
    this.canvas = e;
    this.ctx = e.getContext ('2d');
    this.color = color;
    this.area = area;
    this.m = null;
  }

  cls () {
    let { ctx, canvas } = this;
    ctx.clearRect (0, 0, canvas.width, canvas.height);
    return this;
  }

  set area (p) {
    if (p) {
      let { ctx, canvas } = this;
      [canvas.width, canvas.height] = p.value;
      this.m = null;
      ctx.translate (canvas.offsetWidth / 2, canvas.offsetHeight / 2);
    }
  }

  set color (rgba) {
    this.ctx.strokeStyle = rgba;
  }

  line (p) {
    let {ctx, m} = this;
    if (null === m) {
      ctx.moveTo (...p.value);      
    }
    else {
      ctx.beginPath ();
      ctx.moveTo (...m.value);
      ctx.lineTo (...p.value);
      ctx.stroke ();
    }
    this.m = p;
    return this;
  }
}

//-----------------------------------------------

class P {
  #p = [ ];
  constructor (a) { this.#p = a; }
  add (q) { return new this.constructor (q.value.map ((a,i)=> this.#p[i]+a)) }
  sub (q) { return new this.constructor (q.value.map ((a,i)=> this.#p[i]-a)) }
  get value () { return this.#p }
  set value (p) { this.#p = p }
}

//-----------------------------------------------

class Circle {
  #rad = null; //ラジアン
  #c = null; //円周

  constructor (radius = 1, offset = 1/* (%) */, rad = 0) {
    this.radius = radius;
    this.offset = offset;
    this.#rad = rad;
    this.#c = 2 * Math.PI * radius;
  }

  rotation (n) {
    this.#rad += n / this.#c;
    return this.position;
  }

  get position () {
    let r = this.radius *  this.offset;
    let rad = this.#rad;
    return new P ([Math.sin (rad) * r, Math.cos (rad) * r]);
  }
}

//-----------------------------------------------

let
  CG = new Canvas (document.querySelector ('canvas')),
  C0 = null,
  C1 = null,
  C2 = null,
  ANIME_ID = null,
  PM = [...document.querySelectorAll ('form input[type="number"], form select')],
  STEP = 25,
  SPEED = null;
  

function draw () {
  for (let i = SPEED; i--; )
    CG.line (C0.rotation (STEP).add (C1.rotation (-STEP)).add(C2.rotation(-STEP)));
  requestAnimationFrame (draw);
}

//-----------------------------------------------

function demo () {
  if (ANIME_ID)
    cancelAnimationFrame (ANIME_ID);

  let [a, r, offset, col, sp, gr, go] = PM.map (e=> 'valueAsNumber' in e ? e.valueAsNumber: e.value);
  let r0 = a/2*.95 - ( r * offset);
  CG.area = new P ([a, a]);
  CG.color = col;
  SPEED = +sp;

  C0 = new Circle (r0, 1),
  C1 = new Circle (r, offset);
  C2 = new Circle (gr, go);

  draw ();
}

//-----------------------------------------------

function hndler () { demo (); }
STATE.addEventListener ('change', hndler, false);

demo ();

</script>
    

www.amazon.co.jp

location.hash と webapl を連動させるライブラリ

Ajax を利用してアプリを作るとき、location.hash の値と連動して動く。
onHashChange を使用せずに済む。


とある条件でページをスクリプトで生成するプログラムがあるとする。
その条件が変化するごとに location.hash に書き出し、コールバック(生成プログラム)に条件を渡して呼び出す
外部からある条件下でページを生成したい場合 location.hash に条件を書いて呼び出す
インスタンス化した時点の条件を標準とし、それと違う用件だけを location.hash に記述する

2022-07-09 更新

/*----------------------------------------------
  onHashChange を使用せず
  現在の状態を location.hash に反映させる(逆も可)
  対象となるFORM の要素群には id もしくは name 属性が必要である
----------------------------------------------*/

class ApplicationNavigator {
  
  #defaultState = null;//初期設定時の状態を基本とする

  constructor (form = document.querySelector ('form'), cbFunc = null, cbObj = null, ...cbArgs) {
    this.form = form;
    this.cbFunc = cbFunc;
    this.cbObj = cbObj;
    this.cbArgs = cbArgs;
    this.elements = [...form.querySelectorAll ('input[id], input[name], select[id], select[name], textarea[id], textarea[name]')];
    this.#defaultState = this.constructor.getValues (this.elements);
    this.hash = location.hash;

    if (cbFunc)
      form.addEventListener ('change', this, false);
  }


  execute (e) {
    if ('function' === typeof this.cbFunc) {
      this.cbFunc.apply (this.cbObj || this, [this.status, ...this.cbArgs]);
      location.hash = this.hash; 
    }
    return this;
  }


  reset () {
    this.status = this.#defaultState;
    return this;
  }


  handleEvent (event) {
    let e = event.target;
    if (this.elements.includes (e))
      this.execute (e);
  }


  get status () {//全ての状態を返す
    return this.constructor.getValues (this.elements);
  }

  set status (obj = { }) {//statusには実態がない(その都度要素から収集)
    this.constructor.setValues (this.elements, Object.assign ({ }, obj));
    this.execute ();//コールバック関数を呼び出す
  }


  get hash () {
    let a = this.#defaultState;
    return '#' +
      Object.entries (this.status)
        .filter (([k,v])=> a[k] != v)
        .map (b=> b.join`=`)
        .join('&');
  }
  set hash (hash) {
     this.status = this.constructor.parseParms (hash);
  }


  //______________________


  static parseParms (str = '') {
    let
      reg = /(?:^#)?(\D\w*)(\[\])?=(.*?)(?:\&|\;|$)/g,
      pieces = new Map,
      piece;

    while (piece = reg.exec (str)) {
      let [, key, isAry, val] = piece;
      key = decodeURIComponent (key);
      val = decodeURIComponent (val);

      if (isAry)
        val = [val];

      if (pieces.has (key))
        pieces.set (key, [].concat (pieces.get (key), val));
      else
        pieces.set (key, val);
    }

    return Object.fromEntries (pieces);
  }


  //要素の値を取得してオブジェクトにして返す
  //name属性が複数な場合配列として記憶する
  static getValues ([...es]) {
    const
      excludeType = ['fieldset', 'reset', 'submit', 'image', 'file','button'],//除外する要素
      obj = new Map;

    for (let e of es) {
      let type = e.type, key, value;

      if (excludeType.includes (type))
        continue;
      if (['checkbox', 'radio'].includes (type) && !e.checked)
        continue;//checked属性がfalsse は除外する

      if (key = e.id || e.name) {//keyがあるものだけ有効
        //最初から配列とする
        if ('select-multiple' === type)
          value = [...e.options].reduce ((a, b)=> (b.selected && a.push (b.value), a), [ ]);
        else if ('checkbox' === type)
          value = [value];
        else
          value = e.value;

        if (obj.has (key))//すでに登録されたものは配列
          value = [].concat (obj.get (key), value);

        obj.set (key, value);
      }
    }
    return Object.fromEntries (obj);//オブジェクト化
  }


  //指定要素群に値を代入する
  static setValues ([...es], obj = { }) {
    const
      priority = ['radio', 'checkbox', 'select-one', 'select-multiple'],//優先順位
      prioritize = (a, b)=> priority.indexOf (a.type) - priority.indexOf (b.type);

    let hs = es.reduce ((a,e)=> (a[e.id||e.name] = (a[e.id||e.name] || []).concat(e), a), { });//名前毎にhashにする

    //名前毎の中で優先順に並び替え
    Object.values (hs).forEach (v=> v.sort (prioritize));
    //select要素のmultipleのすべてをリセットする
    es.filter (e=> e.type === 'select-multiple').flatMap (e=> [...e.options]).forEach (e=> e.selected = false);
    //es.filter (e=> e.hasAttribute ('defaulValue')).forEach (e=> e.value = e.defaultValue);
      
    for (let [name, vals] of Object.entries (obj)) {//※valsはコピーしたもの
      let es = hs[name] || [ ];
      vals = [].concat (vals);

      while (vals.length) {
        let val = vals.shift ();
        for (let e of es) {
          switch (e.type) {
            //想定のtype順で設定する
            case 'select-multiple':
              for (let op of e.options) {
                if (op.value == val) {
                  op.selected = true;
                  break;
                }
              }
              break;
            case 'select-one' :  e.value = val; break;
            case 'checkbox' : case 'radio' : e.checked = e.value === val; break;
            default : e.value = val; break;
          }
        }
      }
    }
  }
}