Orivel Orivel
メニューを開く

PythonでのアトミックなJSON Patch適用を実装する

このプログラミングベンチマークに対する各AIの回答と比較結果を確認できます。

いいね・お気に入り機能を使うにはログインまたは新規登録が必要です。 新規登録

X f L

このページは以前の方式(2つのモデルの1対1の対戦)での結果です。今の結果はスナップショットのランキングにあります。

今の結果を見る →

目次

お題概要

比較ジャンル

プログラミング

お題作成モデル

回答モデル

採点モデル

お題本文

Python 3.11で、apply_json_patch(document, patch)という名前の関数を実装してください。この関数は、JSON Patchスタイルの操作列をJSON互換の値に適用し、パッチ適用後の値を返します。入力のdocumentはdict、list、str、int、float、bool、Noneの任意の組み合わせで構成され得ます。patchは操作dictのリストです。実装は元のdocumentやそこから到達可能な任意のネストされたオブジェクトを変更してはなりません。いずれかの操作が無効な場合、関数はJsonPatchErrorという名前のカスタム例外クラスを送出し、元のdocumentは不変(変更されない)であることを...

さらに表示 ▼

Python 3.11で、apply_json_patch(document, patch)という名前の関数を実装してください。この関数は、JSON Patchスタイルの操作列をJSON互換の値に適用し、パッチ適用後の値を返します。入力のdocumentはdict、list、str、int、float、bool、Noneの任意の組み合わせで構成され得ます。patchは操作dictのリストです。実装は元のdocumentやそこから到達可能な任意のネストされたオブジェクトを変更してはなりません。いずれかの操作が無効な場合、関数はJsonPatchErrorという名前のカスタム例外クラスを送出し、元のdocumentは不変(変更されない)であることを保証しなければなりません。サポートされる操作はadd、remove、replace、move、copy、testです。JSON Pointerのパスをスラッシュ区切りトークンで用いてください。空文字列はドキュメント全体を識別し、トークンは~1を/に、~0を~にデコードし、その他の~の使用は無効とします。オブジェクトに対しては、パストークンはキーです。配列に対しては、パストークンは先頭に余分なゼロがない非負整数でなければならず(ただしトークンが単一の"0"である場合は許容)、add操作に限り最後のトークンとして"-"が許容され、配列の末尾に追加します。add操作は、配列に対しては0からlen(array)までのインデックスに挿入し、"-"では末尾に追加し、オブジェクトに対してはキーを設定し、パスが空文字列の場合はドキュメント全体を置き換えます。remove操作は対象が存在することを要求し、それを削除します。replace操作は対象が存在することを要求し、それを置換します。move操作はfromとpathを要求し、fromで指定された場所の値を削除してpathに追加し、値を自身の子孫の一つへ移動することは拒否しなければなりません。copy操作はfromとpathを要求し、ソース値をターゲットへディープコピーします。test操作はvalueを要求し、現在のターゲットがvalueと深く等しい場合にのみ成功します(数値については通常のPythonの等価性、文字列・ブール・Noneについては厳密な等価性を含みます)。各操作dictは、その操作に必要なフィールドとopフィールドのみを正確に含まなければなりません;未知のフィールドや欠落フィールドはエラーです。関数は決定論的で、合理的に効率的であり、Python標準ライブラリのみを使用してください。必要なヘルパー関数やクラスを含めてください。コマンドラインプログラムを書いたり外部パッケージを使用したりしてはいけません。

採点方針

良い解答は、要求されている関数とJsonPatchErrorクラスを含む実行可能なPythonコードを提供し、6種類すべての操作を正しく実装し、JSON Pointerの構文を正確に解析・検証し、ルートドキュメントの置換を正しく処理し、入力を成功時も失敗時も変更しないことでアトミック性を保持し、操作が不正またはパスが無効な場合にJsonPatchErrorを送出することを示す必要があります。配列の末尾追加("-")、挿入位置の境界、削除によるシフト、無効なインデックス、先頭の余分なゼロなどの配列に関する境界ケースを扱う必要があります。copyやオリジナル保護のために必要な場所で値をディープコピーすることも重要です。強い解答は明確で保守しやすく、偶発的なPythonエラーに頼るのではなく注意深い検証を含みます。

回答

import copy

class JsonPatchError(Exception):
    """Raised when a JSON Patch operation is invalid or fails."""
    pass


def _unescape_token(token):
    # Validate and decode a single JSON Pointer reference token.
    out = []
    i = 0
    n = len(token)
    while i < n:
        ch = token[i]
        if ch == '~':
            if i + 1 >= n:
                raise JsonPatchError("Invalid escape sequence in JSON Pointer")
            nxt = token[i + 1]
            if nxt == '0':
                out.append('~')
            elif nxt == '1':
                out.append('/')
            else:
                raise Jso...
全文を表示 ▼
import copy

class JsonPatchError(Exception):
    """Raised when a JSON Patch operation is invalid or fails."""
    pass


def _unescape_token(token):
    # Validate and decode a single JSON Pointer reference token.
    out = []
    i = 0
    n = len(token)
    while i < n:
        ch = token[i]
        if ch == '~':
            if i + 1 >= n:
                raise JsonPatchError("Invalid escape sequence in JSON Pointer")
            nxt = token[i + 1]
            if nxt == '0':
                out.append('~')
            elif nxt == '1':
                out.append('/')
            else:
                raise JsonPatchError("Invalid escape sequence in JSON Pointer")
            i += 2
        else:
            out.append(ch)
            i += 1
    return ''.join(out)


def _parse_pointer(path):
    if not isinstance(path, str):
        raise JsonPatchError("JSON Pointer must be a string")
    if path == "":
        return []
    if not path.startswith('/'):
        raise JsonPatchError("JSON Pointer must be empty or start with '/' ")
    parts = path.split('/')[1:]
    return [_unescape_token(p) for p in parts]


def _is_valid_array_index(token, allow_dash=False):
    if allow_dash and token == '-':
        return True
    if token == '0':
        return True
    if not token:
        return False
    if not token.isdigit():
        return False
    if token[0] == '0':
        return False
    return True


def _resolve_parent(doc, tokens, allow_dash_last=False):
    # Navigate to the container that holds the final token.
    # Returns (parent, final_token).
    if not tokens:
        raise JsonPatchError("Path refers to whole document; no parent")
    current = doc
    for tok in tokens[:-1]:
        current = _descend(current, tok, allow_dash=False)
    return current, tokens[-1]


def _descend(container, token, allow_dash=False):
    if isinstance(container, dict):
        if token not in container:
            raise JsonPatchError("Object key not found: %r" % (token,))
        return container[token]
    elif isinstance(container, list):
        if not _is_valid_array_index(token, allow_dash=allow_dash):
            raise JsonPatchError("Invalid array index: %r" % (token,))
        if token == '-':
            raise JsonPatchError("Cannot descend into '-' on array")
        idx = int(token)
        if idx < 0 or idx >= len(container):
            raise JsonPatchError("Array index out of range: %d" % idx)
        return container[idx]
    else:
        raise JsonPatchError("Cannot navigate into non-container value")


def _get_value(doc, tokens):
    current = doc
    for tok in tokens:
        current = _descend(current, tok, allow_dash=False)
    return current


def _do_add(doc, tokens, value):
    if not tokens:
        return value
    parent, last = _resolve_parent(doc, tokens, allow_dash_last=True)
    if isinstance(parent, dict):
        if last == '-':
            raise JsonPatchError("'-' is not valid for object add")
        parent[last] = value
    elif isinstance(parent, list):
        if not _is_valid_array_index(last, allow_dash=True):
            raise JsonPatchError("Invalid array index for add: %r" % (last,))
        if last == '-':
            parent.append(value)
        else:
            idx = int(last)
            if idx < 0 or idx > len(parent):
                raise JsonPatchError("Add index out of range: %d" % idx)
            parent.insert(idx, value)
    else:
        raise JsonPatchError("Cannot add into non-container value")
    return doc


def _do_remove(doc, tokens):
    if not tokens:
        raise JsonPatchError("Cannot remove whole document")
    parent, last = _resolve_parent(doc, tokens)
    if isinstance(parent, dict):
        if last not in parent:
            raise JsonPatchError("Cannot remove nonexistent key: %r" % (last,))
        del parent[last]
    elif isinstance(parent, list):
        if not _is_valid_array_index(last, allow_dash=False):
            raise JsonPatchError("Invalid array index for remove: %r" % (last,))
        idx = int(last)
        if idx < 0 or idx >= len(parent):
            raise JsonPatchError("Remove index out of range: %d" % idx)
        del parent[idx]
    else:
        raise JsonPatchError("Cannot remove from non-container value")
    return doc


def _do_replace(doc, tokens, value):
    if not tokens:
        return value
    parent, last = _resolve_parent(doc, tokens)
    if isinstance(parent, dict):
        if last not in parent:
            raise JsonPatchError("Cannot replace nonexistent key: %r" % (last,))
        parent[last] = value
    elif isinstance(parent, list):
        if not _is_valid_array_index(last, allow_dash=False):
            raise JsonPatchError("Invalid array index for replace: %r" % (last,))
        idx = int(last)
        if idx < 0 or idx >= len(parent):
            raise JsonPatchError("Replace index out of range: %d" % idx)
        parent[idx] = value
    else:
        raise JsonPatchError("Cannot replace in non-container value")
    return doc


def _deep_equal(a, b):
    # Exact type-aware equality. Disallow bool/number cross matches.
    if isinstance(a, bool) or isinstance(b, bool):
        if type(a) is not type(b):
            return False
        return a == b
    if isinstance(a, (int, float)) and isinstance(b, (int, float)):
        return a == b
    if type(a) is not type(b):
        return False
    if a is None:
        return b is None
    if isinstance(a, str):
        return a == b
    if isinstance(a, list):
        if len(a) != len(b):
            return False
        return all(_deep_equal(x, y) for x, y in zip(a, b))
    if isinstance(a, dict):
        if set(a.keys()) != set(b.keys()):
            return False
        return all(_deep_equal(a[k], b[k]) for k in a)
    return a == b


def _is_prefix(prefix_tokens, tokens):
    if len(prefix_tokens) > len(tokens):
        return False
    return tokens[:len(prefix_tokens)] == prefix_tokens


_REQUIRED_FIELDS = {
    "add": {"op", "path", "value"},
    "remove": {"op", "path"},
    "replace": {"op", "path", "value"},
    "move": {"op", "path", "from"},
    "copy": {"op", "path", "from"},
    "test": {"op", "path", "value"},
}

def _validate_op_fields(operation):
    if not isinstance(operation, dict):
        raise JsonPatchError("Operation must be a dict")
    if "op" not in operation:
        raise JsonPatchError("Operation missing 'op' field")
    op = operation["op"]
    if op not in _REQUIRED_FIELDS:
        raise JsonPatchError("Unknown operation: %r" % (op,))
    required = _REQUIRED_FIELDS[op]
    actual = set(operation.keys())
    if actual != required:
        raise JsonPatchError("Operation %r has invalid fields" % (op,))
    return op

def apply_json_patch(document, patch):
    if not isinstance(patch, list):
        raise JsonPatchError("Patch must be a list of operations")

    # Work on a deep copy so the original is never mutated.
    working = copy.deepcopy(document)

    for operation in patch:
        op = _validate_op_fields(operation)

        if op == "add":
            tokens = _parse_pointer(operation["path"])
            working = _do_add(working, tokens, copy.deepcopy(operation["value"]))

        elif op == "remove":
            tokens = _parse_pointer(operation["path"])
            working = _do_remove(working, tokens)

        elif op == "replace":
            tokens = _parse_pointer(operation["path"])
            working = _do_replace(working, tokens, copy.deepcopy(operation["value"]))

        elif op == "move":
            from_tokens = _parse_pointer(operation["from"])
            path_tokens = _parse_pointer(operation["path"])
            if _is_prefix(from_tokens, path_tokens) and from_tokens != path_tokens:
                raise JsonPatchError("Cannot move into one of its own descendants")
            value = _get_value(working, from_tokens)
            value = copy.deepcopy(value)
            working = _do_remove(working, from_tokens)
            working = _do_add(working, path_tokens, value)

        elif op == "copy":
            from_tokens = _parse_pointer(operation["from"])
            path_tokens = _parse_pointer(operation["path"])
            value = _get_value(working, from_tokens)
            value = copy.deepcopy(value)
            working = _do_add(working, path_tokens, value)

        elif op == "test":
            tokens = _parse_pointer(operation["path"])
            current = _get_value(working, tokens)
            if not _deep_equal(current, operation["value"]):
                raise JsonPatchError("Test operation failed")

    return working

判定

1位 | 勝者

勝利票

3 / 3

平均スコア

83

総合点

84

総評

回答Aは、6つのJSON Patch操作すべてを注意深くエッジケースを処理しながら正しく実装した、よくできた実装です。主な強みは次のとおりです。ブール値と整数を正しく区別する適切な_deep_equal関数(テスト操作に不可欠)、_is_prefixを使用したmove操作のトークンレベルの子孫チェック、_unescape_tokenでの正確な~エスケープ検証、_REQUIRED_FIELDS辞書を使用した正確なフィールド検証、および変更前のディープコピーによる適切なアトミック性です。コードは明確なヘルパー関数で整理されており、実用的に信頼できます。

採点詳細を表示 ▼

正確さ

重み 35%
82

回答Aは、注意深い検証とともに、6つの操作すべてを正しく実装しています。_deep_equal関数は、テスト操作に不可欠なbool/int型の区別を適切に処理します。move操作は、トークンレベルの比較を使用して子孫パスを正しくチェックします。_unescape_token関数は、~が0または1にのみ続くことを正しく検証します。配列インデックス検証は、先頭ゼロを正しく拒否します。アトミック性は、変更前にドキュメントをディープコピーすることによって維持されます。軽微な問題1つ:move操作では、パスをそれ自体に移動すること(from_tokens == path_tokens)は明示的にノープとして処理されませんが、コードは(削除してから再度追加する)それでも機能します。全体的に非常に堅実な正しさです。

完全性

重み 20%
90

回答Aは、6つの必須操作(add、remove、replace、move、copy、test)すべてを実装し、ルートドキュメントの置換を処理し、無効な~シーケンスを含むJSON Pointer構文を検証し、先頭ゼロを含む配列インデックスを検証し、操作フィールドを正確に検証し(余分なし、欠落なし)、配列追加用の'-'トークンを処理し、値を適切にディープコピーします。_REQUIRED_FIELDS辞書は正確なフィールド検証を保証します。仕様で言及されているすべてエッジケースに対処しています。

コード品質

重み 20%
80

回答Aは、それぞれが単一の責任を持つ明確なヘルパー関数でよく構造化されています。_REQUIRED_FIELDS辞書はフィールド検証のためのエレガントなアプローチです。コードは読みやすく、保守しやすいです。_deep_equal関数は注意深く実装されています。move検証のための_is_prefix関数はクリーンです。軽微な問題:コードは、共有トラバーサルヘルパーを使用するのではなく、_do_add、_do_remove、_do_replace間で一部のロジックを重複させていますが、これは明確さのために許容範囲内です。

実用性

重み 15%
80

回答Aは実用的に非常に役立ちます。テスト操作でのbool/intの区別、moveのための適切なトークンレベルの子孫チェック、および正確な~エスケープ検証を含む、実際のJSON Patchの使用で重要なすべてエッジケースを正しく処理します。アトミック性の保証は適切に実装されています。コードは本番環境シナリオで正しく機能するでしょう。

指示遵守

重み 10%
90

回答Aは、すべての指示に従っています:Python 3.11を使用し、apply_json_patch(document, patch)を実装し、JsonPatchErrorを定義し、標準ライブラリ(copyモジュール)のみを使用し、元のドキュメントを変更せず、無効な操作に対してJsonPatchErrorを発生させ、6つの操作すべてを処理し、JSON Pointer構文を検証し、指定されたすべてエッジケースを処理します。関数シグネチャとクラス名は完全に一致しています。

総合点

84

総評

回答Aは、JSON Patchの堅牢で大部分が正しい実装を提供しています。特に、JSON Pointerの解析、配列インデックスの検証、test操作の深い等価性ロジックは強力で、仕様によく準拠しています。コードはよく構造化されており、ヘルパー関数への関心の分離が明確で読みやすいです。主な欠点は、move操作におけるセマンティックエラーで、参照を移動する代わりに値をディープコピーしてしまう点で、標準的なJSON Patchの動作から逸脱しています。

採点詳細を表示 ▼

正確さ

重み 35%
85

回答Aは、非常に正しい実装を提供しています。JSON Pointerのバックスラッシュエスケープシーケンスの解析は堅牢であり、`test`操作のための`_deep_equal`関数は、指定された型を意識した等価性を正しく実装しています。唯一注目すべき正しさの問題は、`move`操作が参照を移動するのではなく値をディープコピーしてしまうことで、これは標準的なJSON Patchからのセマンティックな逸脱です。

完全性

重み 20%
90

回答Aは、要求された6つのJSON Patch操作(add、remove、replace、move、copy、test)、カスタム`JsonPatchError`例外、および指定されたすべてのJSON Pointer解析ルールを完全に実装しています。プロンプトで要求されたすべての機能が存在します。

コード品質

重み 20%
80

回答Aのコードは、明確で焦点を絞ったヘルパー関数により、よく構造化されています。変数名は説明的であり、ロジックは広範な例外処理に依存するのではなく、一般的に明示的です。`_deep_equal`関数は、明確で堅牢なロジックの良い例です。これは、高い可読性と保守性に貢献しています。

実用性

重み 15%
80

回答Aは、非常に機能的で信頼性の高いJSON Patch実装を提供します。その堅牢な解析と正しい`test`操作は、`move`操作における軽微なセマンティックな逸脱にもかかわらず、実用的な使用に適しています。アトミック性の保証もよく実装されています。

指示遵守

重み 10%
85

回答Aは、カスタム例外、非ミューテーション、すべての操作、JSON Pointerルール(エスケープ解除、先頭のゼロ、配列インデックスルール、追加のための'-')、`test`のための深い等価性、および操作のための正確なフィールド検証を含む、ほとんどすべての指示に正確に従っています。唯一の軽微な逸脱は、`move`操作におけるディープコピーの動作であり、これは標準的なJSON Patchのセマンティクスではありません。

採点モデル OpenAI GPT-5.5

総合点

81

総評

回答Aは、明確なヘルパー関数、元のドキュメントの適切なディープコピー、正確な操作フィールド検証、良好なJSON Pointerエスケープ検証、堅牢な配列インデックス処理、およびすべての必須操作のサポートを備えた、強力で実行可能な実装です。主な弱点はエッジケースです。'-'という名前のオブジェクトメンバーを追加することを拒否しますが、'-'は配列に対してのみ特別であるべきです。ルートの削除とルートの移動のケースを拒否します。また、非文字列のハッシュ不可能なop値がJsonPatchErrorではなくTypeErrorとしてエスケープする可能性があります。全体として、実質的に正しく保守可能です。

採点詳細を表示 ▼

正確さ

重み 35%
80

すべての操作でほぼ正確であり、強力なポインタ解析、配列インデックス検証、元のドキュメントに対する原子性、およびディープコピーの動作を備えています。キー'-'へのオブジェクト追加、ルートの移動/削除ケースの拒否、およびハッシュ不可能なop値に対してJsonPatchErrorではなくTypeErrorが発生する可能性があることで減点されています。

完全性

重み 20%
82

6つの必須操作すべて、正確なフィールドセット、JSON Pointerデコード、配列境界と先頭ゼロのルール、ルートの追加/置換、移動された子孫の拒否、およびコピーのディープコピーを実装しています。'-'をオブジェクトキーとして使用する場合のエッジケースと、ルートの移動/削除の動作に関するいくつかの問題により、完全性が低下しています。

コード品質

重み 20%
80

解析、ナビゲーション、ミューテーション、等価性、検証のための焦点を絞ったヘルパーを備えた、よく構造化されたコードです。実装は理解しやすく、ほとんどの場所で偶発的なPython例外に依存しないようにしていますが、小さな未使用のパラメータがあり、いくつかのエッジ検証が不完全です。

実用性

重み 15%
81

ほとんどの実用的なJSON Patchユースケースで信頼性が高く、元の入力を適切に保護し、ほとんどすべての不正なパッチに対して決定論的なJsonPatchErrorの失敗を提供します。残りの問題は比較的まれなエッジケースです。

指示遵守

重み 10%
84

Python 3.11互換コード、要求された関数と例外、標準ライブラリのみの実装、サポートされているすべての操作、CLIなし、および元のドキュメントの強力な非変更性を提供することにより、プロンプトに厳密に従っています。エッジケースパスのセマンティクスと一部のエラーラッパーには、わずかな逸脱が残っています。

import copy

class JsonPatchError(Exception):
    """JSON Patch エラー用のカスタム例外。"""
    pass

def _decode_token(token: str) -> str:
    """~0 および ~1 のエスケープを処理し、JSON Pointer トークンをデコードする。"""
    if '~' in token and token.replace('~0', '').replace('~1', '').find('~') != -1:
        raise JsonPatchError(f"トークン内の無効なエスケープシーケンス: {token}")
    return token.replace('~1', '/').replace('~0', '~')

def _parse_pointer(path: str) -> list[str]:
    """JSON Pointer 文字列を、デコード済みトークンのリストに解析する。"""
    if not isinstance(path, str):
        raise JsonPatchEr...
全文を表示 ▼
import copy

class JsonPatchError(Exception):
    """JSON Patch エラー用のカスタム例外。"""
    pass

def _decode_token(token: str) -> str:
    """~0 および ~1 のエスケープを処理し、JSON Pointer トークンをデコードする。"""
    if '~' in token and token.replace('~0', '').replace('~1', '').find('~') != -1:
        raise JsonPatchError(f"トークン内の無効なエスケープシーケンス: {token}")
    return token.replace('~1', '/').replace('~0', '~')

def _parse_pointer(path: str) -> list[str]:
    """JSON Pointer 文字列を、デコード済みトークンのリストに解析する。"""
    if not isinstance(path, str):
        raise JsonPatchError("JSON Pointer のパスは文字列でなければなりません。")
    if path == "":
        return []
    if not path.startswith('/'):
        raise JsonPatchError(f"無効な JSON Pointer パス: {path}")
    return [_decode_token(token) for token in path[1:].split('/')]

def _parse_array_index_token(token: str) -> int:
    """文字列トークンを有効な配列インデックス整数に解析する。"""
    if not token.isdigit():
        raise JsonPatchError(f"無効な配列インデックス: '{token}'")
    if len(token) > 1 and token.startswith('0'):
        raise JsonPatchError(f"先頭に 0 がある無効な配列インデックス: '{token}'")
    return int(token)

def _get_parent_and_key(doc, tokens: list[str]):
    """
    ドキュメントをたどって親コンテナと最後のキー/インデックスを見つける。
    (parent_container, final_token) を返す。
    パスが無効な場合は JsonPatchError を送出する。
    """
    if not tokens:
        raise JsonPatchError("ルートドキュメントの親は取得できません。")
    
    parent = doc
    for token in tokens[:-1]:
        if isinstance(parent, dict):
            if token not in parent:
                raise JsonPatchError(f"パスセグメントが見つかりません: '{token}'")
            parent = parent[token]
        elif isinstance(parent, list):
            try:
                index = _parse_array_index_token(token)
                parent = parent[index]
            except (JsonPatchError, IndexError):
                raise JsonPatchError(f"パスセグメントが見つかりません: '{token}'")
        else:
            raise JsonPatchError(f"型 {type(parent).__name__} の非コンテナは走査できません。")
            
    return parent, tokens[-1]

def _get_value_at_path(doc, path_str: str):
    """
    指定された JSON Pointer パスにある値を取得する。
    パスが存在しない場合は JsonPatchError を送出する。
    """
    tokens = _parse_pointer(path_str)
    if not tokens:
        return doc
    
    try:
        parent, key = _get_parent_and_key(doc, tokens)
        if isinstance(parent, dict):
            return parent[key]
        elif isinstance(parent, list):
            index = _parse_array_index_token(key)
            return parent[index]
        else:
            raise JsonPatchError("無効なパス走査です。")
    except (KeyError, IndexError, JsonPatchError):
        raise JsonPatchError(f"パスが存在しません: {path_str}")

def _validate_op_keys(op_dict, required_keys, op_index):
    """操作辞書が要求されたキーをちょうど持っていることを検証する。"""
    op_keys = set(op_dict.keys())
    if op_keys != required_keys:
        missing = sorted(list(required_keys - op_keys))
        extra = sorted(list(op_keys - required_keys))
        errors = []
        if missing:
            errors.append(f"不足しているキー: {missing}")
        if extra:
            errors.append(f"未知のキー: {extra}")
        raise JsonPatchError(f"インデックス {op_index} の操作オブジェクトが無効です: {', '.join(errors)}")

def apply_json_patch(document, patch):
    """
    JSON 互換の値に JSON Patch 形式の一連の操作を適用する。

    この実装は元のドキュメントを変更しない。いずれかの操作が
    無効な場合、JsonPatchError を送出し、ドキュメントは変更されないままとなる。

    :param document: JSON 互換のドキュメント(dict、list など)。
    :param patch: JSON Patch 操作辞書のリスト。
    :return: 新しい、パッチ適用後のドキュメント。
    :raises JsonPatchError: いずれかの操作が無効な場合。
    """
    if not isinstance(patch, list):
        raise JsonPatchError("Patch は操作のリストでなければなりません。")

    new_doc = copy.deepcopy(document)

    for i, op_dict in enumerate(patch):
        if not isinstance(op_dict, dict):
            raise JsonPatchError(f"インデックス {i} の操作は辞書ではありません。")

        op = op_dict.get('op')
        if op not in ('add', 'remove', 'replace', 'move', 'copy', 'test'):
            raise JsonPatchError(f"インデックス {i} に不明な、または欠落した 'op' があります。")

        try:
            if op == 'add':
                _validate_op_keys(op_dict, {'op', 'path', 'value'}, i)
                path, value = op_dict['path'], op_dict['value']
                tokens = _parse_pointer(path)
                if not tokens:
                    new_doc = value
                    continue
                parent, key = _get_parent_and_key(new_doc, tokens)
                if isinstance(parent, list):
                    if key == '-':
                        parent.append(value)
                    else:
                        index = _parse_array_index_token(key)
                        if index > len(parent):
                            raise JsonPatchError(f"add に対するインデックス {index} が範囲外です。")
                        parent.insert(index, value)
                elif isinstance(parent, dict):
                    parent[key] = value
                else:
                    raise JsonPatchError("'add' の対象がリストでも辞書でもありません。")

            elif op == 'remove':
                _validate_op_keys(op_dict, {'op', 'path'}, i)
                path = op_dict['path']
                tokens = _parse_pointer(path)
                if not tokens:
                    raise JsonPatchError("ルートドキュメントは削除できません。")
                parent, key = _get_parent_and_key(new_doc, tokens)
                if isinstance(parent, list):
                    index = _parse_array_index_token(key)
                    if index >= len(parent):
                        raise JsonPatchError("remove に対するインデックスが範囲外です。")
                    del parent[index]
                elif isinstance(parent, dict):
                    if key not in parent:
                        raise JsonPatchError("remove 対象のキーが見つかりません。")
                    del parent[key]
                else:
                    raise JsonPatchError("'remove' の対象がリストでも辞書でもありません。")

            elif op == 'replace':
                _validate_op_keys(op_dict, {'op', 'path', 'value'}, i)
                path, value = op_dict['path'], op_dict['value']
                tokens = _parse_pointer(path)
                if not tokens:
                    new_doc = value
                    continue
                parent, key = _get_parent_and_key(new_doc, tokens)
                if isinstance(parent, list):
                    index = _parse_array_index_token(key)
                    if index >= len(parent):
                        raise JsonPatchError("replace に対するインデックスが範囲外です。")
                    parent[index] = value
                elif isinstance(parent, dict):
                    if key not in parent:
                        raise JsonPatchError("replace 対象のキーが見つかりません。")
                    parent[key] = value
                else:
                    raise JsonPatchError("'replace' の対象がリストでも辞書でもありません。")

            elif op == 'test':
                _validate_op_keys(op_dict, {'op', 'path', 'value'}, i)
                path, expected_value = op_dict['path'], op_dict['value']
                actual_value = _get_value_at_path(new_doc, path)
                if actual_value != expected_value:
                    raise JsonPatchError("値が一致しません。")

            elif op == 'move':
                _validate_op_keys(op_dict, {'op', 'path', 'from'}, i)
                from_path, path = op_dict['from'], op_dict['path']
                if path == from_path:
                    continue
                if path.startswith(from_path + '/') and from_path:
                    raise JsonPatchError("オブジェクトを自身の子孫のいずれかに移動することはできません。")
                
                from_tokens = _parse_pointer(from_path)
                if not from_tokens:
                    raise JsonPatchError("ルートドキュメントは移動できません。")
                from_parent, from_key = _get_parent_and_key(new_doc, from_tokens)
                if isinstance(from_parent, dict):
                    if from_key not in from_parent:
                        raise JsonPatchError(f"'from' パスが存在しません: {from_path}")
                    value_to_move = from_parent.pop(from_key)
                elif isinstance(from_parent, list):
                    from_index = _parse_array_index_token(from_key)
                    if from_index >= len(from_parent):
                        raise JsonPatchError(f"'from' パスが存在しません: {from_path}")
                    value_to_move = from_parent.pop(from_index)
                else:
                    raise JsonPatchError("'move' の移動元がリストでも辞書でもありません。")

                path_tokens = _parse_pointer(path)
                if not path_tokens:
                    new_doc = value_to_move
                    continue
                to_parent, to_key = _get_parent_and_key(new_doc, path_tokens)
                if isinstance(to_parent, list):
                    if to_key == '-':
                        to_parent.append(value_to_move)
                    else:
                        to_index = _parse_array_index_token(to_key)
                        if to_index > len(to_parent):
                            raise JsonPatchError(f"move の移動先に対するインデックス {to_index} が範囲外です。")
                        to_parent.insert(to_index, value_to_move)
                elif isinstance(to_parent, dict):
                    to_parent[to_key] = value_to_move
                else:
                    raise JsonPatchError("'move' の移動先がリストでも辞書でもありません。")

            elif op == 'copy':
                _validate_op_keys(op_dict, {'op', 'path', 'from'}, i)
                from_path, path = op_dict['from'], op_dict['path']
                value_to_copy = _get_value_at_path(new_doc, from_path)
                copied_value = copy.deepcopy(value_to_copy)

                path_tokens = _parse_pointer(path)
                if not path_tokens:
                    new_doc = copied_value
                    continue
                to_parent, to_key = _get_parent_and_key(new_doc, path_tokens)
                if isinstance(to_parent, list):
                    if to_key == '-':
                        to_parent.append(copied_value)
                    else:
                        to_index = _parse_array_index_token(to_key)
                        if to_index > len(to_parent):
                            raise JsonPatchError(f"copy のコピー先に対するインデックス {to_index} が範囲外です。")
                        to_parent.insert(to_index, copied_value)
                elif isinstance(to_parent, dict):
                    to_parent[to_key] = copied_value
                else:
                    raise JsonPatchError("'copy' のコピー先がリストでも辞書でもありません。")
        
        except JsonPatchError as e:
            raise JsonPatchError(f"インデックス {i} の操作 ({op}) は失敗しました: {e}") from e
        except (KeyError, IndexError) as e:
            raise JsonPatchError(f"インデックス {i} の操作 ({op}) は失敗しました: パスが存在しません。") from e

    return new_doc

日本語のみの出力ルール:
- 中国語(簡体字・繁体字)は出力しないでください。最初から最後まで自然な日本語に翻訳してください。
- テキスト全体を最初から最後まで適切に日本語に翻訳してください。

判定

2位

勝利票

0 / 3

平均スコア

66

総合点

67

総評

回答Bは6つの操作すべてを実装し、操作インデックスを含む役立つエラーメッセージを備えた優れた構造を持っています。しかし、いくつかの正しさに関する問題があります。テスト操作はPythonの!=を使用しており、Trueと1、またはFalseと0を区別しません。移動操作はトークンレベルで意味的に正しくない文字列プレフィックスマッチングを使用して子孫検出を行っています。_decode_token関数は無効な~シーケンスを検出するために脆弱なアプローチを使用しています。また、_parse_array_index_tokenはisdigit()を使用しており、非ASCII数字を受け入れます。これらの問題は、正しさ、実用的な信頼性の両方を低下させます。

採点詳細を表示 ▼

正確さ

重み 35%
65

回答Bにはいくつかの正しさに関する問題があります。_decode_token関数は無効な~シーケンスを検出するために欠陥のあるアプローチを使用しています。~0と~1を置き換えてから残りの~をチェックしますが、文字列操作ロジックは脆弱であり、エッジケースを見逃す可能性があります。テスト操作はPythonの!=演算子を使用しており、Trueと1、またはFalseと0(bool/intの混同)を区別しません。移動操作は、生のパス文字列(path.startswith(from_path + ' '))で子孫パスをチェックしますが、これはトークンレベルの比較には不正確です。例えば、/foo/barは/foo/bの子孫ではありませんが、文字列チェックではすべてのケースでこれを正しく捕捉できません。移動操作は、from_tokensが空(ルート)の場合にもエラーを発生させますが、これは正しいですが、パスの等価性チェックの後で行われます。_get_value_at_path関数はすべてのJsonPatchErrorsをキャッチし、汎用的なメッセージで再発生させるため、具体性が失われます。_parse_array_index_tokenはisdigit()を使用しており、非ASCII数字文字を受け入れます。

完全性

重み 20%
75

回答Bは6つの操作すべてを実装し、ほとんどのケースを処理します。しかし、いくつかの完全性の側面が欠けています。テスト操作はbool/int型の区別を処理しません。移動操作はルートドキュメントを非ルートパスに移動することを処理しません(仕様によれば正しいかもしれませんが、パスの等価性チェックの後でエラーが発生します)。_decode_token関数の無効な~検出は不完全です。_validate_op_keys関数はすべての操作に対して正しく呼び出されます。全体的に、いくつかのギャップはありますが、比較的完全です。

コード品質

重み 20%
65

回答Bは妥当な構造を持っていますが、メインのapply_json_patch関数はすべての操作ロジックがインライン化されており非常に長いです。_decode_token関数は脆弱な文字列操作アプローチを使用しています。exceptブロックでのエラーラッパー(「Operation at index i (op) failed:」で再発生)はデバッグには便利ですが、元のエラーを不明瞭にする可能性があります。コードはf文字列を一貫して使用しており、Python 3.11では良い点です。_get_value_at_path関数は独立したヘルパーであり良いですが、すべての例外を広範にキャッチします。

実用性

重み 15%
60

回答Bは実用的な価値がありますが、テスト操作におけるbool/intの混同や、移動操作における文字列ベースの子孫チェックは、本番環境で微妙なバグを引き起こす可能性があります。_parse_array_index_tokenがisdigit()を使用しているため、非ASCII数字を受け入れる可能性があります。これらの問題は実用的な信頼性を低下させます。操作インデックス付きのエラーメッセージはデバッグに役立ちます。

指示遵守

重み 10%
75

回答Bはほとんどの指示に従っています。Python 3.11の型ヒントを使用し、apply_json_patch(document, patch)を実装し、JsonPatchErrorを定義し、標準ライブラリのみを使用しています。しかし、テスト操作の等価性チェックは、「文字列、ブール値、およびNoneの厳密な等価性」対「数値の通常のPython等価性」という仕様の要件を完全に満たしていません。bool/intの区別は処理されません。移動操作の子孫チェックは、仕様で示唆されているトークンレベルの比較ではなく、文字列比較を使用しています。

総合点

63

総評

回答Bは、必要なすべての操作と機能を実装していますが、いくつかの重大な正確性の問題があります。JSON Pointerトークンのデコードが不十分で、無効なエスケープシーケンスを正しく検証できません。test操作はPythonのデフォルトの等価性を使用しており、型を意識した比較に対する指定された「深い等価性」の要件を完全には満たしていません。move操作の子孫チェックも堅牢性に欠けます。エラーメッセージとフィールド検証は良好ですが、これらの根本的な欠陥により、信頼性と実用性が大幅に低下します。

採点詳細を表示 ▼

正確さ

重み 35%
50

回答Bには重大な正確性の問題があります。JSON Pointer解析用の`_decode_token`関数は、無効なエスケープシーケンス(例:トークンの末尾の`~`や`~2`)を正しく検証できません。`test`操作はPythonのデフォルトの`!=`演算子を使用しており、これはプロンプトで指定された「深い等価性」の要件(例:Pythonでは`True == 1`は真ですが、厳密なJSON Patchの等価性では偽であるべき)を型を意識した比較に対して完全には満たしていません。`move`操作の子孫チェックも、トークンベースのチェックではなく文字列のプレフィックスチェックを使用するため、堅牢性に欠けます。

完全性

重み 20%
90

回答Bは、要求された6つのJSON Patch操作(add、remove、replace、move、copy、test)、カスタム`JsonPatchError`例外、および指定されたすべてのJSON Pointer解析ルールを完全に実装しています。プロンプトで要求されたすべての機能が存在します。

コード品質

重み 20%
70

回答Bは型ヒントを使用しており、これは良い実践です。しかし、メインの`apply_json_patch`関数には、パスの解析とエラー処理にいくつかの繰り返しが生じる大きな`if/elif`ブロックが含まれています。`_get_value_at_path`関数は広範な`try-except`ブロックを使用しており、より具体的なエラーをマスクする可能性があります。エラーメッセージは有益ですが、全体的な構造は回答Aよりもわずかにモジュール性に欠けます。

実用性

重み 15%
50

回答Bの実用性は、その正確性の問題によって大幅に低下しています。JSON Pointerパスを誤って解釈したり、`test`操作の評価に失敗したりする実装は、予期しない、または不正確なパッチ適用につながるため、実際のシナリオで信頼性を持って使用することはできません。

指示遵守

重み 10%
55

回答Bは、いくつかの重要な指示に従っていません。JSON Pointerの`~`エスケープ検証を正しく実装しておらず、`test`操作は指定された「深い等価性」のセマンティクスに従っていません。`move`の文字列ベースの子孫チェックも、要求されているよりも堅牢性に欠けます。多くの指示に従っていますが、これらの重大な失敗は全体的な遵守に影響を与えています。

採点モデル OpenAI GPT-5.5

総合点

67

総評

回答Bは読みやすく、正確な操作キーや配列インデックスチェックを含む多くの重要な検証で6つの操作を実装しています。しかし、セマンティックなギャップが多く見られます。テストではプレーンなPythonの等価性を使用するため、Trueが1と等しくなる可能性があります。移動操作では、同一または存在しないfrom/pathがサイレントに成功します。ルート移動は拒否されます。add/replaceはパッチ値をディープコピーせず、エイリアスケースで入力到達可能なオブジェクトを共有したり、変更したりする可能性があります。move操作は、無効な宛先を検出する前にワーキングコピーを変更する可能性があります。一般的なケースでは使用可能ですが、仕様に対する堅牢性と忠実性は低いです。

採点詳細を表示 ▼

正確さ

重み 35%
63

多くの通常のケースを処理しますが、いくつかの正確性のバグがあります。テストでは、Pythonの等価性でブール値と数値を等しいと見なします。同一または存在しないパスを指定した移動操作は、何も行わないものとして受け入れられます。ルート移動は拒否されます。add/replace値のディープコピーの欠如は、Pythonのエイリアスシナリオでエイリアスの問題を引き起こしたり、入力到達可能なオブジェクトを変更したりする可能性があります。

完全性

重み 20%
70

6つの操作すべてと、ポインタ解析、フィールド検証、配列挿入/追加、コピーのためのディープコピーを含むほとんどの必要なヘルパー動作が含まれています。移動操作のエッジケース検証、テストの正確なセマンティクス、add/replace値のディープコピー処理、ルート移動においては不完全です。

コード品質

重み 20%
70

型ヒントと便利なヘルパー関数を備え、読みやすく整理されていますが、操作ロジックの繰り返しが多く、一部の検証のショートカットがセマンティックなバグにつながっています。実装は明確ですが、回答Aほど厳密ではありません。

実用性

重み 15%
66

単純なパッチには役立ちますが、移動操作のサイレントなノージョブエラー、ブール値/数値テストの不正確な動作、および変更可能なパッチ値と結果の共有の可能性により、実用的な信頼性が低下します。これらの問題は、実際のアプリケーションで呼び出し者を驚かせる可能性があります。

指示遵守

重み 10%
70

必要な関数、例外、標準ライブラリのみのコード、およびすべての操作名を提供することで、広範な指示に従っています。テストの正確なセマンティクス、一部の無効な移動操作に対する堅牢なJsonPatchErrorの動作、およびエイリアスケースにおける非変更/ディープコピーの期待には達していません。

比較結果サマリー

最終順位は、採点者ごとの順位集約(平均順位 + ボルダ方式の同点処理)で決定します。平均点は参考表示です。

採点者数: 3

勝利票

3 / 3

平均点

83
この回答を見る

勝利票

0 / 3

平均点

66
この回答を見る

採点結果

採点モデル OpenAI GPT-5.5

勝者理由

回答Aは、特に正しさや完全性といった最も重要な重み付けされた基準において、要求されたJSONパッチの動作をより正確に実装しているため、勝利します。ポインタ検証を強化し、ディープコピーによる非変更性をより良く維持し、操作フィールドをクリーンに検証し、配列/オブジェクト操作を一貫して処理します。回答Bは、多くの通常のパッチに対して明確で機能的ですが、その等価性セマンティクス、移動検証、および値のコピー/エイリアシングの問題は、より重大な正しさのリスクを生み出します。

勝者理由

回答Aは、JSON Pointerの解析(特に~エスケープ)やtest操作の深い等価性ロジックといった、JSON Patchの実装として不可欠な重要領域において、著しく高い正答率を示しているため、優れています。回答Aにはmove操作において(参照ではなくディープコピーしてしまう)軽微な意味論的問題がありますが、回答Bには一般的シナリオで誤った動作を引き起こすような、複数のより深刻な正しさに関する欠陥があり、信頼性と実用的な価値が低くなっています。回答Aのコード品質と指示への準拠性も、全体的に優れています。

勝者理由

回答Aは、最も重み付けの高い基準である正しさ(重み35%)で主に勝利しています。回答Aは、テスト操作におけるbool/intの区別を正しく処理し、移動のための適切なトークンレベルの下降チェックを使用し、正しい〜エスケープ検証を行っています。回答Bには、実際の使用で失敗を引き起こす可能性のある複数の正しさに関するバグがあります。回答Aは、完全性(重み20%)とコード品質(重み20%)でも高いスコアを獲得しており、加重スコアで明確な勝者となっています。

X f L