mirror of
https://github.com/mfocko/blog.git
synced 2024-11-14 01:59:42 +01:00
1 line
No EOL
22 KiB
JavaScript
1 line
No EOL
22 KiB
JavaScript
"use strict";(self.webpackChunkfi=self.webpackChunkfi||[]).push([[9771],{93019:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>o,metadata:()=>a,toc:()=>c});var s=n(85893),i=n(11151);const o={id:"postcondition-ambiguity",title:"Vague postconditions and proving correctness of algorithms",description:"Debugging and testing with precise postconditions.\n",tags:["python","testing","postconditions","sorting"],last_update:{date:new Date("2021-03-18T00:00:00.000Z")}},r=void 0,a={id:"algorithms-correctness/postcondition-ambiguity",title:"Vague postconditions and proving correctness of algorithms",description:"Debugging and testing with precise postconditions.\n",source:"@site/algorithms/02-algorithms-correctness/2021-03-18-postcondition-ambiguity.md",sourceDirName:"02-algorithms-correctness",slug:"/algorithms-correctness/postcondition-ambiguity",permalink:"/algorithms/algorithms-correctness/postcondition-ambiguity",draft:!1,unlisted:!1,editUrl:"https://github.com/mfocko/blog/tree/main/algorithms/02-algorithms-correctness/2021-03-18-postcondition-ambiguity.md",tags:[{label:"python",permalink:"/algorithms/tags/python"},{label:"testing",permalink:"/algorithms/tags/testing"},{label:"postconditions",permalink:"/algorithms/tags/postconditions"},{label:"sorting",permalink:"/algorithms/tags/sorting"}],version:"current",lastUpdatedAt:1616025600,formattedLastUpdatedAt:"Mar 18, 2021",frontMatter:{id:"postcondition-ambiguity",title:"Vague postconditions and proving correctness of algorithms",description:"Debugging and testing with precise postconditions.\n",tags:["python","testing","postconditions","sorting"],last_update:{date:"2021-03-18T00:00:00.000Z"}},sidebar:"autogeneratedBar",previous:{title:"Algorithms and Correctness",permalink:"/algorithms/category/algorithms-and-correctness"},next:{title:"Asymptotic Notation and Time Complexity",permalink:"/algorithms/category/asymptotic-notation-and-time-complexity"}},l={},c=[{value:"Introduction",id:"introduction",level:2},{value:"Implementation of select sort from the exercises",id:"implementation-of-select-sort-from-the-exercises",level:2},{value:"Discussed preconditions, loop invariants and postconditions",id:"discussed-preconditions-loop-invariants-and-postconditions",level:2},{value:"Precondition",id:"precondition",level:3},{value:"Loop invariant",id:"loop-invariant",level:3},{value:"Postcondition",id:"postcondition",level:3},{value:"So is the permutation really required?",id:"so-is-the-permutation-really-required",level:2},{value:"Implementation of the broken select sort",id:"implementation-of-the-broken-select-sort",level:2},{value:"Property-based tests for our sorts",id:"property-based-tests-for-our-sorts",level:2},{value:"Loop invariant",id:"loop-invariant-1",level:3},{value:"Postcondition(s)",id:"postconditions",level:3},{value:"Putting it together",id:"putting-it-together",level:3},{value:"Let's run the tests!",id:"lets-run-the-tests",level:3},{value:"Summary",id:"summary",level:2}];function h(e){const t={a:"a",admonition:"admonition",annotation:"annotation",blockquote:"blockquote",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",math:"math",mi:"mi",mrow:"mrow",ol:"ol",p:"p",pre:"pre",semantics:"semantics",span:"span",strong:"strong",ul:"ul",...(0,i.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.h2,{id:"introduction",children:"Introduction"}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.a,{href:"pathname:///files/algorithms/algorithms-correctness/postcondition-ambiguity/test_sort.py",children:"Source code"})," used later on."]}),"\n",(0,s.jsx)(t.h2,{id:"implementation-of-select-sort-from-the-exercises",children:"Implementation of select sort from the exercises"}),"\n",(0,s.jsxs)(t.p,{children:["To implement select sort from the exercises and make it as easy to read as possible, I have implemented maximum function that returns index of the biggest element from the first ",(0,s.jsxs)(t.span,{className:"katex",children:[(0,s.jsx)(t.span,{className:"katex-mathml",children:(0,s.jsx)(t.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,s.jsxs)(t.semantics,{children:[(0,s.jsx)(t.mrow,{children:(0,s.jsx)(t.mi,{children:"n"})}),(0,s.jsx)(t.annotation,{encoding:"application/x-tex",children:"n"})]})})}),(0,s.jsx)(t.span,{className:"katex-html","aria-hidden":"true",children:(0,s.jsxs)(t.span,{className:"base",children:[(0,s.jsx)(t.span,{className:"strut",style:{height:"0.4306em"}}),(0,s.jsx)(t.span,{className:"mord mathnormal",children:"n"})]})})]})," elements."]}),"\n",(0,s.jsxs)(t.p,{children:["For the sake of time and memory complexity, I am also using ",(0,s.jsx)(t.code,{children:"itertools.islice"}),", which makes a slice, but does not copy the elements into the memory like normal slice does."]}),"\n",(0,s.jsxs)(t.p,{children:["There is also a ",(0,s.jsx)(t.code,{children:"check_loop_invariant"})," function that will be described later."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-py",metastring:"showLineNumbers",children:"def compare_by_value(pair):\n index, value = pair\n return value\n\n\ndef maximum(arr, n):\n first_n_elements = itertools.islice(enumerate(arr), n)\n index, value = max(first_n_elements, key=compare_by_value)\n return index\n\n\ndef select_sort(arr, n):\n assert n == len(arr)\n\n check_loop_invariant(arr, n, n)\n for i in reversed(range(1, n)):\n j = maximum(arr, i + 1)\n arr[i], arr[j] = arr[j], arr[i]\n\n check_loop_invariant(arr, n, i)\n\n return arr\n"})}),"\n",(0,s.jsx)(t.h2,{id:"discussed-preconditions-loop-invariants-and-postconditions",children:"Discussed preconditions, loop invariants and postconditions"}),"\n",(0,s.jsxs)(t.p,{children:["You can safely replace ",(0,s.jsx)(t.code,{children:"A"})," with ",(0,s.jsx)(t.code,{children:"arr"})," or array for list."]}),"\n",(0,s.jsx)(t.h3,{id:"precondition",children:"Precondition"}),"\n",(0,s.jsxs)(t.p,{children:["As a precondition we have established that ",(0,s.jsx)(t.code,{children:"A"})," represents an array of values and ",(0,s.jsxs)(t.span,{className:"katex",children:[(0,s.jsx)(t.span,{className:"katex-mathml",children:(0,s.jsx)(t.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,s.jsxs)(t.semantics,{children:[(0,s.jsx)(t.mrow,{children:(0,s.jsx)(t.mi,{children:"n"})}),(0,s.jsx)(t.annotation,{encoding:"application/x-tex",children:"n"})]})})}),(0,s.jsx)(t.span,{className:"katex-html","aria-hidden":"true",children:(0,s.jsxs)(t.span,{className:"base",children:[(0,s.jsx)(t.span,{className:"strut",style:{height:"0.4306em"}}),(0,s.jsx)(t.span,{className:"mord mathnormal",children:"n"})]})})]})," is length of the ",(0,s.jsx)(t.code,{children:"A"}),"."]}),"\n",(0,s.jsx)(t.h3,{id:"loop-invariant",children:"Loop invariant"}),"\n",(0,s.jsx)(t.p,{children:"As for loop invariant we have established that we require two properties:"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"A[i + 1 : n]"})," is sorted"]}),"\n",(0,s.jsxs)(t.li,{children:["all elements of ",(0,s.jsx)(t.code,{children:"A[i + 1 : n]"})," are bigger or equal to the other elements"]}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["This invariant is later defined as ",(0,s.jsx)(t.code,{children:"check_loop_invariant"})," function. It is checked before the first iteration and after each iteration."]}),"\n",(0,s.jsx)(t.h3,{id:"postcondition",children:"Postcondition"}),"\n",(0,s.jsxs)(t.p,{children:["For the postcondition the first suggestion was that ",(0,s.jsx)(t.code,{children:"A"})," must be sorted. And later we have added that ",(0,s.jsx)(t.code,{children:"A'"})," must be a permutation of ",(0,s.jsx)(t.code,{children:"A"}),"."]}),"\n",(0,s.jsxs)(t.blockquote,{children:["\n",(0,s.jsxs)(t.p,{children:["However at the end of the session question arose if it is really required to state in the postcondition that ",(0,s.jsx)(t.code,{children:"A'"})," is a permutation."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"so-is-the-permutation-really-required",children:"So is the permutation really required?"}),"\n",(0,s.jsxs)(t.p,{children:["As I have said it is better to have postconditions explicit and do not expect anything that is not stated explicitly, e.g. ",(0,s.jsx)(t.em,{children:"name suggests it"}),". In reality we could consider it as a smaller mistake (but it has consequences)."]}),"\n",(0,s.jsxs)(t.p,{children:["On the other hand explicit postconditions can be used to our advantage ",(0,s.jsx)(t.strong,{children:"and also"})," help our proof of correctness."]}),"\n",(0,s.jsx)(t.p,{children:"Consequences:"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsx)(t.p,{children:"Property-based testing"}),"\n",(0,s.jsx)(t.p,{children:"If we have explicit postconditions we can use them to define properties of the output from our algorithms. That way we can use property-based testing, which does not depend on specific inputs and expected outputs, but rather on randomly generated input and checking if the output conforms to our expectations (the postconditions are fulfilled)."}),"\n"]}),"\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsx)(t.p,{children:"Proof of correctness"}),"\n",(0,s.jsxs)(t.p,{children:["If we can prove that algorithm is correct even for algorithm that ",(0,s.jsx)(t.strong,{children:"is not"})," correct, we have a problem. That proof has no value and is useless."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(t.p,{children:'For the sake of showcasing the power of postconditions I will introduce "select sort" that is not correct, but will comply with both the loop invariant and our vague postcondition and thus pass the tests.'}),"\n",(0,s.jsx)(t.h2,{id:"implementation-of-the-broken-select-sort",children:"Implementation of the broken select sort"}),"\n",(0,s.jsxs)(t.p,{children:["To make sure this thing passes everything, but our explicit postcondition with permutation will ",(0,s.jsx)(t.em,{children:"blow it up"}),', I have designed this "select sort" as follows:']}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsx)(t.li,{children:"If I get empty list, there is nothing to do."}),"\n",(0,s.jsx)(t.li,{children:"I find maximum in the array."}),"\n",(0,s.jsxs)(t.li,{children:["For each index from the end, I will assign ",(0,s.jsx)(t.code,{children:"maximum + index"}),".\nThis will ensure that even if the maximum in the original array was the first element, I will always satisfy that 2nd part of the loop invariant."]}),"\n"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-py",metastring:"showLineNumbers",children:"def broken_select_sort(arr, n):\n assert n == len(arr)\n\n if not arr:\n return\n\n max_value = max(arr)\n\n check_loop_invariant(arr, n, n)\n for i in reversed(range(n)):\n arr[i] = max_value + i\n\n check_loop_invariant(arr, n, i)\n\n return arr\n"})}),"\n",(0,s.jsx)(t.admonition,{type:"tip",children:(0,s.jsx)(t.p,{children:"There is also an easier way to break this, I leave that as an exercise ;)"})}),"\n",(0,s.jsx)(t.h2,{id:"property-based-tests-for-our-sorts",children:"Property-based tests for our sorts"}),"\n",(0,s.jsx)(t.p,{children:"Since we have talked a lot about proofs at the seminar, I would like to demonstrate it on the testing of the sorts. In the following text I will cover implementation of the loop invariant and both postconditions we have talked about and then test our sorts using them."}),"\n",(0,s.jsx)(t.h3,{id:"loop-invariant-1",children:"Loop invariant"}),"\n",(0,s.jsx)(t.p,{children:"To check loop invariant I have implemented this function:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-py",metastring:"showLineNumbers",children:'def check_loop_invariant(arr, n, i):\n # A[i + 1 : n] is sorted\n for x, y in zip(itertools.islice(arr, i + 1, n), itertools.islice(arr, i + 2, n)):\n assert x <= y\n\n # all elements of A[i + 1 : n] are bigger or equal to the other elements\n if i + 1 >= n:\n # in case there are no elements\n return\n\n # otherwise, since the "tail" is sorted, we can assume that it is enough to\n # check the other elements to the smallest value of the tail\n smallest = arr[i + 1]\n for element in itertools.islice(arr, i + 1):\n assert smallest >= element\n'})}),"\n",(0,s.jsx)(t.p,{children:'First part checks if the "ending" of the array is sorted.'}),"\n",(0,s.jsxs)(t.p,{children:["In second part I have used a ",(0,s.jsx)(t.em,{children:"dirty trick"})," of taking just the first element that is the smallest and compared the rest of the elements to it. Why is it enough? I leave it as an exercise ;)"]}),"\n",(0,s.jsx)(t.h3,{id:"postconditions",children:"Postcondition(s)"}),"\n",(0,s.jsx)(t.p,{children:"I have defined both the vague and explicit postconditions:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-py",metastring:"showLineNumbers",children:"def check_vague_postcondition(original_arr, arr):\n if not arr:\n return\n\n # check ordering\n for x, y in zip(arr, itertools.islice(arr, 1, len(arr))):\n assert x <= y\n\n\ndef check_postcondition(original_arr, arr):\n if not arr:\n return\n\n # check ordering\n for x, y in zip(arr, itertools.islice(arr, 1, len(arr))):\n assert x <= y\n\n # get counts from original list\n original_counts = {}\n for value in original_arr:\n original_counts[value] = 1 + original_counts.get(value, 0)\n\n # get counts from resulting list\n counts = {}\n for value in arr:\n counts[value] = 1 + counts.get(value, 0)\n\n # if arr is permutation of original_arr then all counts must be the same\n assert counts == original_counts\n"})}),"\n",(0,s.jsx)(t.h3,{id:"putting-it-together",children:"Putting it together"}),"\n",(0,s.jsx)(t.p,{children:"Now that we have everything implement, we can move on to the implementation of the tests:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-py",metastring:"showLineNumbers",children:'from hypothesis import given, settings\nfrom hypothesis.strategies import integers, lists\nimport pytest\n\n@given(lists(integers()))\n@settings(max_examples=1000)\n@pytest.mark.parametrize(\n "postcondition", [check_vague_postcondition, check_postcondition]\n)\n@pytest.mark.parametrize("sorting_function", [select_sort, broken_select_sort])\ndef test_select_sort(sorting_function, postcondition, numbers):\n result = sorting_function(numbers[:], len(numbers))\n postcondition(numbers, result)\n'})}),"\n",(0,s.jsx)(t.p,{children:"Since it might seem a bit scary, I will disect it by parts."}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsx)(t.p,{children:"Parameters of test function"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-py",metastring:"showLineNumbers",children:"def test_select_sort(sorting_function, postcondition, numbers):\n"})}),"\n",(0,s.jsx)(t.p,{children:"We are given 3 parameters:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"sorting_function"})," - as the name suggests is the sorting function we test"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"postcondition"})," - as the name suggests is the postcondition that we check"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"numbers"})," - is random list of numbers that we will be sorting"]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsx)(t.p,{children:"Body of the test"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-py",metastring:"showLineNumbers",children:"result = sorting_function(numbers[:], len(numbers))\npostcondition(numbers, result)\n"})}),"\n",(0,s.jsxs)(t.p,{children:["We pass to the sorting function ",(0,s.jsx)(t.strong,{children:"copy"})," of the numbers we got, this ensures that once we are checking the more strict postcondition, we can gather the necessary information even after sorting the list in-situ, i.e. we can check if the ",(0,s.jsx)(t.code,{children:"result"})," is really a ",(0,s.jsx)(t.code,{children:"permutation"})," of the ",(0,s.jsx)(t.code,{children:"numbers"})," even though the sorting functions has modified the passed in list."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(t.admonition,{title:"caution",type:"warning",children:(0,s.jsxs)(t.p,{children:["Now we get to the more complicated part and it is the ",(0,s.jsx)(t.em,{children:"decorators"}),"."]})}),"\n",(0,s.jsxs)(t.ol,{start:"3",children:["\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsxs)(t.p,{children:["1st ",(0,s.jsx)(t.code,{children:"parametrize"})," from the bottom"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-py",metastring:"showLineNumbers",children:'@pytest.mark.parametrize("sorting_function", [select_sort, broken_select_sort])\n'})}),"\n",(0,s.jsxs)(t.p,{children:["This tells pytest, that we want to pass the values from the list to the parameter ",(0,s.jsx)(t.code,{children:"sorting_function"}),". In other words, this lets us use the same test function for both the correct and incorrect select sort."]}),"\n"]}),"\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsxs)(t.p,{children:["2nd ",(0,s.jsx)(t.code,{children:"parametrize"})," from the bottom is similar, but works with the postcondition.\nThe reason why they are separated is pretty simple, this way they act like cartesian product: for each sorting function we also use each postcondition."]}),"\n"]}),"\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"@settings"})," raises the count of tests that hypothesis runs (from default of 100(?))."]}),"\n"]}),"\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"@given(lists(integers()))"}),"\nThis means hypothesis is randomly creating lists of integers and passing them to the function, which has only one parameter left and that is ",(0,s.jsx)(t.code,{children:"numbers"}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(t.h3,{id:"lets-run-the-tests",children:"Let's run the tests!"}),"\n",(0,s.jsxs)(t.p,{children:["In case you want to experiment locally, you should install ",(0,s.jsx)(t.code,{children:"pytest"})," and ",(0,s.jsx)(t.code,{children:"hypothesis"})," from the PyPI."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{children:'% pytest -v test_sort.py\n=================================== test session starts ====================================\nplatform linux -- Python 3.6.8, pytest-3.8.2, py-1.7.0, pluggy-0.13.1 -- /usr/bin/python3\ncachedir: .pytest_cache\nrootdir: /home/xfocko/git/xfocko/ib002/postcondition-ambiguity, inifile:\nplugins: hypothesis-5.16.1\ncollected 4 items\n\ntest_sort.py::test_select_sort[select_sort-check_vague_postcondition] PASSED [ 25%]\ntest_sort.py::test_select_sort[select_sort-check_postcondition] PASSED [ 50%]\ntest_sort.py::test_select_sort[broken_select_sort-check_vague_postcondition] PASSED [ 75%]\ntest_sort.py::test_select_sort[broken_select_sort-check_postcondition] FAILED [100%]\n\n========================================= FAILURES =========================================\n_________________ test_select_sort[broken_select_sort-check_postcondition] _________________\n\nsorting_function = <function broken_select_sort at 0x7fac179308c8>\npostcondition = <function check_postcondition at 0x7fac1786d1e0>\n\n @given(lists(integers()))\n> @settings(max_examples=1000)\n @pytest.mark.parametrize(\n "postcondition", [check_vague_postcondition, check_postcondition]\n )\n @pytest.mark.parametrize("sorting_function", [select_sort, broken_select_sort])\n def test_select_sort(sorting_function, postcondition, numbers):\n\ntest_sort.py:132:\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\ntest_sort.py:139: in test_select_sort\n postcondition(numbers, result)\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n\noriginal_arr = [0, 0], arr = [0, 1]\n\n def check_postcondition(original_arr, arr):\n if not arr:\n return\n\n # check ordering\n for x, y in zip(arr, itertools.islice(arr, 1, len(arr))):\n assert x <= y\n\n # get counts from original list\n original_counts = {}\n for value in original_arr:\n original_counts[value] = 1 + original_counts.get(value, 0)\n\n # get counts from resulting list\n counts = {}\n for value in arr:\n counts[value] = 1 + counts.get(value, 0)\n\n # if arr is permutation of original_arr then all counts must be the same\n> assert counts == original_counts\nE assert {0: 1, 1: 1} == {0: 2}\nE Differing items:\nE {0: 1} != {0: 2}\nE Left contains more items:\nE {1: 1}\nE Full diff:\nE - {0: 1, 1: 1}\nE + {0: 2}\n\ntest_sort.py:128: AssertionError\n----------------------------------- Captured stdout call -----------------------------------\nFalsifying example: test_select_sort(\n sorting_function=<function test_sort.broken_select_sort>,\n postcondition=<function test_sort.check_postcondition>,\n numbers=[0, 0],\n)\n============================ 1 failed, 3 passed in 6.84 seconds ============================\n'})}),"\n",(0,s.jsxs)(t.p,{children:["We can clearly see that our broken select sort has passed the ",(0,s.jsx)(t.em,{children:"vague postcondition"}),", but the explicit one was not satisfied."]}),"\n",(0,s.jsx)(t.h2,{id:"summary",children:"Summary"}),"\n",(0,s.jsxs)(t.p,{children:["For proving the correctness of the algorithm it is better to be explicit than prove that algorithm is correct even though it is not. Being explicit also allows you to test smaller ",(0,s.jsx)(t.em,{children:"chunks"})," of code better."]})]})}function d(e={}){const{wrapper:t}={...(0,i.a)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},11151:(e,t,n)=>{n.d(t,{Z:()=>a,a:()=>r});var s=n(67294);const i={},o=s.createContext(i);function r(e){const t=s.useContext(o);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),s.createElement(o.Provider,{value:t},e.children)}}}]); |