Pythonでライブコーディングテストの対策問題集を準備してみた
Python コーディングテスト対策問題集
概要
実践的なPythonコーディングテスト対策用の練習問題集です。
データ処理、アルゴリズム設計、Web API開発、機械学習など、現代のソフトウェア開発で重要な技術領域を網羅しています。
問題1: 基本データ処理【初級】
問題
月次売上データを分析するプログラムを作成してください。
# 入力例:各月の売上データ(万円)
monthly_sales = [120, 135, 128, 142, 156, 148,
162, 158, 145, 139, 133, 127]
以下の機能を実装してください:
- 年間総売上の計算
- 月平均売上の計算
- 最大・最小売上とその月の特定
- 前月比成長率の計算(2月目以降)
回答例
def analyze_sales_data(monthly_sales):
"""
売上データの分析
Args:
monthly_sales (list): 各月の売上データ
Returns:
dict: 分析結果
"""
# 年間総売上
total_sales = sum(monthly_sales)
# 月平均売上
monthly_average = total_sales / len(monthly_sales)
# 最大・最小売上
max_sales = max(monthly_sales)
min_sales = min(monthly_sales)
max_month = monthly_sales.index(max_sales) + 1
min_month = monthly_sales.index(min_sales) + 1
# 前月比成長率の計算
growth_rates = []
for i in range(1, len(monthly_sales)):
growth_rate = (monthly_sales[i] - monthly_sales[i-1]) / monthly_sales[i-1] * 100
growth_rates.append(round(growth_rate, 2))
return {
'total_sales': total_sales,
'monthly_average': round(monthly_average, 2),
'max_sales': max_sales,
'max_month': max_month,
'min_sales': min_sales,
'min_month': min_month,
'growth_rates': growth_rates
}
# テスト実行
monthly_sales = [120, 135, 128, 142, 156, 148,
162, 158, 145, 139, 133, 127]
result = analyze_sales_data(monthly_sales)
print(result)
問題2: 経路最適化【中級】
問題
複数の拠点を訪問する際の最短経路を求めるプログラムを作成してください。
簡単なTraveling Salesman Problem(TSP)の変形版です。
# 拠点間の距離行列(km)
distances = [
[0, 120, 200, 150], # 拠点A
[120, 0, 80, 170], # 拠点B
[200, 80, 0, 90], # 拠点C
[150, 170, 90, 0] # 拠点D
]
locations = ['A', 'B', 'C', 'D']
回答例
from itertools import permutations
def find_shortest_route(distances, locations, start_location=0):
"""
最短経路を見つける(総当たり法)
Args:
distances (list): 距離行列
locations (list): 拠点名リスト
start_location (int): 開始拠点のインデックス
Returns:
tuple: (最短距離, 最短経路)
"""
n = len(locations)
other_locations = [i for i in range(n) if i != start_location]
min_distance = float('inf')
best_route = None
# 開始拠点以外の全ての順列を試行
for perm in permutations(other_locations):
route = [start_location] + list(perm) + [start_location] # 出発拠点に戻る
distance = calculate_route_distance(distances, route)
if distance < min_distance:
min_distance = distance
best_route = route
# 拠点名に変換
route_names = [locations[i] for i in best_route]
return min_distance, route_names
def calculate_route_distance(distances, route):
"""経路の総距離を計算"""
total_distance = 0
for i in range(len(route) - 1):
total_distance += distances[route[i]][route[i + 1]]
return total_distance
# テスト実行
distances = [
[0, 120, 200, 150],
[120, 0, 80, 170],
[200, 80, 0, 90],
[150, 170, 90, 0]
]
locations = ['A', 'B', 'C', 'D']
min_dist, best_route = find_shortest_route(distances, locations)
print(f"最短距離: {min_dist}km")
print(f"最短経路: {' -> '.join(best_route)}")
問題3: 在庫最適化【中級】
問題
倉庫の収容能力内で価値を最大化するナップサック問題を解いてください。
各商品には重量と価値があり、倉庫の収容能力内で価値を最大化します。
回答例
def optimize_inventory(items, max_capacity):
"""
動的プログラミングによる在庫最適化
Args:
items (list): [(重量, 価値, ID), ...] のリスト
max_capacity (int): 最大収容能力
Returns:
tuple: (最大価値, 選択されたアイテムのID)
"""
n = len(items)
# DPテーブル初期化
dp = [[0 for _ in range(max_capacity + 1)] for _ in range(n + 1)]
# DPテーブル構築
for i in range(1, n + 1):
weight, value, item_id = items[i-1]
for w in range(max_capacity + 1):
# アイテムを選ばない場合
dp[i][w] = dp[i-1][w]
# アイテムを選ぶ場合(重量が許可する限り)
if weight <= w:
dp[i][w] = max(dp[i][w], dp[i-1][w-weight] + value)
# 選択されたアイテムを逆算
selected_items = []
w = max_capacity
for i in range(n, 0, -1):
if dp[i][w] != dp[i-1][w]:
selected_items.append(items[i-1][2]) # ID
w -= items[i-1][0] # 重量
return dp[n][max_capacity], selected_items[::-1]
# テスト実行
items = [
(10, 60, 'ITEM001'), # (重量, 価値, ID)
(20, 100, 'ITEM002'),
(30, 120, 'ITEM003'),
(40, 160, 'ITEM004'),
(15, 80, 'ITEM005'),
(25, 110, 'ITEM006')
]
max_capacity = 50
max_value, selected = optimize_inventory(items, max_capacity)
print(f"最大価値: {max_value}")
print(f"選択されたアイテム: {selected}")
# 検証
total_weight = sum(item[0] for item in items if item[2] in selected)
print(f"総重量: {total_weight}/{max_capacity}")
問題4: 時系列データ分析【中級〜上級】
問題
システムの応答時間の時系列データから異常値を検出するプログラムを作成してください。
回答例
import statistics
import math
def detect_anomalies(time_series_data, threshold=2.0):
"""
移動平均とZスコアを使った異常値検出
Args:
time_series_data (list): 時系列データ
threshold (float): 異常値判定の閾値(Zスコア)
Returns:
list: 異常値のインデックスと値のタプル
"""
anomalies = []
window_size = min(7, len(time_series_data) // 3) # 動的ウィンドウサイズ
for i in range(window_size, len(time_series_data)):
# 移動平均と標準偏差の計算
window = time_series_data[i-window_size:i]
mean = statistics.mean(window)
if len(set(window)) > 1: # 全て同じ値でない場合
std_dev = statistics.stdev(window)
# Zスコア計算
z_score = abs(time_series_data[i] - mean) / std_dev if std_dev > 0 else 0
if z_score > threshold:
anomalies.append((i, time_series_data[i], round(z_score, 2)))
return anomalies
def smooth_time_series(data, window_size=3):
"""
移動平均によるデータ平滑化
"""
smoothed = []
for i in range(len(data)):
start = max(0, i - window_size // 2)
end = min(len(data), i + window_size // 2 + 1)
window = data[start:end]
smoothed.append(statistics.mean(window))
return smoothed
# テスト実行
# システム応答時間データ(ミリ秒)
response_times = [
45, 42, 48, 44, 46, 43, 47, 45,
189, # 異常値
44, 46, 42, 45, 48, 43, 47, 46, 44,
192, # 異常値
45, 43, 48, 44, 46, 47, 45, 42
]
# 異常値検出
anomalies = detect_anomalies(response_times)
print("検出された異常値:")
for idx, value, z_score in anomalies:
print(f" インデックス {idx}: {value}ms (Zスコア: {z_score})")
# データ平滑化
smoothed_data = smooth_time_series(response_times)
print(f"\n元データの平均: {statistics.mean(response_times):.2f}ms")
print(f"平滑化後の平均: {statistics.mean(smoothed_data):.2f}ms")
問題5: グラフアルゴリズム応用【上級】
問題
ネットワークトポロジで、あるノードから他の全てのノードへの最短経路を求めるダイクストラ法を実装してください。
回答例
import heapq
from collections import defaultdict
class NetworkGraph:
def __init__(self):
self.graph = defaultdict(list)
self.nodes = {}
def add_edge(self, from_node, to_node, distance, cost=None):
"""
エッジを追加
Args:
from_node (str): 開始ノード
to_node (str): 終了ノード
distance (int): 距離
cost (int): コスト(省略可)
"""
cost = cost or distance
self.graph[from_node].append((to_node, distance, cost))
self.nodes[from_node] = self.nodes.get(from_node, len(self.nodes))
self.nodes[to_node] = self.nodes.get(to_node, len(self.nodes))
def dijkstra(self, start_node, optimize_for='distance'):
"""
ダイクストラ法による最短経路探索
Args:
start_node (str): 開始ノード
optimize_for (str): 'distance' または 'cost'
Returns:
tuple: (距離辞書, 経路辞書)
"""
distances = {node: float('inf') for node in self.nodes}
previous = {node: None for node in self.nodes}
distances[start_node] = 0
pq = [(0, start_node)]
visited = set()
while pq:
current_dist, current_node = heapq.heappop(pq)
if current_node in visited:
continue
visited.add(current_node)
for neighbor, distance, cost in self.graph[current_node]:
weight = distance if optimize_for == 'distance' else cost
new_dist = current_dist + weight
if new_dist < distances[neighbor]:
distances[neighbor] = new_dist
previous[neighbor] = current_node
heapq.heappush(pq, (new_dist, neighbor))
return distances, previous
def get_shortest_path(self, start_node, end_node, optimize_for='distance'):
"""
指定したノード間の最短経路を取得
"""
distances, previous = self.dijkstra(start_node, optimize_for)
if distances[end_node] == float('inf'):
return None, None
path = []
current = end_node
while current is not None:
path.append(current)
current = previous[current]
path.reverse()
return path, distances[end_node]
# テスト実行
network = NetworkGraph()
# ネットワーク接続データの追加
connections = [
('NodeA', 'NodeB', 30, 25),
('NodeA', 'NodeC', 400, 350),
('NodeB', 'NodeC', 380, 340),
('NodeC', 'NodeD', 50, 40),
('NodeA', 'NodeE', 300, 280),
('NodeE', 'NodeC', 150, 120),
('NodeD', 'NodeF', 200, 180),
('NodeC', 'NodeF', 250, 220)
]
for connection in connections:
network.add_edge(*connection)
# NodeAから全ノードへの最短距離
print("NodeAからの最短距離(距離基準):")
distances, _ = network.dijkstra('NodeA', 'distance')
for node, dist in sorted(distances.items()):
if dist != float('inf'):
print(f" {node}: {dist}km")
print("\nNodeA→NodeFの最短経路:")
path, total_dist = network.get_shortest_path('NodeA', 'NodeF', 'distance')
if path:
print(f" 経路: {' -> '.join(path)}")
print(f" 総距離: {total_dist}km")
問題6: データ集約と統計【中級】
問題
複数のセンサーからのデータを集約し、統計情報を計算するプログラムを作成してください。
回答例
from collections import defaultdict
from datetime import datetime, timedelta
import json
class SensorDataAggregator:
def __init__(self):
self.sensor_data = defaultdict(list)
def add_sensor_reading(self, sensor_id, timestamp, sensor_type, value):
"""
センサーデータを追加
Args:
sensor_id (str): センサーID
timestamp (str): タイムスタンプ
sensor_type (str): センサー種類
value (float): センサー値
"""
self.sensor_data[sensor_id].append({
'timestamp': timestamp,
'sensor_type': sensor_type,
'value': value
})
def calculate_statistics(self, sensor_id, sensor_type, hours_back=24):
"""
指定期間の統計情報を計算
Args:
sensor_id (str): センサーID
sensor_type (str): センサー種類
hours_back (int): 遡る時間数
Returns:
dict: 統計情報
"""
if sensor_id not in self.sensor_data:
return None
# 指定期間のデータを抽出
cutoff_time = datetime.now() - timedelta(hours=hours_back)
values = []
for data in self.sensor_data[sensor_id]:
if (data['sensor_type'] == sensor_type and
datetime.fromisoformat(data['timestamp']) >= cutoff_time):
values.append(data['value'])
if not values:
return None
# 統計計算
values.sort()
n = len(values)
stats = {
'count': n,
'min': min(values),
'max': max(values),
'mean': sum(values) / n,
'median': values[n//2] if n % 2 else (values[n//2-1] + values[n//2]) / 2,
'std_dev': self._calculate_std_dev(values)
}
return stats
def _calculate_std_dev(self, values):
"""標準偏差を計算"""
if len(values) <= 1:
return 0
mean = sum(values) / len(values)
variance = sum((x - mean) ** 2 for x in values) / (len(values) - 1)
return variance ** 0.5
def get_fleet_summary(self, sensor_type):
"""
全センサーのサマリーを取得
"""
summary = {}
for sensor_id in self.sensor_data.keys():
stats = self.calculate_statistics(sensor_id, sensor_type)
if stats:
summary[sensor_id] = stats
return summary
# テスト実行
aggregator = SensorDataAggregator()
# サンプルデータ追加
base_time = datetime.now()
sensors = ['SENSOR001', 'SENSOR002', 'SENSOR003']
sensor_types = ['temperature', 'humidity', 'pressure']
for i, sensor in enumerate(sensors):
for j in range(48): # 48時間分のデータ
timestamp = (base_time - timedelta(hours=j)).isoformat()
# 温度(20-40度)
temp = 25 + (i * 5) + (j % 15)
aggregator.add_sensor_reading(sensor, timestamp, 'temperature', temp)
# 湿度(40-80%)
humidity = 55 + (i * 3) + ((j * 2) % 20)
aggregator.add_sensor_reading(sensor, timestamp, 'humidity', humidity)
# 統計情報表示
print("SENSOR001の温度統計(24時間):")
stats = aggregator.calculate_statistics('SENSOR001', 'temperature', 24)
if stats:
for key, value in stats.items():
print(f" {key}: {value:.2f}")
print("\n全センサーの湿度サマリー:")
fleet_summary = aggregator.get_fleet_summary('humidity')
for sensor_id, stats in fleet_summary.items():
print(f" {sensor_id}: 平均 {stats['mean']:.2f}%, 最大 {stats['max']:.2f}%")
問題7: 画像処理機械学習WebAPI【上級】
問題
商品画像を分類するWeb APIを作成してください。
事前学習済みモデルを使用して、画像アップロード→予測→結果返却の機能を実装します。
回答例
# requirements: flask pillow requests scikit-learn numpy
from flask import Flask, request, jsonify
from PIL import Image
import io
import base64
import numpy as np
from sklearn.ensemble import RandomForestClassifier
import pickle
import os
app = Flask(__name__)
class ProductImageClassifier:
def __init__(self):
self.model = None
self.classes = ['electronics', 'clothing', 'food', 'books']
self.load_or_train_model()
def load_or_train_model(self):
"""モデルの読み込みまたは訓練"""
model_path = 'product_classifier.pkl'
if os.path.exists(model_path):
with open(model_path, 'rb') as f:
self.model = pickle.load(f)
else:
# デモ用の簡単なモデル(実際は画像データで訓練)
self.model = RandomForestClassifier(n_estimators=10, random_state=42)
# ダミーデータで訓練
X_dummy = np.random.rand(100, 1000) # 画像特徴量想定
y_dummy = np.random.choice(4, 100) # 4クラス分類
self.model.fit(X_dummy, y_dummy)
# モデル保存
with open(model_path, 'wb') as f:
pickle.dump(self.model, f)
def extract_features(self, image):
"""画像から特徴量を抽出"""
# リサイズ
image = image.resize((32, 32))
# グレースケール変換
image = image.convert('L')
# numpy配列に変換してフラット化
features = np.array(image).flatten()
# 正規化
features = features / 255.0
return features
def predict(self, image):
"""画像分類予測"""
features = self.extract_features(image)
features = features.reshape(1, -1)
# 予測確率
probabilities = self.model.predict_proba(features)[0]
# 結果整理
results = []
for i, prob in enumerate(probabilities):
results.append({
'class': self.classes[i],
'confidence': float(prob)
})
# 確信度順でソート
results.sort(key=lambda x: x['confidence'], reverse=True)
return results
# グローバル分類器インスタンス
classifier = ProductImageClassifier()
@app.route('/api/classify', methods=['POST'])
def classify_image():
"""画像分類API"""
try:
# リクエスト検証
if 'image' not in request.files:
return jsonify({'error': 'No image file provided'}), 400
file = request.files['image']
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
# 画像読み込み
try:
image = Image.open(file.stream)
except Exception as e:
return jsonify({'error': f'Invalid image file: {str(e)}'}), 400
# 予測実行
predictions = classifier.predict(image)
return jsonify({
'status': 'success',
'predictions': predictions,
'top_prediction': predictions[0]
})
except Exception as e:
return jsonify({'error': f'Internal server error: {str(e)}'}), 500
@app.route('/api/classify/base64', methods=['POST'])
def classify_base64():
"""Base64画像分類API"""
try:
data = request.get_json()
if 'image_data' not in data:
return jsonify({'error': 'No image_data provided'}), 400
# Base64デコード
try:
image_data = base64.b64decode(data['image_data'])
image = Image.open(io.BytesIO(image_data))
except Exception as e:
return jsonify({'error': f'Invalid base64 image: {str(e)}'}), 400
# 予測実行
predictions = classifier.predict(image)
return jsonify({
'status': 'success',
'predictions': predictions
})
except Exception as e:
return jsonify({'error': f'Internal server error: {str(e)}'}), 500
@app.route('/api/health', methods=['GET'])
def health_check():
"""ヘルスチェック"""
return jsonify({
'status': 'healthy',
'model_loaded': classifier.model is not None,
'supported_classes': classifier.classes
})
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
問題8: 表データ可視化【中級】
問題
売上データをグラフで可視化するプログラムを作成してください。
複数のグラフ種類と統計情報を提供します。
回答例
# requirements: matplotlib seaborn pandas numpy
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from datetime import datetime, timedelta
import matplotlib.dates as mdates
class SalesDataVisualizer:
def __init__(self):
self.data = None
plt.style.use('seaborn-v0_8')
# 日本語フォント設定(環境に応じて調整)
plt.rcParams['font.family'] = 'DejaVu Sans'
def generate_sample_data(self):
"""サンプルデータ生成"""
dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')
regions = ['North', 'South', 'East', 'West', 'Central']
categories = ['Electronics', 'Clothing', 'Books', 'Sports']
data = []
for date in dates:
for region in regions:
for category in categories:
# 季節性とランダム性を含むデータ生成
base_sales = np.random.normal(1000, 200)
seasonal_factor = 1 + 0.3 * np.sin(2 * np.pi * date.dayofyear / 365)
sales = max(0, int(base_sales * seasonal_factor))
data.append({
'date': date,
'region': region,
'category': category,
'sales': sales
})
self.data = pd.DataFrame(data)
return self.data
def load_csv_data(self, file_path):
"""CSVファイルからデータ読み込み"""
try:
self.data = pd.read_csv(file_path)
self.data['date'] = pd.to_datetime(self.data['date'])
return self.data
except Exception as e:
print(f"データ読み込みエラー: {e}")
return None
def create_time_series_plot(self, region_name=None, save_path=None):
"""時系列グラフ作成"""
if self.data is None:
return None
plt.figure(figsize=(15, 8))
if region_name:
region_data = self.data[self.data['region'] == region_name]
title = f'{region_name}地域の売上推移'
else:
region_data = self.data.groupby('date')['sales'].sum().reset_index()
title = '全地域の売上推移'
if region_name:
# カテゴリ別の積み上げ面グラフ
pivot_data = region_data.pivot_table(
values='sales', index='date', columns='category', aggfunc='sum'
).fillna(0)
plt.stackplot(pivot_data.index,
pivot_data['Electronics'], pivot_data['Clothing'],
pivot_data['Books'], pivot_data['Sports'],
labels=['Electronics', 'Clothing', 'Books', 'Sports'],
alpha=0.7)
plt.legend(loc='upper left')
else:
plt.plot(region_data['date'], region_data['sales'], linewidth=2)
plt.title(title, fontsize=16, fontweight='bold')
plt.xlabel('Date', fontsize=12)
plt.ylabel('Sales (¥)', fontsize=12)
plt.grid(True, alpha=0.3)
# X軸の日付フォーマット
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=2))
plt.xticks(rotation=45)
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
def create_comparison_chart(self, save_path=None):
"""地域間比較チャート"""
if self.data is None:
return None
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12))
# 1. 地域別総売上(棒グラフ)
region_totals = self.data.groupby('region')['sales'].sum().sort_values(ascending=False)
bars = ax1.bar(region_totals.index, region_totals.values, color='steelblue', alpha=0.8)
ax1.set_title('地域別総売上', fontweight='bold')
ax1.set_ylabel('Sales (¥)')
# 値をバーの上に表示
for bar in bars:
height = bar.get_height()
ax1.text(bar.get_x() + bar.get_width()/2., height,
f'{int(height/1000)}K', ha='center', va='bottom')
# 2. カテゴリ別分布(円グラフ)
category_totals = self.data.groupby('category')['sales'].sum()
ax2.pie(category_totals.values, labels=category_totals.index, autopct='%1.1f%%',
startangle=90, colors=['lightcoral', 'lightblue', 'lightgreen', 'lightyellow'])
ax2.set_title('カテゴリ別分布', fontweight='bold')
# 3. 月別推移(線グラフ)
monthly_data = self.data.groupby(self.data['date'].dt.month)['sales'].mean()
ax3.plot(monthly_data.index, monthly_data.values, marker='o', linewidth=2, markersize=6)
ax3.set_title('月別平均売上', fontweight='bold')
ax3.set_xlabel('Month')
ax3.set_ylabel('Average Sales (¥)')
ax3.set_xticks(range(1, 13))
ax3.grid(True, alpha=0.3)
# 4. ヒートマップ(地域×カテゴリ)
heatmap_data = self.data.groupby(['region', 'category'])['sales'].sum().unstack()
sns.heatmap(heatmap_data, annot=True, fmt='.0f', cmap='YlOrRd', ax=ax4)
ax4.set_title('地域×カテゴリ ヒートマップ', fontweight='bold')
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
def generate_statistics_report(self):
"""統計レポート生成"""
if self.data is None:
return None
report = {
'データ期間': f"{self.data['date'].min().strftime('%Y-%m-%d')} ~ {self.data['date'].max().strftime('%Y-%m-%d')}",
'総レコード数': len(self.data),
'地域数': self.data['region'].nunique(),
'カテゴリ数': self.data['category'].nunique(),
}
# 基本統計
sales_stats = self.data['sales'].describe()
report['売上統計'] = {
'平均': f"{sales_stats['mean']:.1f} ¥",
'標準偏差': f"{sales_stats['std']:.1f} ¥",
'最大': f"{sales_stats['max']:.1f} ¥",
'最小': f"{sales_stats['min']:.1f} ¥"
}
# 地域別TOP3
region_ranking = self.data.groupby('region')['sales'].sum().sort_values(ascending=False)
report['地域別売上TOP3'] = dict(region_ranking.head(3))
return report
# 使用例
def main():
visualizer = SalesDataVisualizer()
# サンプルデータ生成
data = visualizer.generate_sample_data()
print(f"生成されたデータ: {len(data)} 件")
# グラフ作成
visualizer.create_time_series_plot('North')
visualizer.create_comparison_chart()
# 統計レポート
stats = visualizer.generate_statistics_report()
print("\n=== 統計レポート ===")
for key, value in stats.items():
print(f"{key}: {value}")
if __name__ == '__main__':
main()
問題9: Flask CSVアップロード画面【中級】
問題
CSVファイルをアップロードし、データの内容を表示・分析するFlask Webアプリケーションを作成してください。
回答例
# requirements: flask pandas werkzeug
from flask import Flask, render_template, request, flash, redirect, url_for, jsonify
import pandas as pd
import os
from werkzeug.utils import secure_filename
import io
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
# アップロードフォルダの作成
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
ALLOWED_EXTENSIONS = {'csv', 'txt'}
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def analyze_csv_data(df):
"""CSVデータの分析"""
analysis = {
'shape': df.shape,
'columns': list(df.columns),
'dtypes': dict(df.dtypes.astype(str)),
'missing_values': dict(df.isnull().sum()),
'numeric_summary': {},
'sample_data': df.head().to_dict('records')
}
# 数値列の統計情報
numeric_columns = df.select_dtypes(include=['number']).columns
for col in numeric_columns:
analysis['numeric_summary'][col] = {
'mean': float(df[col].mean()),
'std': float(df[col].std()),
'min': float(df[col].min()),
'max': float(df[col].max())
}
return analysis
@app.route('/')
def index():
return render_template('upload.html')
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
flash('ファイルが選択されていません')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
flash('ファイルが選択されていません')
return redirect(request.url)
if file and allowed_file(file.filename):
try:
filename = secure_filename(file.filename)
# CSVファイルを直接読み込み
content = file.read().decode('utf-8')
df = pd.read_csv(io.StringIO(content))
# データ分析
analysis = analyze_csv_data(df)
# セッションやDBに保存する代わりに、一時的に結果を表示
return render_template('results.html',
filename=filename,
analysis=analysis)
except Exception as e:
flash(f'ファイル処理エラー: {str(e)}')
return redirect(url_for('index'))
flash('許可されていないファイル形式です (.csvのみ)')
return redirect(url_for('index'))
@app.route('/api/analyze', methods=['POST'])
def api_analyze():
"""API版のCSV分析"""
if 'file' not in request.files:
return jsonify({'error': 'No file provided'}), 400
file = request.files['file']
try:
content = file.read().decode('utf-8')
df = pd.read_csv(io.StringIO(content))
analysis = analyze_csv_data(df)
return jsonify({
'status': 'success',
'analysis': analysis
})
except Exception as e:
return jsonify({'error': str(e)}), 400
if __name__ == '__main__':
app.run(debug=True)
# HTMLテンプレート: templates/upload.html
upload_html = '''
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSV データ分析ツール</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<h2 class="text-center mb-4">CSV データ分析ツール</h2>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="card">
<div class="card-body">
<form method="POST" enctype="multipart/form-data" action="/upload">
<div class="mb-3">
<label for="file" class="form-label">CSVファイルを選択してください</label>
<input type="file" class="form-control" id="file" name="file" accept=".csv" required>
<div class="form-text">最大ファイルサイズ: 16MB</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">アップロード・分析</button>
</div>
</form>
</div>
</div>
<div class="mt-4">
<h5>サンプル CSV フォーマット:</h5>
<pre class="bg-light p-3 rounded">
region,category,sales,date
North,Electronics,1200,2024-01-01
South,Clothing,800,2024-01-01
East,Books,950,2024-01-01
</pre>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
'''
# HTMLテンプレート: templates/results.html
results_html = '''
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析結果 - CSV データ分析ツール</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>分析結果: {{ filename }}</h2>
<a href="/" class="btn btn-secondary">新しいファイル</a>
</div>
<!-- 基本情報 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">基本情報</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p><strong>行数:</strong> {{ analysis.shape[0] }}</p>
<p><strong>列数:</strong> {{ analysis.shape[1] }}</p>
</div>
<div class="col-md-6">
<p><strong>列名:</strong></p>
<ul>
{% for col in analysis.columns %}
<li>{{ col }} ({{ analysis.dtypes[col] }})</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
<!-- 欠損値情報 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">欠損値情報</h5>
</div>
<div class="card-body">
<div class="row">
{% for col, missing in analysis.missing_values.items() %}
<div class="col-md-3 mb-2">
<strong>{{ col }}:</strong> {{ missing }}
</div>
{% endfor %}
</div>
</div>
</div>
<!-- 数値統計 -->
{% if analysis.numeric_summary %}
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">数値列統計</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>列名</th>
<th>平均</th>
<th>標準偏差</th>
<th>最小値</th>
<th>最大値</th>
</tr>
</thead>
<tbody>
{% for col, stats in analysis.numeric_summary.items() %}
<tr>
<td>{{ col }}</td>
<td>{{ "%.2f"|format(stats.mean) }}</td>
<td>{{ "%.2f"|format(stats.std) }}</td>
<td>{{ "%.2f"|format(stats.min) }}</td>
<td>{{ "%.2f"|format(stats.max) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
<!-- サンプルデータ -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">サンプルデータ (先頭5行)</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
{% for col in analysis.columns %}
<th>{{ col }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in analysis.sample_data %}
<tr>
{% for col in analysis.columns %}
<td>{{ row[col] }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
'''
# HTMLテンプレートファイルの作成
def create_templates():
"""テンプレートファイルの作成"""
os.makedirs('templates', exist_ok=True)
with open('templates/upload.html', 'w', encoding='utf-8') as f:
f.write(upload_html)
with open('templates/results.html', 'w', encoding='utf-8') as f:
f.write(results_html)
print("テンプレートファイルを作成しました")
if __name__ == '__main__':
create_templates()
app.run(debug=True)
問題10: Swagger API連携Flask画面【中級〜上級】
問題
公開API(例:OpenWeatherMap API)のSwagger仕様を利用して、APIを呼び出し結果を表示するFlask画面を作成してください。
回答例
# requirements: flask requests
from flask import Flask, render_template, request, jsonify
import requests
import json
from datetime import datetime
app = Flask(__name__)
class ExternalAPIClient:
def __init__(self):
# OpenWeatherMap API (無料枠で利用可能)
self.weather_base_url = "https://api.openweathermap.org/data/2.5"
# 実際の使用時はAPIキーを環境変数から取得
self.weather_api_key = "YOUR_API_KEY_HERE" # 要登録
# JSONPlaceholder API(テスト用、APIキー不要)
self.jsonplaceholder_url = "https://jsonplaceholder.typicode.com"
def get_weather_data(self, city_name):
"""天気情報を取得"""
url = f"{self.weather_base_url}/weather"
params = {
'q': city_name,
'appid': self.weather_api_key,
'units': 'metric',
'lang': 'ja'
}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
return {
'success': True,
'data': response.json()
}
except requests.exceptions.RequestException as e:
return {
'success': False,
'error': str(e)
}
def get_posts_data(self, user_id=None):
"""投稿データを取得(JSONPlaceholder API)"""
url = f"{self.jsonplaceholder_url}/posts"
params = {}
if user_id:
params['userId'] = user_id
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
return {
'success': True,
'data': response.json()
}
except requests.exceptions.RequestException as e:
return {
'success': False,
'error': str(e)
}
def get_users_data(self):
"""ユーザーデータを取得"""
url = f"{self.jsonplaceholder_url}/users"
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return {
'success': True,
'data': response.json()
}
except requests.exceptions.RequestException as e:
return {
'success': False,
'error': str(e)
}
# APIクライアントのインスタンス
api_client = ExternalAPIClient()
@app.route('/')
def index():
return render_template('api_dashboard.html')
@app.route('/api/weather/<city>')
def get_weather(city):
"""天気情報API"""
result = api_client.get_weather_data(city)
return jsonify(result)
@app.route('/api/posts')
def get_posts():
"""投稿データAPI"""
user_id = request.args.get('userId')
result = api_client.get_posts_data(user_id)
return jsonify(result)
@app.route('/api/users')
def get_users():
"""ユーザーデータAPI"""
result = api_client.get_users_data()
return jsonify(result)
@app.route('/dashboard')
def dashboard():
"""ダッシュボード表示"""
# 初期データ取得
users_result = api_client.get_users_data()
posts_result = api_client.get_posts_data()
return render_template('dashboard_results.html',
users=users_result,
posts=posts_result)
if __name__ == '__main__':
app.run(debug=True)
# HTMLテンプレート: templates/api_dashboard.html
dashboard_html = '''
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API ダッシュボード</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<div class="container mt-4">
<h1 class="text-center mb-4">API ダッシュボード</h1>
<!-- 天気情報セクション -->
<div class="card mb-4">
<div class="card-header">
<h5>天気情報 (OpenWeatherMap API)</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<input type="text" id="cityInput" class="form-control" placeholder="都市名を入力 (例: Tokyo)">
</div>
<div class="col-md-3">
<button id="getWeatherBtn" class="btn btn-primary">天気取得</button>
</div>
</div>
<div id="weatherResult" class="mt-3"></div>
</div>
</div>
<!-- 投稿データセクション -->
<div class="card mb-4">
<div class="card-header">
<h5>投稿データ (JSONPlaceholder API)</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<select id="userSelect" class="form-control">
<option value="">全ユーザー</option>
</select>
</div>
<div class="col-md-3">
<button id="getPostsBtn" class="btn btn-success">投稿取得</button>
</div>
</div>
<div id="postsResult" class="mt-3"></div>
</div>
</div>
<!-- 統計情報 -->
<div class="card">
<div class="card-header">
<h5>API 統計</h5>
</div>
<div class="card-body">
<div id="statsResult">
<p>データを読み込み中...</p>
</div>
</div>
</div>
</div>
<script>
// 初期化
document.addEventListener('DOMContentLoaded', function() {
loadUsers();
loadStats();
});
// ユーザー一覧読み込み
async function loadUsers() {
try {
const response = await axios.get('/api/users');
if (response.data.success) {
const userSelect = document.getElementById('userSelect');
response.data.data.forEach(user => {
const option = document.createElement('option');
option.value = user.id;
option.textContent = `${user.name} (${user.email})`;
userSelect.appendChild(option);
});
}
} catch (error) {
console.error('ユーザー読み込みエラー:', error);
}
}
// 天気情報取得
document.getElementById('getWeatherBtn').addEventListener('click', async function() {
const city = document.getElementById('cityInput').value;
if (!city) {
alert('都市名を入力してください');
return;
}
try {
const response = await axios.get(`/api/weather/${city}`);
const resultDiv = document.getElementById('weatherResult');
if (response.data.success) {
const weather = response.data.data;
resultDiv.innerHTML = `
<div class="alert alert-info">
<h6>${weather.name}</h6>
<p>温度: ${weather.main.temp}°C</p>
<p>天気: ${weather.weather[0].description}</p>
<p>湿度: ${weather.main.humidity}%</p>
</div>
`;
} else {
resultDiv.innerHTML = `<div class="alert alert-danger">エラー: ${response.data.error}</div>`;
}
} catch (error) {
document.getElementById('weatherResult').innerHTML =
`<div class="alert alert-danger">API呼び出しエラー: ${error.message}</div>`;
}
});
// 投稿データ取得
document.getElementById('getPostsBtn').addEventListener('click', async function() {
const userId = document.getElementById('userSelect').value;
try {
let url = '/api/posts';
if (userId) {
url += `?userId=${userId}`;
}
const response = await axios.get(url);
const resultDiv = document.getElementById('postsResult');
if (response.data.success) {
const posts = response.data.data.slice(0, 5); // 最初の5件のみ表示
let html = '<div class="row">';
posts.forEach(post => {
html += `
<div class="col-md-6 mb-3">
<div class="card">
<div class="card-body">
<h6 class="card-title">${post.title}</h6>
<p class="card-text">${post.body.substring(0, 100)}...</p>
<small class="text-muted">User ID: ${post.userId}</small>
</div>
</div>
</div>
`;
});
html += '</div>';
resultDiv.innerHTML = html;
} else {
resultDiv.innerHTML = `<div class="alert alert-danger">エラー: ${response.data.error}</div>`;
}
} catch (error) {
document.getElementById('postsResult').innerHTML =
`<div class="alert alert-danger">API呼び出しエラー: ${error.message}</div>`;
}
});
// 統計情報読み込み
async function loadStats() {
try {
const [usersRes, postsRes] = await Promise.all([
axios.get('/api/users'),
axios.get('/api/posts')
]);
const statsDiv = document.getElementById('statsResult');
const userCount = usersRes.data.success ? usersRes.data.data.length : 0;
const postCount = postsRes.data.success ? postsRes.data.data.length : 0;
statsDiv.innerHTML = `
<div class="row">
<div class="col-md-6">
<div class="card bg-primary text-white">
<div class="card-body">
<h4>${userCount}</h4>
<p>登録ユーザー数</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card bg-success text-white">
<div class="card-body">
<h4>${postCount}</h4>
<p>投稿数</p>
</div>
</div>
</div>
</div>
`;
} catch (error) {
document.getElementById('statsResult').innerHTML =
'<div class="alert alert-warning">統計情報の読み込みに失敗しました</div>';
}
}
</script>
</body>
</html>
'''
# テンプレートファイル作成
def create_api_templates():
os.makedirs('templates', exist_ok=True)
with open('templates/api_dashboard.html', 'w', encoding='utf-8') as f:
f.write(dashboard_html)
if __name__ == '__main__':
create_api_templates()
app.run(debug=True)
問題11: テストコード実装【中級】
問題
上記で作成した関数やクラスに対する包括的なテストコードを作成してください。
回答例
# requirements: pytest pytest-flask pytest-mock requests-mock
import pytest
import pandas as pd
import numpy as np
import tempfile
import os
from unittest.mock import Mock, patch
import json
import io
# テスト対象のモジュールをインポート
from problem7_api import ProductImageClassifier, classify_image, app as image_app
from problem8_visualization import SalesDataVisualizer
from problem9_csv_upload import analyze_csv_data, app as upload_app
class TestProductImageClassifier:
"""画像分類器のテストクラス"""
@pytest.fixture
def classifier(self):
"""テスト用分類器インスタンス"""
return ProductImageClassifier()
def test_model_initialization(self, classifier):
"""モデル初期化テスト"""
assert classifier.model is not None
assert len(classifier.classes) == 4
assert 'electronics' in classifier.classes
@patch('PIL.Image.open')
def test_extract_features(self, mock_image_open, classifier):
"""特徴抽出テスト"""
# モックイメージ作成
mock_image = Mock()
mock_image.resize.return_value = mock_image
mock_image.convert.return_value = mock_image
mock_image_open.return_value = mock_image
# numpy配列をモック
mock_array = np.random.rand(32, 32) * 255
with patch('numpy.array', return_value=mock_array):
features = classifier.extract_features(mock_image)
# 特徴量の形状とレンジを確認
assert len(features) == 1024 # 32*32
assert 0 <= features.min() <= 1
assert 0 <= features.max() <= 1
def test_predict_output_format(self, classifier):
"""予測結果フォーマットテスト"""
# ダミー画像作成
from PIL import Image
dummy_image = Image.new('RGB', (100, 100), color='red')
predictions = classifier.predict(dummy_image)
# 結果フォーマット確認
assert isinstance(predictions, list)
assert len(predictions) == 4
for pred in predictions:
assert 'class' in pred
assert 'confidence' in pred
assert pred['class'] in classifier.classes
assert 0 <= pred['confidence'] <= 1
# ソート確認(確信度順)
confidences = [p['confidence'] for p in predictions]
assert confidences == sorted(confidences, reverse=True)
class TestSalesDataVisualizer:
"""データ可視化のテストクラス"""
@pytest.fixture
def visualizer(self):
"""テスト用可視化インスタンス"""
return SalesDataVisualizer()
@pytest.fixture
def sample_data(self):
"""テスト用サンプルデータ"""
return pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=10),
'region': ['North'] * 5 + ['South'] * 5,
'category': ['Electronics', 'Clothing'] * 5,
'sales': np.random.randint(100, 1000, 10)
})
def test_generate_sample_data(self, visualizer):
"""サンプルデータ生成テスト"""
data = visualizer.generate_sample_data()
# データフレームの基本検証
assert isinstance(data, pd.DataFrame)
assert len(data) > 0
assert all(col in data.columns for col in ['date', 'region', 'category', 'sales'])
# データ型確認
assert pd.api.types.is_datetime64_any_dtype(data['date'])
assert data['sales'].dtype in ['int64', 'float64']
def test_load_csv_data(self, visualizer):
"""CSV読み込みテスト"""
# 一時CSVファイル作成
with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f:
f.write('date,region,category,sales\n')
f.write('2024-01-01,North,Electronics,1000\n')
f.write('2024-01-02,South,Clothing,800\n')
temp_path = f.name
try:
# CSV読み込み
result = visualizer.load_csv_data(temp_path)
assert result is not None
assert len(result) == 2
assert pd.api.types.is_datetime64_any_dtype(result['date'])
finally:
os.unlink(temp_path)
def test_generate_statistics_report(self, visualizer, sample_data):
"""統計レポート生成テスト"""
visualizer.data = sample_data
report = visualizer.generate_statistics_report()
assert report is not None
assert 'データ期間' in report
assert '総レコード数' in report
assert '地域数' in report
assert '売上統計' in report
assert report['総レコード数'] == len(sample_data)
class TestCSVAnalysis:
"""CSV分析機能のテストクラス"""
@pytest.fixture
def sample_df(self):
"""テスト用DataFrame"""
return pd.DataFrame({
'numeric_col': [1, 2, 3, None, 5],
'string_col': ['A', 'B', 'C', 'D', 'E'],
'date_col': pd.date_range('2024-01-01', periods=5),
'mixed_col': [1, 'text', 3.14, None, True]
})
def test_analyze_csv_data_basic(self, sample_df):
"""基本的なCSV分析テスト"""
result = analyze_csv_data(sample_df)
# 基本構造確認
assert 'shape' in result
assert 'columns' in result
assert 'dtypes' in result
assert 'missing_values' in result
assert 'numeric_summary' in result
assert 'sample_data' in result
# 値の確認
assert result['shape'] == (5, 4)
assert len(result['columns']) == 4
assert result['missing_values']['numeric_col'] == 1 # 1つの欠損値
def test_numeric_summary(self, sample_df):
"""数値統計サマリーテスト"""
result = analyze_csv_data(sample_df)
numeric_summary = result['numeric_summary']
assert 'numeric_col' in numeric_summary
stats = numeric_summary['numeric_col']
assert 'mean' in stats
assert 'std' in stats
assert 'min' in stats
assert 'max' in stats
# 統計値の妥当性確認(NaN除く)
expected_mean = sample_df['numeric_col'].dropna().mean()
assert abs(stats['mean'] - expected_mean) < 0.01
class TestFlaskApps:
"""Flask アプリケーションのテストクラス"""
@pytest.fixture
def upload_client(self):
"""CSVアップロードアプリのテストクライアント"""
upload_app.config['TESTING'] = True
with upload_app.test_client() as client:
yield client
@pytest.fixture
def image_client(self):
"""画像分類アプリのテストクライアント"""
image_app.config['TESTING'] = True
with image_app.test_client() as client:
yield client
def test_upload_index_page(self, upload_client):
"""アップロードページテスト"""
response = upload_client.get('/')
assert response.status_code == 200
assert b'CSV' in response.data
def test_api_analyze_valid_csv(self, upload_client):
"""有効なCSV分析APIテスト"""
csv_content = "name,age,city\nJohn,25,Tokyo\nJane,30,Osaka"
response = upload_client.post('/api/analyze',
data={'file': (io.BytesIO(csv_content.encode()), 'test.csv')})
assert response.status_code == 200
data = json.loads(response.data)
assert data['status'] == 'success'
assert 'analysis' in data
assert data['analysis']['shape'] == [2, 3]
def test_api_analyze_invalid_csv(self, upload_client):
"""無効なCSV分析APIテスト"""
invalid_content = "invalid,csv,content\nwith,missing"
response = upload_client.post('/api/analyze',
data={'file': (io.BytesIO(invalid_content.encode()), 'test.csv')})
# エラーハンドリング確認
assert response.status_code == 400
data = json.loads(response.data)
assert 'error' in data
def test_health_check(self, image_client):
"""ヘルスチェックAPIテスト"""
response = image_client.get('/api/health')
assert response.status_code == 200
data = json.loads(response.data)
assert data['status'] == 'healthy'
assert 'model_loaded' in data
assert 'supported_classes' in data
# 統合テスト
class TestIntegration:
"""統合テストクラス"""
def test_data_pipeline(self):
"""データ処理パイプラインの統合テスト"""
# 1. データ生成
visualizer = SalesDataVisualizer()
data = visualizer.generate_sample_data()
# 2. データ分析
analysis = analyze_csv_data(data)
# 3. 統計レポート
visualizer.data = data
report = visualizer.generate_statistics_report()
# 結果の整合性確認
assert analysis['shape'][0] == report['総レコード数']
assert len(analysis['columns']) == len(data.columns)
# パフォーマンステスト
class TestPerformance:
"""パフォーマンステストクラス"""
def test_large_dataset_processing(self):
"""大容量データ処理性能テスト"""
# 大きなデータセット作成
large_data = pd.DataFrame({
'id': range(10000),
'value': np.random.randn(10000),
'category': np.random.choice(['A', 'B', 'C'], 10000)
})
import time
start_time = time.time()
# 分析実行
analysis = analyze_csv_data(large_data)
execution_time = time.time() - start_time
# パフォーマンス要件(2秒以内)
assert execution_time < 2.0
assert analysis['shape'] == (10000, 3)
# カスタムテストマーク
pytestmark = pytest.mark.slow
# テスト実行時のセットアップ
def pytest_configure(config):
"""pytest設定"""
config.addinivalue_line("markers", "slow: marks tests as slow")
config.addinivalue_line("markers", "integration: marks tests as integration tests")
# テスト実行コマンド例:
# pytest test_coding_practice.py -v
# pytest test_coding_practice.py -m "not slow" # スローテスト除外
# pytest test_coding_practice.py --cov=. # カバレッジ付き
if __name__ == '__main__':
pytest.main([__file__, '-v'])
テスト対策のポイント
1. コード品質
- 可読性: 適切な変数名と関数名を使用
- 保守性: 関数の分割と適切なコメント
- 効率性: 時間計算量とメモリ効率を意識
2. エラーハンドリング
def safe_division(a, b):
"""安全な除算"""
try:
if b == 0:
raise ValueError("ゼロ除算エラー")
return a / b
except (TypeError, ValueError) as e:
print(f"エラー: {e}")
return None
3. テストケースの作成
def test_function():
"""テスト関数の例"""
# 正常ケース
assert calculate_distance([0, 0], [3, 4]) == 5.0
# エッジケース
assert calculate_distance([0, 0], [0, 0]) == 0.0
# 異常ケース
try:
calculate_distance(None, [1, 2])
assert False, "例外が発生すべき"
except TypeError:
pass
print("全テスト通過")
4. 時間管理
- 最初に問題を完全に理解
- 簡単な解法から実装開始
- 時間が余れば最適化
5. Chrome-based環境での注意点
- インポート文を最初のセルに集約
- 大きなデータセットはファイルアップロード機能を活用
- 実行結果の確認を怠らない
PyCharm での実行について
✅ 完全に動作するもの
- 基本的なPython処理: 全ての問題のロジック部分
- データ処理・分析: pandas, numpy等での処理
- グラフ描画: matplotlib, seabornでの可視化
- テストコード: pytestでのテスト実行
- 機械学習処理: scikit-learn等でのモデル処理
⚠️ 一部制限があるもの
- Flask Webアプリ: サーバーは起動できるが、ブラウザでの動作確認が必要
- 外部API連携: APIキーの設定や実際のHTTP通信が必要
- 画像処理: PILライブラリで処理可能だが、実際の画像ファイルが必要
推奨セットアップ
# 必要ライブラリのインストール
pip install flask pandas matplotlib seaborn scikit-learn pillow requests pytest
# プロジェクトフォルダ構成
project/
├── main.py
├── templates/
│ ├── upload.html
│ └── results.html
├── uploads/
└── tests/
└── test_main.py
追加練習問題
レベル1: リスト操作、基本的なループと条件分岐
レベル2: 辞書・集合操作、文字列処理、基本アルゴリズム
レベル3: データ構造(スタック、キュー)、再帰、動的プログラミング
レベル4: グラフアルゴリズム、最適化問題、高度なデータ処理
実際のコーディングテストでは、問題解決力、コードの可読性・保守性、実行効率の3つの観点を意識して練習することが重要です。段階的に解決し、動作確認を繰り返すことで、実際の試験でも冷静に対応できるでしょう。
あとがき
もうだいぶ長いことPythonをゼロベースでゴリゴリとコーディングする事もなくなって久しく
Backendを書くにしてもGoやRustや、まさかのC#やJavaで書くことが多かったりで、完全に忘却してしまっている可能性が高いので、復習用のコンテンツをCalude先生に準備して貰いました
少し前にアルゴリズムの復習用に準備して書いたりもしていましたが最近は生成AIが良い感じに書いてくれる事を多く、読み返すとそもそもの書き方のお作法も忘れてしまっている自分に気づきました
そもそも自社の業務ではもうコーディングする機会もなくなってしまい完全に趣味と自己満足の領域でもあるのですが、老害を加速しないためにも、もう少しコーディングできるようリハビリしておこうと思います
全部で10問程度の問題と回答の集まりです
最初のうちは写経で軽く試しながら、答えを隠しても書いて動作できるところまでリハビリしてみたいと思います
昔かった青本と赤本もひっぱり出して試してみるのもアリかも知れないけれども、あっちは線形回帰とかなので、数式と解法含めて余計に忘れているもんで、そこまで広げないようにしよう
だった今のメインの作業は少し前はSwiftUIでモバイルアプリの開発しており、いまはAzureのクラウドアーキテクトとセキュリティ対策なもんで…
そんな中でコーディングテストの準備で予習復習を始めるとか「それって意味あるんですか?」と問いつけられそうですが全く意味は無いですがPalantirを取り扱う別のチームのジョブに若干の興味があるので、その下準備も兼ねて…といったところですかね
Discussion