連載 作りながら学ぶVue.js入門
記事で紹介したサンプルコードの完全版と、その実行方法を記したMarkdown文書をまとめたZIPファイルは、ここからダウンロードできます。
情報は随時更新致します。
test
記事で紹介したサンプルコードの完全版と、その実行方法を記したMarkdown文書をまとめたZIPファイルは、ここからダウンロードできます。
情報は随時更新致します。
拝啓
平素より格別のご愛読を賜り、心より御礼申し上げます。
このたび、2026年2月号(Vol.100)をもちまして「シェルスクリプトマガジン」は休刊させていただくこととなりました。創刊以来、長きにわたり多くの読者の皆様に支えられ、誌面を通じて情報をお届けできましたことは、編集部一同にとってかけがえのない喜びであり、誇りでございます。
時代の変化や読者ニーズの多様化に応えるべく、私たちも挑戦を続けてまいりましたが、今後は新たな形での情報発信に力を注ぐため、休刊という決断に至りました。
これまでご愛顧いただいた読者の皆様、執筆者の方々、関係者の皆様に心より感謝申し上げます。誌面は一区切りとなりますが、培った経験を活かし、皆様に役立つ情報をお届けできるよう新たな挑戦を続けてまいります。
末筆ながら、皆様のご健勝とご多幸をお祈り申し上げます。
今後の計画ですが、新しい読者層に向けたIT情報サイトを立ち上げる予定です。また、公式サイトや公式Xにおいても、デジタル版として情報発信を続けていくことも計画中です。
敬具
シェルスクリプトマガジン編集

004 レポート 制作ソフト「Affinity」が無償化
005 レポート さくらのAI Engine一般提供開始
006 製品レビュー ポータブルSSD「SSD-SDHU3シリーズ」
007 NEWS FLASH
008 特集1 Playwright MCPによるWebシステム自動E2Eテスト/藤原由来 コード掲載
018 特別企画 Boxのワークフロー自動化とAIで業務効率化/藤井健志、葵健晴、武田新之助
026 ARM/RISC-V搭載コンピュータボード/米田聡
031 インタビュー パロアルトネットワークス Hiroshi Alley氏
032 Generative AIコンパス/為藤アキラ コード掲載
036 Markdownを活用する/藤原由来 コード掲載
046 Pythonあれこれ/飯尾淳 コード掲載
052 作りながら学ぶVue.js入門/大津真 コード掲載
060 知っておきたいシェル関連技術/麻生二郎 コード掲載
066 Techパズル
067 コラム「社内のERPプロジェクト」/シェル魔人
著者:藤原 由来
Webシステムに対するE2E(End-to-End)テストの実施は、手間と労力のかかる作業です。しかし、生成AIを活用した「Playwright MCP」を使えば、誰でも簡単にE2Eテストの作成・自動実行が可能です。本特集では、Playwright MCPの基本的な使い方を紹介し、実際に日本語による指示だけでE2Eテストを作成・自動実行する方法を解説します。
シェルスクリプトマガジン Vol.99は以下のリンク先でご購入できます。![]()
![]()
図30 テストシナリオとなるプロンプト
@playwright-mcp Webブラウザを起動して、以下のWebアプリに対して、E2Eテストを実行してください。
## URL
http://localhost:3000/
## テストケース
- ログイン機能
- ユーザー名 test / パスワード mypassword でログインできること
- ユーザー名 test / パスワード bar の場合はエラーメッセージを出力すること
- ユーザー名 foo / パスワード mypassword の場合はエラーメッセージを出力すること
- ログアウト機能
- ユーザー名 test / パスワード mypassword でログインし、ログアウトするとログイン画面に遷移すること
- TODO機能
- タスクを作成できること
- 作成済みのタスクを編集できること
- タスクにチェックを入れると、完了済みの状態になること
- 完了済みタスクを未完了に戻せること
- タスクを削除できること
※「//」を「//」に置き換えてください。
筆者:為藤 アキラ
AI技術の進化は、社会や産業のあり方を大きく変えつつあります。本連載では、国内外の最新動向や実践的な技術トピックを、日本の現場視点も交えながら分かりやすく解説します。執筆は、産学連携で生成AI活用を推進するGenerative AI Japan(略称:GenAI)のメンバーが担当します。第1回では、人間がAIの処理に積極的に関与する仕組みである「HITL(Human In The Loop)」のパターンについて解説します。
シェルスクリプトマガジン Vol.99は以下のリンク先でご購入できます。![]()
![]()
図2 標準化したプロンプトのひな型の例
【入力】
目的/想定読者/素材/制約(文字数・禁止表現)/評価観点
【AI出力構成】
1. 本文案(Markdown形式・見出し3階層まで)
2. 自己検証結果
- 不確実な主張に★マーク
- 各主張の根拠URL(最大3件)
- リスク箇所の指摘
3. 人間レビュー推奨箇所(優先度順3件)
図4 判定結果に関する情報の、人向けの要約を生成するプロンプトの例
① 判定:自動可 / 要承認 / 却下
② 理由(3点)
③ 不足情報(Required)
④ 実行提案(短文)
⑤ ロールバック(失敗時の戻し方)
⑥ 監査メモ:承認者/日時/版数
図5 判定結果に関する、システム連携や監査用の情報を生成するプロンプトの例
【役割】承認ゲート:入力を要約し、実行可否と根拠を機械可読で返す
【返却JSON】
{
"auto_action": "needs_approval",
"confidence": 0.85,
"risk_score": 35,
"reasons": ["金額が社内閾値を超過", "在庫残が3日分で変動大", "取引先情報が更新待ち"],
"required_context": ["最新在庫CSV", "購買稟議URL"],
"proposed_action": "在庫再評価後に数量50%で発注",
"human_checklist": ["金額閾値を満たすか", "在庫の確度", "契約条件の例外有無", "広報リスク", "ロールバック可否"],
"logs": "s3://audit/workflows/po-2025-10/ 24ヶ月保持",
"rollback": "API: /orders/cancel を実行し在庫を戻す。再開条件:在庫確度80%以上"
}
【判定基準】
- confidence ? 0.90 かつ risk_score ? 20 → approve
- 0.70 ? confidence < 0.90 または 20 < risk_score ? 60 → needs_approval
- それ以外 → reject
筆者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。前回に引き続き、文書変換ツール「Pandoc」の文書作成方法を紹介します。今回はPandocのフィルタ機能により柔軟な文書変換を実施する方法を解説します。
シェルスクリプトマガジン Vol.99は以下のリンク先でご購入できます。![]()
![]()
図4 入力とするMarkdownテキストファイル(sample-input.md)
# カレーの作り方
おいしいカレーを作りましょう。
## 材料
- 牛肉
- 玉ねぎ
- にんじん
- カレールー
- 水
図5 フィルタ適用後に出力されるMarkdownテキストファイル(sample-output.md)
# カレーの作り方
おいしいカレーを作りましょう。
## ★材料
- 牛肉
- 玉ねぎ
- にんじん
- カレールー
- 水
図7 Pythonで記述したJSONフィルタ
#!/usr/bin/env -S uv run --script
import panflute as pf
def add_star_to_header2(elem, doc):
"""
レベル2の見出しに星印「★」を追加するフィルタ関数
"""
# 要素の種類がHeaderで、レベルが2の場合
if isinstance(elem, pf.Header) and elem.level == 2:
# 星印を表す要素を生成
star = pf.Str("★")
# 見出しの内容の先頭に星印を追加
elem.content.insert(0, star)
return elem
def main(doc=None):
"""
メイン関数: フィルタを実行
"""
return pf.run_filter(add_star_to_header2, doc=doc)
# スクリプトが直接実行された場合にmain()を呼び出す
if __name__ == "__main__":
main()
図9 Luaフィルタの内容(add_star.lua)
function Pandoc(doc)
-- 各ブロックについて処理
for i, elem in ipairs(doc.blocks) do
-- 要素の書類がHeaderで、レベルが2の場合
if elem.t == "Header" and elem.level == 2 then
-- 内容の先頭に星印「★」を追加
table.insert(elem.content, 1, pandoc.Str("★"))
end
end
return doc
end
図10 別の記述を用いたLuaフィルタの内容(add_star2.lua)
function Header(elem)
-- 見出しのレベルが2の場合
if elem.level == 2 then
-- 内容の先頭に星印「★」を追加
table.insert(elem.content, 1, pandoc.Str("★"))
end
return elem
end
図A-2 図表の定義と、図表の参照を組み合わせた例
[@fig:sample]は、サンプル図を示しています。
[@tbl:sample]は、サンプル表を示しています。
{#fig:sample}
| ヘッダー1 | ヘッダー2 |
|-----------|-----------|
| データ1 | データ2 |
: サンプル表 {#tbl:sample}
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第29回では、物体の動きや力の作用など、現実世界の物理法則を数値的に計算してシミュレーションする「物理演算」にチャレンジします。
シェルスクリプトマガジン Vol.99は以下のリンク先でご購入できます。![]()
![]()
図6 playground.pyファイルの最後の部分にあるコード
def main():
demo = PhysicsDemo()
demo.run()
if __name__ == "__main__":
doprof = 0
if not doprof:
main()
else:
import cProfile
import pstats
prof = cProfile.run("main()", "profile.prof")
stats = pstats.Stats("profile.prof")
stats.strip_dirs()
stats.sort_stats("cumulative", "time", "calls")
stats.print_stats(30)
図7 PhysicsDemoクラスのrun()メソッドのコード
def run(self):
while self.running:
self.loop()
図8 PhysicsDemoクラスのloop()メソッドのコード
def loop(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
(略)
self.space.gravity = g.rotated_degrees(45)
mpos = pygame.mouse.get_pos()
if pygame.key.get_mods() & \
pygame.KMOD_SHIFT and pygame.mouse.get_pressed()[2]:
p = self.flipyv(Vec2d(*mpos))
self.poly_points.append(p)
hit = self.space.point_query_nearest(
self.flipyv(Vec2d(*mpos)), 0, pm.ShapeFilter()
)
if hit != None:
self.shape_to_remove = hit.shape
else:
self.shape_to_remove = None
### Update physics
if self.run_physics:
x = 1
dt = 1.0 / 60.0 / x
for x in range(x):
self.space.step(dt)
for ball in self.balls:
# ball.body.reset_forces()
pass
for poly in self.polys:
# poly.body.reset_forces()
pass
### Draw stuff
self.draw()
### Check for objects outside of the screen,
# we can remove those Balls
xs = []
for ball in self.balls:
if (
ball.body.position.x < -1000
or ball.body.position.x > 1000
or ball.body.position.y < -1000
or ball.body.position.y > 1000
):
xs.append(ball)
for ball in xs:
self.space.remove(ball, ball.body)
self.balls.remove(ball)
# Polys
(略)
### Tick clock and update fps in title
self.clock.tick(50)
pygame.display.set_caption("fps: " + str(self.clock.get_fps()))
図9 create_ball()メソッドのコード
def create_ball(self, point, mass=1.0, radius=15.0):
moment = pm.moment_for_circle(mass, 0.0, radius)
ball_body = pm.Body(mass, moment)
ball_body.position = Vec2d(*point)
ball_shape = pm.Circle(ball_body, radius)
ball_shape.friction = 1.5
ball_shape.collision_type = COLLTYPE_DEFAULT
self.space.add(ball_body, ball_shape)
return ball_shape
図10 PhysicsDemoクラスのコンストラクタのコード
def __init__(self):
self.running = True
### Init pygame and create screen
pygame.init()
self.w, self.h = 600, 600
self.screen = pygame.display.set_mode((self.w, self.h))
self.clock = pygame.time.Clock()
### Init pymunk and create space
self.space = pm.Space()
self.space.gravity = (0.0, -900.0)
### Walls
self.walls = []
self.create_wall_segments([(100, 50), (500, 50)])
## Balls
# balls = [createBall(space, (100,300))]
self.balls = []
### Polys
self.polys = []
h = 10
for y in range(1, h):
# for x in range(1, y):
x = 0
s = 10
p = Vec2d(300, 40) + Vec2d(0, y * s * 2)
self.polys.append(self.create_box(p, size=s, mass=1))
self.run_physics = True
### Wall under construction
self.wall_points = []
### Poly under construction
self.poly_points = []
self.shape_to_remove = None
self.mouse_contact = None
図11 playground.pyファイルの変更差分
133a134,135
> pygame.draw.circle(self.screen,
> pygame.Color("skyblue"), p, int(r), 0)
140c142
< pygame.draw.lines(self.screen, pygame.Color("lightgray"), False, [pv1, pv2])
---
> pygame.draw.lines(self.screen, pygame.Color("gray"), False, [pv1, pv2], 3)
148c150,151
< color = pygame.Color("green")
---
> color = pygame.Color("forestgreen")
> color2 = pygame.Color("lightgreen")
151c154,156
< pygame.draw.lines(self.screen, color, False, ps)
---
> color2 = pygame.Color("orange")
> pygame.draw.polygon(self.screen, color2, ps, 0)
> pygame.draw.polygon(self.screen, color, ps, 2)
※「[」を「[」に、「< 」を「<」に置き換えてください。
著者:大津 真
JavaScriptフレームワークの中でも、学びやすさと柔軟さで人気を集めている「Vue.js」。本連載では、Vue.jsの基礎から実践的な使い方までを、分かりやすく丁寧に解説していきます。一緒にフロントエンド開発の楽しさを体験してみましょう。第4回では、v-onディレクティブの活用と、VueアプリケーションからDOM(Document Object Model)を操作する方法について説明します。
シェルスクリプトマガジン Vol.99は以下のリンク先でご購入できます。![]()
![]()
図1 「event-props/src/App.vue」ファイルのコード
<script setup>
import { ref } from 'vue'
function eventTest(e) {
console.log(e)
}
</script>
<template>
<button @click="eventTest">Click me</button>
</template>
図1 「event-props/src/App.vue」ファイルのコード
<script setup>
import { ref } from 'vue'
const event = ref(null)
function eventTest(e) {
event.value = e
}
</script>
<template>
<div id="back" @click="eventTest" @dblclick="eventTest">
<h1>Event Test</h1>
<div id="box" @mouseover="eventTest" @mouseout="eventTest">
<p>My Box</p>
</div>
<p>Event type: {{ event?.type }}</p>
<p>x:{{ event?.clientX }} y: {{ event?.clientY }}</p>
</div>
</template>
<style scoped>
#back {
width: 100vw;
height: 100vh;
}
#box {
width: 100px;
height: 100px;
background-color: #f00;
margin: 0 auto;
}
</style>
図6 「src/style.css」ファイルの変更例
(略)
#app {
max-width: 1280px;
margin: 0 auto;
/* padding: 2rem; */ ←コメントにする
text-align: center;
}
(略)
図7 useTemplateRef関数でDOM要素を取得するコードの例
<template>
(略)
<img ref="fish" id="fish" src="./assets/fish1.jpg" />
(略)
</template>
<script setup>
(略)
const fish = useTemplateRef("fish");
(略)
</script>
図8 「event-test2/src/App.vue」ファイルのコード
<!-- App.vue -->
<template>
<div id="back" @click="moveImg">
<img ref="fish" id="fish" src="./assets/fish1.jpg" />
</div>
</template>
<script setup>
import { useTemplateRef, onMounted } from 'vue'
// テンプレート参照を取得
const fish_ref = useTemplateRef("fish")
function moveImg(e) {
// transitionの設定
const duration = "1s" // 移動時間
fish_ref.value.style.transition = \
left ${duration} ease, top ${duration} ease, \
transform 0.3s ease
// クリック位置に移動
const x = e.clientX - fish_ref.value.width / 2
const y = e.clientY - fish_ref.value.height / 2
fish_ref.value.style.left = ${x}px
fish_ref.value.style.top = ${y}px
// クリック方向に応じて向きを変える
if (e.clientX < fish_ref.value.offsetLeft) {
fish_ref.value.style.transform = 'scaleX(-1)' // 左向き
} else {
fish_ref.value.style.transform = 'scaleX(1)' // 右向き
}
}
</script>
<style scoped>
#back {
position: fixed;
inset: 0;
}
#fish {
position: absolute;
width: 100px;
left: 100px; /* 初期位置 X */
top: 100px; /* 初期位置 Y */
}
</style>
図10 HTMLテンプレート部分の変更箇所
(略)
<div id="back" @click="moveImg(false, $event)"
@contextmenu="moveImg(true, $event)">
<img ref="fish" id="fish" src="./assets/fish1.jpg" />
</div>
(略)
図11 JavaScript部分の変更箇所(抜粋)
(略)
function moveImg(isFast, e) {
e.preventDefault(); // 右クリックメニューを防ぐ
(略)
const duration = isFast ? "0.3s" : "0.8s";
el.style.transition = left ${duration} ease, \
top ${duration} ease, \
transform 0.3s ease;
(略)
図12 不適切なコードの例
(略)
<script setup>
import { useTemplateRef, onMounted } from 'vue';
const fish = useTemplateRef("fish");
const el = fish.value
el.style.left = '100px' // 左端の座標を設定
el.style.top = '100px' // 上端の座標を設定
(略)
図13 書き換えた部分のコード
onMounted(() => {
const el = fish.value;
if (el) {
// 親要素の位置とサイズを取得
const parentRect = el.offsetParent.getBoundingClientRect();
// 初期位置を画面中央に設定
const x = parentRect.width / 2 - el.width / 2;
const y = parentRect.height / 2 - el.height / 2;
el.style.left = ${x}px;
el.style.top = ${y}px;
}
});
※「< 」を「<」に置き換えてください。
著者:麻生 二郎
シェルは、LinuxやUNIX系OSを操作するためのプログラムです。本連載では、クラウドやリモートなど、近年のコンピュータ環境でのシェル活用のヒントになる情報を扱っていきます。第4回は、crondによる定期ジョブ実行を管理するオープンソースソフトウエア「Cronicle」を紹介します。
シェルスクリプトマガジン Vol.99は以下のリンク先でご購入できます。![]()
![]()
図4 /opt/cronicle/conf/config.jsonファイルの変更箇所
■STARTTLSの場合
"email_from": "Cronicle Admin <shell@example.com>",
"smtp_hostname": "smtp.example.com",
"mail_options":{
"port":587,
"secure": false,
"auth":{
"user":"shell@example.com",
"pass":"password"
}
},
■SMTPSの場合
"email_from": "Cronicle Admin <shell@example.com>",
"smtp_hostname": "smtp.example.com",
"mail_options":{
"port":465,
"secure": true,
"auth":{
"user":"shell@example.com",
"pass":"password"
}
},
記事で紹介したサンプルコードの完全版と、その実行方法を記したMarkdown文書をまとめたZIPファイルは、ここからダウンロードできます。
情報は随時更新致します。

004 レポート GPT-5とgpt-ossが登場
005 レポート Python 3.14が間もなくリリース コード掲載
006 製品レビュー スキャナ「ScanSnap iX2500」
007 NEWS FLASH
008 特集1 自宅で本格ネットワーク環境構築/麻生二郎 コード掲載
016 特集2 耐量子コンピュータ暗号とPKI/林正人
022 特別企画 Red Hat Enerprise Linux 10の新機能と無料利用/麻生二郎
031 インタビュー SUSEソフトウエアソリューションズジャパン 渡辺元氏
032 作りながら学ぶVue.js入門/大津真 コード掲載
038 Raspberry Pi Pico W/WHで始める電子工作/米田聡
042 香川大学SLPからお届け!/井上竜輔 コード掲載
048 Pythonあれこれ/飯尾淳 コード掲載
058 Markdownを活用する/藤原由来 コード掲載
066 知っておきたいシェル関連技術/麻生二郎
074 Techパズル/gori.sh
075 コラム「DX人材育成支援事業の取り組み」/シェル魔人
著者:末安 泰三
Pythonソフトウエア財団が2025年10月1日にリリース予定のPython 3.14では、性能向上の足かせだった大域ロック(GIL)を無効化できる。それにより、マルチスレッド処理を大幅に高速化可能だ。マルチスレッド処理のコードを使って、実際にどの程度高速化するのかを検証する。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図1 検証に使用したPythonコード
import time, threading
N = 100000000
data = list(range(N))
def work(a, b): return sum(x*x for x in data[a:b])
def bench(threads=4):
# シングルスレッド
t=time.perf_counter()
print("single:", work(0,N),
"in", time.perf_counter()-t)
# マルチスレッド
step=N//threads; res=[0]*threads; th=[]
t=time.perf_counter()
for i in range(threads):
a=i*step; b=N if i==threads-1 else a+step
th.append(
threading.Thread(target=lambda j=i: \
res.__setitem__(j, work(a,b)))
)
th[-1].start()
[x.join() for x in th]
print(f"{threads} threads:", sum(res),
"in", time.perf_counter()-t)
bench()
著者:麻生 二郎
スマートフォンやパソコン、スマート家電をインターネットに接続するだけなら市販のルーターを購入するだけでよいでしょう。サーバーソフト、ネットワーク関連のソフトや機器を使ってみたい,試してみたい場合、普段使うネットワークとは分離した環境を用意しておくと便利です。本特集では、そのようなネットワーク環境を構築します。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図16 /etc/hostapd/hostapd.confファイルの設定例
interface=wlan0
driver=nl80211
ssid=shmagtoku1
hw_mode=g
channel=6
auth_algs=1
wpa=2
wpa_passphrase=shmag202510t1
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP
country_code=JP
ieee80211d=1
図17 /etc/dnsmasq.d/dhcp.confファイルの設定例
interface=wlan0
dhcp-range=192.168.2.2,192.168.2.50,12h
domain-needed
bogus-priv
図18 /etc/network/interfacesファイルの設定例
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
auto wlan0
iface wlan0 inet static
address 192.168.2.1
netmask 255.255.255.0
図19 /etc/nftables.d/router.nftファイルの設定例
table inet filter {
chain input {
type filter hook input priority 0;
policy accept;
}
chain forward {
type filter hook forward priority 0;
policy accept;
}
chain output {
type filter hook output priority 0;
policy accept;
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority 100;
oifname "eth0" masquerade
}
}
図20 /etc/dnsmasq.d/host.confの設定例
dhcp-option=option:dns-server,192.168.2.1
domain=home.localnet
local=/home.localnet/
dhcp-host=00:11:22:33:44:55,web,192.168.2.100
dhcp-host=00:22:33:44:55:66,file,192.168.2.120
著者:大津 真
JavaScriptフレームワークの中でも、学びやすさと柔軟さで人気を集めている「Vue.js」。本連載では、Vue.jsの基礎から実践的な使い方までを、分かりやすく丁寧に解説していきます。一緒にフロントエンド開発の楽しさを体験してみましょう。第3回では、HTMLやCSS、JavaScriptのコードをまとめた「SFCファイル」を使用したVueアプリケーションの作成について説明します。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図6 「index.html」ファイルのbody要素
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
図7 「src/main.js」ファイルの内容
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
図8 デフォルトの「src/App.vue」ファイルの内容(抜粋)
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<div>
<a href="https://vite.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
(略)
</style>
図9 CDN版のVueアプリケーション「counter1.html」のコード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Vueサンプル</title>
<style>
h1 {
font-size: 3em;
background-color: #f0f0f0;
color: green;
}
</style></head>
<body>
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
const counter = ref(0)
function increment() { counter.value++ }
return { counter, increment }
}
}).mount('#app')
</script>
<div id="app">
<h1>カウント: {{ counter }}</h1>
<button v-on:click="increment">+</button>
</div>
</body>
</html>
図11 図9のVueアプリケーションの処理をApp.vueファイルに記述した例
<script setup>
import { ref } from 'vue'
const counter = ref(0)
function increment() { counter.value++ }
</script>
<template>
<h1>カウント: {{ counter }}</h1>
<button v-on:click="increment">+</button>
</template>
<style scoped>
h1 {
font-size: 3em;
background-color: #f0f0f0;
color: green;
}
</style>
図A 「vite.config.js」ファイルの変更例
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
base: '/myapp/'
})
著者:井上竜輔
最終回では、筆者が作成したプログラミング学習アプリの概要を紹介します。このアプリは、AIなどの外部APIを活用して、オンラインで動的に問題を生成、実行、評価できるものです。安全性を考慮して、ユーザーが提出したプログラムは「Judge0 CE」というオンラインコード実行システムで、実行、評価します。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図7 「components/CodeEditor.tsx」ファイルのコード(抜粋)
import React from 'react';
import Editor from '@monaco-editor/react';
import { Select, MenuItem, FormControl, InputLabel, Box } from '@mui/material';
(略)
const CodeEditor: React.FC<Props> = ({ language, setLanguage, code, setCode }) => {
return (
<Box sx={{ mt: 2 }}>
<FormControl sx={{ mb: 1, minWidth: 120 }}>
<InputLabel>Language</InputLabel>
<Select
value={language}
onChange={(e) => setLanguage(e.target.value)}
label="Language"
>
<MenuItem value="python">Python</MenuItem>
<MenuItem value="cpp">C++</MenuItem>
<MenuItem value="javascript">JavaScript</MenuItem>
</Select>
</FormControl>
<Editor
height="40vh"
language={language}
value={code}
onChange={(value) => setCode(value || '')}
theme="vs-dark"
/>
</Box>
);
};
図8 handleSubmitCode関数の定義コード
const handleSubmitCode = async () => {
if (!problem) return;
setIsSubmitting(true);
let finalVerdict = "AC (Accepted)";
// サンプルケースごとにコードを実行、検証
for (let i = 0; i < problem.samples.length; i++) {
const sample = problem.samples[i];
try {
// APIを呼び出してコードを実行
const judgeResult = await submitCode(language, code, sample.input);
// 結果を検証
if (judgeResult.status.id === 3) {
const userOutput = (judgeResult.stdout || '').trim();
const expectedOutput = sample.output.trim();
if (userOutput !== expectedOutput) {
finalVerdict = "WA (Wrong Answer)";
}
} else {
finalVerdict = judgeResult.status.description;
}
} catch (error) {
}
// 最初の失敗で処理を中断
if (finalVerdict !== "AC (Accepted)") {
break;
}
// APIのレート制限を避けるための待ち時間
if (i < problem.samples.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
setResult(finalVerdict);
setIsSubmitting(false);
};
図9 フロントエンドからのリクエストを受け取るコード
import type { NextApiRequest, NextApiResponse } from 'next';
import axios from 'axios';
// Judge0 CEの言語IDをマッピング
const LANGUAGE_MAP: { [key: string]: number } = {
python: 71, // Python 3.8.1
cpp: 54, // C++ (GCC 9.2.0)
javascript: 63, // JavaScript (Node.js 12.14.0)
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'メソッドはポストではありません。' });
}
const { language, code, stdin } = req.body;
if (!language || !code || !stdin) {
return res.status(400).json({ error: '要素が足りません。' });
}
const languageId = LANGUAGE_MAP[language];
if (!languageId) {
return res.status(400).json({ error: 対応していない言語です。 ${language} });
}
(略)
}
図10 Judge0 CEのAPIにリクエストを送り、結果をフロントエンドに返すコード
const judge0ApiKey = process.env.NEXT_PUBLIC_JUDGE0_API_KEY;
const options = {
method: 'POST',
url: 'https://judge0-ce.p.rapidapi.com/submissions',
params: {
base64_encoded: 'false',
wait: 'true', // 実行完了まで待機する
},
headers: {
'x-rapidapi-host': 'judge0-ce.p.rapidapi.com',
'x-rapidapi-key': judge0ApiKey, // 環境変数から読み込んだAPIキー
'content-type': 'application/json',
},
data: {
language_id: languageId,
source_code: code,
stdin: stdin,
},
};
try {
const submissionResponse = await axios.request(options);
// Judge0 CEからの結果をフロントエンドに返す
res.status(200).json(submissionResponse.data);
} catch (error) {/* エラーハンドリング*/}
図11 Geminiに送信するプロンプトの例
あなたは優秀な競技プログラミングの問題作成者です。
以下の制約に従って、新しいプログラミング問題を1つ作成してください。
# 制約
- 難易度: AtCoder ProblemsのDifficulty基準で「B」レベル
- トピック: 配列の操作、または文字列の探索
- 入力: 標準入力から受け取ること
- 出力: 標準出力へ書き出すこと
- 形式: 以下のJSONフォーマットに従うこと
# JSONフォーマット
{
"title": "問題タイトル",
"statement": "問題文。背景やストーリーを簡潔に記述。",
"constraints": [
"Nは1以上100以下の整数",
"Sは英小文字からなる長さNの文字列"
],
"input_format": "入力形式の説明",
"output_format": "出力形式の説明",
"samples": [
{ "input": "入力例1", "output": "出力例1" },
{ "input": "入力例2", "output": "出力例2" },
{ "input": "入力例3", "output": "出力例3" }
],
"solution_code": "Pythonによる解答例のコード"
}
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第28回では、インタラクティブなグラフを手軽に作成できる可視化ライブラリ「Plotly」を使って、さまざまなグラフを作成します。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図2 散布図を描くコード
fig = px.scatter(df, x='sepal_width', y='sepal_length',
color='species', size='petal_length')
fig.update_layout(width=800, height=600)
fig.show()
図5 花弁の長さのデータを表示できるようにしたコード
fig = px.scatter(df, x='sepal_width', y='sepal_length',
color='species', size='petal_length',
hover_data=['petal_width'])
fig.update_layout(width=800, height=600)
fig.show()
図7 がくの幅の分布をヒストグラムで表示するコード
fig = px.histogram(df, x='sepal_width', color='species')
fig.update_layout(width=800, height=600)
fig.show()
図11 カラースキームを変更するコードの例
# 離散的なデータ向けのカラースキーム設定
px.defaults.color_discrete_sequence = px.colors.qualitative.T10
# 連続的なデータ向けのカラースキーム設定
px.defaults.color_continuous_scale = px.colors.sequential.GnBu
図13 四つのヒストグラムをまとめて表示するコードの例
from plotly.subplots import make_subplots
fig = make_subplots(rows=2, cols=2, shared_yaxes=True)
figs = [px.histogram(df, x='sepal_width',
color='species', barmode='overlay'),
px.histogram(df, x='sepal_length',
color='species', barmode='overlay'),
px.histogram(df, x='petal_width',
color='species', barmode='overlay'),
px.histogram(df, x='petal_length',
color='species', barmode='overlay')]
i = 0
for f in figs:
for g in f.data:
fig.add_trace(g, row=(i//2)+1, col=(i%2)+1)
i += 1
fig.update_layout(height=600, width=800,
title_text='4種類のヒストグラム',
barmode='overlay',
xaxis_title=‘sepal_width',
xaxis2_title='sepal_length',
xaxis3_title='petal_width',
xaxis4_title='petal_length')
fig.show()
図15 図13のコードの改良版
from plotly import graph_objects as go
from plotly.subplots import make_subplots
fig = make_subplots(rows=2, cols=2, shared_yaxes=True)
i = 0
for types in ['sepal_width', 'sepal_length',
'petal_width', 'petal_length']:
for species, g in df.groupby('species'):
fig.add_trace(go.Histogram(x=g[types],
name=f'{species} ({types})',
opacity=0.7),
row=(i//2)+1, col=(i%2)+1)
i += 1
fig.update_layout(height=600, width=800,
title_text='4種類のヒストグラム',
barmode='overlay',
xaxis_title=‘sepal_width',
xaxis2_title='sepal_length',
xaxis3_title='petal_width',
xaxis4_title='petal_length')
fig.show()
図17 tipsデータセットを読み込んで散布図を描くコード
df = px.data.tips()
fig = px.scatter(df, x='total_bill', y='tip',
color='day', size='size',
hover_data=['time', 'sex', 'smoker'])
fig.update_layout(width=800, height=600)
fig.show()
図19 曜日ごとのチップの額を箱ひげ図で描くコード
fig = px.box(df, x='day', y='tip', color='day',
category_orders={'day': ['Thur', 'Fri',
'Sat', 'Sun']})
fig.update_layout(width=800, height=600)
fig.show()
図21 二つの箱ひげ図を並べて描くコード
fig = make_subplots(rows=1, cols=2)
figs = [px.box(df, x='day', y='tip', color='day',
category_orders={'day': ['Thur', 'Fri',
'Sat', 'Sun']}),
px.box(df, x='day', y='total_bill', color='day',
category_orders={'day': ['Thur', 'Fri',
'Sat', 'Sun']})]
i = 0
for f in figs:
for g in f.data: fig.add_trace(g, row=1, col=(i % 2)+1)
i += 1
fig.update_layout(width=800, height=600,
title_text='tipsとtotal_billの比較',
xaxis_title='tips',
xaxis2_title='total_bill')
図23 バイオリン図を描くコード
fig = px.violin(df, x='day', y='tip', color='day',
category_orders={'day': ['Thur', 'Fri',
'Sat', 'Sun']})
fig.update_layout(width=800, height=600)
fig.show()
図24 ヒートマップを描くコード
fig = px.density_heatmap(df, x='day', y='time', z='tip',
histfunc='avg',
category_orders={'day': ['Thur', 'Fri',
'Sat', 'Sun'],
'time': ['Dinner', 'Lunch']})
fig.update_layout(width=800, height=400,
title_text='曜日と時間帯のヒートマップ(tip)')
fig.show()
著者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。前回に引き続き、文書変換ツール「Pandoc」の文書作成方法を紹介します。今回はPandocを用いて、EPUB形式電子書籍を作成する方法を紹介します。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図1 Markdown形式にした「銀河鉄道の夜」の冒頭部分
---
title: 銀河鉄道の夜
author: 宮沢賢治
---
# 一、午后《ごご》の授業
「ではみなさんは、そういうふうに川だと云《い》われたり、乳の流れたあとだと云われたりしていたこのぼんやりと白いものがほんとうは何かご承知ですか。」先生は、黒板に吊《つる》した大きな黒い星座の図の、上から下へ白くけぶった銀河帯のようなところを指《さ》しながら、みんなに問《とい》をかけました。
カムパネルラが手をあげました。それから四五人手をあげました。ジョバンニも手をあげようとして、急いでそのままやめました。たしかにあれがみんな星だと、いつか雑誌で読んだのでしたが、このごろはジョバンニはまるで毎日教室でもねむく、本を読むひまも読む本もないので、なんだかどんなこともよくわからないという気持ちがするのでした。
ところが先生は早くもそれを見附《みつ》けたのでした。
「ジョバンニさん。あなたはわかっているのでしょう。」
ジョバンニは勢《いきおい》よく立ちあがりましたが、立って見るともうはっきりとそれを答えることができないのでした。ザネリが前の席からふりかえって、ジョバンニを見てくすっとわらいました。ジョバンニはもうどぎまぎしてまっ赤になってしまいました。先生がまた云いました。
(略)
図12 本文中の画像を挿入するために追記
(略)
そして教室中はしばらく机《つくえ》の蓋《ふた》をあけたりしめたり本を重ねたりする音がいっぱいでしたがまもなくみんなはきちんと立って礼をすると教室を出ました。
{height=90%}
# 二、活版所
ジョバンニが学校の門を出るとき、同じ組の七八人は家へ帰らずカムパネルラをまん中にして校庭の隅《すみ》の桜《さくら》の木のところに集まっていました。それはこんやの星祭に青いあかりをこしらえて川へ流す烏瓜《からすうり》を取りに行く相談らしかったのです。
(略)
※ 「[」の箇所は「[」です。
図14 オプションをまとめたデフォルトファイル
input-files:
- gingatetsudono_yoru_image.md
from: markdown+hard_line_breaks
output-file: gingatetsudono_yoru_06.epub
css: style.css
epub-cover-image: img/Copilot_Generated_Cover.png
いつもUSP研究所の通信販売をご利用いただき、誠にありがとうございます。
USP研究所では、環境保護および業務効率化の取り組みの一環として、2025年9月1日(月曜日)より、領収書を電子化いたします。
ご注文・お支払い完了後に、領収書をご希望された方へメールにてPDF形式の領収書をお送りいたします。なお、紙の領収書をご希望の場合は、印刷・郵送にかかる事務手数料が必要です。以下のご対応をお願いいたします。
紙の領収書が必要な場合は、商品ご購入時に「URX0001 紙の領収書発行手数料」(税込み220円)を必ずカートに追加してください。こちらのご購入が確認でき次第、紙の領収書を郵送いたします。
電子領収書は迅速にお届けでき、保管も容易です。理解とご協力のほどよろしくお願い申し上げます。

004 レポート WSL2版Red Hat Enterprise Linux公開
005 レポート KubeCon + CloudNativeCon Japan開催
006 製品レビュー PC「ThinkPad X1 Carbon Gen 13」
007 NEWS FLASH
008 特集1 Red Hat OpenShift Virtualization/宇都宮卓也、田中司恩
019 インタビュー デジサート・ジャパン/二宮要氏
020 特集2 OCIクラウドネイティブアプリ開発/曽川宥輝
034 作りながら学ぶVue.js入門/大津真 コード掲載
041 Markdownを活用する/藤原由来 コード掲載
050 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
056 Pythonあれこれ/飯尾淳 コード掲載
064 中小企業手作りIT化奮戦記/菅雄一
070 知っておきたいシェル関連技術/麻生二郎
074 Techパズル/gori.sh
075 コラム「意識教育」/シェル魔人
著者:大津 真
JavaScriptフレームワークの中でも、学びやすさと柔軟さで人気を集めている「Vue.js」。本連載では、Vue.jsの基礎から実践的な使い方までを、分かりやすく丁寧に解説していきます。一緒にフロントエンド開発の楽しさを体験してみましょう。第2回では、テンプレート構文において、各要素に特別な動作を与えるための機能であるディレクティブの基礎について説明します。
シェルスクリプトマガジン Vol.97は以下のリンク先でご購入できます。![]()
![]()
図1 オブジェクトリテラルの例
{
name: name,
age: age,
scores: { math: [40, 50], eng: [70, 60] },
sayHello: function() {
console.log('こんにちは');
}
}
図2 シンプルなVueアプリ「sample1.html」の主要部分のコード
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
const msg = ref('ハローVue')
const person = ref({name:'大津真', age: 35})
return {
msg, person
}
}
}).mount('#app')
</script>
<div id="app">
<h1>{{ msg }}</h1>
<p>{{ person }}</p>
</div>
図3 ボタンクリックで値を増減させるVueアプリ「counter1.html」のコード(抜粋)
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
// counterをリアクティブな変数として定義
const counter = ref(0)
// counterを「1」増やす
function increment() {
counter.value++
}
// counterを「1」減らす
function decrement() {
counter.value--
}
return {
counter,
increment,
decrement
}
}
}).mount('#app')
</script>
<div id="app">
<h1>カウント: {{ counter }}</h1>
<button v-on:click="increment">+</button>
<button v-on:click="decrement">-</button>
</div>
図4 図3のアプリをインラインイベントハンドラを用いて書き換えた「counter2.html」のコード(抜粋)
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
// counterをリアクティブな変数として定義
const counter = ref(0)
return {
counter,
}
}
}).mount('#app')
</script>
<div id="app">
<h1>カウント: {{ counter }}</h1>
<button @click="counter += 1">+</button>
<button @click="counter -= 1">-</button>
</div>
図5 図4のアプリに「3倍にする」ボタンを追加した「counter3.html」のコード(抜粋)
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
// counterをリアクティブな変数として定義
const counter = ref(0)
const multiplier = ref(3)
// counterをn倍にする
function multiply(n) {
counter.value *= n
}
return {
counter,
multiply,
multiplier
}
}
}).mount('#app')
</script>
<div id="app">
<h1>カウント: {{ counter }}</h1>
<button @click="counter += 1">+</button>
<button @click="counter -= 1">-</button>
<button @click="multiply(multiplier)">
{{ multiplier }}倍にする</button>
</div>
図6 マスタッシュ構文とv-htmlの違いを示す「v-html1.html」のコード(抜粋)
<script type="module">
import { createApp } from 'vue'
createApp({
setup() {
const msg1 = 'msg1:\
<span style="font-size:3em;color: red;">\
ハローVue</span>'
return { msg1 }
}
}).mount('#app')
</script>
<div id="app">
<p>{{ msg1 }}</p>
<p v-html="msg1"></p>
</div>
図9 おみくじアプリ「omikuji1.html」のコード(抜粋)
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
// おみくじの画像ファイル名を配列で定義
const kujis = ["daikichi.png", "chukichi.png", "syokichi.png", "kyo.png"]
// 初期画像を設定
const kuji = ref('default.png')
// おみくじを引く関数
function changeKuji() {
const randomIndex = Math.floor(Math.random() * kujis.length)
kuji.value = kujis[randomIndex]
}
return {
kuji,
changeKuji
}
}
}).mount('#app')
</script>
<div id="app">
<h1>おみくじ</h1>
<div>
<button @click="changeKuji">おみくじを引く</button>
</div>
<img :src="'figs/' + kuji" alt="おみくじ" @click="changeKuji">
</div>
図11 「style1.html」のコード(抜粋)
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
const fcolor = 'red'
const size = '4em'
const align = ref('left')
function setAlign(value) {
align.value = value
}
return {
fcolor, align, size, setAlign
}
}
}).mount('#app')
</script>
<div id="app">
<div>
<button @click="setAlign('left')">左寄せ</button>
<button @click="setAlign('center')">中央寄せ</button>
<button @click="setAlign('right')">右寄せ</button>
<p :style="{color:fcolor, textAlign:align, fontSize:size}">Vue.js</p>
</div>
</div>
図12 「style2.html」のコード(抜粋)
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
const styleAll = ref({
color: 'red'
fontSize: '4em'
textAlign: 'left'
})
function setAlign(value) {
styleALL.value.textAlign = value
}
return {
styleAll, setAlign
}
}
}).mount('#app')
</script>
<div id="app">
<div>
<button @click="setAlign('left')">左寄せ</button>
<button @click="setAlign('center')">中央寄せ</button>
<button @click="setAlign('right')">右寄せ</button>
<p :style="styleAll">Vue.js</p> ←③
</div>
</div>
図13 「style3.html」のコード(抜粋)
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
const styleAll = ref({
color: 'red'
fontSize: '4em'
textAlign: 'left'
})
const styleAdd = ref({
color: 'blue',
backgroundColor: 'yellow',
})
(略)
return {
styleAll, styleAdd, setAlign
} }
}).mount('#app')
</script>
<div id="app">
<div>
(略)
<p :style="[styleAll, styleAdd]">Vue.js</p>
</div>
</div>
図17 「class1.html」のコード(抜粋)
<style>
.class1 {
text-align: center;
color: blue;
}
.class2 {
font-size: 4em;
color: red;
}
</style>
(略)
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
const enableClass2 = ref(false)
return {
enableClass2
}
}
}).mount('#app')
</script>
<div id="app">
<button @click="enableClass2=!enableClass2">class2を有効/無効</button>
<p :class="{class1:true, class2:enableClass2}">Vue.jsの世界</p>
</div>
筆者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。前回に引き続き、文書変換ツール「Pandoc」を扱います。今回は、Pandocの概要と基本的な使い方について説明します。
シェルスクリプトマガジン Vol.97は以下のリンク先でご購入できます。![]()
![]()
図3 Markdownテキストファイル(sample.md)の内容
# カレーの作り方
おいしいカレーを作りましょう。
## 材料
- 牛肉
- 玉ねぎ
- にんじん
- カレールー
- 水
図9 数式を用いたMarkdown記法
インライン数式 $f(x) = ax^2 + bx + c$ です。
## ブロック数式
$$
e^{i \theta} = \cos \theta + i \sin \theta
$$
筆者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico/Pico 2」シリーズを活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載するモデルもあり、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。今回は、前回Raspberry Pi Pico 2に取り付けた小型LCDに画像を表示するプログラムを紹介します。
シェルスクリプトマガジン Vol.97は以下のリンク先でご購入できます。![]()
![]()
図1 RGB565の非圧縮ビットマップ画像に変換するPythonプログラム(rgb565.py)
#!/usr/bin/python3
import sys
import argparse
from PIL import Image
def rgb565_convert(image_path, output_path ,order='little'):
"""
画像をRGB565形式に変換し、バイナリファイルに保存します。
Args:
image_path: 入力画像のパス
output_path: 出力バイナリファイルのパス
"""
try:
img = Image.open(image_path).convert("RGB") # RGBに変換
except FileNotFoundError:
print(f"Error: 画像ファイル '{image_path}' が見つかりません。")
return
except Exception as e:
print(f"Error: 画像を開く際にエラーが発生しました: {e}")
return
width, height = img.size
with open(output_path, "wb") as f:
for y in range(height):
for x in range(width):
r, g, b = img.getpixel((x, y))
# RGB565に変換
r5 = r >> 3 # 上位5ビット
g6 = g >> 2 # 上位6ビット
b5 = b >> 3 # 上位5ビット
rgb565 = (r5 << 11) | (g6 << 5) | b5
# バイナリ書き込み (リトルエンディアン)
f.write(rgb565.to_bytes(2, byteorder=order))
print(f"画像 '{image_path}' をRGB565形式に変換し、'{output_path}' に保存しました。")
if __name__=="__main__":
parser = argparse.ArgumentParser()
parser.add_argument("files",type=str,nargs="*")
parser.add_argument("--order", type=str, choices=['little', 'big'], default="little")
args = parser.parse_args()
files = args.files
order = args.order
if len(files) != 2:
print("Usage: rgb565.py infile outfile [--order little | big ]")
else:
rgb565_convert(files[0], files[1], order)
図2 sample.binをLCDに表示するC++プログラム(main.cpp)
#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>
// 以下がPico SDKのヘッダーファイル
#include "hardware/dma.h"
#include "hardware/spi.h"
#include "pico/stdlib.h"
#define TFT_CS 17
#define TFT_RST 21
#define TFT_DC 20
// サンプル画像を埋め込む
__asm(\
".section \".rodata\" \n"\
".balign 4\n"\
".global _sample_picture\n"\
".global _picture_size\n"\
"_sample_picture:\n"\
".incbin \"include/sample.bin\"\n"\
".set _picture_size, . - _sample_picture\n"\
".section \".text\"\n"\
);
extern const uint16_t _sample_picture[];
extern uint32_t _picture_size[];
// Adafruit ST77xxを初期化生成
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
void setup() {
Serial1.begin(115200); // USBシリアルを使用する場合はSerialに変更、以下同じ
// SPIクロック100MHz
tft.setSPISpeed(100*000*000);
// 解像度240×240、SPIモード3
tft.init(240, 240, SPI_MODE3);
// 黒で消去
tft.fillScreen(ST77XX_BLACK);
// 空いているDMAチャンネルを得る
int dma_ch = dma_claim_unused_channel(true);
// DMA設定を取得
dma_channel_config c = dma_channel_get_default_config(dma_ch);
// データ転送単位:DMA_SIZE_8=8iビット
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
// 読み出し側アドレスのインクリメントを有効
channel_config_set_read_increment(&c, true);
// 書き込み側アドレスのインクリメントは行わない
channel_config_set_write_increment(&c, false);
// spi0送信FIFOのデータ要求を使用する
channel_config_set_dreq(&c, DREQ_SPI0_TX);
// DMAにパラメータを設定する
dma_channel_configure(
dma_ch, // DMAチャンネル
&c, // 設定
&spi_get_hw(spi0)->dr, // 書き込み先:SPI FIFOデータレジスタ(SSPDR)
_sample_picture, // 読み出し元アドレス
(uint)_picture_size, // 転送するデータカウント数
false // 今すぐ開始するか(しない)
);
// LCDディスプレイメモリーの書き込み矩形を設定
tft.setAddrWindow(0, 0, tft.width(), tft.height());
ulong starttime = micros();
tft.startWrite(); // 書き込み開始
dma_channel_start(dma_ch); // DMA開始
dma_channel_wait_for_finish_blocking(dma_ch); // DMA終了待ち
tft.endWrite(); // 書き込み終了
ulong elapsed = micros() - starttime;
// 描画に費やした時間を表示
Serial1.printf("elapsed time = %lu\n", elapsed);
}
void loop() {
}
図4 埋め込んだアセンブリ言語のリスト
.section ".rodata"
.balign 4
.global _sample_picture
.global _picture_size
_sample_picture:
.incbin "include/sample.bin"
.set _picture_size, . - _sample_picture
.section ".text
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第27回では、異文化間交流教育プロジェクトで得られたオンライン交流データを分析する、筆者の研究例を紹介します。
シェルスクリプトマガジン Vol.97は以下のリンク先でご購入できます。![]()
![]()
図2 必要なライブラリをインポートするコード
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
import re, os, math
from graphviz import Graph
from IPython.display import Image, display, display_png
from sklearn.linear_model import LinearRegression
図4 mk_relations()関数を定義するコード
def mk_relations(df):
# 関係性を抽出する
prev = []
relations = {}
f = df.columns
for i, row in df.iterrows():
# 発言文字数が「0」ではない発言者を抽出
d = dict(zip(f, list(row)))
speakers = list(filter(lambda k: d[k] != 0, d.keys()))
# 前行のスピーカーと今の行のスピーカーの関係を抽出
# どちらかもしくは両方が空行のときはスキップ
if len(prev)*len(speakers) > 0:
for p_speaker in prev:
for n_speaker in speakers:
r = tuple(sorted([p_speaker, n_speaker]))
if r not in relations.keys():
relations[r] = 1
else:
relations[r] += 1
prev = speakers
return relations
図5 ファイルからターンテイキングの状況を抽出するコード
filename = 'data/file00.xlsx'
df = pd.read_excel(filename).drop(['Unnamed: 0'], axis=1)
d = mk_relations(df)
d
図7 conv_dict()関数を定義するコード
def conv_dict(d):
v = d.values()
return dict(zip(d.keys(), map(lambda x: x/max(v), v)))
図9 count_talk()関数を定義するコード
def count_talk(df):
retvar = {}
for (name, col) in df.T.iterrows():
retvar[name] = len(list(filter(lambda x: x > 0, col)))
return retvar
図11 render_graph()関数を定義するコード
def render_graph(filename):
print(f'[source] {filename}')
(basename, extention) = re.match(r'(.*)\.(.*)', filename).groups()
pathname = 'data/'+filename
outputfile = f'graphs/{basename}'
# データフレームの準備
df = pd.read_excel(pathname).drop(['Unnamed: 0'], axis=1)
names = df.columns
# 発言の関係性と発言回数の抽出
relations = conv_dict(mk_relations(df))
talk_counts = count_talk(df)
talks = conv_dict(talk_counts)
# グラフ描画に関する定数など
ARROW_WIDTH = 3
colors = ['white', 'azure', 'lavender', 'slateblue', 'navy']
textcolors = ['black', 'black', 'black', 'white', 'white']
# dot属性の準備
dot = Graph(format='png')
dot.attr('node', shape='ellipse')
dot.attr('node', fontname='Arial')
# ノードの追加
for n in talks.keys():
idx = int(talks[n]*(len(colors)-1))
dot.node(n, f'{n} ({talks[n]:4.2f})',
style='filled', fillcolor=colors[idx], fontcolor=textcolors[idx])
# エッジの追加
for e in relations.keys():
(src, dst) = e
dot.edge(src, dst, penwidth=f'{relations[e]*ARROW_WIDTH:5.3f}')
# サマリー表の作成
df_summary = pd.DataFrame({'ID': talk_counts.keys(),
'発言回数': talk_counts.values()})
# レンダリング
display(df_summary)
dot.render(outputfile)
display_png(Image(f’graphs/{basename}.png'))
図15 calc_num_cv()関数を定義するコード
def calc_num_cv(filename):
print(f'[source] {filename}')
(basename, extention) = re.match(r'(.*)\.(.*)', filename).groups()
pathname = 'data/'+filename
# データフレームの準備
df = pd.read_excel(pathname).drop(['Unnamed: 0'], axis=1)
names = df.columns
num = len(names)
# 発言回数の抽出
talk_counts = count_talk(df)
# サマリー表の作成
df_summary = pd.DataFrame({'ID': talk_counts.keys(),
'発話回数': talk_counts.values()})
# 平均・標準偏差・変動係数の計算
mu = sum(df_summary['発話回数'])/num # 平均値
sd = math.sqrt(sum((df_summary['発話回数'] - mu) *
(df_summary['発話回数'] - mu))/(num-1)) # 不偏標準偏差
print(f'参加者数 = {num}')
print(f'平均 = {mu:4.2f}')
print(f'不偏標準偏差 = {sd:4.2f}')
print(f'変動係数 = {(sd/mu):6.4f}')
return num, sd/mu
図16 すべてのデータから参加者数と変動係数を計算するコード
files = sorted(os.listdir('data'))
table = []
Xlabel = '参加者数'
Ylabel = '変動係数'
for filename in files:
num, cv = calc_num_cv(filename)
table.append({ 'Name': filename, Xlabel: num, Ylabel: cv })
著者:大津 真
JavaScriptフレームワークの中でも、学びやすさと柔軟さで人気を集めている「Vue.js」。本連載では、Vue.jsの基礎から実践的な使い方までを、分かりやすく丁寧に解説していきます。一緒にフロントエンド開発の楽しさを体験してみましょう。第1回では、Vue.jsの概要と、簡単なアプリの作成方法を紹介します。
シェルスクリプトマガジン Vol.96は以下のリンク先でご購入できます。![]()
![]()
図3 Options APIを利用したコードの例(抜粋)
<script type="module">
import { createApp } from 'vue'
createApp({
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}).mount('#app')
</script>
<div id="app">
<button @click="increment">Count is: {{ count }}</button>
</div>
図4 Composition APIを利用したコードの例(抜粋)
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
const count = ref(0)
function increment() {
count.value++
}
return {
count,
increment
}
}
}).mount('#app')
</script>
<div id="app">
<button @click="increment">Count is: {{ count }}</button>
</div>
図5 SFCファイルの記述例
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">Count is: {{ count }}</button>
</template>
<style scoped>
button {
font-weight: bold;
}
</style>
図6 HTML文書のテンプレート例
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Vueサンプル</title>
</head>
<body>
<script type="importmap">
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
// 処理はここに記述
}
}).mount('#app')
</script>
<div id="app">
</div>
</body>
</html>
図8 「sample1.html」ファイルに記述するコード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Vueサンプル</title>
</head>
<body>
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
const msg = ref('ハローVue')
const person = ref({name:'大津真', age: 35})
return{
msg, person
}
}
}).mount('#app')
</script>
<div id="app">
<h1>{{ msg }}</h1>
<p>{{ person }}</p>
</div>
</body>
</html>
図10 setup()関数の定義コードをこのように変更
setup() {
const msg = ref('ハローVue')
const person = ref({name:'大津真', age: 35})
// 3秒後にリアクティブな変数msgを変更する
setTimeout(() => {
msg.value = "ようこそVueの世界へ"
}, 3000)
return{
msg, person
}
}
図12 div要素の記述をこのように変更
<div id="app">
<h1>{{ "反転→ " + msg.split("").reverse().join("")}}</h1>
<p>{{ 名前: ${person.name}, 年齢: ${person.age}才 }}</p>
</div>
筆者:織田 悠暉
今回は、ゲームエンジン「Unity」で2Dキャラクタを動かす方法を紹介します。具体的には、プレーヤのキャラクタと、地形の作成、左右の移動と、ジャンプ動作の実装、各動作に合わせてキャラクタの画像を変更する方法について解説します。とても手軽に実装できるので、皆さんもぜひチャレンジしてください。
図9 「Move」ファイル(「Move.cs」ファイル)に記述するコード
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class Move : MonoBehaviour
{
Rigidbody2D rigid2D;
void Start()
{
rigid2D = GetComponent<Rigidbody2D>();
}
void Update()
{
Vector2 right = new Vector2 (30f, 0); // 右向きの移動速度
Vector2 left = new Vector2 (-30f, 0); // 左向きの移動速度
// 右向きの移動
if (Keyboard.current.dKey.isPressed &&
(rigid2D.linearVelocity.magnitude < 7f))
{
rigid2D.AddForce(right);
}
// 左向きの移動
if (Keyboard.current.aKey.isPressed &&
(rigid2D.linearVelocity.magnitude < 7f))
{
rigid2D.AddForce(left);
}
}
}
図10 「Move」ファイル(「Move.cs」ファイル)に挿入するコード
void OnTriggerStay2D(Collider2D collision)
{
if (Keyboard.current.dKey.wasReleasedThisFrame ||
Keyboard.current.aKey.wasReleasedThisFrame)
{
rigid2D.linearVelocity = Vector2.zero;
}
}
図11 「Jump」ファイル(「Jump.cs」ファイル)に記述するコード
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class Jump : MonoBehaviour
{
Rigidbody2D rigid2D;
// ジャンプの速度
float speed = 350.0f;
void Start()
{
rigid2D = GetComponent<Rigidbody2D>();
}
void OnTriggerStay2D(Collider2D collision)
{
if(Keyboard.current.spaceKey.isPressed)
{
rigid2D.AddForce(Vector2.up * speed);
}
}
}
図13 「Anime」ファイル(「Anime.cs」ファイル)に記述するコード
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class Anime : MonoBehaviour
{
public Sprite[] character_anime;
SpriteRenderer spriteRenderer;
Rigidbody2D rigid2D;
bool direction_right = true;
void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
rigid2D = GetComponent<Rigidbody2D>();
}
private void Update()
{
if (rigid2D.linearVelocity.x > 0)
direction_right = true;
if (rigid2D.linearVelocity.x < 0)
direction_right = false;
}
void OnTriggerStay2D(Collider2D collision)
{
if (direction_right)
{
if (rigid2D.linearVelocity.x == 0)
spriteRenderer.sprite = character_anime[0];
else
spriteRenderer.sprite = character_anime[2];
}
else
{
if (rigid2D.linearVelocity.x == 0)
spriteRenderer.sprite = character_anime[3];
else
spriteRenderer.sprite = character_anime[5];
}
}
void OnTriggerExit2D(Collider2D collision)
{
if (direction_right)
spriteRenderer.sprite = character_anime[1];
else
spriteRenderer.sprite = character_anime[4];
}
}

004 レポート 米OpenAI社が「Codex CLI」を公開
005 レポート VSCodeの拡張機能「Docker DX」登場
006 製品レビュー USB電源「Anker Charger(140W,4Ports)」
007 NEWS FLASH
008 特集1 ゼロトラストセキュリティ入門/石井英男、小寺勝仁、荻野隼
019 インタビュー BrainPad AAA 辻陽行氏
020 特集2 開発ツールのプリザンター/内田太志
032 作りながら学ぶVue.js入門/大津真 コード掲載
038 Markdownを活用する/藤原由来
044 Raspberry Pi Pico W/WHで始める電子工作/米田聡
048 Pythonあれこれ/飯尾淳 コード掲載
054 香川大学SLPからお届け!/織田悠暉 コード掲載
060 中小企業手作りIT化奮戦記/菅雄一
066 知っておきたいシェル関連技術/麻生二郎
069 IT関連の新刊
072 Techパズル/gori.sh
073 コラム「毎週開催の金曜勉強会」/シェル魔人
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第26回では、データを視覚化(ビジュアライゼーション)してデータの傾向を探るためのツール「PyGWalker」の利用に挑戦します。
シェルスクリプトマガジン Vol.96は以下のリンク先でご購入できます。![]()
![]()
図5 コードセルを追加して実行するコード
df['Weekend'] = df['Day of the week'].isin([0, 6]).astype(int)
df['Holiday'] = (df['holiday'] == 'yes').astype(int)
df['Holiday or Weekend'] = (df['Weekend'] | df['Holiday']).astype(str)
pyg.walk(df)

004 レポート 米Adobe社の商用利用可能な動画生成AI
005 レポート MongoDB代替のFerretDB新版 コード掲載
006 製品レビュー パソコン「Dell Pro 13 Premium」
007 NEWS FLASH
008 特集1 はじめてのシェルスクリプト/麻生二郎 コード掲載
013 インタビュー Linux Foundation Japan 福安徳晃氏
014 特集2 ベクトル検索エンジンのVald/湯川輝一朗 コード掲載
022 特集3 Hinemos入門 ジョブ編/倉田晃次、加藤達也
030 Markdownを活用する/藤原由来
039 Raspberry Pi Pico W/WHで始める電子工作/米田聡
046 Pythonあれこれ/飯尾淳 コード掲載
052 香川大学SLPからお届け!/遠藤幸太郎 コード掲載
058 中小企業手作りIT化奮戦記/菅雄一
064 SSHによるリモート管理入門/大津真
074 Techパズル/gori.sh
075 コラム「小売業のビジネスリーダーを育成する講義」/シェル魔人
著者:末安 泰三
米FerretDB社は2025年3月5日、MongoDB互換のドキュメント指向データベース管理システム「FerretDB」のバージョン2.0をリリースした。米Microsoft社のNoSQLデータベース実装「DocumentDB」を採用したことで、従来版比で20倍以上の性能を出せるケースがあるという。
シェルスクリプトマガジン Vol.95は以下のリンク先でご購入できます。![]()
![]()
図1 「docker-compose.yml」ファイルに記述する内容
services:
postgres:
image: ghcr.io/ferretdb/postgres-documentdb:17-0.102.0-ferretdb-2.0.0
platform: linux/amd64
restart: on-failure
environment:
- POSTGRES_USER=username
- POSTGRES_PASSWORD=password
- POSTGRES_DB=postgres
volumes:
- ./data:/var/lib/postgresql/data
ferretdb:
image: ghcr.io/ferretdb/ferretdb:2.0.0
restart: on-failure
ports:
- 27017:27017
environment:
- FERRETDB_POSTGRESQL_URL=postgres://username:password@postgres:5432/postgres
networks:
default:
name: ferretdb
著者:麻生 二郎
シェルスクリプトは、LinuxやUNIX系OSのコマンドのみで記述できるプログラムです。さまざまな処理をテキストファイルに書き込み、LinuxやUNIX系OSの環境ですぐに試せるので便利です。本特集では、はじめてシェルスクリプトを書く人向けにその記述テクニックを分かりやすく紹介します。
シェルスクリプトマガジン Vol.95は以下のリンク先でご購入できます。![]()
![]()
図5 引数の受け取りを試すシェルスクリプト(test1.sh)
#!/bin/bash -e
echo $1
echo $2
echo $3
図6 readコマンドを試すシェルスクリプト(test2.sh)
#!/bin/bash -e
read -p 'Please input: ' INPUT01
echo $INPUT01 | tee input.txt
図7 ifを試すシェルスクリプト(test3.sh)
#!/bin/bash -e
if [ $1 == '100' ]; then
echo 'good'
elif [ $1 == '0' ]; then
echo 'bad'
else
echo 'unknown'
fi
図8 caseを試すシェルスクリプト(test4.sh)
#!/bin/bash -e
case $1 in
100)
echo 'very good'
;;
80)
echo 'pretty good'
;;
60)
echo 'good'
;;
0)
echo 'bad'
;;
esac
図9 Whileを試すシェルスクリプト(test5.sh)
#!/bin/bash -e
i=0
while [ $i -le 10 ]
do
echo $i
i=$((i + 1))
done
図10 forを試すシェルスクリプト(test6.sh)
#!/bin/bash -e
for i in 0 1 2 3 4 5
do
echo $i
done
図11 関数を試すシェルスクリプト(test7.sh)
#!/bin/bash -e
function output()
{
echo $1
}
output run1
output run2
著者:湯川 輝一朗
ベクトル検索エンジン「Vald」は、純国産のオープンソースソフトウエア(OSS)です。画像や音声などの非構造データを数値ベクトル化にして検索に利用します。クラウド上でも動作し、大規模な高速検索に向いています。ライセンスはApache License 2.0であり、無料利用が可能です。本特集では、Valdについて解説します。
シェルスクリプトマガジン Vol.95は以下のリンク先でご購入できます。![]()
![]()
図7 values.yamlファイル
defaults:
## Log level
logging:
level: debug
## 利用するDockerイメージのtag(componentごとに個別設定も可能)
image:
tag: v1.7.16
server_config:
## ヘルスチェックの設定
healths:
liveness:
livenessProbe:
initialDelaySeconds: 60
readiness:
readinessProbe:
initialDelaySeconds: 60
## O11yのための設定
servers:
grpc:
server:
grpc:
interceptors:
- RecoverInterceptor
- TraceInterceptor
- MetricInterceptor
## 以下もO11yのための設定
grpc:
client:
dial_option:
interceptors:
- TraceInterceptor
observability:
enabled: true
otlp:
collector_endpoint: "opentelemetry-collector-collector.default.svc.cluster.local:4317"
trace:
enabled: true
networkPolicy:
enabled: true
custom:
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: pyroscope
egress:
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: opentelemetry-collector-collector
## gatewayの設定
gateway:
lb:
minReplicas: 3
maxReplicas: 3
resources:
requests:
cpu: 150m
memory: 150Mi
hpa:
enabled: false
ingress:
# Kubernetesのingressを利用する
enabled: true
# ingressのホスト
host: localhost
service:
# k3dでingressを利用するための設定
annotations:
traefik.ingress.kubernetes.io/service.serversscheme: h2c
## vald-agentの設定
agent:
minReplicas: 6
maxReplicas: 6
## Production環境では推奨しないが、今回はParallelで行う
podManagementPolicy: Parallel
resources:
requests:
cpu: 150m
memory: 150Mi
ngt:
# 次元数
dimension: 784
# 距離関数
distance_type: l2
# ベクトルのオブジェクトタイプ
object_type: float
# 自動Indexを行うためのチェック周期(Index Mangerを利用するためマイナスにして無効化)
auto_index_check_duration_limit: "-1m"
# 自動Indexの周期(Index Mangerを利用するためマイナスにして無効化)
auto_index_duration_limit: "-20s"
# 自動インデックス作成操作のバッチプロセスプールサイズ
auto_create_index_pool_size: 100
# インデックス構築時のデフォルトバッチサイズ
default_pool_size: 100
## vald-discovererの設定
discoverer:
resources:
requests:
cpu: 150m
memory: 50Mi
## vald-managerの設定
manager:
index:
resources:
requests:
cpu: 150m
memory: 30Mi
indexer:
# Index Managerを有効化
enable: true
# 自動インデックス作成の制限期間
# これにより、各Vald Agentで強制インデックス作成がトリガーされる
auto_index_duration_limit: 1m
# 自動インデックス作成のチェック期間
# これにより、コミットされていないインデックスが制限を超えた場合、各Vald Agnetでのインデックス作成がトリガーされる
auto_index_check_duration: 40s
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第25回では、人間がどのように文章の意味を理解するかという認知能力に関わる、少し不思議で面白い現象について取り上げます。
シェルスクリプトマガジン Vol.95は以下のリンク先でご購入できます。![]()
![]()
図4 タイポグリセミア度を算出する関数tl()の定義コード
import re
# タイポグリセミアに関係する部分を取り出して文字のリストを返す
def s2cl(s):
sp_chars = '[,.;:.,。、??!!]'
s2 = re.sub(sp_chars, '', s.strip()).split()
return list("".join(
[x[1:-1] for x in filter(lambda x: len(x) > 3, s2)]
))
# タイポグリセミア度を計算する関数
def tl(s1, s2):
s1_chars = s2cl(s1)
s2_chars = s2cl(s2)
# 入れ換えた文字を数える
ctr = 0
for i in range(len(s1_chars)):
if s1_chars[i] != s2_chars[i]:
ctr += 1
# タイポグリセミア度を返す
return ctr / len(s1_chars)
図5 関数make_candidate_index()の定義コード
def make_candidate_index(orig, debug=False):
tmp = ' '
candidate = ' '
stopwords = ' ,.!?!?.。,、「」\"\#\*\&\n'
indexes = []
# ストップワード文字と、それに隣接する文字を空白に変換
for i in range(1, len(orig)-1):
tmp = tmp[:i] + \
(' ' if orig[i-1] in stopwords \
or orig[i+1] in stopwords \
or orig[i] in stopwords \
else orig[i])
# 直前、直後がストップワード文字の文字を空白に変換
for i in range(1, len(tmp)-1):
candidate = candidate[:i] + \
(' ' if (tmp[i-1] in stopwords \
and tmp[i+1] in stopwords) \
or (tmp[i] in stopwords) \
else tmp[i])
# debug=True フラグで動作確認できるようにする
if debug:
print('ORG:' + orig)
print('TMP:' + tmp)
print('CDD:' + candidate)
# 候補の文字位置のリストを要素にするリストを作成
flag:bool = False
for i in range(1, len(candidate)-1):
if candidate[i] not in stopwords:
if not flag:
flag = True; subindexes = [i]
else:
subindexes.append(i)
else:
if flag:
indexes.append(subindexes)
flag = False
return indexes
図7 タイポグリセミア文を作成する関数typoglycemia()の定義コード
from functools import reduce
import random
def typoglycemia(orig, tlval, debug=False):
if tlval < 0.0 or tlval > 1.0:
print('tlvalは0.0以上1.0以下でなくてはいけません')
return None
# 最初にインデックスのリストを作成する
indexes = make_candidate_index(orig)
# indexesに含まれる全要素数を計算する
denominator = len(reduce(lambda x, y: x+y, indexes))
# 文字を交換すべき処理対象のリスト
procs = []
# 交換する文字数
swap_nums = 0
while swap_nums < tlval * denominator:
# 対象を決める
target_idx = random.randint(0, len(indexes) - 1)
target_list = indexes[target_idx]
if debug: print('---\n処理の対象: ' + str(target_list))
# 単語が十分に長い場合は、処理対象の2文字を特定する
if len(target_list) >= 4:
char_idx = 0
swap_idx = 0
while char_idx == swap_idx:
char_idx = random.randint(0, len(target_list) - 1)
swap_idx = random.randint(0, len(target_list) - 1)
c1 = target_list[char_idx]
c2 = target_list[swap_idx]
target_list.remove(c1)
target_list.remove(c2)
procs.append([c1, c2])
swap_nums += 2
# 処理対象の単語が2文字または3文字の場合
elif len(target_list) == 2 or len(target_list) == 3:
procs.append(target_list)
indexes.remove(target_list)
swap_nums += len(target_list)
target_list = []
else:
print('エラー')
return None
if debug:
print('処理リスト: ' + str(procs))
print('処理後対象: ' + str(target_list))
if debug: print('---\n残り: ' + str(indexes) + '\n')
# procsに入れられた情報に基づき文字の入れ替え処理を行う
chars:list[str] = list(orig)
for l in procs:
if len(l) == 2:
tmp = chars[l[0]]
chars[l[0]] = orig[l[1]]
chars[l[1]] = tmp
else:
tmp = chars[l[0]]
if random.randint(0, 1) == 0: # 時計回りの3文字入れ替え
chars[l[0]] = orig[l[1]]
chars[l[1]] = orig[l[2]]
chars[l[2]] = tmp
else: # 反時計回りの3文字入れ替え
chars[l[0]] = orig[l[2]]
chars[l[2]] = orig[l[1]]
chars[l[1]] = tmp
return ''.join(chars)
図8 タイポグリセミア度「0.7」の文を作成した例
org = 'The superpowered quick brown fox jumps over the beautiful lazy dog.'
typo = typoglycemia(org, 0.7, debug=True)
typo
著者:遠藤幸太郎
AIと自然言語処理技術の発展により、「ChatGPT」に代表される、人間と自然な形で対話できる会話型AIが注目を集めています。今回は、PythonでAIチャットボットアプリを開発していきます。米Google社の「Gemini API」を利用し、対話だけでなく、音声や映像ファイルをアップロードして分析できるようにします。
シェルスクリプトマガジン Vol.95は以下のリンク先でご購入できます。![]()
![]()
図3 「win.py」ファイルに記述するコード
from PySide6.QtWidgets import (
QApplication, QMainWindow, QLabel
)
from PySide6.QtCore import Qt
import sys
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("ウィンドウタイトル")
self.setGeometry(100, 100, 400, 300)
label = QLabel("テキスト", self)
label.setGeometry(0, 0, 400, 300)
label.setAlignment(Qt.AlignCenter)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
図5 基本的なチャットボットアプリのコード
from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget,
QVBoxLayout, QHBoxLayout, QPushButton,
QTextEdit, QLineEdit,
①
)
from PySide6.QtCore import Qt, QThread, Signal
import os, sys, re ②
import google.generativeai as genai
from dotenv import load_dotenv
from markdown2 import markdown
MAX_HISTORY_LENGTH = 10
class ChatWindow(QMainWindow):
def __init__(self):
super().__init__()
self.history = []
self.setWindowTitle("ChatBot")
self.setGeometry(100, 100, 600, 400)
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.main_layout = QVBoxLayout()
self.chat_layout = QVBoxLayout()
self.input_layout = QHBoxLayout()
self.file_layout = QHBoxLayout()
self.chat_display = QTextEdit()
self.chat_display.setReadOnly(True)
self.main_layout.addWidget(self.chat_display)
self.input_box = QLineEdit()
self.input_box.setPlaceholderText("ここにメッセージを入力")
self.input_layout.addWidget(self.input_box)
self.send_button = QPushButton("送信")
self.send_button.clicked.connect(self.handle_send)
self.input_layout.addWidget(self.send_button)
self.main_layout.addLayout(self.input_layout)
③
self.central_widget.setLayout(self.main_layout)
def handle_send(self):
user_message = self.input_box.text().strip()
if not user_message: return
self.send_button.setEnabled(False)
self.chat_display.append(f"<b>あなた:</b> {user_message}")
④
self.input_box.clear()
self.response_thread = BotResponseThread(
user_message, self.history ⑤
)
self.response_thread.response_ready.connect(
self.display_bot_response
)
self.response_thread.start()
def safe_markdown(self, text):
lines = text.splitlines()
formatted_lines = []
for i, line in enumerate(lines):
if re.match(r"^\s*(\d+\.\s+|[-*+]\s+)", line) and (
i + 1 < len(lines) and lines[i + 1].strip()
):
formatted_lines.append(line)
formatted_lines.append("")
else:
formatted_lines.append(line)
formatted_text = "\n".join(formatted_lines)
return markdown(formatted_text)
def display_bot_response(self, response_text):
html_content = self.safe_markdown(response_text)
self.chat_display.append(f"<b>Bot:</b> {html_content}")
self.send_button.setEnabled(True)
⑥
class BotResponseThread(QThread):
response_ready = Signal(str)
def __init__(self, user_message, history): ⑦
super().__init__()
self.user_message = user_message
⑧
self.history = history
def run(self):
response_text = self.get_bot_response(self.user_message) ⑨
self.response_ready.emit(response_text)
def get_bot_response(self, message): ⑩
input_text = message
self.history.append({"role": "user", "parts": [input_text]})
⑪
response = model.generate_content(self.history)
self.history.append(response.candidates[0].content)
if len(self.history) > MAX_HISTORY_LENGTH:
self.history.pop(0)
return response.text
def initialize_genai():
load_dotenv()
api_key = os.getenv("GOOGLE_API_KEY")
genai.configure(api_key=api_key)
return genai.GenerativeModel("gemini-1.5-flash")
if __name__ == "__main__":
model = initialize_genai()
app = QApplication(sys.argv)
window = ChatWindow()
window.show()
sys.exit(app.exec())
図9 図5の③で示す箇所に挿入するコード
self.file_label = QLabel("ファイルが選択されていません")
self.file_label.setAlignment(Qt.AlignLeft)
self.file_layout.addWidget(self.file_label)
self.upload_button = QPushButton("ファイルを選択")
self.upload_button.clicked.connect(self.handle_file_selection)
self.file_layout.addWidget(self.upload_button)
self.main_layout.addLayout(self.file_layout)
self.selected_file_path = None
図10 図5の④で示す箇所に挿入するコード
if self.selected_file_path:
self.chat_display.append(
f"<b>添付ファイル:</b> {self.selected_file_path}"
)
file_path = self.selected_file_path
self.selected_file_path = None
self.file_label.setText("ファイルが選択されていません")
else:
file_path = None
図11 図5の⑥で示す箇所に挿入するコード
def handle_file_selection(self):
file_dialog = QFileDialog()
file_path, _ = file_dialog.getOpenFileName(
self, "ファイルを選択"
)
if file_path:
self.selected_file_path = file_path
self.file_label.setText(
f"選択したファイル: {file_path.split('/')[-1]}"
)
図12 図5の⑪で示す箇所に挿入するコード
if file_path:
attached_file = genai.upload_file(path=file_path)
while attached_file.state.name == "PROCESSING":
time.sleep(10)
attached_file = genai.get_file(attached_file.name)
self.history.append(
{"role": "user", "parts": [attached_file]}
)
else:
pass
Hinemosの運用管理機能を提供する「Hinemosマネージャ」などのソフトウエアを、「Red Hat Enterprise Linux 9」が稼働するホストにインストールする手順を、こちらで紹介しています。
静的サイトジェネレータ「Hugo」およびバージョン管理システム「Git」のインストール、Gitのセットアップ、サイトの構築手順は、こちらから参照できます。
記事で紹介したコードを拡張して、アプリのボタンなどの見栄えを調整しました。コードを記したファイルはここからダウンロードできます(ファイルはZIPで圧縮しています)。
情報は随時更新致します。
2025年4月号特集3を読むための準備として、Linuxディストリビューション「Red Hat Enterprise Linux 9」(RHEL9)環境にHinemosをインストールしてエージェントを起動する方法を紹介します。このコンテンツは、2024年10月号のシェルスクリプトマガジンVol.92特集2を基にしています。以下では、インストール先のホスト名が「rhel-manager」、IPアドレスが「172.16.63.245」の場合を例に、作業手順を解説します。他のホスト名やIPアドレスを利用している場合には、適宜読み替えてください。
Hinemosのシステムは、「マネージャサーバ」「クライアント」「管理対象ノード」で構成されます。(図1)。

マネージャサーバは、Himemosの運用管理機能を提供するHinemosマネージャ(以下、マネージャ)が稼働するマシンです。管理対象の情報を保持したリポジトリと、監視やジョブといった各機能で扱うデータを保管するデータベースを保持します。
クライアントは、オペレータが利用するGUIベースの操作端末です。OSに専用ソフトウエアをインストールして使う「リッチクライアント」と、Webブラウザで操作できる「Webクライアント」の2種類があります。今回は、マネージャサーバにWebクライアントをインストールして、それをWebブラウザで使うことにします。
管理対象ノードは、監視やジョブ実行などを行うHinemosの管理対象となるマシンです。利用する機能によっては、管理対象ノードに「Hinemosエージェント」(以下、エージェント)やSNMP*サーバーなどのソフトウエアをインストールする必要があります。今回は、マネージャサーバ自身にエージェントをインストールして、管理対象ノードにします。
【SNMP】 リモート機器を監視、制御するためのネットワークプロトコル。Simple Network Management Protocolの略。
マネージャをインストールする前に、マネージャの動作に必要なパッケージをインストールします。rhel-managerに管理者(rootユーザー)でログインしてから、次のコマンドを実行してください。
# dnf install -y java-1.8.0-openjdk tar unzip vim ↵
# dnf install -y google-noto-sans-cjk-ttc-fonts java-1.8.0-openjdk-devel lsof net-snmp net-snmp-utils sysstat tcpdump wsmancli zip ↵
なお、2回目のdnfコマンドでインストールしているパッケージは必須ではありませんが、インストールが推奨されているものです。特別な理由がなければインストールすることをお勧めします。以下では、これらの推奨パッケージもインストールしたものとします。
続いて、次のコマンドを実行してロケール(言語設定)を日本語にします。
# dnf install -y glibc-langpack-ja ↵
# localectl set-locale LANG=ja_JP.UTF-8 ↵
さらにSELinux*を無効にします。SELinuxが有効になっていると、マネージャをインストールできないからです。SELinuxを無効にするには、「/etc/selinux/config」ファイルの「SELINUX」行を次のように書き換えてから、RHEL9を再起動します。
SELINUX=disabled
【SELinux】 強制アクセス制御と呼ばれるセキュリティ機能を提供するセキュリティモジュール。Security-Enhanced Linuxの略。
なお、同行が「SELINUX=enforcing」の場合には、SELinuxが有効になります。「SELINUX=permissive」の場合には、SELinuxによるアクセス制御は無効になりますが、監査ログの記録は有効になります。
マネージャサーバがクライアントや管理対象ノードからの接続を受け付けられるように、ファイアウォールの設定もします。RHEL9では、デフォルトでファイアウォールが有効になっています。そのため、ファイアウォールを無効にするか、接続を待ち受けるポートを開放する必要があります。
ファイアウォールを無効にするには、次のコマンドを実行します。
# systemctl stop firewalld ↵
# systemctl disable firewalld ↵
ファイアウォールを有効にしておきたい場合には、ポートを開放する設定をします。例えば、TCPの22番ポートと80番ポートを開放するには、次のようにfirewalldコマンドを実行します。
# firewall-cmd --permanent --add-port={22/tcp,80/tcp} ↵
同様の手順で、表1に示すポートをすべて開放する設定をしてください*1。設定後、次のコマンドを実行することでファイアウォールに反映されます。
# firewall-cmd --reload ↵
*1 開放するポートの詳細については、「Hinemos ver.7.1 基本機能マニュアル」(https://github.com/hinemos/hinemos/releases/download/v7.1.0/ja_Base_Linux_7.1_rev1.pdf)の「2.2.9 ネットワークの要件」を参照してください。
表1 マネージャサーバで開放すべきポート
| プロトコル | リッチクライアントや管理対象ノードからの接続待ち受けポート | Webクライアントの接続待ち受けポート |
|---|---|---|
| TCP | 22,8080,8081,8082,8083,8443,8444,8445,24001 | 80,443 |
| UDP | 161,162,514 | – |
最後に、マネージャサーバのホスト名の名前解決ができることを確認します。名前解決ができない場合、マネージャがうまく動作しないことがあります。
次のようにpingコマンドを実行して、自分自身から応答があることを確認してください。
# ping rhel-manager ↵
「Name or service not known」と表示される場合には、名前解決ができていません。そのときには、次のコマンドを実行して名前解決用の情報を登録します。他のホスト名やIPアドレスを利用している場合には、適宜読み替えてください。
# echo 172.16.63.245 rhel-manager >> /etc/hosts ↵
以上で事前準備は完了です。
マネージャやWebクライアント用コンポーネント、管理対象ノードにインストールするエージェントのパッケージは、GitHubリポジトリのリリースページ(https://github.com/hinemos/hinemos/releases)から入手できます。ここから、最新版をインストールしてください。
今回は、2024年9月上旬時点の最新版である「Hinemos ver.7.1.0」のパッケージをインストールします。マネージャのパッケージは「hinemos-7.1-manager-7.1.0-1.el9.x86_64.rpm」、Webクライアント用コンポーネントのパッケージは「hinemos-7.1-web-7.1.0-1.el9.x86_64.rpm」です。これらを「/tmp」ディレクトリなどにダウンロードし、ダウンロードしたディレクトリで次のコマンドを実行するとインストールできます。
# rpm -ivh hinemos-7.1-manager-7.1.0-1.el9.x86_64.rpm ↵
# rpm -ivh hinemos-7.1-web-7.1.0-1.el9.x86_64.rpm ↵
GitHubリポジトリからも直接インストールできます。それには、次のようにrpmコマンドを実行します。
# rpm -ivh https://github.com/hinemos/hinemos/releases/download/v7.1.0/hinemos-7.1-manager-7.1.0-1.el9.x86_64.rpm ↵
# rpm -ivh https://github.com/hinemos/hinemos/releases/download/v7.1.0/hinemos-7.1-web-7.1.0-1.el9.x86_64.rpm ↵
パッケージのインストール後、次のコマンドを実行するとマネージャとWebクライアントが起動します。
# systemctl start hinemos_manager ↵
# systemctl start hinemos_web ↵
続いて、マネージャサーバにエージェントを導入して、自分自身を管理対象ノードとして取り扱えるようにします*2。それにはまず、次のコマンドを実行して、エージェントの実行に必要なパッケージをインストールします。
# dnf install -y openssh-clients ↵
*2 マネージャサーバ以外のコンピュータにエージェントをインストールする場合の作業手順については、「Hinemos ver.7.1 基本機能マニュアル」の「2.2.5Hinemosエージェントの要件」などを参照してください。
ファイアウォールも設定します。ファイアウォールを無効にするか、表2に示すポートを開放してください。
表2 管理対象ノードで開放すべきポート
| プロトコル | 開放すべきポート |
|---|---|
| TCP | 22 |
| UDP | 161,24005 |
さらに、次のコマンドを実行してsnmpdサービス(SNMPサーバー)を起動します。これにより、マネージャサーバのCPUやメモリーなどのリソース情報をHinemosが取得できるようになります。
# systemctl start snmpd ↵
ここまでの作業で、エージェントをインストールする準備が整いました。エージェントのパッケージは「hinemos-7.1-agent-7.1.0-1.el.noarch.rpm」です。これを「/tmp」ディレクトリなどにダウンロードし、ダウンロードしたディレクトリで次のコマンドを実行するとインストールできます。
# HINEMOS_MANAGER=172.16.63.245 rpm -ivh hinemos-7.1-agent-7.1.0-1.el.noarch.rpm ↵
シェル変数HINEMOS_MANAGERには、マネージャサーバのIPアドレスを設定します。
先ほどと同様に、GitHubリポジトリからも直接インストールできます。それには、次のようにrpmコマンドを実行します。
# HINEMOS_MANAGER=172.16.63.245 rpm -ivh https://github.com/hinemos/hinemos/releases/download/v7.1.0/hinemos-7.1-agent-7.1.0-1.el.noarch.rpm ↵
最後に、次のコマンドを実行すればエージェントが起動します。
# systemctl start hinemos_agent ↵
連載「多種多様な文書作成が可能 Markdownを活用する」の第6回(シェルスクリプトマガジン Vol.94で掲載)で作成したWebサイトを第7回でも使用します。第7回で紹介する作業が進めやすいように、HugoとGitのインストール、Gitの初期設定、Webサイトの構築手順を以下にまとめてました。
なお、手順自体は、第6回の「Hugoのインストール」「HugoによるWebサイトの構築」で紹介した内容と同じです。
Hugoを使用するには、次のソフトウエアをインストールします。
・Hugo本体
・Git
まず、Hugo本体をインストールします。以降は、導入先のパソコンのOSとしてWindows 11を前提に進めます。導入後は、macOSやLinuxでも同じようにHugoを利用できます。また、Markdownを記述するためのテキストエディタとしてVisual Studio Code(VSCode)を利用します。VSCodeは、あらかじめ
インストールされていることを前提とします。
[Windows]キーを押しながら[X]キーを押すと、図1のようなメニューが表示されます。このメニューの「ターミナル( 管理者)」を選択します*1。選択後に、「ユーザー アカウント制御」ダイアログが表示されるので、「はい」ボタンをクリックします(図2)。管理者権限付きのPowerShellが起動します*2。
図1「ターミナル(管理者)」を選択 図2「ユーザーアカウント制御」ダイアログ


PowerShellで、次のコマンドを実行してHugo本体をインストールします。
> winget install -e --id Hugo.Hugo.Extended ↵
図3のようにインストールが実行されます。最後に「インストールが完了しました」のメッセージが表示されれば、インストール完了です。
図3 Hugoのインストール

右上の「×」ボタンをクリックして、管理者権限付きのPowerShellを閉じます。再び、[Windows]キーを押しながら[X]キーを押し、表示されるメニューから「ターミナル」(もしくは「Windows PowerShell」)を選択します。一般権限でPowerShellが起動します。
PowerShellで、次のコマンドを実行します。
> hugo version ↵
次のようにバージョン番号の文字列が表示されれば、Hugoが利用できます。
hugo v0.139.4-3afe91d4b1b069abbedd6a96ed755b1e12581dfe+extended windows/amd64 BuildDate=2024-12-09T17:45:23Z VendorInfo=gohugoio
右上の「×」ボタンをクリックして、PowerShellを閉じます。
続いて、Gitをインストールします。その前に、Gitがすでにインストールされているかどうかを確認します。[Windows]キーを押しながら[X]キーを押し、表示されるメニューから「ターミナル(管理者)」(もしくは「Windows PowerShell( 管理者)」)を選択します。選択後、「ユーザー アカウント制御」ダイアログが表示されたら、「はい」ボタンをクリックします。管理者権限付きのPowerShellが起動します。
このPowerShellで、次のコマンドを実行します。
> git --version ↵
次のような形式でバージョン番号が表示されれば、Gitがインストールされています*4。このような状態ならGitのインストールをスキップしてください。
git version 2.47.0.windows.2
一方、次のように表示された場合は、Gitがインストールされていません。
git : 用語 'git' は、コマンドレット、関数、スクリプトファイル、または操作可能なプログラムの名前として認識されません。
次のコマンドでGitをインストールします。
> winget install --id Git.Git -e --source winget ↵
途中でダイアログが表示されますが、そのまま待ってください。最後に「インストールが完了しました」のメッセージが表示されればインストール完了です。
右上の「×」ボタンをクリックして、管理者権限付きのPowerShellを閉じます。[Windows]キーを押しながら[X]キーを押し、表示されるメニューから「ターミナル」(もしくは「Windows PowerShell」)を選択します。一般権限でPowerShellが起動します。
再度、
> git --version ↵
を実行し、バージョン番号が表示されればインストール完了です。
Gitを初めて使用する場合、次のコマンドで自身の氏名とメールアドレスを設定します。氏名は、全角の平仮名や片仮名、漢字ではなく、半角のアルファベットやスペース、数字、記号で入力します。
git config --global user.name "氏名"
git config --global user.email "メールアドレス"
ここで入力した氏名とメールアドレスは、次回の連載で使用する、ソースコードのホスティングサービス「GitHub」で公開される情報となります。したがって、自分の本名を隠したい場合、氏名はハンドルネーム(インターネット上のニックネーム)で構いません。
> git config --global user.name "free writer" ↵
また、記事内で紹介した方法でGitHub専用のメールアドレスを発行します。まだ、GitHubのアカウントを持っていない場合は、次のように仮のメールアドレスを設定しておいてください。すでにGitHubアカウントを持っている場合は、そのアカウントに登録しているメールアドレスを設定します。
> git config --global user.email "dummy@example.com" ↵
Power Shellで次のコマンドを実行します。
> cd ~ ↵
> hugo new site first-hugo --format yaml ↵
> cd first-hugo ↵
> git init ↵
> git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke ↵
> hugo new content content/posts/first-post.md ↵
> hugo new content content/posts/public-post.md ↵
以上のコマンドを実行した後に、次のコマンドでVSCode(Visual Studio Code)を起動します*3。
> code . ↵
「このフォルダー内のファイルの作成者を信頼しますか?」というダイアログが表示されたら「はい、作成者を信頼します」をクリックします。
VSCodeが起動したら、VSCode上で次の三つのファイルを書き換えます。
「hugo.yaml」ファイルでは、「languageCode」「title」「theme」の行を書き換えます。
baseURL: https://example.org/
languageCode: ja-jp
title: "はじめてのHugoサイト"
theme: ananke
「first-post.md」ファイルでは、「title」の行を書き換えて、「# はじめての記事」から始まる本文を追加します。
---
date: '2024-11-30T13:02:12+09:00'
draft: true
title: 'はじめての記事'
---
# はじめての記事
こんにちは!
「public-post.md」ファイルでは、「draft」と「title」の行を書き換えて、「# 公開テスト」から始まる本文を追加します。
---
date: '2024-11-30T13:50:00+09:00'
draft: false
title: '公開テスト'
---
# 公開テスト
この記事は公開されます。
first-post.mdファイルは下書き状態(draft: true)であり、public-post.mdファイルは公開状態(draft: false)にしています。
次のコマンドを実行してWebサーバーを起動します。
> hugo server ↵
コマンドを実行すると、次のような表示がされます。
Built in 252 ms
Environment: "development"
Serving pages from disk
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop
ここで表示されているURLの「http://localhost:1313/」(「1313」は環境によって異なる)をコピーし、Webブラウザを開いてアドレスバーに貼り付けてアクセスします。「公開テスト」のコンテンツのみが表示され、「はじめての記事」のコンテンツは表示されません。
PowerShellに戻って[Ctrl]キーを押しながら[C]キーを押し、Webサーバーを停止します。
最後に、次のコマンドを実行して、Webサイトをビルドします。
> hugo ↵
*1 Windows 10では「Windows PowerShell( 管理者)」を選択します。
*2 「WSL」(Windows Subsystem for Linux)でLinuxディストリビューション「Ubuntu」などを導入している場合は、PowerShellではなくWSLが優先されます。その場合は、ターミナル上部タブの右にある下向き矢印のアイコンをクリックしてメニューから「Windows PowerShell」を選んでください。以降、PowerShellのターミナルを開くときはこの手順を実施してください。
*3 VSCodeインストール時の「追加タスクの選択」「- その他」で、「PATHへの追加(再起動後に使用可能)」にチェックが付けられていないとcodeコマンドを実行できません(デフォルトでチェックあり)。なお、チェックを付けてVSCodeを再インストールすれば、使えます。
著者:前佛雅人
さくらインターネットが提供する「さくらのレンタルサーバ」を利用すれば、「WordPress」を使ったブログサイトなどの制約が少ない、自分だけの情報発信拠点をインターネット上に構築できます。ファイル共有やWikiなどのアプリケーションも思い通りにカスタマイズして利用できます。本特集では、さくらのレンタルサーバを活用した情報発信の方法を紹介します。
シェルスクリプトマガジン Vol.94は以下のリンク先でご購入できます。![]()
![]()
図8 サンプルHTMLファイル「index.html」の内容
<!DOCTYPE html>
<html>
<head><title>サンプルWebページ</title></head>
<body>
<h1>これはサンプルWebページです</h1>
<p>次に示すのはカワセミの写真です。</p>
<p><img src="image.jpg" width="320"></p>
</body>
</html>
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無
線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第8回は前回に引き続き、「Raspberry Pi Pico 2」を扱います。
シェルスクリプトマガジン Vol.94は以下のリンク先でご購入できます。![]()
![]()
図10 LED点滅のプログラム(blink.c)
#include <stdio.h>
#include "pico/stdlib.h"
int main()
{
stdio_init_all();
int flag = 0;
const unit LED_PIN = PICO_DEFAULT_LED_PIN;
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
while(true) {
gpio_put(LED_PIN, flag ^= 1);
sleep_ms(500);
}
return 0;
}
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第24回では、高校で学ぶレベルの数学を用いて、問題を最適にする解は何かを探索します。題材は、線形計画法と組合せ最適問題の二つです。
シェルスクリプトマガジン Vol.94は以下のリンク先でご購入できます。![]()
![]()
図3 図1の問題を解くPythonコード
from ortools.linear_solver import pywraplp
# ソルバーの定義
solver = pywraplp.Solver.CreateSolver('GLOP')
# 変数の準備
x = solver.NumVar(0, solver.infinity(), 'x')
y = solver.NumVar(0, solver.infinity(), 'y')
# 目的関数と制約条件の定義
solver.Maximize(400 * x + 300 * y)
solver.Add(60 * x + 40 * y <= 3200)
solver.Add(20 * x + 30 * y <= 2000)
solver.Add(20 * x + 10 * y <= 1000)
# 問題を解く
status = solver.Solve()
# 結果を出力
if status == pywraplp.Solver.OPTIMAL:
print(f'解:')
print(f'x = {x.solution_value()}')
print(f'y = {y.solution_value()}')
else:
print('最適解は存在しません')
図4 CBCソルバーを使うように書き換えた図3のPythonコード
(略)
# ソルバーの定義
solver = pywraplp.Solver.CreateSolver('CBC')
# 変数の準備
x = solver.IntVar(0, solver.infinity(), 'x')
y = solver.IntVar(0, solver.infinity(), 'y')
(略)
図6 データを準備するためのPythonコード
teachers = ['田中', '山本', '竹村', '飯島', '佐藤']
days = ['月', '火', '水', '木', '金']
periods = ['1限', '2限', '3限', '4限']
図7 lessons連想配列を作成するPythonコード
lessons = { (t, d, p): \
model.NewBoolVar('lesson_%s_%s_%s' % (t, d, p)) \
for t in teachers for d in days for p in periods }
図8 lessons連想配列の内容
{('田中', '月', '1限'): lesson_田中_月_1限(0..1),
('田中', '月', '2限'): lesson_田中_月_2限(0..1),
('田中', '月', '3限'): lesson_田中_月_3限(0..1),
('田中', '月', '4限'): lesson_田中_月_4限(0..1),
('田中', '火', '1限'): lesson_田中_火_1限(0..1),
('田中', '火', '2限'): lesson_田中_火_2限(0..1),
(略)
図9 「同時に2人以上の先生が授業することはない」制約条件を与えるPythonコード
for d in days:
for p in periods:
model.Add(sum(lessons[(t, d, p)] for t in teachers) == 1)
図10 「先生は1日に1コマだけ授業を担当する」制約条件を与えるPythonコード
for t in teachers:
for d in days:
model.Add(sum(lessons[(t, d, p)] for p in periods) <= 1)
図11 「担当時間数に偏りが出ないように配置する」制約条件を与えるPythonコード
for t in teachers:
model.Add(sum(lessons[(t, d, p)] \
for d in days for p in periods) == 4)
図12 requests連想配列を定義するコードの例
requests = {
'田中': {
'月': {'1限': 0, '2限': 0, '3限': 1, '4限': 0},
'火': {'1限': 0, '2限': 0, '3限': 0, '4限': 0},
'水': {'1限': 0, '2限': 1, '3限': 0, '4限': 0},
'木': {'1限': 0, '2限': 0, '3限': 0, '4限': 1},
'金': {'1限': 0, '2限': 1, '3限': 0, '4限': 0} },
'山本': {
'月': {'1限': 1, '2限': 0, '3限': 0, '4限': 0},
'火': {'1限': 0, '2限': 1, '3限': 0, '4限': 0},
'水': {'1限': 0, '2限': 0, '3限': 1, '4限': 0},
'木': {'1限': 1, '2限': 0, '3限': 0, '4限': 0},
'金': {'1限': 0, '2限': 0, '3限': 0, '4限': 0} },
'竹村': {
'月': {'1限': 0, '2限': 1, '3限': 0, '4限': 0},
'火': {'1限': 0, '2限': 1, '3限': 0, '4限': 0},
'水': {'1限': 1, '2限': 0, '3限': 0, '4限': 0},
'木': {'1限': 1, '2限': 0, '3限': 0, '4限': 0},
'金': {'1限': 0, '2限': 0, '3限': 0, '4限': 0} },
'飯島': {
'月': {'1限': 0, '2限': 0, '3限': 0, '4限': 1},
'火': {'1限': 0, '2限': 0, '3限': 0, '4限': 0},
'水': {'1限': 0, '2限': 1, '3限': 0, '4限': 0},
'木': {'1限': 0, '2限': 1, '3限': 0, '4限': 0},
'金': {'1限': 1, '2限': 0, '3限': 0, '4限': 0} },
'佐藤': {
'月': {'1限': 0, '2限': 0, '3限': 0, '4限': 0},
'火': {'1限': 0, '2限': 0, '3限': 1, '4限': 0},
'水': {'1限': 0, '2限': 0, '3限': 1, '4限': 0},
'木': {'1限': 0, '2限': 1, '3限': 0, '4限': 0},
'金': {'1限': 0, '2限': 0, '3限': 0, '4限': 1} }
}
図13 目的関数を定義するPythonコード
model.Maximize( \
sum(requests[t][d][p] * lessons[(t, d, p)] \
for t in teachers for d in days for p in periods))
図14 結果を表示するPythonコード
for d in days:
print(d + "曜日:")
for p in periods:
for t in teachers:
if solver.Value(lessons[(t, d, p)]) == 1:
print(t+'先生が'+p+'の授業を担当する。', end="")
print() if requests[t][d][p] == 1 else print('※')
print()
図16 「先生の希望は少なくとも3コマはかなえる」制約条件を与えるPythonコード
for t in teachers:
model.Add(sum(lessons[(t, d, p)]*requests[t][d][p] \
for d in days for p in periods) >= 3)
著者:川本真子
私は最近、To Doリストを管理するGU(I Graphical User Interface)アプリをPythonで作成しました。作成には、Pythonの標準GUIライブラリの「Tkinter」(https://docs.python.org/ja/3/library/tkinter.html)を用いています。今回は、そのGUIアプリの実装について紹介します。
シェルスクリプトマガジン Vol.94は以下のリンク先でご購入できます。![]()
![]()
図7 フレーム配置用のコード
# フレームの作成
fr_title = tk.Frame(root, width=650, height=40, bd=5, relief='solid')
fr_genre = tk.Frame(root, width=650, height=45, bd=5, relief='solid')
fr_add = tk.Frame(root, width=650, height=70, bd=5, relief='solid')
fr_op = tk.Frame(fr_add, width=100, height=65, bd=5, relief='solid')
fr_must = tk.Frame(root, width=325, height=260, bd=5, relief='solid')
fr_mlist = tk.Frame(fr_must, width=315, height=190, bd=5, relief='solid')
fr_want = tk.Frame(root, width=325, height=260, bd=5, relief='solid')
fr_wlist = tk.Frame(fr_want, width=315, height=190, bd=5, relief='solid')
# フレームのサイズを固定
fr_title.grid_propagate(False)
fr_add.grid_propagate(False)
fr_op.grid_propagate(False)
fr_genre.grid_propagate(False)
fr_must.grid_propagate(False)
fr_mlist.grid_propagate(False)
fr_want.grid_propagate(False)
fr_wlist.grid_propagate(False)
# フレームを配置
fr_title.grid(row=0, column=0, columnspan=2, sticky=tk.EW)
fr_genre.grid(row=1, column=0, columnspan=2, sticky=tk.EW)
fr_add.grid(row=2, column=0, columnspan=2, sticky=tk.EW)
fr_must.grid(row=3, column=0)
fr_want.grid(row=3, column=1)
図9 グローバル変数を定義するコード
btnge = 'sc'
color = '#A8EEFF'
la_text = '学校のTo Do'
textlist = []
図10 ウィジェット配置用のコード
# fr_genreのウィジェット
buttonsc = tk.Button(fr_genre, text="学校", state=tk.DISABLED, bg='#A8EEFF', font=font1,
command=lambda:genre(buttonli, buttonsc, 'sc', la_title, label_ge, label_m, label_w))
buttonli = tk.Button(fr_genre, text="生活", state=tk.NORMAL, bg='#FFF9A8', font=font1,
command=lambda:genre(buttonsc, buttonli, 'li', la_title, label_ge, label_m, label_w))
# fr_titleのウィジェット
la_title = tk.Label(fr_title, text="今日のTo Do", bg=color, font=font1)
# fr_addのウィジェット
label_ge = tk.Label(fr_add, text=la_text, bg=color, font=font1)
entry = tk.Entry(fr_add, width=30, font=font1)
rad = tk.StringVar()
rad.set('Must')
radio_m = tk.Radiobutton(fr_op, text='Must', font=font1, value='Must', variable=rad)
radio_w = tk.Radiobutton(fr_op, text='Want', font=font1, value='Want', variable=rad)
button_add = tk.Button(fr_add, text="追加", font=font1, command = lambda:add(rad))
# fr_mustのウィジェット
label_m = tk.Label(fr_must, text="Must", bg=color, font=font1)
de_must = tk.Button(fr_must, text="削除", font=font1, command=lambda:deletion('Must'))
# fr_wantのウィジェット
label_w = tk.Label(fr_want, text="Want", bg=color, font=font1)
de_want = tk.Button(fr_want, text="削除", font=font1, command=lambda:deletion('Want'))
# ウィジェットと残りのフレームの配置
la_title.grid()
buttonsc.grid(row=0, column=0)
buttonli.grid(row=0, column=1)
label_ge.grid(row=0, column=0)
entry.grid(row=0, column=1)
fr_op.grid(row=0, column=2)
button_add.grid(row=0, column=3)
radio_m.grid(row=0, column=0)
radio_w.grid(row=1, column=0)
label_m.grid()
fr_mlist.grid()
de_must.grid()
label_w.grid()
fr_wlist.grid()
de_want.grid()
図12 クラスと関数を定義するコード
class Text:
def __init__(self, box, de, fr, te, ch, pr, ge):
self.box = box # チェックボックスの変数
self.de = de # 完了は「fin」、未完了は「unfin」
self.fr = fr # チェックボックスの表示フレーム
self.te = te # To Doの内容
self.ch = ch # チェックの有無
self.pr = pr # MustかWantか
self.ge = ge # 学校か生活か
def add(rad):
text = tk.StringVar()
text.set((entry.get()))
entry.delete(0, tk.END)
check = tk.StringVar()
check.set('0')
if rad.get() == 'Must':
frame = fr_mlist
priority = 'Must'
else:
frame = fr_wlist
priority = 'Want'
box = tk.Checkbutton(frame, textvariable=text,
variable=check, font=font1)
tex=Text(box, 'unfin' ,frame, text, check, priority, btnge)
textlist.append(tex)
todo()
def todo():
i = 0
for t in textlist:
if t.de == 'unfin':
if t.ge == btnge:
t.box.grid(row=i, sticky = tk.W)
i += 1
def deletion(pri):
for t in textlist:
if t.pr == pri:
if t.ge == btnge:
if t.ch.get() == '1':
t.de = 'fin'
t.box.destroy()
todo()
def genre(button, buclick, gen, la_title,
label_ge, label_m, label_w):
button['state'] = tk.NORMAL
buclick['state'] = tk.DISABLED
global btnge
global color
global la_text
btnge = gen
if (gen == 'sc'):
color = '#A8EEFF'
la_text = '学校のTo Do'
else:
color = '#FFF9A8'
la_text = '生活のTo Do'
la_title['bg'] = color
label_ge['bg'] = color
label_ge['text'] = la_text
label_m['bg'] = color
label_w['bg'] = color
# チェックボックスを破棄して再作成
for t in textlist:
t.box.destroy()
t.box = tk.Checkbutton(t.fr, textvariable=t.te,
variable = t.ch, font=font1)
todo()

004 レポート AIチャット「Grok」がXで無料利用
005 レポート 完全オープンなLLM「OpenCoder」
006 製品レビュー ウエアラブル「ViXion01」
007 NEWS FLASH
008 特集1 パソコン仮想化ソフトのVMware Workstation Pro/麻生二郎
019 インタビュー Infinidat Japan 山田秀樹氏
020 特集2 さくらのレンタルサーバで始める情報発信ガイド/前佛雅人 コード掲載
028 特集3 Red Hat Enterprise Linux AI/村木暢哉
034 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
038 Markdownを活用する/藤原由来
046 Pythonあれこれ/飯尾淳 コード掲載
052 香川大学SLPからお届け!/川本真子 コード掲載
058 中小企業手作りIT化奮戦記/菅雄一
064 SSHによるリモート管理入門/大津真
072 Techパズル/gori.sh
073 コラム「ユニケージ流のデータ設計法」/シェル魔人

004 レポート Python用の国産GUIライブラリ公開 コード掲載
005 レポート Open Source Summit + AI_dev開催
製品レビュー 組み込み機器「ラズパイ AI HAT+」
007 NEWS FLASH
008 特集1 Streamlitで作るデータ分析Webアプリ/邑川真也 コード掲載
018 特集2 Oracleデータベース入門/宮本拓弥、百木和美、武井菜々子、北村海人
034 イベントレポート「第35回 プロコン」/塩田哲也
036 Raspberry Pi Pico W/WHで始める電子工作/米田聡
040 Pythonあれこれ/飯尾淳 コード掲載
046 Markdownを活用する/藤原由来 コード掲載
056 中小企業手作りIT化奮戦記/菅雄一
062 香川大学SLPからお届け!/原口莉奈 コード掲載
068 これだけは覚えておきたいLinuxコマンド/大津真
076 Techパズル/gori.sh
077 コラム「仕組みを作るSE」/シェル魔人
著者:末安 泰三
クジラ飛行机氏は2024年11月7日、手軽にGUIを作成可能なPythonライブラリ「TkEasyGUI」のバージョン1.0系列をリリースした。バージョン1.0系列では、複数項目を入力できるフォームダイアログが追加されるなどの機能拡充やバグ修正が施されている。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。![]()
![]()
図1 個人情報を入力させるウィンドウを作成するコードの例
import TkEasyGUI as eg
layout = [
[eg.Text('氏名', size=(15, 1)), eg.InputText()],
[eg.Text('住所', size=(15, 1)), eg.InputText()],
[eg.Text('電話番号', size=(15, 1)), eg.InputText()],
[eg.Text()],
[eg.Button('OK'), eg.Button('Cancel')]
]
with eg.Window('個人情報を入力', layout) as window:
for event in window.event_iter():
pass
図3 フォームダイアログを表示するコードの例
iimport TkEasyGUI as eg
form = eg.popup_get_form(["氏名", "住所", "電話番号"],
title="個人情報の入力")
著者:邑川 真也
「Streamlit」は、OSS(オープンソースソフトウエア)のPython向けフレームワーク(ライブラリ)です。データの可視化機能や分析機能、ダッシュボードなどを備えるWebアプリケーションを、Pythonを使って迅速かつ容易に作成できます。本特集では、さまざまなWebアプリケーションを実際に作成しながら、基本的なデータの可視化方法から、ノーコードでデータ分析を実現する方法まで、Streamlitのさまざまな活用方法を紹介します。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。![]()
![]()
図2 HTMLとCSS、JavaScriptを用いて棒グラフを描くコードの例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<title>Bar Chart Example</title>
<style>
.chart-container {
width: 70%;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="chart-container">
<canvas id="myBarChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
var ctx = document.getElementById('myBarChart').getContext('2d');
var myBarChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['January', 'February', 'March',
'April', 'May', 'June', 'July'],
datasets: [{
label: 'Sample Data',
data: [65, 59, 80, 81, 56, 55, 40],
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
</script>
</body>
</html>
図3 Streamlitを用いて棒グラフを描くコードの例
import streamlit as st
import pandas as pd
import altair as alt
data = pd.DataFrame({
'Month': ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
'Value': [65, 59, 80, 81, 56, 55, 40]
})
chart = alt.Chart(data).mark_bar().encode(
x='Month',
y='Value',
color=alt.value('steelblue')
)
st.title('Bar Chart Example')
st.altair_chart(chart, use_container_width=True)
図6 シンプルなWebアプリケーションのコード
import streamlit as st
# タイトルを表示
st.title("シェルスクリプトマガジン")
図9 架空の株価データを生成して表示するコード
import streamlit as st
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
# 株価データを生成(仮のデータ)
dates = [datetime.today() - timedelta(days=i) for i in range(14)]
dates.reverse()
data = {
'Date': dates,
'Google': np.random.uniform(2700, 3000, 14),
'Apple': np.random.uniform(140, 160, 14),
'Meta': np.random.uniform(320, 350, 14),
'Amazon': np.random.uniform(3300, 3500, 14)
}
# データフレームに変換
df = pd.DataFrame(data)
df.set_index('Date', inplace=True)
# タイトルを表示
st.title("株価データの可視化")
# データフレームの表示
st.dataframe(df)
図14 「data.csv」ファイルに記述するデータ(抜粋)
Case ID,Company,Inquiry Summary,Status,Start Date,Severity
1,Acme Corp,大規模データセットに対するクエリの最適化について,Open,2024-05-19,Sev4
2,Globex Inc,DWHのベストプラクティスについて,Open,2024-05-26,Sev3
3,Globex Inc,データロード時のエラー,Open,2024-05-21,Sev3
4,Soylent Corp,新機能の有効化について,Open,2024-05-22,Sev2
5,Soylent Corp,レプリケートに失敗しています,Open,2024-05-22,Sev3
(略)
図15 ダッシュボードアプリのコード
import streamlit as st
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# ページ設定
st.set_page_config(
page_title="サポートデスクダッシュボード",
initial_sidebar_state="expanded",
layout="wide"
)
st.title("サポートデスクダッシュボード")
# チケット一覧を表示
st.subheader("チケット一覧")
# データ読み込み
df = pd.read_csv("data.csv")
with st.container(border=True, height=100):
filter1, filter2 = st.columns(2)
with filter1:
# ステータスによるフィルタ
status_list = df['Status'].unique()
selected_status = st.multiselect("ステータス",
options=status_list,
default=status_list)
with filter2:
# 重要度によるフィルタ
severity_list = df['Severity'].unique()
selected_severity = st.multiselect("重要度",
options=severity_list,
default=severity_list)
# フィルタを適用
if selected_status or selected_severity:
df = df[(df['Status'].isin(selected_status) &
df['Severity'].isin(selected_severity))]
# データを表示
st.dataframe(df, use_container_width=True)
# グラフを横並びに表示するレイアウトを作成
st.markdown("")
col1, col2 = st.columns(2)
# 縦棒グラフでステータスの数を表示
with col1:
st.subheader("チケットごとのステータス集計")
fig, ax = plt.subplots()
status_counts = df["Status"].value_counts()
ax.bar(status_counts.index, status_counts.values,
color="skyblue")
ax.set_xlabel("Status")
ax.set_ylabel("Count")
ax.set_title("Tickets by Status")
with st.container(border=True):
st.pyplot(fig)
# 縦棒グラフで重要度の数を表示
with col2:
st.subheader("チケットごとの重要度集計")
severity_counts = df["Severity"].value_counts()
fig, ax = plt.subplots()
ax.bar(severity_counts.index, severity_counts.values, color="coral")
ax.set_xlabel("Severity")
ax.set_ylabel("Count")
ax.set_title("Tickets by Severity")
with st.container(border=True):
st.pyplot(fig)
# CSVダウンロード
csv = df.to_csv(index=False)
st.download_button(
label="Download CSV",
data=csv,
file_name='sample_data.csv',
mime='text/csv'
)
図17 「config.toml」ファイルに記述するテーマ設定の例
[theme]
primaryColor = "#F14143" # プライマリカラー
backgroundColor = "#101216" # 背景色
secondaryBackgroundColor = "#191B20" # セカンダリ背景色
textColor = "#F9F9F9" # テキストカラー
font = "sans serif" # フォント
図18 「pygw_test.py」ファイルに記述するコード
from pygwalker.api.streamlit import StreamlitRenderer
import pandas as pd
import streamlit as st
# Streamlitの画面幅を調整
st.set_page_config(
page_title="Use Pygwalker In Streamlit",
layout="wide"
)
# データの取得
df = pd.read_csv("https://kanaries-app.s3.ap-northeast-1.amazonaws.com/public-datasets/bike_sharing_dc.csv")
# PYGWalkerによる可視化
pyg_app = StreamlitRenderer(df)
pyg_app.explorer()
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第23回では、機械学習ライブラリ「scikit-learn」で何ができるのかを紹介した上で、データを二つのクラスに分類するPythonプログラムを作成します。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。![]()
![]()
図9 テストデータを作成するPythonコード
import pandas as pd
from sklearn.datasets import make_classification
(data, label) = make_classification(n_samples=1000,
n_features=2, n_classes=2,
n_informative=2,
n_redundant=0,
random_state=133)
df = pd.DataFrame({'x1':[d[0] for d in data],
'x2':[d[1] for d in data],
'label':label})
df.plot.scatter(x='x1', y='x2',
c=df['label'].map({0:'blue', 1:'red'}), s=5)
図10 LinearSVCを用いて学習と分類をするPythonコード
from sklearn.svm import LinearSVC
from sklearn.metrics import accuracy_score
clf = LinearSVC(random_state=0, dual='auto', tol=1e-5)
clf.fit(data, label)
accuracy_score(clf.predict(data), label)
図11 分離面を描画するPythonコード
from mlxtend.plotting import plot_decision_regions
plot_decision_regions(data, label, clf=clf)
図13 NuSVCを用いて学習と分類をするPythonコード
from sklearn.svm import NuSVC
clf = NuSVC(nu=0.1, random_state=0)
clf.fit(data, label)
accuracy_score(clf.predict(data), label)
図15 SVCを用いて学習と分類をするPythonコード(その1)
from sklearn.svm import SVC
clf = SVC(gamma=0.1, random_state=0)
clf.fit(data, label)
accuracy_score(clf.predict(data), label)
図17 SVCを用いて学習と分類をするPythonコード(その2)
from sklearn.svm import SVC
clf = SVC(gamma=0.1, random_state=0)
clf.fit(data, label)
accuracy_score(clf.predict(data), label)
著者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。今回は、プレゼンテーション用のスライドを作成するためのプレゼンテーションアプリ「Marp」について解説します。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。![]()
![]()
図1 Marp用にMarkdownで記述したテキストの例
---
marp: true
---
# 美味しいカレーの作り方
シェル花子
---
# 材料
- 玉ねぎ
- にんじん
- じゃがいも
- 肉
- カレールー
- 水
---
# 作り方
1. 材料を切る
2. 肉を炒める
3. 野菜を加える
4. 水を加えて煮る
5. カレールーを入れる
図8 見出しレベル1をスライドの区切りにしたMarkdown形式のテキスト例
---
marp: true
headingDivider: 1
---
# 美味しいカレーの作り方
## シェル花子
# 材料
## ここに
### 材料を書く
# 作り方
## 適当に作る
図11 スライドの背景や文字色を変更したMarkdown形式のテキスト例
---
marp: true
---
# 通常のスライド
---
<!--
_backgroundColor: black
_color: white
-->
# 背景が黒く文字が白いスライド
---
<!--
_backgroundImage: "linear-gradient(to bottom, #67b8e3, #0288d1)"
_color: white
-->
# 背景が青いグラデーションのスライド
---
<!--
_backgroundImage: url('cat1.png')
-->
# 背景が画像のスライド
図13 背景画像表示とぼかしフィルタ適用のMarkdown形式のテキスト例
---
marp: true
---
# 背景画像を表示する

---
# 背景画像にぼかしを入れる

図15 背景画像の横並びと縦並びのMarkdown形式のテキスト例
---
marp: true
---
# 背景に横並びに配置



---
# 背景に縦並びに配置



図20 ChatGPTが生成したMarkdown形式のテキスト
---
marp: true
theme: default
paginate: true
backgroundColor: #f0f0f0
---
# 知られざるインドカレー
### シェル花子
---
# 目次
1. 北インド: パニール・バターマサラ
2. 南インド: チェッティナード・チキンカレー
3. 西インド: ゴア・フィッシュカレー
4. 東インド: コサ・マンシャ
5. 中央インド: ダールバーフライ
6. 北東インド: アクホニカレー
---
# 1. 北インド: パニール・バターマサラ
- クリーミーで濃厚なグレイビー
- トマト、バター、クリームを使用
- パニール(カッテージチーズ)入り
- パンジャーブ地方で有名
- ナンやバスマティライスと食べる
---
(略)
著者:原口 莉奈
私は最近、RPG(Role-Playing Game)の戦闘シーンをC++言語で作成しました。作成には、無償で利用できるゲーム開発用ライブラリ「DXライブラリ」(https://dxlib.xsrv.jp/)を用いています。今回は、その一部であるコマンド選択画面の実装について紹介します。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。![]()
![]()
図2 ビルド用のバッチファイル「build.bat」に記述する内容
g++ -c Main.cpp -DDX_GCC_COMPILE -I"DxLib"
g++ -o game.exe Main.o -L"DxLib" -lgcc -lDxLib -lDxUseCLib ^
-lDxDrawFunc -ljpeg -lpng -lzlib -ltiff -ltheora_static ^
-lvorbis_static -lvorbisfile_static -logg_static ^
-lbulletdynamics -lbulletcollision -lbulletmath -lopusfile ^
-lopus -lsilk_common -lcelt
図3 「Main.cpp」ファイルに記述するコード
#define WIN_X 256
#define WIN_Y 224
#include <DxLib.h>
int WINAPI WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR IpCmdLine,
_In_ int nShowCmd)
{
ChangeWindowMode(TRUE);
DxLib_Init();
// ウィンドウ初期化
SetWindowText("game"); // タイトル
SetGraphMode(WIN_X, WIN_Y, 32); // 解像度と色深度
SetBackgroundColor(255, 255, 255); // 背景色
WaitKey();
DxLib_End();
return 0;
}
図5 「Main.cpp」ファイルに記述するコード
#define WIN_X 256
#define WIN_Y 224
#include <DxLib.h>
#include "Status.h"
#include "Battle.h"
int WINAPI WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR IpCmdLine,
_In_ int nShowCmd)
{
ChangeWindowMode(TRUE);
DxLib_Init();
SetWindowText("game");
SetGraphMode(WIN_X, WIN_Y, 32);
SetBackgroundColor(255, 255, 255);
SetDrawScreen(DX_SCREEN_BACK); // 裏画面描画
SetWindowSize(1024, 960); // ウィンドウサイズ
Battle.materialLoad();
while (ProcessMessage() == 0)
{
SetBackgroundColor(0, 0, 0);
Battle.BattleDraw();
}
DxLib_End();
return 0;
}
図6 「Status.h」ファイルに記述するコード
class BATTLE {
public:
struct Status {
char name[50];
int HP, ATK, DEF; //体力、攻撃力、防御力
};
struct Status player = { "player",30, 10, 5 };
struct Status villain = { "villain",30, 7, 5 };
struct Status* Player = &player;
const char* str[4] = { "たたかう", "まもる",
"どうぐ", "にげる" };
const char* item[1] = { "回復" };
int firstsym = 80;
int sym = firstsym;
int sym_limit = firstsym + (4 - 1) * 17;
int symbol = -1, symnext = 0;
int pattern = 1;
int firstarrow = 80;
int arrow = firstarrow;
int limit; // firstarrow + (num - 1) * 17
int a_posi = -1;
int re = 0;
int black, white, ms;
void materialLoad();
void BattleDraw();
void one();
void two();
void Choices(int x, const char* str[], int n);
private:
};
図7 「Battle.h」ファイルに記述するコード
BATTLE Battle;
void BATTLE::materialLoad() {
black = GetColor(0, 0, 0);
white = GetColor(255, 255, 255);
ms = CreateFontToHandle("MS ゴシック", 10, 6,
DX_FONTTYPE_ANTIALIASING_4X4);
}
void BATTLE::Choices(int x, const char* str[], int n) {
int w = 0;
for (int i = 0; i < n; i++) {
DrawBox(x, firstarrow + w,
x + 80, firstarrow + 16 + w, white, TRUE);
DrawFormatStringToHandle(x + 5, firstarrow + 5 + w,
black, ms, str[i]);
w += 17;
}
}
// 初期表示
void BATTLE::one() {
sym = firstsym;
pattern = 2;
}
// 矢印の移動
void BATTLE::two() {
if (symnext == 0) {
switch (WaitKey()) {
case KEY_INPUT_DOWN:
if (sym != sym_limit) sym += 17;
break;
case KEY_INPUT_UP:
if (sym != firstsym) sym -= 17;
break;
case KEY_INPUT_ESCAPE:
DxLib_End();
break;
}
if (symbol != -1) symnext++;
}
}
void BATTLE::BattleDraw() {
SetDrawScreen(DX_SCREEN_BACK);
ClearDrawScreen();
switch(pattern):
if (pattern == 1) one();
else if (pattern == 2) two();
Choices(160, str, 4);
DrawFormatStringToHandle(144, sym, white, ms, "→");
ScreenFlip();
}
図9 「Status.h」ファイルにHpDraw()関数の記述を追加
(略)
void HpDraw();
void BattleDraw();
(略)
図10 「Battle.h」ファイルのBattleDraw()関数定義部分をこのように書き換える
(略)void BATTLE::HpDraw() {
DrawBox(180, 160, 250, 184, white, FALSE);
DrawBox(180, 186, 250, 210, white, FALSE);
DrawFormatStringToHandle(182, 162, white, ms,
"villain\nHP: %d", villain.HP);
DrawFormatStringToHandle(182, 188, white, ms,
"player\nHP:%d", player.HP);
}
void BATTLE::BattleDraw() {
SetDrawScreen(DX_SCREEN_BACK);
ClearDrawScreen();
switch(pattern):
if (pattern == 1) one();
else if (pattern == 2) two();
Choices(160, str, 4);
DrawFormatStringToHandle(144, sym, white, ms, "→");
HpDraw();
ScreenFlip();
}

004 レポート 仮想化ソフト「VirtualBox」の新版リリース
005 レポート マイコンボード「Raspberry Pi Pico 2」登場
006 製品レビュー 組み込み機器「CH32V003ボード」
007 NEWS FLASH
008 特集1 古いRaspberry Piを復活させる/麻生二郎
016 特集2 統合運用管理ソフトウエア Hinemos入門/倉田晃次、加藤達也
026 特別企画 イノシシ撃退機を作る ソフト作成編/米田聡 コード掲載
036 特別企画 Rocky Linux 9のインストール/麻生二郎
042 Pythonあれこれ/飯尾淳 コード掲載
048 Markdownを活用する/藤原由来 コード掲載
058 中小企業手作りIT化奮戦記/菅雄一
062 香川大学SLPからお届け!/池内稜來斗 コード掲載
068 これだけは覚えておきたいLinuxコマンド/大津真
078 Techパズル/gori.sh
079 コラム「SEの働き方の新しい仕組みを作る」/シェル魔人
著者:米田 聡
「Interface」「CQ ham radio」「トランジスタ技術」などの雑誌を出版するCQ出版社の電子部品セット「イノシシ撃退機部品セットVer.1」を組み立て、ソフトウエアに一工夫を加えてイノシシ撃退機を作ります。今回は完成したハードウエア向けにソフトウエアを作成します。
シェルスクリプトマガジン Vol.92は以下のリンク先でご購入できます。![]()
![]()
図18 PWM機能の初期化
(略)
#define PWM_PIN 20
(略)
#define PWM_SLICE_NUM pwm_gpio_to_slice_num(PWM_PIN)
(略)
void pwm_gpio_init() {
(略)
gpio_set_function(PWM_PIN, GPIO_FUNC_PWM);
pwm_set_wrap(PWM_SLICE_NUM, 4095);
pwm_set_clkdiv(PWM_SLICE_NUM, 2);
pwm_set_irq_enabled(PWM_SLICE_NUM, true);
irq_set_exclusive_handler(PWM_IRQ_WRAP, on_pwm_wrap);
irq_set_enabled(PWM_IRQ_WRAP, true);
(略)
}
(略)
図20 割り込みハンドラ
void on_pwm_wrap() {
pwm_clear_irq(PWM_SLICE_NUM);
(略)
uint16_t duty = convert_flash_data_to_duty(spi_rx_buf[flash_data_index], spi_rx_buf[flash_data_index + 1]);
pwm_set_chan_level(PWM_SLICE_NUM, PWM_CHAN_A, duty);
flash_data_index += 2;
if (flash_data_index >= 256) {
flash_data_index = 0;
}
(略)
}
図21 ダブルバッファを使用する修正
■34~37行目の修正コード
#ifdef FLASH_DATA_TO_DUTY
// ダブルバッファ化
uint8_t spi_rx_buf[2][256];
volatile uint32_t flash_data_index = 0;
#endif
■45~54行目の修正コード
// バッファ選択
volatile uint32_t buf_flip = 0;
void on_pwm_wrap() {
pwm_clear_irq(PWM_SLICE_NUM);
#ifdef FLASH_DATA_TO_DUTY
uint16_t duty = convert_flash_data_to_duty(spi_rx_buf[buf_flip][flash_data_index], spi_rx_buf[buf_flip][flash_data_index + 1]);
pwm_set_chan_level(PWM_SLICE_NUM, PWM_CHAN_A, duty);
flash_data_index += 2;
if (flash_data_index >= 256) {
// バッファ切り替え
buf_flip ^= 1;
flash_data_index = 0;
}
}
■87~105行目の修正コード
void play_sound() {
// バッファ切り替え(ローカル)
int flip = 0;
flash_data_index = 0;
buf_flip = 0;
pwm_set_enabled(PWM_SLICE_NUM, true); //mu0918
for(uint32_t address = 0; address < FLASH_SIZE; address += 256) {
gpio_put(PICO_DEFAULT_SPI_SSEL_PIN, 0); // Set the CS pin to active LOW
// Send the read command
uint8_t spi_tx_buf[] = {0x03, (address >> 16) & 0xFF, (address >> 8) & 0xFF, address & 0xFF};
spi_write_blocking(spi, spi_tx_buf, 4);
// Read the data
spi_read_blocking(spi, 0, spi_rx_buf[flip], 256);
// バッファ切り替え
flip ^= 1;
gpio_put(PICO_DEFAULT_SPI_SSEL_PIN, 1); // Set the CS pin to non-active HIGH
}
pwm_set_enabled(PWM_SLICE_NUM, false); //mu0918
}
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第22回では、機械学習ライブラリ「PyTorch」を使って、画像内の、歩行者が写っている領域を自動認識するPythonプログラムを作成します。
シェルスクリプトマガジン Vol.92は以下のリンク先でご購入できます。![]()
![]()
図3 47番のデータを確認するPythonコード
import matplotlib.pyplot as plt
from torchvision.io import read_image
image =read_image(
"data/PennFudanPed/PNGImages/FudanPed00047.png")
mask = read_image(
"data/PennFudanPed/PedMasks/FudanPed00047_mask.png")
plt.figure(figsize=(16, 8))
plt.subplot(121)
plt.title("Image")
plt.imshow(image.permute(1, 2, 0))
plt.subplot(122)
plt.title("Mask")
plt.imshow(mask.permute(1, 2, 0))
図5 PennFudanDataSetクラスを定義するPythonコード
import os
import torch
from torchvision.io import read_image
from torchvision.ops.boxes import masks_to_boxes
from torchvision import tv_tensors
from torchvision.transforms.v2 import functional as F
class PennFudanDataset(torch.utils.data.Dataset):
def __init__(self, root, transforms):
self.root = root
self.transforms = transforms
# イメージデータとマスクデータをロードし、ソートして並べておく
self.imgs = \
list(sorted(os.listdir(os.path.join(root, "PNGImages"))))
self.masks = \
list(sorted(os.listdir(os.path.join(root, "PedMasks"))))
def __getitem__(self, idx):
# イメージとマスクのロード
img_path = \
os.path.join(self.root, "PNGImages", self.imgs[idx])
mask_path = \
os.path.join(self.root, "PedMasks", self.masks[idx])
img = read_image(img_path)
mask = read_image(mask_path)
# インスタンスは色別にエンコードされていて……
obj_ids = torch.unique(mask)
# 最初のIDは背景色なので削除
obj_ids = obj_ids[1:]
num_objs = len(obj_ids)
# 色別にエンコードされているマスクを2値マスクに分ける
masks = \
(mask == obj_ids[:, None, None]).to(dtype=torch.uint8)
# マスクデータに対してバウンディングボックスを求める
boxes = masks_to_boxes(masks)
labels = torch.ones((num_objs,), dtype=torch.int64)
image_id = idx
area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])
# 群衆でないと仮定
iscrowd = torch.zeros((num_objs,), dtype=torch.int64)
# tv_tensorsイメージに変換する
img = tv_tensors.Image(img)
target = {}
target["boxes"] = \
tv_tensors.BoundingBoxes(boxes,
format="XYXY", canvas_size=F.get_size(img))
target["masks"] = tv_tensors.Mask(masks)
target["labels"] = labels
target["image_id"] = image_id
target["area"] = area
target["iscrowd"] = iscrowd
if self.transforms is not None:
img, target = self.transforms(img, target)
return img, target
def __len__(self):
return len(self.imgs)
図6 get_model_instance_segmentation()関数を定義するPythonコード
import torchvision
from torchvision.models.detection.faster_rcnn \
import FastRCNNPredictor
from torchvision.models.detection.mask_rcnn \
import MaskRCNNPredictor
def get_model_instance_segmentation(num_classes):
# COCOで事前学習済みのモデルデータをロードする
model = torchvision.models.detection.maskrcnn_resnet50_fpn(
weights="DEFAULT")
# 入力特徴量
in_features = \
model.roi_heads.box_predictor.cls_score.in_features
# num_classesで指定するクラスの分類器をセット(今回は2クラス)
model.roi_heads.box_predictor = \
FastRCNNPredictor(in_features, num_classes)
# マスク判別器も同様に設定
in_features_mask = \
model.roi_heads.mask_predictor.conv5_mask.in_channels
hidden_layer = 256
model.roi_heads.mask_predictor = MaskRCNNPredictor(
in_features_mask,
hidden_layer,
num_classes
)
return model
図7 get_transform()関数を定義するPythonコード
from torchvision.transforms import v2 as T
def get_transform(train):
transforms = []
if train:
transforms.append(T.RandomHorizontalFlip(0.5))
transforms.append(T.ToDtype(torch.float, scale=True))
transforms.append(T.ToPureTensor())
return T.Compose(transforms)
図8 追加学習の準備をするためのPythonコード
import utils
from engine import train_one_epoch, evaluate
device = torch.device('cuda') \
if torch.cuda.is_available() else torch.device('cpu')
# 背景と人物の2クラス判別器を作成する
num_classes = 2
# データセットはすでにロード済みのものを用いる
dataset = \
PennFudanDataset('data/PennFudanPed',
get_transform(train=True))
dataset_test = \
PennFudanDataset('data/PennFudanPed',
get_transform(train=False))
# データセットを学習用とテスト用に分ける
indices = torch.randperm(len(dataset)).tolist()
dataset = torch.utils.data.Subset(dataset, indices[:-50])
dataset_test = torch.utils.data.Subset(
dataset_test, indices[-50:])
# 学習と評価用のデータローダを定義
data_loader = torch.utils.data.DataLoader(
dataset,
batch_size=2,
shuffle=True,
num_workers=4,
collate_fn=utils.collate_fn
)
data_loader_test = torch.utils.data.DataLoader(
dataset_test,
batch_size=1,
shuffle=False,
num_workers=4,
collate_fn=utils.collate_fn
)
# 先ほど定義したヘルパーファンクションを用いてモデルを用意
model = get_model_instance_segmentation(num_classes)
# モデルをデバイスに結び付ける
model.to(device)
# 最適化器を作成
params = [p for p in model.parameters() if p.requires_grad]
optimizer = torch.optim.SGD(
params,
lr=0.005,
momentum=0.9,
weight_decay=0.0005
)
# 学習レートのスケジューラを設定
lr_scheduler = torch.optim.lr_scheduler.StepLR(
optimizer,
step_size=3,
gamma=0.1
)
図9 物体認識と領域セグメンテーションを実施するPythonコード
import matplotlib.pyplot as plt
from torchvision.utils \
import draw_bounding_boxes, draw_segmentation_masks
image = \
read_image("data/PennFudanPed/PNGImages/FudanPed00047.png")
eval_transform = get_transform(train=False)
model.eval()
with torch.no_grad():
x = eval_transform(image)
# RGBA -> RGBにコンバートしてデバイスにひも付ける
x = x[:3, ...].to(device)
predictions = model([x, ])
pred = predictions[0]
image = (255.0 * (image - image.min()) /
(image.max() - image.min())).to(torch.uint8)
image = image[:3, ...]
pred_labels = [f"pedestrian: {score:.3f}" for label, \
score in zip(pred["labels"], pred["scores"])]
pred_boxes = pred["boxes"].long()
output_image = draw_bounding_boxes(image,
pred_boxes, pred_labels, colors="red")
masks = (pred["masks"] > 0.7).squeeze(1)
output_image = draw_segmentation_masks(output_image,
masks, alpha=0.5, colors="blue")
plt.figure(figsize=(12, 12))
plt.imshow(output_image.permute(1, 2, 0))
図11 Penn-Fudanデータセットを用いて追加学習するPythonコード
# 2エポックだけ学習させてみる
num_epochs = 2
for epoch in range(num_epochs):
# 10回ごとに表示させながら1エポックの学習を実行
train_one_epoch(model, optimizer, data_loader,
device, epoch, print_freq=10)
# 学習レートをアップデート
lr_scheduler.step()
# テストデータで評価
evaluate(model, data_loader_test, device=device)
著者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。今回は前回に引き続き、Markdownを用いて書籍を制作できる組版アプリ「Vivliostyle」について解説します。特に、技術同人誌の制作方法と、印刷所へ入稿する際の注意
点について説明します。
シェルスクリプトマガジン Vol.92は以下のリンク先でご購入できます。![]()
![]()
図7 書き換えたVivliostyleの設定ファイル
module.exports = {
title: 'sample-book-kagakunofushigi',
author: 'アンリイ・ファブル(大杉栄、伊藤野枝訳)',
language: 'ja',
size: 'JIS-B5',
theme: '@vivliostyle/theme-techbook@^1.0.1',
entryContext: './manuscripts',
entry: [
'index.md',
'01.md',
'02.md',
'03.md',
'04.md',
'05.md',
'06.md',
'07.md',
'08.md',
'09.md',
'colophon.md',
],
workspaceDir: '.vivliostyle',
}
図8 扉のindex.mdファイル
# 科学の不思議
アンリイ・ファブル(大杉栄、伊藤野枝訳)
© 某サークル, 20xx
図9 奥付のcolophon.mdファイル
## 科学の不思議
20xx年x月x日 初版発行
----------------
- 発行 某サークル
- 著者 アンリイ・ファブル
- 翻訳 大杉栄、伊藤野枝
- 編集 シェル花子
- 連絡先 foo@example.com
- 印刷 サンプル印刷
----------------
底本は青空文庫から転載・改変しました。
- ファーブル ジャン・アンリ『科学の不思議』(大杉 栄、伊藤 野枝 訳)
© 某サークル, 20xx
図11 my-theme.cssファイル内のCSSを反映する設定
theme: [
'@vivliostyle/theme-techbook@^1.0.1',
'themes/my-theme.css',
],
図12 扉と奥付用のCSS
/* 扉 */
section#title-page {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
height: 50vh;
margin: 0;
}
/* 奥付 */
section#colophon {
position: relative;
float-reference: page;
float: bottom;
margin-bottom: 0;
}
図13 扉のindex.mdファイルの変更
<section id="title-page">
# 科学の不思議
アンリイ・ファブル(大杉栄、伊藤野枝訳)
© 某サークル, 20xx
</section>
図14 奥付のcolophon.mdファイルの変更
<section id="colophon" role="doc-colophon">
## 科学の不思議
20xx年x月x日 初版発行
----------------
- 発行 某サークル
- 著者 アンリイ・ファブル
- 翻訳 大杉栄、伊藤野枝
- 編集 シェル花子
- 連絡先 foo@example.com
- 印刷 サンプル印刷
----------------
底本は青空文庫から転載・改変しました。
- ファーブル ジャン・アンリ『科学の不思議』(大杉 栄、伊藤 野枝 訳)
© 某サークル, 20xx
</section>
図17 目次を自動設定する設定
(略)
entryContext: './manuscripts',
toc: {
htmlPath: 'toc.html',
title: '目次',
},
entry: [
'index.md',
{ rel: 'contents' },
'01.md',
(略)
著者:池内稜來斗
各都道府県の都道府県庁所在地を尋ねるクイズを出題するWebサイトをPHPで作成しました。今回は、そのWebサイトの概要や作成手順などを紹介します。PHPの実行環境や、各種サーバーソフトウエアは、「XAMPP」(https://www.apachefriends.org/)を使ってインストールします。
シェルスクリプトマガジン Vol.92は以下のリンク先でご購入できます。![]()
![]()
図2 「db.sql」ファイルに記述するSQL文
use pref_capitals_quiz;
insert into pref_capitals
(id, name_p, name_c, image_p) values
(1, '北海道', '札幌市', 'img/hokkaidou.png'),
(2, '青森県', '青森市', 'img/aomori.png'),
(3, '岩手県', '盛岡市', 'img/iwate.png'),
(4, '秋田県', '秋田市', 'img/akita.png'),
(5, '宮城県', '仙台市', 'img/miyagi.png'),
(6, '山形県', '山形市', 'img/yamagata.png'),
(7, '福島県', '福島市', 'img/fukushima.png'),
(8, '茨城県', '水戸市', 'img/ibaraki.png'),
(9, '栃木県', '宇都宮市', 'img/tochigi.png'),
(10, '群馬県', '前橋市', 'img/gunma.png'),
(11, '埼玉県', 'さいたま市', 'img/saitama.png'),
(12, '千葉県', '千葉市', 'img/chiba.png'),
(13, '東京都', '東京', 'img/tokyo.png'),
(略)
(47, '沖縄県', '那覇市', 'img/okinawa.png');
commit;
図3 「index.html」ファイルに記述するコード
<html lang=ja>
<head>
<meta charset="utf-8">
<title>都道府県庁所在地クイズ!!</title>
</head>
<body>
<h1>都道府県庁所在地クイズ!!</h1>
<p>表示された都道府県の都道府県庁所在地を回答してください。<br>
東京都の場合は、区名と地域名のどちらで回答しても構いません。<br>
すべての都道府県について回答したら終了です。
</p>
<button type="button"
onclick="location.href='question.php'">
クイズを始める
</button>
</body>
</html>
図4 「question.php」ファイルに記述するコード
<?php
session_start();
if (!isset($_SESSION['times'])) {
$_SESSION['times'] = 0;
$_SESSION['right'] = 0;
}
if ($_SESSION['times'] == 0) {
$r = new \Random\Randomizer();
$order = $r->shuffleArray(range(1, 47));
$_SESSION['order'] = $order;
}
$order = $_SESSION['order'];
$times = $_SESSION['times'];
$pref = $order[$times];
?>
<html lang=ja>
<head>
<meta charset="UTF-8">
<title>都道府県庁所在地クイズ!!</title>
</head>
<body>
<?php
$link = mysqli_connect('localhost','root','',
'pref_capitals_quiz');
mysqli_set_charset($link,'utf8');
$sql = "SELECT id, name_p, name_c, image_p " .
"FROM pref_capitals WHERE id = $pref";
$result = mysqli_query($link, $sql);
$row = mysqli_fetch_assoc($result);
$path = $row['image_p'];
echo "<br>";
echo "問題", $times + 1;
echo '<img src="' . $path .
'" alt="' . $row['name_p'] . 'の地図">';
print(' '.$row['name_p']);
?>
<form method="POST" class="form" action="answer.php">
<input type="text" name="Answer" class="input">
<p>
<input type="submit" value="回答" class="submit">
</p>
</form>
</body>
</html>
図5 「answer.php」ファイルに記述するコード
<?php
session_start();
$times = $_SESSION['times'];
$order = $_SESSION['order'];
$right = $_SESSION['right'];
$pref = $order[$times];
?>
<html lang=ja>
<head>
<meta charset="UTF-8">
<title>都道府県庁所在地クイズ!!</title>
</head>
<body>
<?php
$received_Answer = $_POST['Answer'];
$link = mysqli_connect('localhost','root','',
'pref_capitals_quiz');
mysqli_set_charset($link,'utf8');
$sql = "SELECT id, name_p, name_c, image_p " .
"FROM pref_capitals WHERE id = $pref";
$result = mysqli_query($link, $sql);
$row = mysqli_fetch_assoc($result);
// 東京都かどうか
if ($row['id'] == 13) {
if (($received_Answer == $row['name_c']) ||
($received_Answer == '新宿区')) {
echo "正解\n";
$right++;
}
} else if (($received_Answer == $row['name_c']) ||
(($received_Answer . "市") == $row['name_c'])) {
echo "正解\n";
$right++;
} else {
echo "不正解\n";
}
$_SESSION['times'] = ++$times;
$_SESSION['right'] = $right;
echo "<br>";
echo "問題{$times} {$row['name_p']} の答え";
echo "<br>";
echo $row['name_c'];
if ($times != 47) {
echo '
<form method="POST" class="form" action="question.php">
<button type="submit">次の問題</button>
</form>';
}
?>
<form method="POST" class="form" action="result.php">
<button type="submit">回答終了</button>
</form>
</body>
</html>
図6 「result.php」ファイルに記述するコード
<?php
session_start();
if (isset($_POST['reset'])) {
$_SESSION['times'] = 0;
$_SESSION['right'] = 0;
header("Location: index.html");
}
?>
<html lang=ja>
<head>
<meta charset="UTF-8">
<title>都道府県庁所在地クイズ!!</title>
</head>
<body>
<?php
echo "今回の正答数は<br>";
echo $_SESSION['times'], "問中",
$_SESSION['right'], "問";
echo "<br>";
echo "お疲れさまでした<br>";
?>
<form method="POST" action="result.php">
<input type="submit"
value="トップページに戻る"
name="reset">
</form>
</body>
</html>
記事中で作成したクイズWebサイト用のコードや、データベースへのデータ入力用のファイルをまとめたアーカイブファイルがここからダウンロードできます。
情報は随時更新致します。

004 レポート 統合開発環境「Theia IDE」正式版リリース
005 レポート サイバーエージェントがVLM、新LLM公開
006 製品レビュー パソコン「ASUS Vivobook S 15 S5507QA」
007 NEWS FLASH
008 特集1 Oracle Cloud Infrastructure入門/福島耕平、中村峻大
020 特集2 ChatGPTで手軽に実現/佐藤秀輔 コード掲載
028 特別企画 イノシシ撃退機を作る ハード製作編/米田聡
036 Pythonあれこれ/飯尾淳 コード掲載
042 Markdownを活用する/藤原由来 コード掲載
052 香川大学SLPからお届け!/佐藤楓真 コード掲載
058 中小企業手作りIT化奮戦記/菅雄一
064 これだけは覚えておきたいLinuxコマンド/大津真 コード掲載
073 ユニケージレポート/田中湧也
076 Techパズル/gori.sh
077 コラム「要求工学を取り入れる」/シェル魔人
著者:佐藤 秀輔
対話型AIサービス「ChatGPT」が注目を集めています。特に、2023年3月に登場した「GPT-4」という大規模言語モデル(LLM)を利用可能になってからは自然言語の理解力が飛躍的に向上し、指示に基づいて、さまざまな処理を自動化できるようになっています。本特集では、ChatGPTを個人で活用する例として、家計簿レビューをさせる方法と、API(Application Programming Interface)を利用してWebスクレイピングをさせる方法を紹介します。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。![]()
![]()
図1 ランダムな家計簿データを生成するPythonスクリプト
import datetime
import random
itemlist = ['食料品', '外食費', '日用品', '家財道具',
'交通費', '電気代', '水道代', 'ガス代',
'ガソリン代', 'その他']
for i in range(366):
basedate = datetime.datetime(2024, 1, 1)
date = basedate + datetime.timedelta(days=i)
item = itemlist[random.randrange(10)]
amount = random.randrange(100) * 100
print(f"{date.date()},{date.time()},{item},{amount}")
図10 Webスクレイピング対象となるWebページの例
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>サンプル商品リスト</title>
</head>
<body>
<h1>商品リスト</h1>
<div class="product" id="product1">
<h2>商品名:スニーカー</h2>
<p>価格:\5000</p>
<p>評価:4.5 星</p>
</div>
<div class="product" id="product2">
<h2>商品名:バックパック</h2>
<p>価格:\8000</p>
<p>評価:4.0 星</p>
</div>
<div class="product" id="product3">
<h2>商品名:水筒</h2>
<p>価格:\1500</p>
<p>評価:4.8 星</p>
</div>
</body>
</html>
図11 Webスクレイピング用のPythonスクリプトの例
import json
from bs4 import BeautifulSoup
# HTMLファイルを開く
with open('sample.html', 'r', encoding='utf-8') as file:
html_content = file.read()
# BeautifulSoupオブジェクトを作成
soup = BeautifulSoup(html_content, 'lxml')
# 商品情報を抽出
products = soup.find_all('div', class_='product')
product_list = []
for product in products:
product_name = product.find('h2').text.replace('商品名:', '').strip()
price = product.find_all('p')[0].text.replace('価格:', '').strip()
rating = product.find_all('p')[1].text.replace('評価:', '').strip()
# 各商品情報を辞書として追加
product_list.append({
'商品名': product_name,
'価格': price,
'評価': rating
})
# JSON形式で結果を出力
json_output = json.dumps(product_list, ensure_ascii=False, indent=4)
print(json_output)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第21回では、「ハノイの塔」の解法「GDHP(Gniibe Distributed Hanoi Protocol)」の手順をアニメーションで表示するPythonアプリを作成します。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。![]()
![]()
図2 GDHPの手順をアニメーションで表示するJavaScriptコード
const COLORS = [ 'crimson', 'forestgreen', 'yellow', 'royalblue',
'saddlebrown', 'hotpink', 'darkorange', 'darkmagenta' ];
const NUM_OF_DISKS = COLORS.length;
const BASE_LENGTH = 200;
const C_WIDTH = 3.732 * BASE_LENGTH;
const C_HEIGHT = 3.500 * BASE_LENGTH;
const DISK_R = 0.9 * BASE_LENGTH;
const POLE_R = 15;
const POSITIONS = { 'Source' : [0.268, 0.714],
'Auxiliary' : [0.500, 0.286],
'Destination' : [0.732, 0.714] };
const FLASHING_COUNTER = 20;
const STEPS = 30;
class Vector {
constructor(x,y) {
this.x = x;
this.y = y;
}
}
class Position extends Vector {
constructor(x,y) { super(x,y); }
move(vec) {
this.x += vec.x;
this.y += vec.y;
}
}
class Disk {
constructor(level) {
this.level = level;
this.color = COLORS[level];
this.r = (DISK_R-POLE_R)*(NUM_OF_DISKS-level)/NUM_OF_DISKS + POLE_R;
}
}
class MovingDisk extends Disk {
constructor(level,from,to) {
super(level);
[sx,sy] = [from.pos.x,from.pos.y];
[dx,dy] = [to.pos.x, to.pos.y];
this.pos = new Position(sx,sy);
this.mvec = new Vector((dx-sx)/STEPS,(dy-sy)/STEPS);
this.move_ctr = 0;
this.from = from;
this.to = to;
}
step_forward() {
this.pos.move(this.mvec);
this.move_ctr++;
}
finish_p() {
var ret_flag = false;
if (ret_flag = (this.move_ctr == STEPS)) {
this.to.disks.push(new Disk(this.level));
}
return ret_flag;
}
}
class Tower {
constructor(name, disks, direction=null) {
this.name = name;
this.disks = [];
for (var i = 0; i < disks; i++) {
this.disks.push(new Disk(i));
}
this.direction = direction;
this.moving = false;
this.flash_ctr = 0;
}
get toplevel() {
var l = this.disks.length;
// '-1' means there is no disk.
return (l > 0) ? this.disks[l-1].level : -1;
}
}
var src = new Tower('Source', NUM_OF_DISKS);
var aux = new Tower('Auxiliary', 0, src);
var dst = new Tower('Destination', 0, src);
// In the case of NUM_OF_DISKS is odd,
// the src must face the src.
// Otherwise, the src faces the aux.
src.direction = (COLORS.length % 2 == 1) ? dst : aux;
// the reference to moving disk is stored to this variable.
var moving_disk = null;
function setup() {
createCanvas(C_WIDTH, C_HEIGHT);
frameRate(30);
[src,aux,dst].forEach(function(t) {
[rx,ry] = POSITIONS[t.name];
t.pos = new Position(rx * C_WIDTH, ry * C_HEIGHT);
})
}
function base_drawing() {
background('beige');
[src,aux,dst].forEach(function(t) {
// draw disks
t.disks.forEach(function(d) {
stroke('black');
fill(d.color);
ellipse(t.pos.x,t.pos.y,2*d.r);
})
// draw a pole
stroke('brown');
fill(t.moving & (t.flash_ctr < FLASHING_COUNTER/2) ? 'gold' : 'white');
ellipse(t.pos.x,t.pos.y,2*POLE_R);
// draw a direction
stroke('navy');
[sx, sy] = [t.pos.x, t.pos.y];
[dx, dy] = [t.direction.pos.x, t.direction.pos.y];
r = POLE_R / Math.sqrt((dx-sx)*(dx-sx)+(dy-sy)*(dy-sy));
[dx, dy] = [(dx-sx)*r+sx, (dy-sy)*r+sy];
line(sx,sy,dx,dy);
})
}
function flash_poles() {
[src,aux,dst].forEach(function(t) {
t.moving = (t.direction.direction === t);
t.flash_ctr += 1;
t.flash_ctr %= FLASHING_COUNTER;
})
}
function pop_disk(src,aux,dst) {
var towers = [src,aux,dst].filter(t => t.moving);
var idx,from,to;
idx = (towers[0].toplevel > towers[1].toplevel) ? 0 : 1;
[from, to] = [towers[idx], towers[1-idx]];
return new MovingDisk(from.disks.pop().level,from,to);
}
function draw_moving_disk() {
var d = moving_disk;
d.step_forward();
stroke('black');
fill(d.color);
ellipse(d.pos.x,d.pos.y,2*d.r);
return d.finish_p();
}
function turn() {
[moving_disk.from,moving_disk.to].forEach(function(t) {
t.direction = ([src,aux,dst]
.filter(x => (x !== t) && (x !== t.direction)))[0];
t.moving = false;
})
}
function draw() {
// base drawing
base_drawing();
// find two exchange-towers out of three
flash_poles();
// start moving
if (moving_disk == null) {
moving_disk = pop_disk(src,aux,dst);
} else {
if (draw_moving_disk()) {
turn();
moving_disk = null;
}
}
}
図3 Pygameを用いて描画ウィンドウを表示するPythonコード
import pygame
pygame.init()
screen = pygame.display.set_mode((640,480))
pygame.display.set_caption('Pygame Window')
while True:
for event in pygame.event.get():
if event.type == pygame.TEXTINPUT and event.text == 'q':
pygame.quit()
exit()
screen.fill('lavender')
pygame.display.flip()
pygame.time.delay(30)
図5 GDHPの手順をアニメーションで表示するPythonコード
import pygame
import math
COLORS = [ 'crimson', 'forestgreen', 'yellow', 'royalblue',
'saddlebrown', 'hotpink', 'darkorange', 'darkmagenta' ]
NUM_OF_DISKS = len(COLORS)
BASE_LENGTH = 200
C_WIDTH = 3.732 * BASE_LENGTH
C_HEIGHT = 3.500 * BASE_LENGTH
DISK_R = 0.9 * BASE_LENGTH
POLE_R = 15
POSITIONS = { 'Source' : [0.268, 0.714],
'Auxiliary' : [0.500, 0.286],
'Destination' : [0.732, 0.714] }
FLASHING_COUNTER = 20
STEPS = 30
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
class Position(Vector):
def __init__(self, x, y):
super().__init__(x, y)
def move(self, vec):
self.x += vec.x
self.y += vec.y
class Disk:
def __init__(self, level):
self.level = level
self.color = COLORS[level]
self.r = (DISK_R-POLE_R)*(NUM_OF_DISKS-level)/NUM_OF_DISKS + POLE_R
class MovingDisk(Disk):
def __init__(self, level, frm, to):
super().__init__(level)
[sx, sy] = [frm.pos.x, frm.pos.y]
[dx, dy] = [to.pos.x, to.pos.y]
self.pos = Position(sx,sy)
self.mvec = Vector((dx-sx)/STEPS,(dy-sy)/STEPS)
self.move_ctr = 0
self.frm = frm
self.to = to
def step_forward(self):
self.pos.move(self.mvec)
self.move_ctr += 1
def finish_p(self):
ret_flag = (self.move_ctr == STEPS)
if ret_flag:
self.to.disks.append(Disk(self.level))
return ret_flag
class Tower:
def __init__(self, name, disks, direction=None):
self.name = name
self.disks = []
for i in range(disks):
self.disks.append(Disk(i))
self.direction = direction
self.moving = False
self.flash_ctr = 0
def toplevel(self):
l = len(self.disks)
# '-1' means there is no disk.
return self.disks[l-1].level if l > 0 else -1
def setup():
pygame.init()
screen = pygame.display.set_mode((C_WIDTH, C_HEIGHT))
pygame.display.set_caption('GDHP')
for t in [src,aux,dst]:
[rx, ry] = POSITIONS[t.name]
t.pos = Position(rx * C_WIDTH, ry * C_HEIGHT)
return screen
def base_drawing():
screen.fill('beige')
for t in [src,aux,dst]:
# draw disks
for d in t.disks:
pygame.draw.circle(screen, d.color, (t.pos.x,t.pos.y), d.r)
pygame.draw.circle(screen, 'black', (t.pos.x,t.pos.y), d.r, 1)
# draw a pole
fillcolor = 'gold' \
if t.moving and t.flash_ctr < FLASHING_COUNTER/2 else 'white'
pygame.draw.circle(screen, fillcolor, (t.pos.x,t.pos.y), POLE_R)
pygame.draw.circle(screen, 'brown', (t.pos.x,t.pos.y), POLE_R, 1)
# draw a direction
[sx, sy] = [t.pos.x, t.pos.y]
[dx, dy] = [t.direction.pos.x, t.direction.pos.y]
r = POLE_R / math.sqrt((dx-sx)*(dx-sx)+(dy-sy)*(dy-sy))
[dx, dy] = [(dx-sx)*r+sx, (dy-sy)*r+sy]
pygame.draw.line(screen, (0,0,128), (sx,sy), (dx,dy), 3)
def flash_poles():
for t in [src,aux,dst]:
t.moving = (t.direction.direction == t)
t.flash_ctr += 1
t.flash_ctr %= FLASHING_COUNTER
def pop_disk(src,aux,dst):
towers = list(filter(lambda x: x.moving, [src,aux,dst]))
idx = 0 if towers[0].toplevel() > towers[1].toplevel() else 1
[frm, to] = [towers[idx], towers[1-idx]]
return MovingDisk(frm.disks.pop().level,frm,to) \
if len(frm.disks) > 0 else None
def draw_moving_disk():
d = moving_disk
d.step_forward()
pygame.draw.circle(screen, d.color, (d.pos.x,d.pos.y), d.r)
pygame.draw.circle(screen, 'black', (d.pos.x,d.pos.y), d.r, 1)
return d.finish_p()
def turn():
for t in [moving_disk.frm,moving_disk.to]:
t.direction = list(filter(
lambda x: (x != t) and (x != t.direction), [src,aux,dst]))[0]
t.moving = False
def draw():
# base drawing
base_drawing()
# find two exchange-towers out of three
flash_poles()
# start moving
finish_p = False
mdisk = moving_disk
if mdisk == None:
mdisk = pop_disk(src,aux,dst)
if mdisk == None:
finish_p = True
else:
if draw_moving_disk():
turn()
mdisk = None
return mdisk, finish_p
# main routine
if __name__ == '__main__':
src = Tower('Source', NUM_OF_DISKS)
aux = Tower('Auxiliary', 0, src)
dst = Tower('Destination', 0, src)
# In the case of NUM_OF_DISKS is odd,
# the src must face the src.
# Otherwise, the src faces the aux.
src.direction = dst if len(COLORS) % 2 == 1 else aux
# the reference to moving disk is stored to this variable.
moving_disk = None
screen = setup()
while True:
for event in pygame.event.get():
if event.type == pygame.TEXTINPUT and event.text == 'q':
pygame.quit()
exit()
moving_disk, finish_p = draw()
if finish_p:
pygame.quit()
exit()
pygame.display.flip()
pygame.time.delay(30)
著者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。今回は、Markdownを用いて書籍を制作できる組版アプリ「Vivliostyle」の導入と、書籍作成の基本について説明します。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。![]()
![]()
図11 chap1.mdファイルの内容
# 後注と脚注の例
これは後注[^1]の例です。
インラインで後注^[これも章の後ろに表示される]もできます。
[^1]: 各章の後部にまとめて出力されるタイプの注釈
こちらは脚注<span class="footnote">ページ直下に表示される</span>です。
図12 chap2.mdファイルの内容
# {吾輩|わがはい}は猫である。
{吾輩|わがはい}は猫である。名前はまだ無い。
著者:佐藤 楓真
今回は、「焼きなまし法」と呼ばれる手法で、すべての都市を1回訪問して出発地点に帰るまでの最短経路を求める「巡回セールスマン問題」を近似的に解いてみます。焼きなまし法のベースとなる「山登り法」についても解説します。使用するプログラミング言語は、C++です。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。![]()
![]()
図3 図1の巡回セールスマン問題を山登り法で解くC++プログラムの例
#include <iostream>
#include <algorithm>
using namespace std;
#define TL 3 //制限時間
#define N 4
int d[N][N] = {{0, 2, 10, 5},
{2, 0, 5, 3},
{10, 5, 0, 4},
{5, 3, 4, 0}};
int ans[N+1] = {0, 1, 3, 2, 0};
int rand_num(void) {
return rand() % N + 1;
}
int calc_dist(void) {
int sum = 0;
for (int i = 0; i < N; i++) {
sum += d[ans[i]][ans[i+1]];
}
return sum;
}
int main(void) {
srand((unsigned)time(NULL));
int st_time = clock();
while ((clock() - st_time) / CLOCKS_PER_SEC < TL) {
int L = rand_num(), R = rand_num();
if (L > R) swap(L, R);
int bf_dist = calc_dist();
reverse(ans + L, ans + R);
int af_dist = calc_dist();
if (bf_dist < af_dist) reverse(ans + L, ans + R);
}
for (int i = 0; i < N+1; i++) {
cout << ans[i] + 1 << " ";
}
cout << endl << calc_dist() << endl;
return 0;
}
図4 都市数「5000」のテスト用データを作成するC++プログラム
#include <iostream>
#include <vector>
#include <fstream>
#include <string>
using namespace std;
#define N 5000
int main(void) {
srand((unsigned)time(NULL));
for (int num = 1; num <= 100; num++) {
vector<vector<int>> d(N, vector<int>(N));
for (int i = 0; i < N; i++) {
for (int j = i; j < N; j++) {
if (i == j) {
d[i][j] = 0;
} else {
d[i][j] = rand() % 5000 + 1;
}
}
}
for (int i = 0; i < N; i++) {
for (int j = 0; j < i; j++) {
d[i][j] = d[j][i];
}
}
string name = "case" + to_string(num) + ".txt";
ofstream out(name);
out << N << endl;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
out << d[i][j] << " ";
}
out << endl;
}
}
return 0;
}
図5 テスト用データに基づく巡回セールスマン問題を山登り法で解くC++プログラムの例
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <fstream>
using namespace std;
#define TL 3 //制限時間
int calc_dist(vector<vector<int>>& dist, vector<int>& ans, int N) {
int sum = 0;
for (int i = 0; i < N; i++) {
sum += dist[ans[i]][ans[i+1]];
}
return sum;
}
int rand_num(int N){
return rand() % N + 1;
}
int main(void) {
long long ave = 0;
for (int num = 1; num <= 100; num++) {
srand((unsigned)time(NULL));
string name = "case" + to_string(num) + ".txt";
ifstream infile(name);
int N;
infile >> N;
vector<vector<int>> d(N, vector<int>(N));
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
infile >> d[i][j];
}
}
vector<int> ans(N + 1);
for (int i = 0; i < N; i++) ans[i] = i;
ans[N] = 0;
int st_time = clock();
while ((clock() - st_time) / CLOCKS_PER_SEC < TL) {
int bf_dist = calc_dist(d, ans, N);
int L = rand_num(N), R = rand_num(N);
if (L > R) swap(L, R);
reverse(ans.begin() + L, ans.begin() + R);
int af_dist = calc_dist(d, ans, N);
if (af_dist > bf_dist) reverse(ans.begin() + L, ans.begin() + R);
}
ave += calc_dist(d, ans, N);
}
cout << ave / 100.0 << endl;
return 0;
}
図7 テスト用データに基づく巡回セールスマン問題を焼きなまし法で解くC++プログラムの例
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <fstream>
#include <cmath>
using namespace std;
#define MAX_TMP 100.0 //初期温度
#define TL 3 //制限時間
int calc_dist(vector<vector<int>>& dist, vector<int>& ans, int N) {
int sum = 0;
for (int i = 0; i < N; i++) {
sum += dist[ans[i]][ans[i+1]];
}
return sum;
}
int rand_num(int N) {
return rand() % N + 1;
}
double rand_p(void) {
return (double)rand() / RAND_MAX;
}
int main(void) {
srand((unsigned)time(NULL));
long long ave = 0;
for (int num = 1; num <= 100; num++) {
string name = "case" + to_string(num) + ".txt";
ifstream infile(name);
int N;
infile >> N;
vector<vector<int>> d(N, vector<int>(N));
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
infile >> d[i][j];
}
}
vector<int> ans(N + 1);
for (int i = 0; i < N; i++) ans[i] = i;
ans[N] = 0;
int st_time = clock(); //焼きなまし開始時間
while ((clock() - st_time) / CLOCKS_PER_SEC < TL ) {
int bf_dist = calc_dist(d, ans, N);
int L = rand_num(N), R = rand_num(N);
if (L > R) swap(L, R);
reverse(ans.begin() + L, ans.begin() + R);
int af_dist = calc_dist(d, ans, N);
double t = (double)(clock() - st_time) / CLOCKS_PER_SEC; //経過時間
double tmp = MAX_TMP - MAX_TMP * (t / TL); //現在の温度
double p = exp((bf_dist - af_dist) / tmp); //採用確率
if (rand_p() > p) reverse(ans.begin() + L, ans.begin() + R);
}
ave += calc_dist(d, ans, N);
}
cout << ave / 100.0 <<endl;
return 0;
}
著者:大津 真
本連載では、Linuxの初心者に覚えておいてほしいコマンドを紹介していきます。ここで紹介するコマンドさえ押さえておけば、基本的な操作に困ることはなくなるでしょう。第2回では、主にテキストファイルを処理するための基本コマンドについて解説します。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。![]()
![]()
図6 「members.csv」ファイルの内容
001,大津一郎,男,43,東京
002,江南直子,女,8,埼玉
003,唐木田信ー,男,43,福岡
004,森岡一郎,男,41,東京
005,秋山敬一郎,男,44,秋田
006,山田謙一,男,9,福岡
007,山田一郎,男,45,東京
009,福岡花子,女,35,東京
010,白戸二郎,男,39,北海道
クラウド型家計簿アプリ「Dr.Wallet」のサンプルデータがここからダウンロードできます。家計簿を付けていない人は、こちらを利用してください。なお、本サンプルデータ中のショップ名、電話番号、住所の情報はすべて架空のものです。実在の企業や事業者とは関係ありません。
情報は随時更新致します。
p.36の「2024年6月31日」は「2024年6月30日」の誤りです。同じページの最初から4行目の「をして」は「として」の誤りです。p.37の右段下から2行目の「Ubuntu Server」は「AlmaLinux」の誤りです。お詫びして訂正します。
p.37で紹介したISOイメージの書き込みツール「balenaEtcher」の最新版(バージョン1.19.×)では、「PORTABLE」版が提供されていません。記事で紹介したバージョン1.18.11のPORTABLE版(balenaEtcher-Portable-1.18.11.exe)はここからダウンロードできます。
情報は随時更新致します。

004 レポート 米OpenAI社の新LLM「GPT-4o」登場
005 レポート JPEGライブラリ「jpegli」公開
006 製品レビュー バイク用エアバッグ「TECH-AIR 5」
007 NEWS FLASH
008 特集1 2024年大注目のMicrosoft Copilot/三沢友治
018 特集2 NVIDIA NeMoで独自の生成AIを構築する/澤井理紀、藤田充矩 コード掲載
026 特集3 Raspberry Pi 5を使う/麻生二郎
036 特別企画 AlmaLinux 9のインストール/麻生二郎
042 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
046 Markdownを活用する/藤原由来 コード掲載
052 中小企業手作りIT化奮戦記/菅雄一
056 香川大学SLPからお届け!/小河原直道 コード掲載
062 Pythonあれこれ/飯尾淳 コード掲載
068 これだけは覚えておきたいLinuxコマンド/大津真
076 Techパズル/gori.sh
077 コラム「プロジェクトリーダーの役割」/シェル魔人
著者:澤井 理紀、藤田 充矩
大規模言語モデル(LLM)に基づく生成AIが注目を集めています。そうした生成AIを活用するには、LLMを特定のタスクやデータセットに最適化する「ファインチューニング」が欠かせません。本特集では、生成AIを支える技術を解説した上で、NVIDIAが提供する生成AI向けのフレームワーク「NVIDIA NeMo」を使って独自の生成AIをローカル環境に構築し、LLMをファインチューニングする方法を紹介します。
シェルスクリプトマガジン Vol.90は以下のリンク先でご購入できます。![]()
![]()
図2 LLMダウンロード用のPythonスクリプト
import os
from huggingface_hub import snapshot_download
MODEL_DIR = "./models"
os.makedirs(MODEL_DIR, exist_ok=True)
snapshot_download(
repo_id="elyza/ELYZA-japanese-Llama-2-7b",
local_dir=f"{MODEL_DIR}/ELYZA-japanese-Llama-2-7b",
local_dir_use_symlinks=False
)
図4 PEFTに使用するデータセットの形式を変換するPythonスクリプト
import json
import os
import random
INPUT_TRAIN = "./JGLUE/datasets/jcommonsenseqa-v1.1/train-v1.1.json"
INPUT_VALID = "./JGLUE/datasets/jcommonsenseqa-v1.1/valid-v1.1.json"
OUTPUT_DIR = "./data/jcommonsenseqa-v1.1"
random.seed(42)
os.makedirs(OUTPUT_DIR, exist_ok=True)
def write_jsonl(fname, json_objs):
with open(fname, 'wt') as f:
for o in json_objs:
f.write(json.dumps(o, ensure_ascii=False)+"\n")
def form_question(obj):
st = ""
st += "### 指示:\n"
st += "与えられた選択肢の中から、最適な答えを選んでください。"
st += "出力は以下から選択してください:\n"
st += f"- {obj['choice0']}\n"
st += f"- {obj['choice1']}\n"
st += f"- {obj['choice2']}\n"
st += f"- {obj['choice3']}\n"
st += f"- {obj['choice4']}\n"
st += "### 入力:\n"
st += f"{obj['question']}\n"
st += "### 応答:"
return st
def prosess(input_path, train=False):
with open(input_path) as f:
dataset = [json.loads(line) for line in f.readlines()]
processed = []
for data in dataset:
prompt = form_question(data)
answer = data[f"choice{data['label']}"]
processed.append({"input": prompt, "output": f"{answer}"})
if train:
random.shuffle(processed)
train_ds = processed[:-1000]
valid_ds = processed[-1000:]
write_jsonl(f"{OUTPUT_DIR}/train-v1.1.jsonl", train_ds)
write_jsonl(f"{OUTPUT_DIR}/valid-v1.1.jsonl", valid_ds)
else:
write_jsonl(f"{OUTPUT_DIR}/test-v1.1.jsonl", processed)
return
def main():
prosess(INPUT_TRAIN, train=True)
prosess(INPUT_VALID)
if __name__ == "__main__":
main()
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無
線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第6回は、DHT-11で測った湿温度をグラフ化します。
シェルスクリプトマガジン Vol.90は以下のリンク先でご購入できます。![]()
![]()
図2 10 分おきに湿温度を測定して配列に保存する「DhtDataLogger」クラスのコード(ddl.py)
import dht
import time
import gc
import micropython
import _thread
from machine import Pin
from machine import RTC
from machine import Timer
DHT_PIN = 15
TICK = 10
SIZE = 144
class DhtDataLogger():
def __init__(self, dht_gpio = DHT_PIN, tick=TICK, size=SIZE):
self.rtc = RTC()
self.dht = dht.DHT11(Pin(dht_gpio))
self.data = []
self.tick = tick
self.size = size
# 1分おきにタイマー割り込みを呼ぶ
self.timer = Timer(period=60*1000, mode=Timer.PERIODIC, callback=self._timer_handler)
self.lock = _thread.allocate_lock()
def _timer_handler(self, t):
now = self.rtc.datetime()
if now[5] % self.tick == 0:
micropython.schedule(self._logger, now)
def _logger(self, now):
# ロックをかける
self.lock.acquire()
try:
self.dht.measure()
except OSError as e:
pass
# 測定日時
nowstr = "{0:02d}:{1:02d}:{2:02d}".format(now[4],now[5],now[6])
self.data.append([nowstr, self.dht.temperature(), self.dht.humidity()])
if len(self.data) > self.size:
del self.data[0]
# debug
# print(self.data[-1])
# ロック解除
self.lock.release()
# ガベージコレクト
gc.collect()
def get_data(self):
if len(self.data) == 0:
return None
# 配列のコピーを返す
rval = []
self.lock.acquire()
for v in self.data:
rval.append(v)
self.lock.release()
return rval
図3 蓄積した湿温度をグラフとして表示する簡易Webサーバーのプログラム(main.py)
import usocket as socket
import network
import time
import ujson as json
import gc
from machine import Timer
from ddl import DhtDataLogger
http_header = '''
HTTP/1.1 200 OK\r
Content-Type: text/html;charset=UTF-8\r
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate\r
Connection: close\r
\r
'''
html_base='''
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Pico W</title>
</head>
%s
</html>
'''
graph_body = '''
<body>
<canvas id="DHTChart"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
<script>
var dhtc = document.getElementById("DHTChart");
var DHTChart = new Chart(dhtc, {
type: 'line',
data: {
labels: %s,
datasets: [
{
label: '気温(度)',
data: %s,
borderColor: "rgba(255,0,0,1)",
backgroundColor: "rgba(0,0,0,0)"
},
{
label: '湿度(%)',
data: %s,
borderColor: "rgba(0,0,255,1)",
backgroundColor: "rgba(0,0,0,0)"
}
],
},
options: {
title: {
display: true,
text: '気温と湿度'
},
scales: {
yAxes: [{
ticks: {
suggestedMax: 100,
suggestedMin: -10,
stepSize: 10,
}
}]
},
}
});
</script>
</body>
'''
logger = DhtDataLogger()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 80))
sock.listen(4)
try:
while True:
conn, addr = sock.accept()
connect_from = str(addr[0])
req = conn.recv(1024).decode()
if req.startswith('GET / HTTP/1.1'):
tlist = logger.get_data()
if tlist is None:
body = html_base % ("<body><h1>Data not ready</h1></body>")
else:
labels = []
temps = []
hums = []
for v in tlist:
labels.append(v[0])
temps.append(v[1])
hums.append(v[2])
body = graph_body % (json.dumps(labels), json.dumps(temps), json.dumps(hums))
html = html_base % (body)
# 送信
conn.send(http_header)
conn.sendall(html)
conn.close()
else:
conn.sendall('HTTP/1.1 404 Not Found\r\n')
conn.close()
gc.collect()
except KeyboardInterrupt:
pass
sock.close()
著者:小河原 直道
Raspberry Pi 5(以下、ラズパイ5)が日本でも発売され、私も購入しました。センサーとの接続やサーバーの構築をしてみたいと思ったため、湿温度/気圧/CO₂濃度を測定できるセンサーを接続し、その値をWebブラウザから確認できるシステムを作成しました。このシステムの作成ポイントについて解説します。
シェルスクリプトマガジン Vol.90は以下のリンク先でご購入できます。![]()
![]()
図8 BME280のチップIDを読み出すPythonコード
import spidev
# 読み取りは8ビット目を「1」に, 書き込みは「0」にする
def get_reg_addr(rw: str, addr: int) -> int:
rw_flag = 0b1000_0000
if rw == "w":
rw_flag = 0b0000_0000
return rw_flag | (addr & 0b0111_1111)
spi = spidev.SpiDev()
spi.open(0, 0) # bus0, cs0
spi.mode = 0 # mode0, CPOL=CPHA=0
spi.max_speed_hz = 10 * 10**6 # 10MHz
bme_id = spi.xfer2([get_reg_addr("r", 0xD0), 0])
print(f"id: {hex(bme_id[1])}")
spi.close()
図9 BME280の気温データを読み出すPythonコード
import spidev
# 読み取りは8ビット目を「1」に, 書き込みは「0」にする
def get_reg_addr(rw: str, addr: int) -> int:
rw_flag = 0b1000_0000
if rw == "w":
rw_flag = 0b0000_0000
return rw_flag | (addr & 0b0111_1111)
spi = spidev.SpiDev()
spi.open(0, 0) # bus0, cs0
spi.mode = 0 # mode0, CPOL=CPHA=0
spi.max_speed_hz = 10 * 10**6 # 10MHz
# 湿度の測定を有効化。オーバーサンプリングx1
r = spi.xfer2([get_reg_addr("r", 0xF2), 0])[1]
ctrl_hum = r & 0b1111_1000 | 0b001
spi.xfer2([get_reg_addr("w", 0xF2), ctrl_hum])
# 気圧、気温の測定を有効化。オーバーサンプリングx1、ノーマルモードに
ctrl_meas = (0b001 << 5) + (0b001 << 3) + 0b11
spi.xfer2([get_reg_addr("w", 0xF4), ctrl_meas])
# 気温データの読み出し
temp_raw_bins = spi.xfer2([get_reg_addr("r", 0xFA), 0, 0, 0])
temp_raw = (temp_raw_bins[1] << 8+4) | (temp_raw_bins[2] << 4) |\
((temp_raw_bins[3]>>4) & 0b00001111)
print(f"気温: {temp_raw}")
spi.close()
図10 MH-Z19CのCO2濃度データを読み出すPythonコード
import serial
import sys
import time
serial = serial.Serial(port="/dev/ttyAMA0", timeout=5)
# 念のためバッファをリセットする
serial.reset_input_buffer()
serial.reset_output_buffer()
time.sleep(2) # 待ち時間は適宜増減して調整
s = bytearray.fromhex("FF 01 86 00 00 00 00 00 79")
serial.write(s)
r = serial.read(size=9)
serial.close()
if r[0] != 0xFF:
print("invalid data\n")
sys.exit(-1)
for_check=0
for i, b in enumerate(r):
if i==0 or i==8:
continue
# チェックサムは1バイトのみ、あふれた分は捨てる
for_check = for_check + b & 0b1111_1111
for_check = 0xFF - for_check + 1
if r[8] != for_check:
print("checksum error\n")
sys.exit(-1)
print(f"{r[2]*256 + r[3]} ppm")
図12 測定データをデータベースに保存するPythonコード
import mariadb
import sys
try:
conn = mariadb.connect(
user = ユーザー名, password = パスワード,
host = "127.0.0.1", port = 3306,
database = "sensor1", autocommit = True
)
except mariadb.Error as e:
print(f"db connect error: {e}")
sys.exit(-1)
cur = conn.cursor()
sql = 'INSERT INTO sensor (datetime, temp, humid, press, co2) VALUES(NOW(), ?, ?, ?, ?)'
データ(temp_data、humid_data、press_data、co2_data)取得用コードをここに記述
try:
cur.execute(sql, (temp_data, humid_data, press_data, co2_data))
except Exception as e:
print(f"insert error: {e}")
finally:
cur.close()
conn.close()
図13 「db/db_pool.js」ファイルに記述するコード
const mariadb = require("mariadb");
const pool = mariadb.createPool({
host: "localhost",
user: ユーザー名,
password: パスワード,
database: "sensor1",
connectionLimit: 5,
});
module.exports = pool;
図14 「route/api_v1.js」ファイルに記述するコード(抜粋)
const router = require("express").Router();
const pool = require("../db/db_pool");
function getColumnName(sensor){
switch(sensor){
case "all":return "*";
case "temperature": return "datetime, temp"
case "humidity": return "datetime, humid"
case "pressure": return "datetime, press"
case "co2": return "datetime, co2"
default: throw new Error("sensor name error");
}
}
router.get("/:sensor/latest", async(req, res) => {
let sqlCol;
try{
sqlCol = getColumnName(req.params.sensor);
} catch (e) { return res.status(404).send(bad sensor name); }
try{
let dbRes = await pool.query(
select ${sqlCol} from sensor order by datetime desc limit 1
);
// クエリー結果の最初のレコードをJSON形式で送る
return res.status(200).json(dbRes[0]);
} catch(err) {
return res.status(500).json({mes:err.toString()});
}
});
module.exports = router;
図15 「route/index.js」ファイルに記述するコード
const express = require('express');
const router = express.Router();
const path = require('path');
router.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "../view/index.html"));
});
router.use("/api/v1", require("./api_v1"));
module.exports = router;
図16 「app.js」ファイルに記述するコード
const express = require("express");
const app = express();
const path = require('path');
app.use("/", require("./route/index"));
app.use(express.static(path.join(__dirname, "public")));
app.all("*", (req, res) => {
return res.sendStatus(404);
});
app.listen(3000, () => {
console.log("Listening at port 3000");
});
筆者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第20回では、データの可視化などにも活用できるWebアプリケーションフレームワーク「Streamlit」の、より進んだ使い方を紹介します。
シェルスクリプトマガジン Vol.90は以下のリンク先でご購入できます。![]()
![]()
図4 プルダウンメニューでサブページに移動できるようにしたコード
import streamlit as st
selected = st.selectbox('なに食べる?',
('-', 'ramen', 'curry', 'katsudon'))
pages = {'ramen': 'pages/1_🍜_ramen.py',
'curry': 'pages/2_🍛_curry.py',
'katsudon': 'pages/3_🍚_katsudon.py' }
if selected in pages.keys():
st.switch_page(pages[selected])
図8 「1_edit.py」ファイルに記述するコード
import streamlit as st
import pandas as pd
df = pd.read_excel('TODO.xlsx')
df = st.data_editor(df)
clicked = st.button('保存', type='primary',
on_click=lambda:\
df.to_excel('TODO.xlsx', index=False))
if (clicked):
st.switch_page('index.py')
図11 「2_new.py」ファイルに記述するコード
import streamlit as st
import pandas as pd
df = pd.read_excel('TODO.xlsx')
title = st.text_input(label='タイトル')
memo = st.text_area(label='内容')
date = st.date_input(label='締切')
done = False
def append():
global df
df_new = pd.DataFrame({'タイトル': [title],
'内容': [memo],
'締切': [date],
'実施': [done]})
df = pd.concat([df, df_new])
df.to_excel('TODO.xlsx', index=False)
clicked = st.button('登録', type='primary', on_click=append)
if (clicked):
st.switch_page('index.py')
図11 「2_new.py」ファイルに記述するコード
import streamlit as st
import pandas as pd
df = pd.read_excel('TODO.xlsx')
title = st.text_input(label='タイトル')
memo = st.text_area(label='内容')
date = st.date_input(label='締切')
done = False
def append():
global df
df_new = pd.DataFrame({'タイトル': [title],
'内容': [memo],
'締切': [date],
'実施': [done]})
df = pd.concat([df, df_new])
df.to_excel('TODO.xlsx', index=False)
clicked = st.button('登録', type='primary', on_click=append)
if (clicked):
st.switch_page('index.py')
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第19回では、データの可視化などにも活用できるWebアプリケーションフレームワーク「Streamlit」の基本的な使い方を紹介します。
シェルスクリプトマガジン Vol.89は以下のリンク先でご購入できます。![]()
![]()
図7 「magic.py」ファイルに記述するコード
import streamlit as st
# 素のテキストはマークダウンで表示される(docstring以外)
'''
# Magicという仕組み
説明を _markdown_ で書けます。
'''
'### 文字列や変数の取り扱い'
a = st.number_input("整数aの値を入れてください:+1:", value=0)
b = st.number_input("整数bの値を入れてください:+1:", value=0)
x = a + b
'a + b = ', x # 文字列と変数の値を並べて直接描画する
'### 表の取り扱い'
import pandas as pd
df = pd.DataFrame({'名前': ['あや','ゆみ','みなこ','とめ'],
'年齢': [24,22,32,95]})
df # データフレームを直接描画する
'### チャートの取り扱い'
import matplotlib.pyplot as plt
import numpy as np
arr = np.random.normal(1, 1, size=100)
fig, ax = plt.subplots()
ax.hist(arr, bins=20)
fig # チャートも直接描画できる
図9 キャッシュを設定するコードの例
'### チャートの取り扱い'
import matplotlib.pyplot as plt
import numpy as np
@st.cache_data
def make_chart():
arr = np.random.normal(1, 1, size=100)
fig, ax = plt.subplots()
ax.hist(arr, bins=20)
return fig
fig = make_chart()
fig # チャートも直接描画できる
図10 山手線の駅の位置を地図上にプロットするWebアプリのコード
import streamlit as st
import pandas as pd
import json
with open('yamate.geojson','r') as f:
data = json.load(f)
coords = [f['geometry']['coordinates'] \
for f in data ['features'] if f['geometry']['type'] == 'Point']
map_data = pd.DataFrame(coords, columns=['lon', 'lat'])
st.map(map_data)
図12 ウィジェットをサイドバーに配置するコードの例
import streamlit as st
selected = st.sidebar.selectbox(
'なに食べる?',
('ramen', 'curry', 'katsudon'),
)
st.image(selected + '.jpg')
筆者:大津 真
文章の作成やプログラミングに欠かせないのがテキストエディタ(以下、エディタ)です。この連載では、Linuxで利用できる定番エディタの特徴と使い方を解説していきます。最終回となる今回も、LinuxだけでなくWindowsやmacOSでも利用可能なGUIエディタの定番「Visual Studio Code(VSCode)」を引き続き紹介します。
シェルスクリプトマガジン Vol.89は以下のリンク先でご購入できます。![]()
![]()
図1 「test1.md」ファイルに記述するテキストデータ
# VSCode 入門
VSCode はシンプルで扱いやすいエディタです。
## いろいろなエディタ
1. VSCode
2. Vim
3. nano
## VSCode の特徴
- プラグインによる拡張
- ワークスペースによる管理
- コマンドパレットによるコマンド実行

004 レポート 進研ゼミに生成AI活用新サービス
005 レポート HHKB Studioのキートップカスタム
006 製品レビュー スマートリング「SOXAI RING 1」
007 NEWS FLASH
008 特集1 Amazon EC2入門/飯村望、清水祥伽
023 Hello Nogyo!
024 特集2 コンテンツクラウドのBox/有賀友三、結城亮史、葵健晴、辰己学
040 Markdownを活用する/藤原由来 コード掲載
050 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
054 中小企業手作りIT化奮戦記/菅雄一
058 香川大学SLPからお届け!/宇佐美志久眞
062 Pythonあれこれ/飯尾淳 コード掲載
068 Linux定番エディタ入門/大津真 コード掲載
076 Techパズル/gori.sh
077 コラム「顧客と一心同体の精神を育成」/シェル魔人
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第5回は、DHT-11で測った湿温度をスマートフォンに通知します。
シェルスクリプトマガジン Vol.89は以下のリンク先でご購入できます。![]()
![]()
図4 LINE Notifyを使って通知を送る「notify()」関数を定義したPythonプログラム
import usocket as socket
from urequests import post
from micropython import const
TOKEN = const("your_access_token")
def notify(msg):
url = 'https://notify-api.line.me/api/notify'
rheaders = {
'Content-type':'application/x-www-form-urlencoded',
'Authorization':'Bearer ' + TOKEN
}
message = "message=" + msg
req = post(url,headers=rheaders,data=message)
return req.status_code
図6 1時間おきに湿温度をスマートフォンに通知するサンプルプログラム(sample.py)
from machine import Pin
from machine import Timer
from micropython import const
from micropython import schedule
import _thread
import dht
import line
import time
DHT_PIN = const(15)
TICK = const(60)
class Climate():
def __init__(self, dht_gpio=DHT_PIN, tick=TICK, callback=None, expire=TICK):
self.dht = dht.DHT11(Pin(dht_gpio))
self.tick = tick
self.lock = _thread.allocate_lock()
self.timer = Timer(period=tick*1000, mode=Timer.PERIODIC, callback=self._timer_handler)
self._humidity = 0
self._temperature = 0
self._measurement_dt = 0
self.measure()
self._callback_func = callback
self._expire = expire
self._counter = 0
def set_callback(self, callback, expire):
self._callback_func = callback
self._expire = expire
def measure(self, arg=0):
self.dht.measure()
self.lock.acquire()
self._humidity = self.dht.humidity()
self._temperature = self.dht.temperature()
self._measurement_dt = time.time()
self.lock.release()
def _timer_handler(self,t):
schedule(self.measure, 0)
self._counter -= 1
if self._counter <= 0:
self._counter = self._expire
if self._callback_func is not None:
schedule(self._callback_func, self)
@property
def temp(self):
self.lock.acquire()
rval = self._temperature
self.lock.release()
return rval
@property
def hum(self):
self.lock.acquire()
rval = self._humidity
self.lock.release()
return rval
@property
def measurement_date_time(self):
self.lock.acquire()
rval = self._measurement_dt
self.lock.release()
return rval
def mycalback(c):
tm = time.localtime(c.measurement_date_time)
line.notify("temp=%d,hum=%d@%4d/%02d/%02d_%02d:%02d:%02d" % (c.temp, c.hum, tm[0],tm[1],tm[2],tm[3],tm[4],tm[5]))
if __name__ == '__main__':
cl = Climate(callback = mycalback)
while(True):
machine.idle()
p.14の「Windows 10のサポートが終了する2024年6月末ごろ」は「Windows 10(バージョン22H2)のサポートが終了する2025年10月14日」の誤りです(詳しくサポート期間は、こちら)。お詫びして訂正いたします。
Windows 10(バージョン22H2)は、まだ1年半以上のサポートが受けられますので、安心してお使いください。
情報は随時更新致します。

004 レポート Open Source Summit Japan開催
005 レポート 国内開発の日本語LLM
006 製品レビュー スマートテレビ「32S2」
007 NEWS FLASH
008 特集1 CentOS Linux 7からの移行先を考える/弦本春樹
013 Hello Nogyo!
014 特集2 自宅Linuxサーバー構築/麻生二郎 コード掲載
028 特別企画 ゆっくりMovieMaker4で映像制作 シナリオ作り編/ふる(FuruyamD)
038 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
042 大規模言語モデル/桑原滝弥、イケヤシロウ
044 Pythonあれこれ/飯尾淳 コード掲載
049 中小企業手作りIT化奮戦記/菅雄一
054 法林浩之のFIGHTING TALKS/法林浩之
056 タイ語から分かる現地生活/つじみき
062 香川大学SLPからお届け!/谷知紘
068 Linux定番エディタ入門/大津真
076 Techパズル/gori.sh
077 コラム「SELiunxとユニケージ」/シェル魔人
著者:麻生 二郎
Linuxやサーバーソフト、インターネットの仕組みを知りたいのなら、Linuxサーバーを構築するのが一番です。使わなくなった古いノートパソコンを活用し、自宅内にLinuxサーバーを構築してみましょう。本特集では、その手順を分かりやすく紹介します。
シェルスクリプトマガジン Vol.88は以下のリンク先でご購入できます。![]()
![]()
図1 Sambaの設定(/etc/samba/smb.conf)
[global]
workgroup = SHMAG
dos charset = CP932
unix charset = UTF8
[share]
path = /var/share
browsable = yes
writable = yes
create mask = 0777
directory mask = 0777
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第18回では、米Google社が提供する機械学習フレームワーク「MediaPipe」の顔検出機能を用いて、お遊びのプログラム作成に挑戦します。
シェルスクリプトマガジン Vol.88は以下のリンク先でご購入できます。![]()
![]()
図1 顔ランドマーク検出をするコード
import cv2
import mediapipe as mp
spec = mp.solutions.drawing_utils.DrawingSpec(thickness=1,
circle_radius=1)
cap = cv2.VideoCapture(0)
with mp.solutions.face_mesh.FaceMesh(
min_detection_confidence=0.5,
min_tracking_confidence=0.5) as face_mesh:
while True:
success, image = cap.read()
if not success: print("Warning: No camera image"); continue
image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB)
image.flags.writeable = False
results = face_mesh.process(image)
# Annotate the face mesh
image.flags.writeable = True
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
if results.multi_face_landmarks:
for landmarks in results.multi_face_landmarks:
mp.solutions.drawing_utils.draw_landmarks(
image=image, landmark_list=landmarks,
connections=mp.solutions.face_mesh.FACEMESH_TESSELATION,
landmark_drawing_spec=spec,
connection_drawing_spec=spec)
cv2.imshow('MediaPipe FaceMesh', image)
if cv2.waitKey(5) & 0xFF == 27: break
cap.release()
図4 「顔ハメ」アプリのコード
import cv2
import numpy as np
from PIL import Image, ImageDraw
import mediapipe as mp
def scaleToHeight(img, height):
h, w = img.shape[:2]
width = round(w * (height / h))
dst = cv2.resize(img, dsize=(width, height))
return dst
def convertToRGBA(src, type):
return Image.fromarray(cv2.cvtColor(src, type))\
.convert('RGBA')
def trimOutside(base, over, loc):
drw = ImageDraw.Draw(base)
drw.rectangle([(0, 0), (loc[0]-1, over.size[1]-1)],
fill=(0, 0, 0))
drw.rectangle([(loc[0]+over.size[0], 0),
(base.size[0]-1,over.size[1]-1)],
fill=(0, 0, 0))
def overlayImage(src, overlay, location):
# convert images to PIL format
pil_src = convertToRGBA(src, cv2.COLOR_BGR2RGB)
pil_overlay = convertToRGBA(overlay, cv2.COLOR_BGRA2RGBA)
# conpose two images
pil_tmp = Image.new('RGBA', pil_src.size, (0, 0, 0, 0))
pil_tmp.paste(pil_overlay, location, pil_overlay)
trimOutside(pil_tmp, pil_overlay, location)
result_image = Image.alpha_composite(pil_src, pil_tmp)
# convert result to OpenCV format
return cv2.cvtColor(np.asarray(result_image),
cv2.COLOR_RGBA2BGRA)
def decrementTimer(timer, image, p_idx):
h, w = image.shape[:2]
if timer < 0:
p_idx = (p_idx + 1) % len(panels)
return TIMER_INITIAL_VALUE, p_idx
elif timer == 30:
global still
still = image
cv2.rectangle(image, (0, 0), (w, h), (255,255,255),
thickness=-1)
return timer - 1, p_idx
elif timer < 30:
image = still
return timer - 1, p_idx
d, r = timer // 30, timer % 30
c = 255 / 60 * r + 128
cv2.putText(image, org=(int(w/2-100), int(h/2+100)),
text=str(d),
fontFace=cv2.FONT_HERSHEY_DUPLEX,
fontScale=10.0, color=(c, c, c),
thickness=30,
lineType=cv2.LINE_AA)
return timer - 1, p_idx
# prepare the kao_hame_panels
panels = []
panels.append(cv2.imread('img1.png', cv2.IMREAD_UNCHANGED))
panels.append(cv2.imread('img2.png', cv2.IMREAD_UNCHANGED))
panels.append(cv2.imread('img3.png', cv2.IMREAD_UNCHANGED))
# capture from a camera
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
# rescale the kao_hame image
height, width = frame.shape[:2]
for i in range(len(panels)):
panels[i] = scaleToHeight(panels[i], height)
p_idx = 0
panel = panels[p_idx]
p_height, p_width = panel.shape[:2]
# timing counter
TIMER_INITIAL_VALUE = 119
timer = TIMER_INITIAL_VALUE
with mp.solutions.face_mesh\
.FaceMesh(max_num_faces=1,
refine_landmarks=True,
min_detection_confidence=0.5,
min_tracking_confidence=0.5)\
as face_mesh:
while cap.isOpened():
success, image = cap.read()
if not success:
print("Ignoring empty camera frame.")
continue
image = cv2.flip(image, 1)
location = ((width-p_width)//2, 0)
image = overlayImage(image, panel, location)
image2 = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
results = face_mesh.process(image2)
if results.multi_face_landmarks != None:
timer, p_idx = decrementTimer(timer, image, p_idx)
panel = panels[p_idx]
p_height, p_width = panel.shape[:2]
else:
timer = TIMER_INITIAL_VALUE # reset timer
# triming the image
image = image[0:p_height,
location[0]:location[0]+p_width]
cv2.imshow('Virtual Face-in-Hole Cutout', image)
if cv2.waitKey(1) & 0xFF == ord('q'): break
cap.release()
cv2.destroyAllWindows()
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無
線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第4回は、モノクロ有機ELディスプレイと湿温度センサー「DHT-11」で日本語表示の湿温度計を作ります。
シェルスクリプトマガジン Vol.88は以下のリンク先でご購入できます。![]()
![]()
図9 温度と湿度を日本語で表示するプログラム(temp_hum.py)
from machine import Pin
from machine import I2C
from ssd1306 import SSD1306_I2C
from display import PinotDisplay
from pnfont import Font
import dht
import time
# ELパネル
i2c=I2C(0, freq=400000)
ssd = SSD1306_I2C(128, 32, i2c)
# pinotライブラリ
fnt = Font('/fonts/shnmk12u.pfn')
disp = PinotDisplay(panel = ssd, font = fnt)
# DHT-11センサー
sensor = dht.DHT11(Pin(15))
while True:
os_error = False
try:
sensor.measure()
except OSError as e:
os_error = True
line1 = "温度: {0:2d} C".format(sensor.temperature())
line2 = "湿度: {0:2d} %".format(sensor.humidity())
line3 = "DHT-11エラー" if os_error else " "
# 温度表示
disp.locate(1, 0)
disp.text(line1)
# 湿度表示
disp.locate(1,12)
disp.text(line2)
# エラー表示
disp.locate(1,24)
disp.text(line3)
time.sleep(2)

004 レポート Raspberry Pi 5が登場
005 レポート キーボード「HHKB Studio」が発売
006 製品レビュー 小型パソコン「X68000 Z PRODUCT EDITION BLACK MODEL」
007 NEWS FLASH
008 特集1 ゆっくりMovieMaker4で映像制作 編集作業編/ふる(FuruyamD)
014 特集2 電子帳簿保存法を知る/永禮啓大
021 Hello Nogyo!
022 特別企画 アプリバで業務システム開発/前佛雅人
032 Raspberry Pi Pico W/WHで始める電子工作/米田聡
035 行動経済学と心理学で円滑に業務を遂行/請園正敏
038 Pythonあれこれ/飯尾淳 コード掲載
044 法林浩之のFIGHTING TALKS/法林浩之
046 中小企業手作りIT化奮戦記/菅雄一 コード掲載
052 エコシステム/桑原滝弥、イケヤシロウ
054 香川大学SLPからお届け!/大村空良
060 タイ語から分かる現地生活/つじみき
066 ユニケージ通信/ぱぺぽ
070 Linux定番エディタ入門/大津真
076 Techパズル/gori.sh
077 コラム「ユニケージが実現・目指しているところ」/シェル魔人
著者:飯尾淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第17回では、データを記録して残す「永続化」の手法の続編として、O/Rマッパーを使ってデータベースにアクセスする方法について解説します。
シェルスクリプトマガジン Vol.87は以下のリンク先でご購入できます。![]()
![]()
図3 宣言的マッピングをするコード
from typing import List
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "user_account"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(30))
fullname: Mapped[Optional[str]]
addresses: Mapped[List["Address"]] = relationship(
back_populates="user", cascade="all, delete-orphan"
)
def __repr__(self) -> str:
return f"User(id={self.id!r},\
name={self.name!r}, fullname={self.fullname!r})"
class Address(Base):
__tablename__ = "address"
id: Mapped[int] = mapped_column(primary_key=True)
email_address: Mapped[str]
user_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
user: Mapped["User"] = relationship(back_populates="addresses")
def __repr__(self) -> str:
return f"Address(id={self.id!r},\
email_address={self.email_address!r})"
図4 データベースエンジンとなるオブジェクトを作成するコード
from sqlalchemy import create_engine
engine = create_engine("sqlite:///a_db.sqlite3", echo=True)
図5 発行されるSQL
CREATE TABLE user_account (
id INTEGER NOT NULL,
name VARCHAR(30) NOT NULL,
fullname VARCHAR,
PRIMARY KEY (id)
)
CREATE TABLE address (
id INTEGER NOT NULL,
email_address VARCHAR NOT NULL,
user_id INTEGER NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES user_account (id)
)
図6 データ操作用オブジェクトを作成するコード
from sqlalchemy.orm import Session
with Session(engine) as session:
spongebob = User(
name="spongebob",
fullname="Spongebob Squarepants",
addresses=[Address(email_address="spongebob@sqlalchemy.org")],
)
sandy = User(
name="sandy",
fullname="Sandy Cheeks",
addresses=[
Address(email_address="sandy@sqlalchemy.org"),
Address(email_address="sandy@squirrelpower.org"),
],
)
patrick = User(name="patrick", fullname="Patrick Star")
session.add_all([spongebob, sandy, patrick])
session.commit()
図7 図6のコードを実行した際の画面出力(抜粋)
BEGIN (implicit)
INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id
(略)('spongebob', 'Spongebob Squarepants')
INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id
(略)('sandy', 'Sandy Cheeks')
INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id
(略)('patrick', 'Patrick Star')
INSERT INTO address (email_address, user_id) VALUES (?, ?) RETURNING id
(略)('spongebob@sqlalchemy.org', 1)
INSERT INTO address (email_address, user_id) VALUES (?, ?) RETURNING id
(略)('sandy@sqlalchemy.org', 2)
INSERT INTO address (email_address, user_id) VALUES (?, ?) RETURNING id
(略)('sandy@squirrelpower.org', 2)
COMMIT
図8 簡単な検索をするコード
from sqlalchemy.orm import Session
from sqlalchemy import select
session = Session(engine)
stmt = select(User).where(User.name.in_(["spongebob", "sandy"]))
for user in session.scalars(stmt):
print(user)
図9 図8のコードを実行した際に発行されるSQL
BEGIN (implicit)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name IN (?, ?)
(略) ('spongebob', 'sandy')
図10 図8のコードを実行した際に表示される検索結果
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
図11 少し複雑な検索をするコード
stmt = (
select(Address)
.join(Address.user)
.where(User.name == "sandy")
.where(Address.email_address == "sandy@sqlalchemy.org")
)
sandy_address = session.scalars(stmt).one()
図12 図11のコードを実行した際に発行されるSQL
SELECT address.id, address.email_address, address.user_id
FROM address JOIN user_account ON user_account.id = address.user_id
WHERE user_account.name = ? AND address.email_address = ?
(略) ('sandy', 'sandy@sqlalchemy.org')
図13 データ変更のシンプルなコード例
sandy_address.email_address = "sandy_cheeks@sqlalchemy.org"
session.commit()
図14 図13のコードを実行した際に発行されるSQL
UPDATE address SET email_address=? WHERE address.id = ?
(略)('sandy_cheeks@sqlalchemy.org', 2)
COMMIT
図15 データ変更の少し複雑なコード例
stmt = select(User).where(User.name == "patrick")
patrick = session.scalars(stmt).one()
patrick.addresses \
.append(Address(email_address="patrickstar@sqlalchemy.org"))
session.commit()
図16 図15のコードを実行した際に発行されるSQL
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ?
(略)('patrick',)
SELECT address.id AS address_id, address.email_address
AS address_email_address, address.user_id AS address_user_id
FROM address
WHERE ? = address.user_id
(略)(3,)
INSERT INTO address (email_address, user_id) VALUES (?, ?)
(略)('patrickstar@sqlalchemy.org', 3)
COMMIT
著者:大村 空良
プログラミングにおいて重要な要素の一つが、プログラムの実行速度です。プログラムの実行速度は、より良いアルゴリズムを選択することで大きく改善できます。今回は、巡回セールスマン問題を解くC++プログラムを、アルゴリズムを変更して高速化した例を紹介します。
シェルスクリプトマガジン Vol.87は以下のリンク先でご購入できます。![]()
![]()
図2 図1の巡回セールスマン問題を解くC++コード「sample1.cpp」
#include <bits/stdc++.h>
using namespace std;
int main() {
int n = 4, ans = 1e9-1, sum;
int a[4][4] = {
{0, 3, 7, 8},
{3, 0, 4, 4},
{7, 4, 0, 3},
{8, 4, 3, 0}
};
vector<int> v_ans, v = {0, 1, 2, 3};
do {
//初期化
sum = 0;
//距離を足す
for (int i = 1; i < n; i++) {
sum += a[v[i-1]][v[i]];
}
sum += a[v[n-1]][v[0]];
//比較
if (ans > sum) {
ans = sum;
v_ans = v;
}
} while (next_permutation(v.begin(), v.end()));
//結果表示
for_each(v_ans.begin(), v_ans.end(),
[](int& item) { item++; });
copy(v_ans.begin(), v_ans.end(),
ostream_iterator<int>(cout, "→"));
cout << v_ans[0] << endl;
cout << ans << endl;
return 0;
}
図3 都市数を変えられるように図2のコードを拡張した「sample2.cpp」
#include <bits/stdc++.h>
#include "sales.h"
using namespace std;
int main() {
int ans = 1e9-1, sum;
vector<int> v_ans;
do {
sum = 0;
for (int i = 1; i < n; i++) {
sum += a[v[i-1]][v[i]];
}
sum += a[v[n-1]][v[0]];
if (ans > sum) {
ans = sum;
v_ans = v;
}
} while (next_permutation(v.begin(), v.end()));
for_each(v_ans.begin(), v_ans.end(),
[](int& item) { item++; });
copy(v_ans.begin(), v_ans.end(),
ostream_iterator<int>(cout, "→"));
cout << v_ans[0] << endl;
cout << ans << endl;
return 0;
}
図4 ヘッダーファイル生成用のPythonスクリプト「make_header.py」
import random
import sys
text = "dummy"
while not text.isdecimal():
print("整数値を入力してください: ",
file=sys.stderr, end='')
text = input()
num = int(text)
if num > 15 or num < 1:
sys.exit(1)
array = [[0] * num for i in range(num)]
random.seed()
for i in range(num):
for j in range(num):
if i != j:
if array[j][i] != 0:
array[i][j] = array[j][i]
else:
array[i][j] = random.randint(1, 9)
print('#include <vector>')
print('using namespace std;')
print(f'int n = {num};')
print(f'int a[{num}][{num}] = {{')
for i in range(num):
line = str(array[i])[1:-1]
print('{' + line + '},')
print('};')
line = list(range(0, num))
line = str(line)[1:-1]
print(f'vector<int> v = {{{line}}};')
図5 フィボナッチ数を求めるC++コード「sample3.cpp」
#include <bits/stdc++.h>
using namespace std;
unsigned long long fib(char n) {
if ((n <= 0) || (n > 93)) return 0;
if (n == 1) return 1;
return fib(n - 1) + fib(n - 2);
}
int main() {
char n = 5;
cout << fib(n) << endl;
return 0;
}
図7 動的計画法を用いて改良したコード「sample4.cpp」
#include <bits/stdc++.h>
using namespace std;
unsigned long long fib(char n) {
if ((n <= 0) || (n > 93)) return 0;
if (n == 1) return 1;
unsigned long long f[128];
f[0] = 0;
f[1] = 1;
for (char i = 2; i <= n; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f[n];
}
int main() {
char n = 5;
cout << fib(n) << endl;
return 0;
}
図10 動的計画法を用いて改良したコード「sample5.cpp」
#include <bits/stdc++.h>
#include "sales.h"
using namespace std;
int main() {
int dp[(1<<n)][n];
for (int i = 0; i < (1<<n); i++) {
for (int j = 0; j < n; j++) {
dp[i][j] = 1e9-1;
}
}
dp[0][0] = 0;
for (int i = 0; i < (1<<n); i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
if (i==0 || !(i & (1<<k)) && (i & (1<<j))) {
if( j != k ) {
dp[i | (1<<k)][k] = min(dp[i | (1<<k)][k],
dp[i][j] + a[j][k]);
}
}
}
}
}
cout << dp[(1<<n) - 1][0] << endl;
return 0;
}
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第16回では、データを永続的に残す「永続化」の手法の一つとして、Pythonプログラムからデータベースにアクセスする方法について解説します。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。![]()
![]()
図3 データベースにテーブルを作成してデータを格納するコード
from sqlalchemy import text
with engine.connect() as conn:
conn.execute(text("CREATE TABLE a_table (id int, name string)"))
conn.execute(
text("INSERT INTO a_table (id, name) VALUES (:id, :name)"),
[{"id":1, "name":"John"}, {"id":2, "name":"Bob"}],
)
conn.commit()
図5 テーブルのデータを読み出すコード
with engine.connect() as conn:
result = conn.execute(text("SELECT * FROM a_table"))
for record in result:
print(f"id: {record.id}, name: {record.name}")
図6 commit()を忘れたコードの例
with engine.connect() as conn:
conn.execute(
text("INSERT INTO a_table (id, name) VALUES (:id, :name)"),
[{"id":3, "name":"Kate"}, {"id":4, "name":"Bob"}],
)
conn.commit()
図8 データベースに含まれるテーブルを表示するコード
from sqlalchemy import create_engine
engine = create_engine("sqlite:///:memory:", echo=True)
with engine.connect() as conn:
result = conn.execute(
text("SELECT name FROM sqlite_master WHERE type='table'")
)
for record in result:
print(record)
図9 ファイル内にデータベースを作成するコード
from sqlalchemy import create_engine, text
engine = create_engine("sqlite:///db.sqlite3")
with engine.connect() as conn:
conn.execute(text("CREATE TABLE a_table (id int, name string)"))
conn.execute(
text("INSERT INTO a_table (id, name) VALUES (:id, :name)"),
[{"id": 1, "name": "John"}, {"id": 2, "name": "Bob"}],
)
conn.commit()
図10 ファイル内データベースのテーブルからデータを読み出すコード
from sqlalchemy import create_engine, text
engine = create_engine("sqlite:///db.sqlite3")
with engine.connect() as conn:
result = conn.execute(text("SELECT * FROM a_table"))
for record in result:
print(f"id: {record.id}, name: {record.name}")
著者:石上 椋一
今回は、私が開発した文章校正用のWebアプリケーションについて紹介します。文章校正機能は「textlint」というNode.js上で稼働するアプリケーションを使って実現しているため、少ないコード量で実装できました。米Google社のアプリケーション開発プラットフォーム「Firebase」を利用したユーザー認証機能を付加する方法も解説します。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。![]()
![]()
図1 テンプレートファイル「index.html」に記述するコード
<!doctype html>
<html lang="ja">
<head>
<title>文章校正アプリ</title>
</head>
<body>
<h1>こんにちは!文章校正アプリです</h1>
<h2>PDFファイルアップローダー</h2>
<form action="/result" method="POST" enctype="multipart/form-data">
<input type=file name="pdf_file">
<button>ファイル送信</button>
</form>
</body>
</html>
図2 テンプレートファイル「result.html」に記述するコード
<!doctype html>
<html lang="ja">
<head>
<title>文章校正アプリ 結果表示</title>
</head>
<body>
<h1>こんにちは!文章校閲アプリです</h1>
<h2>PDFファイルアップローダー</h2>
<form action="/result" method="POST" enctype="multipart/form-data">
<input type=file name="pdf_file">
<button>ファイル送信</button>
</form>
<table border="1">
<thead>
<tr>
<th>何行目</th>
<th>何文字目</th>
<th>修正内容</th>
</tr>
</thead>
<tbody>
{% for result in result_table %}
<tr>
<td>{{result[0]}}</td>
<td>{{result[1]}}</td>
<td>{{result[2]}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<br>
<p>修正した文章</p>
<textarea rows="10" cols="80">{{result_text}}</textarea>
</body>
</html>
図3 「web_app.py」ファイルに記述するコード
from flask import Flask, request, render_template, \
redirect, url_for, session
import subprocess
import re
app = Flask(__name__)
def upload_pdf_file(pdf_file):
pdf_file.save("uploads/target.pdf")
def run_pdf2text():
cmd = "pdftotext -nopgbrk uploads/target.pdf uploads/target.md"
subprocess.run(cmd, shell=True, capture_output=True, text=True)
def run_textlint_and_get_result_list():
cmd = "npx textlint uploads/target.md"
result = subprocess.run(cmd, shell=True,
capture_output=True, text=True)
result_list = result.stdout.split("\n")
result_table = []
for result in result_list[2:-4]:
tmp_result = result.split(" error ")
if len(tmp_result) >= 2:
row = int(tmp_result[0].split(":")[0].strip())
colum = tmp_result[0].split(":")[1].strip()
error_data = re.sub("ja-technical-writing/[a-z | -]*",
"",tmp_result[1]).strip()
result_table.append([row, colum, error_data])
return result_table
def run_textlint_fix_data():
cmd = "npx textlint --fix ./uploads/target.md"
subprocess.run(cmd, shell=True, capture_output=True, text=True)
with open("./uploads/target.md", ) as md_file:
data_lines = md_file.read()
return data_lines
@app.route("/index", methods=["GET"])
def index():
return render_template("index.html")
@app.route("/", methods=["GET"])
def root():
return redirect(url_for("index"))
@app.route("/result", methods=["GET", "POST"])
def result():
if request.method == "POST":
pdf_file = request.files["pdf_file"]
upload_pdf_file(pdf_file)
run_pdf2text()
result_table = run_textlint_and_get_result_list()
result_text = run_textlint_fix_data()
return render_template("result.html",
result_table=result_table,
result_text=result_text)
return redirect(url_for('index'))
if __name__ == "__main__":
app.debug = True
app.run(host='0.0.0.0', port=5000)
図4 「.textlintrc.json」ファイルに記述する設定の例
{
"rules": {
"preset-ja-technical-writing": {
"sentence-length": {
"max": 100
},
"max-comma": {
"max": 4
},
"max-ten": {
"max": 4
},
"max-kanji-continuous-len": {
"max": 7,
"allow": []
},
"arabic-kanji-numbers": true,
"no-mix-dearu-desumasu": {
"preferInHeader": "",
"preferInBody": "である",
"preferInList": "である",
"strict": true
},
"ja-no-mixed-period": {
"periodMark": "。"
},
"no-double-negative-ja": true,
"no-dropping-the-ra": true,
"no-doubled-conjunctive-particle-ga": true,
"no-doubled-conjunction": true,
"no-doubled-joshi": {
"min_interval": 1
},
"no-invalid-control-character": true,
"no-zero-width-spaces": true,
"no-exclamation-question-mark": true,
"no-hankaku-kana": true,
"ja-no-weak-phrase": true,
"ja-no-successive-word": true,
"ja-no-abusage": true,
"ja-no-redundant-expression": true,
"ja-unnatural-alphabet": true,
"no-unmatched-pair": true
}
}
}
図10 「~/webapp/static/json/firebase.json」ファイルに記述する設定の例
{
"apiKey": "AIzaSyA1LQooSfvSda0jkBQl20ZfGR6lHOC5XBw",
"authDomain": "test-1d03e.firebaseapp.com",
"databaseURL": "https://test-1d03e-default-rtdb.firebaseio.com",
"projectId": "test-1d03e",
"storageBucket": "test-1d03e.appspot.com",
"messagingSenderId": "919322583901",
"appId": "1:919322583901:web:abb5d744e9ed04b8927eb4"
}
図11 テンプレートファイル「create_account.html」に記述するコード
<!doctype html>
<html lang="ja">
<head>
<title>文章校正アプリ アカウント作成ページ</title>
</head>
<body>
<form class="login-form" action='/create_account' method='POST'>
<input type="text" name="email" placeholder="email address"/>
<input type="password" name="password" placeholder="password"/>
<button>Create an account</button>
<p>{{msg}}</p>
<p>Already registered?
<a href="/login">Login</a>
</p>
</form>
</body>
</html>
図12 テンプレートファイル「login.html」に記述するコード
<!doctype html>
<html lang="ja">
<head>
<title>文章校正アプリ ログインページ</title>
</head>
<body>
<form class="login-form" action="/login" method="POST">
<input type="text" name="email" placeholder="email address"/>
<input type="password" name="password" placeholder="password"/>
<button>Login</button>
<p>{{msg}}</p>
<p>Not registered?
<a href="/create_account">Create an account</a>
</p>
</form>
</body>
</html>
図13 「web_app.py」ファイルの「app = Flask(name)」行の前後に追加するコード
import os, json
import pyrebase
app = Flask(__name__)
app.config["SECRET_KEY"] = os.urandom(24)
with open("static/json/firebase.json") as f:
firebaseConfig = json.loads(f.read())
firebase = pyrebase.initialize_app(firebaseConfig)
auth = firebase.auth()
図14 「web_app.py」ファイルの既存のルーティング設定にコードを追加
(略)
@app.route("/index", methods=["GET"])
def index():
if session.get("usr") == None:
return redirect(url_for("login"))
return render_template("index.html")
@app.route("/", methods=["GET"])
def root():
return redirect(url_for("index"))
@app.route("/result", methods=["GET", "POST"])
def result():
if session.get("usr") == None:
return redirect(url_for("login"))
if request.method == "POST":
pdf_file = request.files["pdf_file"]
(略)
図15 「web_app.py」ファイルにルーティング設定を追加
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "GET":
return render_template("login.html", msg="")
email = request.form["email"]
password = request.form["password"]
try:
auth.sign_in_with_email_and_password(email, password)
session["usr"] = email
return redirect(url_for("index"))
except:
message = "メールアドレスまたはパスワードが違います"
return render_template("login.html", msg=message)
@app.route("/create_account", methods=["GET", "POST"])
def create_account():
if request.method == "GET":
return render_template("create_account.html", msg="")
email = request.form["email"]
password = request.form["password"]
try:
auth.create_user_with_email_and_password(email, password)
session["usr"] = email
return redirect(url_for("index"))
except:
message = "アカウントを作成できませんでした"
return render_template("login.html", msg=message)
@app.route('/logout')
def logout():
del session["usr"]
return redirect(url_for("login"))

004 レポート Python in Excelの開発版登場
005 レポート iPhone 15シリーズを発表
006 製品レビュー 電子小物「koko Tag(ココタグ)」
007 NEWS FLASH
008 特集1 Markdown入門ガイド/藤原由来 コード掲載
024 特集2 最新COBOL入門/比毛寛之 コード掲載
034 特別企画 CMSのPlone/酒井忠臣、烈
046 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
050 CMS/桑原滝弥、イケヤシロウ
052 Pythonあれこれ/飯尾淳 コード掲載
057 Hello Nogyo!
058 中小企業手作りIT化奮戦記/菅雄一
062 法林浩之のFIGHTING TALKS/法林浩之
064 香川大学SLPからお届け!/石上椋一 コード掲載
072 行動経済学と心理学で円滑に業務を遂行/請園正敏
076 タイ語から分かる現地生活/つじみき
082 Linux定番エディタ入門/大津真
088 ユニケージ通信/田渕智也、高橋未来哉 コード掲載
091 Techパズル/gori.sh
093 コラム「ユニケージアーキテクチャ」/シェル魔人
著者:藤原 由来
本特集では、文書の装飾や構造付けを手軽に実施できる記法「Markdown」について解説します。Markdownを使うと、テキストファイルでレポートやスライド、書籍などを作成でき、GitHubなどの各種Webサービスでの投稿やコミュニケーションがより便利になります。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。![]()
![]()
図2 Markdownの記述内容
# カレーの作り方
おいしいカレーを作りましょう。
## 材料
- カレールー
- 牛肉
- 野菜
- サラダ油
- 水
図19 Markdownテキストの例
#␣カレーの作り方
おいしいカレーを作りましょう。
##␣材料
-␣カレールー
-␣牛肉
-␣野菜
-␣サラダ油
-␣水
図46 MarpによるスライドのMarkdownテキスト例
---
marp: true
---
# カレーの作り方
おいしいカレーを作りましょう。
---
# 材料
- カレールー
- 牛肉
- 野菜
- サラダ油
- 水
著者:比毛 寛之
レガシー問題や技術者不足などで話題の多いプログラミング言語が「COBOL」です 。古い言語というイメージがありますが、ISOから2023年の新規格が公表されたことでも分かるように、現在も進化を続けています。本特集では、COBOLの現状を紹介しつつ、「opensource COBOL 4J」というオープンソースソフトウエアのCOBOLコンパイラを使ってCOBOL言語の基本を解説します。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。![]()
![]()
図1 「HELLO WORLD!」という文字列を表示するCOBOLのサンプルコード
----+----1----+----2----+----3----+----4----+----5
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLOWORLD.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 MY-TEXT PIC X(20).
PROCEDURE DIVISION.
MAIN-RTN.
MOVE "HELLO WORLD!" TO MY-TEXT.
DISPLAY MY-TEXT.
STOP RUN.
----+----1----+----2----+----3----+----4----+----5
図4 COBOLのサンプルコード
----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8
IDENTIFICATION DIVISION.
PROGRAM-ID. MYCOBOL.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 MY-GRP.
* 03 MY-WORK-X PIC X(10). 20230804
03 MY-WORK-X2 PIC X(10). 20230804
03 MY-WORK-9 PIC 9(05).
********************************************
PROCEDURE DIVISION.
********************************************
PROC1 SECTION.
PROC1-000.
* MOVE "ABC" TO MY-WORK-X. 20230804
MOVE "DEF" TO MY-WORK-X2. 20230804
MOVE 100 TO MY-WORK-9.
PROC1-900.
STOP RUN.
----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8
図5 コンソールからの入力と画面への出力をするサンプルコード
----+----1----+----2----+----3----+----4----+----5----+
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 MY-NAME PIC X(20).
PROCEDURE DIVISION.
MAIN-RTN.
DISPLAY "Enter your name: " NO ADVANCING.
ACCEPT MY-NAME.
DISPLAY "Hello " MY-NAME.
MAIN-EXIT.
STOP RUN.
----+----1----+----2----+----3----+----4----+----5----+
図6 ファイルにデータを出力するサンプルコード
----+----1----+----2----+----3----+----4----+----5----+----6----+----7
IDENTIFICATION DIVISION.
PROGRAM-ID. EMPWRITE.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT EMP-FILE ASSIGN TO "EMPFILE"
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS EMP-CD
FILE STATUS IS EMP-STS.
DATA DIVISION.
FILE SECTION.
FD EMP-FILE.
01 EMP-REC.
03 EMP-CD PIC X(04).
03 EMP-NAME PIC X(20).
03 EMP-DPT-CD PIC X(02).
03 EMP-ENT-DATE PIC 9(08).
WORKING-STORAGE SECTION.
01 EMP-STS PIC 9(02).
PROCEDURE DIVISION.
MAIN-CONTROL SECTION.
MAIN-000.
DISPLAY "*** Creating Employee file ***".
OPEN OUTPUT EMP-FILE.
*
MOVE "0011" TO EMP-CD.
MOVE "Saitama Saburo" TO EMP-NAME.
MOVE "01" TO EMP-DPT-CD.
MOVE 20020401 TO EMP-ENT-DATE.
WRITE EMP-REC.
* ----+----+----+----+----+----+----+
WRITE EMP-REC FROM "0012Chiba Jiro 0219990401".
WRITE EMP-REC FROM "0013Tokyo Taro 0319970401".
WRITE EMP-REC FROM "0014Kanagawa Shiro 0120120401".
WRITE EMP-REC FROM "0015Niigata Goroo 0220010401".
* ----+----+----+----+----+----+----+
CLOSE EMP-FILE.
MAIN-900.
STOP RUN.
----+----1----+----2----+----3----+----4----+----5----+----6----+----7
図7 ファイル中のレコードを読み出して画面に表示するサンプルコード
----+----1----+----2----+----3----+----4----+----5----+----6----+
IDENTIFICATION DIVISION.
PROGRAM-ID. EMPLIST.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT EMP-FILE ASSIGN TO "EMPFILE"
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS EMP-CD
FILE STATUS IS EMP-STS.
DATA DIVISION.
FILE SECTION.
FD EMP-FILE.
01 EMP-REC.
03 EMP-CD PIC X(04).
03 EMP-NAME PIC X(20).
03 EMP-DPT-CD PIC X(02).
03 EMP-ENT-DATE PIC 9(08).
WORKING-STORAGE SECTION.
01 EMP-STS PIC 9(02).
01 DSP-REC.
03 DSP-CD PIC X(04).
03 FILLER PIC X.
03 DSP-NAME PIC X(20).
03 FILLER PIC XX.
03 DSP-DPT-CD PIC X(02).
03 FILLER PIC X.
03 DSP-ENT-DATE PIC 9999/99/99.
PROCEDURE DIVISION.
MAIN-CONTROL SECTION.
MAIN-000.
OPEN INPUT EMP-FILE.
DISPLAY "*** Employee List ***".
DISPLAY "ID Employee Name Dpt Enter date".
DISPLAY "---- -------------------- --- ----------".
PERFORM UNTIL (EMP-STS NOT = ZERO)
READ EMP-FILE NEXT
AT END
DISPLAY "EOF"
NOT AT END
MOVE EMP-CD TO DSP-CD
MOVE EMP-NAME TO DSP-NAME
MOVE EMP-DPT-CD TO DSP-DPT-CD
MOVE EMP-ENT-DATE TO DSP-ENT-DATE
DISPLAY DSP-REC
END-READ
END-PERFORM.
CLOSE EMP-FILE.
MAIN-900.
STOP RUN.
----+----1----+----2----+----3----+----4----+----5----+----6----+
図9 「EMPSEARCH.cbl」ファイルに記述するコード
----+----1----+----2----+----3----+----4----+----5----+----6----+-
IDENTIFICATION DIVISION.
PROGRAM-ID. EMPSEARCH.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WK-AREA.
03 WK-CD PIC X(04).
03 WK-NAME PIC X(20).
03 WK-DPT-CD PIC X(02).
03 WK-ENT-DATE PIC 9(08).
03 WK-RETURN PIC 9(01).
PROCEDURE DIVISION.
MAIN-RTN.
DISPLAY "*** Employee Search ***".
DISPLAY "Code: : " NO ADVANCING.
ACCEPT WK-CD.
CALL "EMPREAD" USING WK-CD, WK-NAME, WK-DPT-CD,
WK-ENT-DATE, WK-RETURN.
IF WK-RETURN = ZERO
DISPLAY "Name : " WK-NAME
DISPLAY "Dept code : " WK-DPT-CD
DISPLAY "Enter date: " WK-ENT-DATE
ELSE
DISPLAY "Employee not found!"
END-IF.
MAIN-EXIT.
STOP RUN.
----+----1----+----2----+----3----+----4----+----5----+----6----+-
図10 「EMPREAD.cbl」ファイルに記述するコード
----+----1----+----2----+----3----+----4----+----5----+----6----+----7
IDENTIFICATION DIVISION.
PROGRAM-ID. EMPREAD.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT EMP-FILE ASSIGN TO "EMPFILE"
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS EMP-CD
FILE STATUS IS EMP-STS.
DATA DIVISION.
FILE SECTION.
FD EMP-FILE.
01 EMP-REC.
03 EMP-CD PIC X(04).
03 EMP-NAME PIC X(20).
03 EMP-DPT-CD PIC X(02).
03 EMP-ENT-DATE PIC 9(08).
WORKING-STORAGE SECTION.
01 EMP-STS PIC 9(02).
LINKAGE SECTION.
01 LK-CD PIC X(04).
01 LK-NAME PIC X(20).
01 LK-DPT-CD PIC X(02).
01 LK-ENT-DATE PIC 9(08).
01 LK-RETURN PIC 9(01).
PROCEDURE DIVISION USING LK-CD, LK-NAME, LK-DPT-CD,
LK-ENT-DATE, LK-RETURN.
MAIN-CONTROL SECTION.
MAIN-000.
INITIALIZE EMP-REC.
MOVE ZERO TO LK-RETURN.
OPEN INPUT EMP-FILE.
MOVE LK-CD TO EMP-CD.
READ EMP-FILE KEY IS EMP-CD
INVALID KEY
MOVE 1 TO LK-RETURN
END-READ.
MOVE EMP-NAME TO LK-NAME.
MOVE EMP-DPT-CD TO LK-DPT-CD.
MOVE EMP-ENT-DATE TO LK-ENT-DATE.
CLOSE EMP-FILE.
MAIN-900.
EXIT PROGRAM.
----+----1----+----2----+----3----+----4----+----5----+----6----+----7
図11 「EmpSearchDemo.java」ファイルに記述するコード
import jp.osscons.opensourcecobol.libcobj.ui.*;
public class EmpSearchDemo {
public static void main(String[] args) throws Exception {
EMPREAD prog = new EMPREAD();
CobolResultSet rs = prog.execute("0011", "", "", 0, 0);
System.out.println("*** Employee Search from Java ***");
System.out.println("Code : " + rs.getString(1));
System.out.println("Name : " + rs.getString(2));
System.out.println("Dept code : " + rs.getString(3));
System.out.println("Enter date: " + rs.getInt(4));
}
}
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W」を活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第2回は、有機ELディスプレイを取り付けます。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。![]()
![]()
図6 SSD1306搭載有機ELディスプレイを動作させるサンプルプログラム(ssd1306_sample.py)
from machine import I2C
from ssd1306 import SSD1306_I2C
import network
i2c = I2C(0)
ssd = SSD1306_I2C(width=128, height=32, i2c=i2c, addr=0x3C)
ssd.fill(0) # 0クリア
ssd.text("Hello, world", 0, 0)
# IPアドレスを描画
conn = network.WLAN(network.STA_IF)
if conn.isconnected():
conf = conn.ifconfig()
ssd.text(conf[0], 0, 8)
ssd.show() # フレームバッファをSSD1306に転送

004 レポート Wasm向けPOSIX互換ライブラリ「WASIX」
005 レポート 手のひらネットワーク機器
006 製品レビュー ロボット「NICOBO(ニコボ)」
007 NEWS FLASH
008 特集1 ゆっくりMovieMaker4で映像制作 準備編/ふる(FuruyamD)
015 Hello Nogyo!
016 特集2 ZabbixのAWS監視機能を使ってみよう/寺島広大、渡邊隼人、水谷和弘、狩俣樹
026 特集3 ChatGPTで注目の対話型AI/三沢友治
034 特別企画 成功するアジャイル開発/奥村剛史
044 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
054 Pythonあれこれ/飯尾淳
060 行動経済学と心理学で円滑に業務を遂行/請園正敏
064 中小企業手作りIT化奮戦記/菅雄一
068 SEO/桑原滝弥、イケヤシロウ
070 タイ語から分かる現地生活/つじみき
076 法林浩之のFIGHTING TALKS/法林浩之
078 香川大学SLPからお届け!/谷知紘 コード掲載
084 Linux定番エディタ入門/大津真
091 ユニケージ通信/田渕智也、高橋未来哉
096 Techパズル/gori.sh
097 コラム「ユニケージはデータ移行も得意」/シェル魔人
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載し、入手しやすく、価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第1回は、プログラムの開発環境を構築します。
シェルスクリプトマガジン Vol.85は以下のリンク先でご購入できます。![]()
![]()
図17 初期化プログラム(boot.py)
import sys
import time
import network
import ntptime
from machine import RTC
SSID='your_ssid'
PASS='your_passphrase'
IFCONFIG=('192.168.1.80', '255.255.255.0', '192.168.1.1', '192.168.1.1')
def wifi_connect(ssid, passkey, timeout=20):
conn = network.WLAN(network.STA_IF)
if conn.isconnected():
return conn
conn.active(True)
conn.connect(ssid, passkey)
while not conn.isconnected() and timeout > 0:
time.sleep(1)
timeout -= 1
if conn.isconnected():
return conn
else:
return None
conn = wifi_connect(SSID, PASS)
if conn is None:
print('Can not connect to ' + SSID)
else:
conn.ifconfig(IFCONFIG)
print('Connect to ' + SSID)
# 日時設定
rtc = RTC()
ntptime.host = 'ntp.nict.jp'
now = time.localtime(ntptime.time() + 9 * 60 * 60)
rtc.datetime((now[0], now[1], now[2], now[6], now[3], now[4], now[5], 0))
now = rtc.datetime()
print("%04d-%02d-%02d %02d:%02d:%02d" %(now[0], now[1], now[2], now[4], now[5], now[6]))
図18 簡易ネットワークサーバー(main.py)
import usocket as socket
import network
import time
html = '''
<html>
<head><title>Pico W</title></head>
<body>
<h1>Welcome to Pico W</h1>
</body>
</html>
'''
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 80))
sock.listen(4)
while True:
conn, addr = sock.accept()
print('Connect from %s' %(str(addr)));
req = conn.recv(1024).decode()
if not req.startswith('GET / HTTP/1.1'):
conn.send('HTTP/1.1 404 Not Found\r\n')
conn.close()
else:
conn.send('HTTP/1.1 200 OK\r\n')
conn.send('Content-Type: text/html\r\n')
conn.send('Connection: close\r\n\r\n')
conn.sendall(html)
conn.close()
著者:谷 知紘
今回は、C言語を用いて作成した、コンピュータと対戦できるリバーシを紹介します。あまり強くはありませんが、きちんとコンピュータが相手をしてくれます。C99以降の規格に対応するCコンパイラと標準Cライブラリがあれば、環境を問わずに動作するプログラムになっていますので、皆さんもコンパイルして楽しんでください。
シェルスクリプトマガジン Vol.85は以下のリンク先でご購入できます。![]()
![]()
図5 盤面の生成用コード
//盤面 空白(0) 黒(-1) 白(1) 番兵(2)
int board[10][10] = {0};
//スコアを格納する配列
int weightdata[10][10] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 30,-12, 0, -1, -1, 0,-12, 30, 0},
{0,-12,-15, -3, -3, -3, -3,-15,-12, 0},
{0, 0, -3, 0, -1, -1, 0, -3, 0, 0},
{0, -1, -3, -1, -1, -1, -1, -3, -1, 0},
{0, -1, -3, -1, -1, -1, -1, -3, -1, 0},
{0, 0, -3, 0, -1, -1, 0, -3, 0, 0},
{0,-12,-15, -3, -3, -3, -3,-15,-12, 0},
{0, 30,-12, 0, -1, -1, 0,-12, 30, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
//盤面の生成
void make_board(){
//番兵
for(int i = 0; i < 10; i++){
board[0][i] = 2;
board[9][i] = 2;
board[i][0] = 2;
board[i][9] = 2;
}
//初期配置する石
board[4][4] = 1;
board[5][5] = 1;
board[4][5] = -1;
board[5][4] = -1;
}
図6 check_plc()関数のコード
//指定したマスに石を置けるかどうかを判定する関数
bool check_plc(int i, int j, int now_board[10][10]){
//マスが空かどうか
if(board[i][j] == 0){
//全方向を探索
for(int dir_i = -1; dir_i < 2; dir_i++){
for(int dir_j = -1; dir_j < 2; dir_j++){
if(check_dir(i,j,dir_i,dir_j,now_board)){
//配置可能であればtrueを返す
return true;
}
}
}
}
return false;
}
図8 check_dir()関数のコード
//指定方向に何個の石を挟めるかを調べる関数
int check_dir(int i, int j,
int dir_i, int dir_j,
int now_board[10][10]){
//指定方向に相手の石がある場合は次のマスを探索
int times = 1;
while(now_board[i+dir_i*times][j+dir_j*times] == player*-1){
times++;
}
//指定方向の最後に自分の石がある場合
if(now_board[i+dir_i*times][j+dir_j*times] == player){
//指定方向に相手の石が何個あるかを返す
return times-1;
}
//指定方向の最後に自分の石がなければ0を返す
return 0;
}
図9 place_stn()関数のコード
//指定したマスに石を配置して、挟んだ石を自分の石にする関数
void place_stn(int i, int j, int now_board[10][10]){
//全方向を調査
for(int dir_i = -1; dir_i < 2; dir_i++){
for(int dir_j = -1; dir_j < 2; dir_j++){
//挟んだ石の数
int change_num = check_dir(i,j,dir_i,dir_j,now_board);
//挟んだ石の数だけ自分の石に変更
for(int k = 1; k < change_num+1; k++){
now_board[i+dir_i*k][j+dir_j*k] = player;
}
}
}
//指定したマスに自分の石を配置
now_board[i][j] = player;
}
図10 think()関数のコード
//コンピュータの手番で呼び出される関数
void think(){
//ハイスコアの初期化
int hightscore = -1000;
int px, py;
for(int y = 0; y < 10; y++){
for(int x = 0; x < 10; x++){
//石を置けない場合はスキップ
if(!check_plc(y, x, board)){
continue;
}
int tmpdata[10][10] = {0};
//盤面データをコピーした仮の盤面を作成
copydata(tmpdata);
//仮の盤面に石を置く
place_stn(y, x, tmpdata);
//総スコアを計算する
int score = calcweight(tmpdata);
//ハイスコアよりも総スコアが良ければ更新する
if(score > hightscore){
hightscore = score;
px = x; py = y;
}
}
}
//総スコアが最大のマスに石を置く
place_stn(py, px, board);
printf("PCは(x , y) = (%d , %d)に置きました\n",
px, py);
}
図11 copydata()関数のコード
//盤面データをコピーする関数
void copydata(int tmpdata[10][10]){
for(int y = 0; y < 10; y++){
for(int x = 0; x < 10; x++){
tmpdata[y][x] = board[y][x];
}
}
}
図12 calcweight()関数のコード
//総スコアを計算する関数
int calcweight(int tmpdata[10][10]){
int score = 0;
for(int y = 0; y < 10; y++){
for(int x = 0; x < 10; x++){
//番兵はスキップ
if(tmpdata[y][x] == 2){
continue;
}
//自分の石がある場所のスコアを足す
if(tmpdata[y][x] == 1){
score += weightdata[y][x];
}
}
}
return score;
}
p.12の表1の一番下の行にある「00d.png」は「00e.png」の誤りです。お詫びして訂正いたします。
p.18に記した「後述するAWS用のテンプレートの利用を含め、すべてAWSの無料利用枠で試せる」という情報は誤りでした。正しくは、AWS用のテンプレート利用時には、CloudWatchからGetMetricData APIでメトリクスを取得する料金がかかります。料金は、メトリクス1000件当たり0.01ドルです。お詫びして訂正いたします。なお、Kindle版とPDF版(定期購読特典)は修正済みです。
情報は随時更新致します。
カテゴリー コード のアーカイブを表示しています。