VSCode の Auto Markdownで目次や章番号が出てこなくなったら、とにかく、拡張機能の全部にチェックをつける
Golangでcsvのパースをする時に、結構な頻度でハマること (reader.ReadAll()を使うと頻発する問題 strings.TrimSpaceを使うこと)
以下のGo言語プログラムで、"small2_bus_data.csv"が1行のみの
1, 93, 139.62957005198, 35.36604342344, 12:55:00
を使って、を読み込ませたのですが、その結果が
1 0 0 12:55:00 [{1 0001-01-01 00:00:00 +0000 UTC {0 0}}]
となってしまいます。
package main
import (
"encoding/csv"
"fmt"
"os"
"strconv"
"time"
)
// 緯度経度の型定義
type LatLng struct {
Lat, Lng float64
}
// 時間と緯度経度の情報を持つ構造体
type BusData struct {
NodeID int
Time time.Time
Location LatLng
}
func main() {
// CSVファイルを開く
file, err := os.Open("small2_bus_data.csv")
if err != nil {
fmt.Println("Error:", err)
return
}
defer file.Close()
// CSVファイルの内容をパースする
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
fmt.Println("Error:", err)
return
}
// データを格納するためのスライス
var busData []BusData
// CSVの各行を処理する
for _, record := range records {
nodeID, _ := strconv.Atoi(record[0])
lng, _ := strconv.ParseFloat(record[2], 64)
lat, _ := strconv.ParseFloat(record[3], 64)
timeStr := record[4]
fmt.Println(nodeID, lng, lat, timeStr)
// 時間のパース
var parsedTime time.Time
if timeStr != "" {
parsedTime, _ = time.Parse("15:04:05", timeStr)
}
// データを構造体に格納
data := BusData{
NodeID: nodeID,
Time: parsedTime,
Location: LatLng{
Lat: lat,
Lng: lng,
},
}
busData = append(busData, data)
}
fmt.Println(busData)
}
で、かなり、すったもんだした結果、文字列に余分なスペースが含まれていたため であることが分かりました(このくらい自動で対処して欲しいが)。
strings.TrimSpace がキモだったようです。
修正後のプログラムは以下の通り。
package main
import (
"encoding/csv"
"fmt"
"os"
"strconv"
"strings"
"time"
)
// 緯度経度の型定義
type LatLng struct {
Lat, Lng float64
}
// 時間と緯度経度の情報を持つ構造体
type BusData struct {
NodeID int
Time time.Time
Location LatLng
}
func main() {
// CSVファイルを開く
file, err := os.Open("small2_bus_data.csv")
if err != nil {
fmt.Println("Error:", err)
return
}
defer file.Close()
// CSVファイルの内容をパースする
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
fmt.Println("Error:", err)
return
}
// データを格納するためのスライス
var busData []BusData
// CSVの各行を処理する
for _, record := range records {
nodeID, _ := strconv.Atoi(record[0])
// スペースをトリムしてから実数に変換
lng, err := strconv.ParseFloat(strings.TrimSpace(record[2]), 64)
if err != nil {
fmt.Println("Error parsing lng:", err)
return
}
lat, err := strconv.ParseFloat(strings.TrimSpace(record[3]), 64)
if err != nil {
fmt.Println("Error parsing lat:", err)
return
}
timeStr := strings.TrimSpace(record[4]) // スペースをトリム
fmt.Println(nodeID, lng, lat, timeStr)
// 時間のパース
var parsedTime time.Time
if timeStr != "" {
parsedTime, err = time.Parse("15:04:05", timeStr)
if err != nil {
fmt.Println("Error parsing time:", err)
return
}
}
// データを構造体に格納
data := BusData{
NodeID: nodeID,
Time: parsedTime,
Location: LatLng{
Lat: lat,
Lng: lng,
},
}
busData = append(busData, data)
}
fmt.Println(busData)
}
出力結果は
>go run main28.go
1 139.62957005198 35.36604342344 12:55:00
[{1 0000-01-01 12:55:00 +0000 UTC {35.36604342344 139.62957005198}}]
となり、一安心です。
GitHub Copilotの"Copilot Chat"をプログラム表示画面の右側の画面に配置する方法
これが正解か分かりませんが、とりあえず力づくでやりまた。
異なるユーザを考慮した3次元を取り扱うDBSCANプログラム
このプログラムでは、同一座標は1つの座標として纏められてしまって、異なるユーザの座標として取り扱ってくれません。
これに対応するために修正したプログラムは以下の通りです。
// ~/tomioka_school/src/trip_school/dbscan_3d_2.go
package main
import (
"fmt"
"math"
)
// Point represents a 3D point with coordinates x, y, and t
type Point struct {
User string
X, Y, T float64
}
// DistanceTo calculates the Euclidean distance between two 3D points
func (p Point) DistanceTo(other Point) float64 {
dx := p.X - other.X
dy := p.Y - other.Y
dt := p.T - other.T
return math.Sqrt(dx*dx + dy*dy + dt*dt)
}
// Cluster represents a cluster of points
type Cluster struct {
Points []Point
}
// DBSCAN performs density-based clustering of 3D points
func DBSCAN(points []Point, epsilon float64, minPts int) []Cluster {
var clusters []Cluster
var visited = make(map[string]bool)
for _, point := range points {
pointKey := fmt.Sprintf("%s,%f,%f,%f", point.User, point.X, point.Y, point.T)
if visited[pointKey] {
continue
}
visited[pointKey] = true
neighbours := getNeighbours(points, point, epsilon)
if len(neighbours) < minPts {
continue
}
var clusterPoints []Point
expandCluster(&clusterPoints, points, visited, point, neighbours, epsilon, minPts)
clusters = append(clusters, Cluster{Points: clusterPoints})
}
return clusters
}
// getNeighbours returns all points within distance epsilon of the given point
func getNeighbours(points []Point, point Point, epsilon float64) []Point {
var neighbours []Point
for _, other := range points {
if point.DistanceTo(other) <= epsilon {
neighbours = append(neighbours, other)
}
}
return neighbours
}
// expandCluster expands the cluster from the given point
func expandCluster(cluster *[]Point, points []Point, visited map[string]bool, point Point, neighbours []Point, epsilon float64, minPts int) {
*cluster = append(*cluster, point)
for _, neighbour := range neighbours {
neighbourKey := fmt.Sprintf("%s,%f,%f,%f", neighbour.User, neighbour.X, neighbour.Y, neighbour.T)
if !visited[neighbourKey] {
visited[neighbourKey] = true
neighbourNeighbours := getNeighbours(points, neighbour, epsilon)
if len(neighbourNeighbours) >= minPts {
expandCluster(cluster, points, visited, neighbour, neighbourNeighbours, epsilon, minPts)
}
}
}
}
func main() {
points := []Point{
{"A", 1, 2, 0},
{"A", 1.5, 1.8, 1},
{"A", 5, 8, 2},
{"A", 8, 8, 3},
{"A", 1, 0.6, 4},
{"A", 9, 11, 5},
{"A", 8, 2, 6},
{"A", 10, 2, 7},
{"A", 9, 3, 8},
{"B", 1, 2, 0},
{"B", 1.5, 1.8, 1},
{"B", 5, 8, 2},
{"B", 8, 8, 3},
{"B", 1, 0.6, 4},
{"B", 9, 11, 5},
{"B", 8, 2, 6},
{"B", 10, 2, 7},
{"B", 9, 3, 8},
{"C", 1, 2, 0},
{"C", 1.5, 1.8, 1},
{"C", 5, 8, 2},
{"C", 8, 8, 3},
{"C", 1, 0.6, 4},
{"C", 9, 11, 5},
{"C", 8, 2, 6},
{"C", 10, 2, 7},
{"C", 9, 3, 8},
}
// epsilon := 3.0
// minPts := 5
epsilon := 2.5
minPts := 5
clusters := DBSCAN(points, epsilon, minPts)
fmt.Println("Combined Clusters:")
for i, cluster := range clusters {
fmt.Printf("Cluster %d:\n", i+1)
for _, point := range cluster.Points {
fmt.Printf(" (%s, %.2f, %.2f, %.2f)\n", point.User, point.X, point.Y, point.T)
}
}
}
出力結果は以下の通りです。
C:\Users\ebata\tomioka_school\src\trip_school>go run dbscan_3d_2.go
Combined Clusters:
Cluster 1:
(A, 1.00, 2.00, 0.00)
(A, 1.50, 1.80, 1.00)
(B, 1.00, 2.00, 0.00)
(B, 1.50, 1.80, 1.00)
(C, 1.00, 2.00, 0.00)
(C, 1.50, 1.80, 1.00)
Cluster 2:
(A, 8.00, 2.00, 6.00)
(A, 10.00, 2.00, 7.00)
(A, 9.00, 3.00, 8.00)
(B, 8.00, 2.00, 6.00)
(B, 10.00, 2.00, 7.00)
(B, 9.00, 3.00, 8.00)
(C, 8.00, 2.00, 6.00)
(C, 10.00, 2.00, 7.00)
(C, 9.00, 3.00, 8.00)
京急富岡駅を基準として正規化された緯度と経度を返す関数
// NormalizeCoordinates 京急富岡駅を基準として正規化された緯度と経度を返す関数
// C:\Users\ebata\tomioka_school\src\trip_school\NormalizeCoordinates.go
package main
import (
"fmt"
"math"
)
func NormalizeCoordinates(referenceLat, referenceLng, lat, lng float64) (float64, float64) {
// 1度あたりの緯度経度のメートル換算
metersPerDegreeLat := 111319.9 // 緯度1度あたりのメートル数
metersPerDegreeLng := 111319.9 * math.Cos(referenceLat*(math.Pi/180.0)) // 経度1度あたりのメートル数
// 緯度と経度の差を計算
deltaLat := lat - referenceLat
deltaLng := lng - referenceLng
// 正規化された緯度と経度を計算
normalizedLat := deltaLat * metersPerDegreeLat / 100.0
normalizedLng := deltaLng * metersPerDegreeLng / 100.0
return normalizedLat, normalizedLng
}
func main() {
// 京急富岡駅の緯度経度
keikyuTomiokaLat := 35.367131726654705
keikyuTomiokaLng := 139.62988318023088
// 確認用の緯度経度(富岡駅のバス停)
lat := 35.36605614545459
lng := 139.6295094178281
// 緯度経度の正規化
normalizedLat, normalizedLng := NormalizeCoordinates(keikyuTomiokaLat, keikyuTomiokaLng, lat, lng)
fmt.Printf("正規化された緯度: %.6f\n", normalizedLat)
fmt.Printf("正規化された経度: %.6f\n", normalizedLng)
}
2つの日時文字列の時間差を秒単位で計算し、600秒を1として正規化する関数
// NormalizeTime 2つの日時文字列の時間差を秒単位で計算し、600秒を1として正規化する関数
// C:\Users\ebata\tomioka_school\src\trip_school\NormalizeTime.go
package main
import (
"fmt"
"time"
)
func NormalizeTime(timeStr1, timeStr2 string) float64 {
layout := "2006-01-02 15:04:05"
t1, err := time.Parse(layout, timeStr1)
if err != nil {
fmt.Println("Error parsing time string 1:", err)
return 0
}
t2, err := time.Parse(layout, timeStr2)
if err != nil {
fmt.Println("Error parsing time string 2:", err)
return 0
}
duration := t2.Sub(t1).Seconds()
normalizedDuration := duration / 600.0
return normalizedDuration
}
func main() {
timeStr1 := "2024-01-01 00:00:00"
timeStr2 := "2024-02-11 23:45:00"
normalized := NormalizeTime(timeStr1, timeStr2)
fmt.Println("正規化された時間差:", normalized)
}
3次元を取り扱うDBSCANプログラム
/*
c:\users\ebata\dscan\dscan3.go
DBSCANアルゴリズムを実装し、3次元空間のデータ(x、y、t)をクラスタリングするシンプルなプログラムです。
このプログラムでは、各データポイントは3次元座標(x、y、t)で表され、距離の計算にはユークリッド距離を使用します。
DBSCANをk-meansの違いで説明します。
DBSCAN(Density-Based Spatial Clustering of Applications with Noise)とk-meansは、クラスタリングアルゴリズムですが、そのアプローチや動作原理にはいくつかの違いがあります。
(1)クラスタリング方法:
- DBSCAN: 密度ベースのクラスタリングアルゴリズムであり、データポイントの密度に基づいてクラスタを形成します。各点は、一定の距離(ε、epsilon)内に最小限の近傍点数(minPts)が存在する場合、その点を中心としたクラスタが形成されます。
- k-means: 距離ベースのクラスタリングアルゴリズムであり、データポイントの距離に基づいてクラスタを形成します。クラスタの数(k)を事前に指定し、各点を最も近いセントロイド(クラスタの中心)に割り当てます。
(2)クラスタの形状:
- DBSCAN: クラスタの形状は任意であり、密度の高い領域に基づいて形成されます。したがって、DBSCANは非凸形状のクラスタを処理できます。
- k-means: クラスタの形状は円形(球形)であり、各クラスタのセントロイドからの距離に基づいて決定されます。したがって、k-meansは凸形状のクラスタを前提としています。
(3)ハイパーパラメータ:
- DBSCAN: ε(epsilon)とminPtsの2つのハイパーパラメータを必要とします。εは近傍点の距離の閾値を定義し、minPtsはクラスタと見なすための最小の近傍点数を指定します
- k-means: クラスタの数(k)を指定する必要があります。
(4)ノイズの処理:
- DBSCAN: ノイズポイントを自動的に検出し、外れ値として扱います。密度が低い領域に存在するポイントは、任意のクラスタに割り当てられず、ノイズとして扱われます。
-k-means: 外れ値やノイズの処理を明示的に行いません。各点は必ずどれかのクラスタに割り当てられます。
要するに、DBSCANは密度ベースのアルゴリズムであり、任意の形状のクラスタを検出し、ノイズを処理する能力があります。一方、k-meansは距離ベースのアルゴリズムであり、クラスタの形状が円形であることを前提としています。
*/
package main
import (
"fmt"
"math"
)
// Point represents a 3D point with coordinates x, y, and t
type Point struct {
X, Y, T float64
}
// DistanceTo calculates the Euclidean distance between two 3D points
func (p Point) DistanceTo(other Point) float64 {
dx := p.X - other.X
dy := p.Y - other.Y
dt := p.T - other.T
return math.Sqrt(dx*dx + dy*dy + dt*dt)
}
// Cluster represents a cluster of points
type Cluster struct {
Points []Point
}
// DBSCAN performs density-based clustering of 3D points
func DBSCAN(points []Point, epsilon float64, minPts int) []Cluster {
var clusters []Cluster
var visited = make(map[Point]bool)
for _, point := range points {
if visited[point] {
continue
}
visited[point] = true
neighbours := getNeighbours(points, point, epsilon)
if len(neighbours) < minPts {
continue
}
var clusterPoints []Point
expandCluster(&clusterPoints, points, visited, point, neighbours, epsilon, minPts)
clusters = append(clusters, Cluster{Points: clusterPoints})
}
return clusters
}
// getNeighbours returns all points within distance epsilon of the given point
func getNeighbours(points []Point, point Point, epsilon float64) []Point {
var neighbours []Point
for _, other := range points {
if point.DistanceTo(other) <= epsilon {
neighbours = append(neighbours, other)
}
}
return neighbours
}
// expandCluster expands the cluster from the given point
func expandCluster(cluster *[]Point, points []Point, visited map[Point]bool, point Point, neighbours []Point, epsilon float64, minPts int) {
*cluster = append(*cluster, point)
for _, neighbour := range neighbours {
if !visited[neighbour] {
visited[neighbour] = true
neighbourNeighbours := getNeighbours(points, neighbour, epsilon)
if len(neighbourNeighbours) >= minPts {
expandCluster(cluster, points, visited, neighbour, neighbourNeighbours, epsilon, minPts)
}
}
// Add neighbour to cluster if not already in another cluster
var isInCluster bool
for _, c := range *cluster {
if c == neighbour {
isInCluster = true
break
}
}
if !isInCluster {
*cluster = append(*cluster, neighbour)
}
}
}
func main() {
// Example usage
points := []Point{
{X: 1, Y: 2, T: 0},
{X: 1.5, Y: 1.8, T: 1},
{X: 5, Y: 8, T: 2},
{X: 8, Y: 8, T: 3},
{X: 1, Y: 0.6, T: 4},
{X: 9, Y: 11, T: 5},
{X: 8, Y: 2, T: 6},
{X: 10, Y: 2, T: 7},
{X: 9, Y: 3, T: 8},
}
epsilon := 3.0
minPts := 2
clusters := DBSCAN(points, epsilon, minPts)
for i, cluster := range clusters {
fmt.Printf("Cluster %d:\n", i+1)
for _, point := range cluster.Points {
fmt.Printf(" (%.2f, %.2f, %.2f)\n", point.X, point.Y, point.T)
}
}
}
Web版 MindManagerが「ファイルを開けませんでした」と言ってきた時の対応
を起動したら、
となることがあるので、この対応方法をメモしておきます。
https://app.mindmanager.com/ と入力(#my-filesはつけない)
ファイルが表示されるので、これを選択。
表示されるようになる。
のプロパティを開いて新しいURLをコピペする。
以上
Webサーバに繋っているブラウザが、全部いなくなったと確認できた場合(30秒後)に、url = "http://localhost:8000/cancelAllVideoStreamGround"へメッセージを渡すプログラム
本プログラムは、
を拡張したものです。index.htmlは、このページに記載されているものを、そのまま、使って下さい。
こちらのプログラムでは、Webサーバに繋っているブラウザが、全部いなくなったと確認できた場合(30秒後)に、url = "http://localhost:8000/cancelAllVideoStreamGround"へメッセージを渡すプログラムです。
デバッグの為に使っていたコメントが残っていますので、適当に削除して下さい。
# c:\users\ebata\webMonitor.py
# https://wp.kobore.net/%e6%b1%9f%e7%ab%af%e3%81%95%e3%82%93%e3%81%ae%e6%8a%80%e8%a1%93%e3%83%a1%e3%83%a2/post-12475/を参照のこと
import threading
import time
import requests
import sys
from flask import Flask, request, jsonify
from requests.exceptions import Timeout
from flask_cors import CORS
app = Flask(__name__)
CORS(app) # すべてのリクエストに対してCORSを有効にする
last_heartbeat_time = 0
lock = threading.Lock()
def send_notification():
url = "http://localhost:8000/cancelAllVideoStreamGround"
try:
response = requests.post(url, timeout=3)
response.raise_for_status()
print("Notification sent successfully.")
sys.stdout.flush() # 標準出力をフラッシュする
except Timeout:
print("Error: Timeout while sending notification")
except requests.exceptions.RequestException as e:
print(f"Error sending notification: {e}")
def check_heartbeat():
global last_heartbeat_time
while True:
current_time = time.time()
print("ct:",current_time)
print("lht:",last_heartbeat_time)
diff = current_time - last_heartbeat_time
print("diff:",diff)
with lock:
if current_time - last_heartbeat_time > 30:
send_notification()
last_heartbeat_time = current_time
time.sleep(1)
@app.route('/heartbeat', methods=['POST'])
def receive_heartbeat():
print("pass receive_heartbeat()")
global last_heartbeat_time
data = request.get_json()
print(f"Received heartbeat: {data}")
sys.stdout.flush() # 標準出力をフラッシュする
with lock:
last_heartbeat_time = time.time()
print("lht_2:",last_heartbeat_time)
# data = request.get_json()
# print(f"Received heartbeat: {data}")
# sys.stdout.flush() # 標準出力をフラッシュする
return jsonify({"status": "OK"})
if __name__ == "__main__":
heartbeat_thread = threading.Thread(target=check_heartbeat)
heartbeat_thread.start()
app.run(host='0.0.0.0', port=3000)
私用のコメント
(1)receive_heartbeat():と receive_heartbeat()を同時に走らせる為に、スレッド化しなければならなくなった → で Ctrl-Cで止められなくなったので、ちょっと困っている(が、シェル画面ごと落せばいいので、現在はそうしている)
(2)url = "http://localhost:8000/cancelAllVideoStreamGround" からの時間がもたつく場合には、Timeoutを設定すると良いかもしれない。send_notification()を丸ごと差し替えると、こんな感じ。
def send_notification():
url = "http://localhost:8000/cancelAllVideoStreamGround"
try:
response = requests.post(url, timeout=3)
response.raise_for_status()
print("Notification sent successfully.")
sys.stdout.flush() # 標準出力をフラッシュする
except Timeout:
print("Error: Timeout while sending notification")
except requests.exceptions.RequestException as e:
print(f"Error sending notification: {e}")
(3)Webブラウザは、バックエンドに置いておくと、Heatbeatを出力しないことがあるので、常にアクティブにしておく必要があります(バックエンドのままにしておくと、cancelAllVideoStreamGroundが動き出します)
以上
GitHub Copilotをvscodeにアドインしたが、tabキーを押しても提案を採用できない
「まだChatGPTを使ってない人は『人生を悔い改めた方がいい』」 ―― と言った、ソフトバンクの孫社長に申し上げます。『いらんこと言うな』と。
にも書いていますが、
『当初、私は、ChatGPT(対話型AIアシスタント))、Grammerly(英語文章構成サービス), Deepl(翻訳サービス)を、無料で使ってきたのですが、私のそのサービスの利用頻度は、無料の範囲を越えてしまい、全て有料の会員となっています』
で、ここにAmazon Lightsailがあって、さらに、さくらインターネットのサービス(kobore.net)が入って、当然、ドメイン名の使用料も含まれていて、もう、これは、IT/AI搾取と称呼してもしても良いのではないかと思います。
で、先程、GitHub Copilot(10ドル/月)にも入りました ―― 貢いでいる対象を考えると「ホスト/ホステスに貢いだ方が楽しそう」です。金額の規模感は違いますが。
ジュニアに質問しにくいシニアにとっては、十分ペイする「お助けサービス」ではあるのですが ―― 私の人生、ハレがない とは思います。
それはさておき
先程まで、GitHub Copilotをvscodeにアドインしたが、tabキーを押しても提案を採用できないという問題に悩まされていました。
参考にさせて頂いたのは、こちら(https://mindtech.jp/?p=2330)のページです。
上記の"承諾する Tab"の部分をマウスでクリックすれば、確定はできるのですが、そんなコーディング作業、かったるくてやっていられません。
私の方も、やはり、Awesome Emacs Keymap と、vscode-emacs-indent が悪さをしていたようです。
これらを無効にした後、vscodeを再立ち上げしたら、コード確定ができるようにはなりましたが、当然ですが、emacsのキーバインドが使えなくなりました。しかし、私にはEmacsの環境が使えないコーディング環境は耐えられそうにありません(体が矯正不能)。
で、ちょっと試しに、Awesome Emacs Keymap を無効して、vscodeを再立ち上げして、GitHub Copilotを起動している状態で、Awesome Emacs Keymap を再び有効にしてみたら、両立に成功しているようです(すぐにボロが出るかもしれませんが)。
とりあえず、メモとして残しおきます。
またコケたら、こちらのメモに追記します。