かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript Object <---> Array の操作 Object.keys(), Object.values(), Object.entries(), Object.fromEntries()

Object.keys()

オブジェクトのキー一覧が配列で取れる

const Idols = {
  'cute': ['Ichigo', 'Sakura', 'Akari', 'Madoka', 'Maria'],
  'cool': ['Aoi', 'Yurika', 'Shion', 'Sumire', 'Seira'],
  'sexy': ['Ran', 'Mitsuki', 'Hikari', 'Juri', 'Sora'],
  'pop': ['Otome', 'Kaede', 'Mikuru', 'Hinaki', 'KoKone']
};

Object.keys(Idols)
// => ["cute", "cool", "sexy", "pop"]

Object.values()

オブジェクトの値の一覧が配列で取れる

const Idols = {
  'cute': ['Ichigo', 'Sakura', 'Akari', 'Madoka', 'Maria'],
  'cool': ['Aoi', 'Yurika', 'Shion', 'Sumire', 'Seira'],
  'sexy': ['Ran', 'Mitsuki', 'Hikari', 'Juri', 'Sora'],
  'pop': ['Otome', 'Kaede', 'Mikuru', 'Hinaki', 'KoKone']
};

Object.values(Idols);
/*
[
  ["Ichigo", "Sakura", "Akari", "Madoka", "Maria"],
  ["Aoi", "Yurika", "Shion", "Sumire", "Seira"],
  ["Ran", "Mitsuki", "Hikari", "Juri", "Sora"],
  ["Otome", "Kaede", "Mikuru", "Hinaki", "KoKone"]
]
*/

Object.entries()

Object の {key: value}[key, value] な配列に変換する。
※ 変換された配列の順番は元のオブジェクトの key の並び順を担保しない
配列の形になるので map や sort で処理したい時に便利かも。

例えば API からこんな感じの JSON データが帰ってきたものを no 順に並べたいとか

// API から取得されたデータ
const Idols = {
  'hash1': { no: 1, name: 'Ichigo' },
  'hash7': { no: 7, name: 'Yurika' },
  'hash6': { no: 6, name: 'Hinaki' },
  'hash4': { no: 4, name: 'Akari' },
  'hash5': { no: 5, name: 'Sumire' },
  'hash9': { no: 9, name: 'Mikuru' },
  'hash8': { no: 8, name: 'Mituki' },
  'hash2': { no: 2, name: 'Aoi' },
  'hash3': { no: 3, name: 'Ran' },
};

const list = Object.entries(Idols);
/*
[
  ['hash1', { no: 1, name: 'Ichigo' }],
  ['hash7', { no: 7, name: 'Yurika' }],

]
*/

// hash を id に変換
const listFormat = (list) => {
  return list.map((item) => ({
    id: item[0],
    ...item[1]
  }));
};
// リスト内のオブジェクトのキーでソート
const sortByKey = (list) => (key) => {
  return list.sort((a, b) => a[key] - b[key]);
};
sortByKey(listFormat(list))('no');
/*
[
  {id: "hash1", no: 1, name: "Ichigo"},
  {id: "hash2", no: 2, name: "Aoi"},
  {id: "hash3", no: 3, name: "Ran"},
  {id: "hash4", no: 4, name: "Akari"},
  {id: "hash5", no: 5, name: "Sumire"},
  {id: "hash6", no: 6, name: "Hinaki"},
  {id: "hash7", no: 7, name: "Yurika"},
  {id: "hash8", no: 8, name: "Mituki"},
  {id: "hash9", no: 9, name: "Mikuru"}
]
*/

₍ ᐢ. ̫ .ᐢ ₎👌

Object.fromEntries()

これはオブジェクトの操作ではないけれど [[key1, value1], [key2, value2], ...] な配列を {key1: value1, key2: value2, ...} なオブジェクト形式に変換する。
Object.entries() で変換したものをもとに戻すイメージ。

// {id: name} なオブジェクトにしたい
const Idols = [
  { id: 1, name: 'Ichigo' },
  { id: 7, name: 'Yurika' },
  { id: 6, name: 'Hinaki' },
  { id: 4, name: 'Akari' },
  { id: 5, name: 'Sumire' },
  { id: 9, name: 'Mikuru' },
  { id: 8, name: 'Mituki' },
  { id: 2, name: 'Aoi' },
  { id: 3, name: 'Ran' },
];

const formatList = Idols.map(({id, name}) => [id, name]);
/*
[
  [1, 'Ichigo'],
  [7, 'Yurika'],
  ...
]
*/
Object.fromEntries(formatList);
/*
{
  1: "Ichigo"
  2: "Aoi"
  3: "Ran"
  4: "Akari"
  5: "Sumire"
  6: "Hinaki"
  7: "Yurika"
  8: "Mituki"
  9: "Mikuru"
}
*/

₍ ᐢ. ̫ .ᐢ ₎👌

配列の3番目以降の要素は切り捨てられる
Object.fromEntries([[1, 2, 3]]);
// => {1: 2}
同じキーがあると配列のインデックスが後ろにある要素が残る
Object.fromEntries([['cute', 'Ichigo'], ['cute', 'Akari']])
// => {cute: "Akari"}

hasOwnProperty はどうなっているか?

Object.keys()Object.hasOwnPropertytrue のものだけが列挙される
const obj = new Object({foo: 1});
const myObj = Object.create(obj);
myObj.bar = 2;
for (key in myObj) { console.log(key, myObj.hasOwnProperty(key)) }
// => bar true
// => foo false
Object.keys(myObj);
// => ["bar"]
Object.values()Object.hasOwnPropertytrue のものだけが列挙される
const obj = new Object({foo: 1});
const myObj = Object.create(obj);
myObj.bar = 2;
for (key in myObj) { console.log(key, myObj.hasOwnProperty(key)) }
// => bar true
// => foo false
Object.values(myObj);
// => [2]
Object.entries()Object.hasOwnPropertytrue のものだけが列挙される
const obj = new Object({foo: 1});
const myObj = Object.create(obj);
myObj.bar = 2;
for (key in myObj) { console.log(key, myObj.hasOwnProperty(key)) }
// => bar true
// => foo false
Object.entries(myObj);
// => [["bar", 2]]

 
おわり


[参考]

ハンズオンJavaScript

ハンズオンJavaScript

コードで永遠を描いちゃぉ

こんにちは、こんばんわ、おはようございます。
2020年の春から G's Academy に通っていました。インターネッツに生きる永遠の5歳児です。

TL;DR コードは描く

自分語り。(コレ必要か?)
僕自身は高校生の時 f(x) = x + 2 みたいな呪文と動く点 x が〇〇のときの解を求めよ のような問題に、なんで点 x は動くんですか?そもそも動く点 x って何???? と、数IAの段階で "なんもわからん。" と挫折ししたのでした。 その挫折から世界のことは yes / no の二択ではないのに受験やテストは○か✗の結果で判断されるのでこの戦場は不利だと悟りました。それなら戦い方を変えればええやんと ○・✗ で判断できない所を目指し美術系に進んだのでした。完全に人間性が捻くれていますね。

そして潜り込んだ美術系の学校で、絵を書いたり、宇宙について考えたり、ゴミを溶接したり、美味しい雑草を探したり、炬燵を走らせたり到底将来社会の役に立ちそうにもない阿呆の限りを尽くしていた訳ですが、あろうことか就職をしてしまい紆余曲折を経てプログラミングにも触れるようになってしまいました!芸術家になるという意味に於いては何たる敗北でありましょう!!

まぁ、そんな事はどうでもいいのですが、前述の通り数学的基礎学力皆無なので雰囲気でコードを書いていました。社員が僕1人だったからレビューされるとか良し悪しもよく分からずネットの検索結果と質問投稿サービスに投稿してボロクソに言われるを繰り返す試行錯誤の日々を送っていたのです。( F-site の皆様には大変お世話になりました。)
そんな当時は Flash が盛り上がっていた時代で「プログラム書けるならFlashでゲーム作れるやろ」と無茶振りされたのをきっかけに FlashActionScript を触るようになり、コードで絵が描ける!それも人に反応したり動く絵が描ける!!すごい!!! ということに気づき感動したのでした。
そこで初めて高校生の時に挫折した f(x) = x + 2function(x) { return x + 2 } であり、動く点 x は setInterval で高速に関数を呼び出しているから返される値が変わって x と言う名前で描画される点が動いているように見えるのか〜と理解したのでした。なるほどね。完全理解。(高校の時に先にプログラミングに触れていたら僕の人生は大きく変わっていたことでしょう)

プログラミングで絵が描けると気づいてからは複雑な絵は ActionScript を 1フレームで描くのが管理しやすいとか Processing でパーティクル作ったりとプログラミングが楽しくなっていました。
結局はプログラミングも自分の作りたいものを見せたい方法で作成するための手段にしか過ぎない。アウトプットをどうしたしたいか・は作りたい絵を作るための手段をどうするかで、筆を取るのか、カメラを持つのか、彫刻刀や溶接マシンを持つのか、キーボードの前に座るのかの違いでしがない。と、考えるようになったのでした。
なので、今もコード書くではなく、描くだと思っているのです。どうでもいい話を終わります

では、プログラミングで描くこと選択する理由はなにか。アクションを起こしたりというのもありますが、シンプルなところで言えば無限なものを描けることな気がします。
例えば、無限に続く海岸線や水平線の絵は物理的なキャンバスに描くことは難しいです。プログラミングなら f(x) = x + 2 のように無限な線を引くことができるのです。嗚呼、数学をやっておけばもっとすごい絵が描けたでしょうに…
もっとすごい絵を自分で描けるようになりたい。その思いが ガッコ の門を叩いた理由でもありました。

コードで永遠を描く 📛

せっかくなので、コードで永遠を書いていきたいと思います。
インターネッツに触れている方は耳にしたことがあるかもしれないこのこの言葉
ゆゆ式は終わらない
そう、ゆゆ式は永遠に続いていて毎週の放送を楽しまれている方が居る。そんな世界線があるのです!

さて、この blog を見たことがある方はお察しかと思いますが、僕の中では
アイカツ!は人生
でありますから、アイカツ!も永遠に続いている世界線があるはずです。

あー今週はアイカツ!何話だったっけ?分からないと困りますね。
プログラミングを使ってこの問題を解決していきましょう。僕の幸せのために

無限に続く永遠データ

まず、永遠とは何かを考えます。(ろまんちっくですね。)
ほい。無限を表せる永遠データは、現在値と次の値が取得できる関数で構成されていれば永遠を表現できそうです。 (ロマン無)

const stream = (no) => [no, () => stream(no + 1)];

const story = stream(1);
// 1話
console.log(story[0])
// 2話
console.log(story[1]()[0])
// 3話
console.log(story[1]()[1]()[0])
// n 話 ( n - 初期値 1 回 [1]() を繰り返す )
console.log(story[1]()...[0])

アイカツ!は毎週放送されるので、178話、2016年3月31日 を初期値として、ここから次の話数と放送日が取得できるデータ構造を作れば、永遠にアイカツ!の話数と放送日が判るデータが表現できそうです!

interface storyData {
  no: number; // 話数
  date: string; // "YYYY-MM-DD" 放送日
}

const getStoryData = (no: number): storyData => {
  return {
    no: no,
    // 話数から放送日を返す
    date: getOnAirDateByStoryNo(no), 
  }
};

const storyStream = (no: number) => {
  return [getStoryData(no), () => storyStream(no + 1)];
};

イメージとしてはこんな感じ。 storyStream[0]{no: 178, date: '2016年3月31日'} が入り storyStream[1]()[0] に次の放送情報、storyStream[1]()[1]()[0] にその次の…と入っていけばOK。

初期値の設定と、話数から放送日を返す関数をつくる

放送日は毎週放送されるので、放送日は前の放送日 +7 日で表現できそうです。
初期値が 178 話 2016年3月31日 なので、179 話は 2016年3月31日 +7 * 179 - 178 という感じ。
日付の操作には dayjs を使ってみます

import * as dayjs from 'dayjs'

const INI_STORY_DATA = {
  no: 178,
  date: '2016-03-31',
};

// 話数の差分を返す
const generateStoryDiff = (initialNo) => (no) => {
  return no - initialNo;
};

// 初期日から step 週間進んだ日付を返す
const generateOnAirDateByStep = (initialDate) => {
  const startDate = dayjs(initialDate);
  return (step) => {
    const week = 7;
    return startDate.add(step * week, 'day');
  }
};

// 初期話数・放送日を渡して、話数から放送日を取得できる関数を返す
const generateOnAirDate = (initialNo, initialDate) => {
  const getOnAirDateByStep = generateOnAirDateByStep(initialDate);
  const getStoryStepDiff = generateStoryDiff(initialNo);
  
  return (no) => {
    const storyStep = getStoryStepDiff(no);
    return getOnAirDateByStep(storyStep);
  }
};

// 話数と放送日をフォーマットしたデータを返す
const generateStoryData = (getOnAirDate) => (no) => {
  return {
    no,
    date: getOnAirDate(no).format('YYYY-MM-DD')
  };
};

// 永遠に続くデータ構造を返す
const generateStoryStream = (getStoryData) => {
  const storyStream = (no) => {
    return [getStoryData(no), () => storyStream(no + 1)];
  };

  return storyStream;
};

// 初期値を設定して永遠に続くアイカツ!話数と放送日のデータを返す
const getStoryStream = ({ no, date }) => {
  const getStoryData = generateStoryData(generateOnAirDate(no, date));
  const stream = generateStoryStream(getStoryData);
  return stream(no);
};

// 178話始まりのデータ
const story178 = getStoryStream(INI_STORY_DATA);
story178[0]; // => { no: 178, date: '2016-03-31' }
// 179話
story178[1]()[0]; // => { no: 179, date: '2016-04-07' }

アイカツ!は終わらないコンテンツになりました!!!!
₍ ᐢ. ̫ .ᐢ ₎👌 ヤッタネ

話数から放送日を取得する

永遠にアイカツ!の放送日がわかるようになったので、この永遠データを使って話数から放送日を取得できると便利ですね。(さっき差分を取れる関数を作ったので、それを使えば楽ですが敢えて永遠データから取得する面倒くさいことをします!)

// 178話始まりのデータ
const story178 = getStoryStream(INI_STORY_DATA);

const getOnAirDate = (stream, no) => {
  const step = stream[0].no - no;
  console.log(step, stream);
  if (step === 0) {
    return stream[0].date;
  }
  return getOnAirDate(stream[1](), no);
};

// 180話の放送日
getOnAirDate(story178, 180); // => 2016-04-14
// 300話の放送日
getOnAirDate(story178, 300); // => 2018-08-02

永遠データをループさせて都度次の放送日を求めているので、効率的な方法ではないのですが永遠データからn話の放送日を取得することができました。
この永遠に再帰でループさせられるという点が永遠データの永遠たる所以なのかなと思います。(効率的ではないので本当に無駄…)

今日の日付から今週何話放送なのかを求める

また無駄に永遠データを使って、今週は何話が放送されるのか取得できるようにしてみます。
今日 <= 放送日 になった時の話数が今週放送される話数です。

const getThisWeekStoryNo = (date) => {
  const currentDate = dayjs(date);

  const getStoryNo = (stream) => {
    const diff = currentDate.diff(stream[0].date);
    if (diff <= 0) {
      return stream[0]
    }
    return getStoryNo(stream[1]());
  };

  return getStoryNo;
};

// 2016-04-18 の次の放送は?
getThisWeekStoryNo('2016-04-18')(story178);
// => { no: 181, date: '2016-04-21' }

// 2020-12-18 の次の放送は?
getThisWeekStoryNo('2020-12-18')(story178)
// => { no: 425, date: '2020-12-24' }

次は 2020年 12月24日に 425話が放送ですね!きっとクリスマス会に違いありません!! 今年は何でツリーを切り倒すのか気になりますね。たのしみ〜
はい。永遠データをループさせて今週の放送話数も取得できるようになしました!!

まとめ

と、こんな具合に永遠データを使ってアイカツ!を終わらないコンテンツにすることができました!
これを bot 化するとかして使えばとっても便利そうです!!
₍ ᐢ. ̫ .ᐢ ₎👌 ヤッタネ

起点となる 178 話のデータから毎回ループでデータ作成をしているので効率はめちゃめちゃ悪いですが、アイカツ!は終わらないコンテンツという永遠データを描くという浪漫が目的だったのでヨシ! (普通に考えれば初期値からの差分でほしいデータだけ作成すればよいのです。本当に無駄な遊びをしました)

全然関係のない宣伝

このサイトのデザインとコーディングと絵描いてるので見てくれ〜!
宇宙の事知るのめちゃ楽しい!!

そして最後に、僕が言いたいたった一つのこと… それは… アイカツ!みて…

おわり。


Python3 クロージャやってみた

次のような、年が変わった時だけ年数が入っている日付のデータを datetime でフォーマットしたかった。

data = [
  "2012年10月8日",
  "10月15日",
  # … 
  "12月24日",
  "2013年1月7日",
  "1月14日",
  # …
  "12月26日",
  "2014年1月9日",
  "1月16日",
  # …  
]

年数を global 変数に置いておいて \d+年 が出現する度に変数を更新して使えば済むのだけど、global の変数を更新していくのはイケてない感があります。年数内部で保持できるクロージャにできるとスッキリしそうです。

クロージャーを使ってフォーマットする

from re import search

def froamt(_year):
  year = _year

  def _format(date):
    nonlocal year

    match = search(r'\d+年', date)
    if match:
      year = match.group()
      ymd = date
    else:
      ymd = f'{year}{date}'

    return ymd

  return _format

START_YEAR = '2012年'
f = froamt(START_YEAR)
format_data = [f(v) for v in data]

これで全て YYYY年M月D日 という形式に変換することができました。
Python のスコープはまだよく解ってないのですが、クロージャーの内側の関数の中で外の関数で定義された変数を更新するには nonlocal キーワードでアクセスできるように宣言してあげる必要がありました。

datetime で変換する

データが全て YYYY年M月D日 と言う形にできたので、これを datetime.date でオブジェクトにして strftime でフォーマットしてあげれば良さそうです。
フォーマットする関数を追加します。

from re import search, split
from datetime import date as dt

def format_date(date_str):
  date = dt(*[int(n) for n in split('[年月日]', date_str) if n])
  return date.strftime('%Y-%m-%d (%a)')

def froamt(_year):
  year = _year

  def _format(date):
    nonlocal year

    match = search(r'\d+年', date)
    if match:
      year = match.group()
      ymd = date
    else:
      ymd = f'{year}{date}'
    
    # datetime でフォーマットしたものを返す
    return format_date(ymd)

  return _format

START_YEAR = '2012年'
f = froamt(START_YEAR)
format_data = [f(v) for v in data]

フォーマット関数内のフォーマット方法を変えれば好きな形にフォーマットできます。
これで YYYY-MM-DD (What day of the week) の形のデータに整形することができました!

date オブジェクト化するのにYYYY年M月D日 な文字列を配列にして datetime.date(year, month, date) でオブジェクト化しているのですが、もっとスマートな変換方法がありそうな気がしています。

所感

Python でも 関数を返す関数を作成できることが判ったので良かったです。(個人的に PythonJavaScript 似てるな〜って感じてます。)
クロージャーの内部の関数から外部の変数にアクセスするのに nonlocal 宣言が必要で、このあたりのスコープまわりは理解が足りてないので引き続き調べて理解しておきたいです。


[参考]

ゼロから作るDeep Learning ❷ ―自然言語処理編

ゼロから作るDeep Learning ❷ ―自然言語処理編

  • 作者:斎藤 康毅
  • 発売日: 2018/07/21
  • メディア: 単行本(ソフトカバー)

アイカツ!1stシーズン Blu-ray BOX1

アイカツ!1stシーズン Blu-ray BOX1

  • 発売日: 2014/11/05
  • メディア: Blu-ray

日付でお察しの方が居るかもですがアイカツ! 放送日のリストでした