[{"data":1,"prerenderedAt":831},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fguides\u002Fbrute-force-too-slow":3},{"id":4,"title":5,"body":6,"description":813,"extension":814,"faq":815,"meta":825,"navigation":96,"noindex":826,"path":827,"seo":828,"stem":829,"__hash__":830},"content\u002Fdeveloper\u002Fguides\u002Fbrute-force-too-slow.md","Your brute force is too slow — it's an optimization problem",{"type":7,"value":8,"toc":807},"minimark",[9,13,44,49,52,191,198,202,213,605,610,614,776,780,798,803],[10,11,5],"h1",{"id":12},"your-brute-force-is-too-slow-its-an-optimization-problem",[14,15,16,17,21,22,25,26,29,30,34,35,39,40,43],"p",{},"If you wrote a loop over ",[18,19,20],"code",{},"itertools.permutations",", ",[18,23,24],{},"itertools.combinations",", or a\n",[18,27,28],{},"product"," of choices to find the ",[31,32,33],"em",{},"best"," option, and it grinds to a halt as the\ninput grows, you don't have a performance bug — you have an ",[36,37,38],"strong",{},"optimization\nproblem",". The fix is not a faster loop; it's to model the problem and hand it to\na solver like ",[36,41,42],{},"quicopt",", which prunes the search instead of enumerating it.",[45,46,48],"h2",{"id":47},"recognize-the-pattern","Recognize the pattern",[14,50,51],{},"The naive code almost always looks like one of these:",[53,54,59],"pre",{"className":55,"code":56,"language":57,"meta":58,"style":58},"language-python shiki shiki-themes github-dark","# ordering \u002F sequencing  -> exponential (n!)\nfor order in itertools.permutations(items): ...\n\n# pick a subset          -> exponential (2^n)\nfor pick in itertools.product([0, 1], repeat=n): ...\n\n# split \u002F allocate       -> exponential or a coarse grid\nfor a in range(N):\n    for b in range(N - a): ...\n","python","",[18,60,61,70,91,98,104,140,145,151,167],{"__ignoreMap":58},[62,63,66],"span",{"class":64,"line":65},"line",1,[62,67,69],{"class":68},"sAwPA","# ordering \u002F sequencing  -> exponential (n!)\n",[62,71,73,77,81,84,87],{"class":64,"line":72},2,[62,74,76],{"class":75},"snl16","for",[62,78,80],{"class":79},"s95oV"," order ",[62,82,83],{"class":75},"in",[62,85,86],{"class":79}," itertools.permutations(items): ",[62,88,90],{"class":89},"sDLfK","...\n",[62,92,94],{"class":64,"line":93},3,[62,95,97],{"emptyLinePlaceholder":96},true,"\n",[62,99,101],{"class":64,"line":100},4,[62,102,103],{"class":68},"# pick a subset          -> exponential (2^n)\n",[62,105,107,109,112,114,117,120,122,125,128,132,135,138],{"class":64,"line":106},5,[62,108,76],{"class":75},[62,110,111],{"class":79}," pick ",[62,113,83],{"class":75},[62,115,116],{"class":79}," itertools.product([",[62,118,119],{"class":89},"0",[62,121,21],{"class":79},[62,123,124],{"class":89},"1",[62,126,127],{"class":79},"], ",[62,129,131],{"class":130},"s9osk","repeat",[62,133,134],{"class":75},"=",[62,136,137],{"class":79},"n): ",[62,139,90],{"class":89},[62,141,143],{"class":64,"line":142},6,[62,144,97],{"emptyLinePlaceholder":96},[62,146,148],{"class":64,"line":147},7,[62,149,150],{"class":68},"# split \u002F allocate       -> exponential or a coarse grid\n",[62,152,154,156,159,161,164],{"class":64,"line":153},8,[62,155,76],{"class":75},[62,157,158],{"class":79}," a ",[62,160,83],{"class":75},[62,162,163],{"class":89}," range",[62,165,166],{"class":79},"(N):\n",[62,168,170,173,176,178,180,183,186,189],{"class":64,"line":169},9,[62,171,172],{"class":75},"    for",[62,174,175],{"class":79}," b ",[62,177,83],{"class":75},[62,179,163],{"class":89},[62,181,182],{"class":79},"(N ",[62,184,185],{"class":75},"-",[62,187,188],{"class":79}," a): ",[62,190,90],{"class":89},[14,192,193,194,197],{},"Each of these is a known problem with a compact model. Instead of enumerating,\nyou declare variables, constraints and an objective, and call ",[18,195,196],{},"solve()",".",[45,199,201],{"id":200},"the-fix-concretely","The fix, concretely",[14,203,204,205,208,209,212],{},"Assigning N workers to N tasks by trying every permutation is ",[18,206,207],{},"O(n!)",". As a MILP\nit is a few lines and comes back ",[36,210,211],{},"proven optimal",":",[53,214,217],{"className":55,"code":215,"filename":216,"language":57,"meta":58,"style":58},"from ortools.math_opt.python import mathopt\nfrom quicopt import Client\ncost = [[9,11,14,11,7],[6,15,13,13,10],[12,13,6,8,8],[11,9,10,12,9],[7,12,14,10,14]]\nN = len(cost)\nmodel = mathopt.Model(name=\"assignment\")\nx = [[model.add_binary_variable(name=f\"x_{w}_{t}\") for t in range(N)] for w in range(N)]\nfor w in range(N):\n    model.add_linear_constraint(sum(x[w][t] for t in range(N)) == 1)\nfor t in range(N):\n    model.add_linear_constraint(sum(x[w][t] for w in range(N)) == 1)\nmodel.minimize(sum(cost[w][t] * x[w][t] for w in range(N) for t in range(N)))\nprint(Client(\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\").solve(model).display)\n","assignment.py",[18,218,219,233,245,368,381,403,472,484,514,526,551,590],{"__ignoreMap":58},[62,220,221,224,227,230],{"class":64,"line":65},[62,222,223],{"class":75},"from",[62,225,226],{"class":79}," ortools.math_opt.python ",[62,228,229],{"class":75},"import",[62,231,232],{"class":79}," mathopt\n",[62,234,235,237,240,242],{"class":64,"line":72},[62,236,223],{"class":75},[62,238,239],{"class":79}," quicopt ",[62,241,229],{"class":75},[62,243,244],{"class":79}," Client\n",[62,246,247,250,252,255,258,261,264,266,269,271,273,275,278,281,284,286,289,291,294,296,298,300,303,305,308,310,312,314,316,318,321,323,325,327,329,331,333,335,337,339,341,343,345,347,349,351,353,355,357,359,361,363,365],{"class":64,"line":93},[62,248,249],{"class":79},"cost ",[62,251,134],{"class":75},[62,253,254],{"class":79}," [[",[62,256,257],{"class":89},"9",[62,259,260],{"class":79},",",[62,262,263],{"class":89},"11",[62,265,260],{"class":79},[62,267,268],{"class":89},"14",[62,270,260],{"class":79},[62,272,263],{"class":89},[62,274,260],{"class":79},[62,276,277],{"class":89},"7",[62,279,280],{"class":79},"],[",[62,282,283],{"class":89},"6",[62,285,260],{"class":79},[62,287,288],{"class":89},"15",[62,290,260],{"class":79},[62,292,293],{"class":89},"13",[62,295,260],{"class":79},[62,297,293],{"class":89},[62,299,260],{"class":79},[62,301,302],{"class":89},"10",[62,304,280],{"class":79},[62,306,307],{"class":89},"12",[62,309,260],{"class":79},[62,311,293],{"class":89},[62,313,260],{"class":79},[62,315,283],{"class":89},[62,317,260],{"class":79},[62,319,320],{"class":89},"8",[62,322,260],{"class":79},[62,324,320],{"class":89},[62,326,280],{"class":79},[62,328,263],{"class":89},[62,330,260],{"class":79},[62,332,257],{"class":89},[62,334,260],{"class":79},[62,336,302],{"class":89},[62,338,260],{"class":79},[62,340,307],{"class":89},[62,342,260],{"class":79},[62,344,257],{"class":89},[62,346,280],{"class":79},[62,348,277],{"class":89},[62,350,260],{"class":79},[62,352,307],{"class":89},[62,354,260],{"class":79},[62,356,268],{"class":89},[62,358,260],{"class":79},[62,360,302],{"class":89},[62,362,260],{"class":79},[62,364,268],{"class":89},[62,366,367],{"class":79},"]]\n",[62,369,370,373,375,378],{"class":64,"line":100},[62,371,372],{"class":79},"N ",[62,374,134],{"class":75},[62,376,377],{"class":89}," len",[62,379,380],{"class":79},"(cost)\n",[62,382,383,386,388,391,394,396,400],{"class":64,"line":106},[62,384,385],{"class":79},"model ",[62,387,134],{"class":75},[62,389,390],{"class":79}," mathopt.Model(",[62,392,393],{"class":130},"name",[62,395,134],{"class":75},[62,397,399],{"class":398},"sU2Wk","\"assignment\"",[62,401,402],{"class":79},")\n",[62,404,405,408,410,413,415,417,420,423,426,429,432,435,437,440,442,445,448,450,453,455,457,460,462,465,467,469],{"class":64,"line":142},[62,406,407],{"class":79},"x ",[62,409,134],{"class":75},[62,411,412],{"class":79}," [[model.add_binary_variable(",[62,414,393],{"class":130},[62,416,134],{"class":75},[62,418,419],{"class":75},"f",[62,421,422],{"class":398},"\"x_",[62,424,425],{"class":89},"{",[62,427,428],{"class":79},"w",[62,430,431],{"class":89},"}",[62,433,434],{"class":398},"_",[62,436,425],{"class":89},[62,438,439],{"class":79},"t",[62,441,431],{"class":89},[62,443,444],{"class":398},"\"",[62,446,447],{"class":79},") ",[62,449,76],{"class":75},[62,451,452],{"class":79}," t ",[62,454,83],{"class":75},[62,456,163],{"class":89},[62,458,459],{"class":79},"(N)] ",[62,461,76],{"class":75},[62,463,464],{"class":79}," w ",[62,466,83],{"class":75},[62,468,163],{"class":89},[62,470,471],{"class":79},"(N)]\n",[62,473,474,476,478,480,482],{"class":64,"line":147},[62,475,76],{"class":75},[62,477,464],{"class":79},[62,479,83],{"class":75},[62,481,163],{"class":89},[62,483,166],{"class":79},[62,485,486,489,492,495,497,499,501,503,506,509,512],{"class":64,"line":153},[62,487,488],{"class":79},"    model.add_linear_constraint(",[62,490,491],{"class":89},"sum",[62,493,494],{"class":79},"(x[w][t] ",[62,496,76],{"class":75},[62,498,452],{"class":79},[62,500,83],{"class":75},[62,502,163],{"class":89},[62,504,505],{"class":79},"(N)) ",[62,507,508],{"class":75},"==",[62,510,511],{"class":89}," 1",[62,513,402],{"class":79},[62,515,516,518,520,522,524],{"class":64,"line":169},[62,517,76],{"class":75},[62,519,452],{"class":79},[62,521,83],{"class":75},[62,523,163],{"class":89},[62,525,166],{"class":79},[62,527,529,531,533,535,537,539,541,543,545,547,549],{"class":64,"line":528},10,[62,530,488],{"class":79},[62,532,491],{"class":89},[62,534,494],{"class":79},[62,536,76],{"class":75},[62,538,464],{"class":79},[62,540,83],{"class":75},[62,542,163],{"class":89},[62,544,505],{"class":79},[62,546,508],{"class":75},[62,548,511],{"class":89},[62,550,402],{"class":79},[62,552,554,557,559,562,565,568,570,572,574,576,579,581,583,585,587],{"class":64,"line":553},11,[62,555,556],{"class":79},"model.minimize(",[62,558,491],{"class":89},[62,560,561],{"class":79},"(cost[w][t] ",[62,563,564],{"class":75},"*",[62,566,567],{"class":79}," x[w][t] ",[62,569,76],{"class":75},[62,571,464],{"class":79},[62,573,83],{"class":75},[62,575,163],{"class":89},[62,577,578],{"class":79},"(N) ",[62,580,76],{"class":75},[62,582,452],{"class":79},[62,584,83],{"class":75},[62,586,163],{"class":89},[62,588,589],{"class":79},"(N)))\n",[62,591,593,596,599,602],{"class":64,"line":592},12,[62,594,595],{"class":89},"print",[62,597,598],{"class":79},"(Client(",[62,600,601],{"class":398},"\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\"",[62,603,604],{"class":79},").solve(model).display)\n",[606,607],"term-result",{":rows":608,"cmd":609},"[\"├── status:     optimal\",\"├── feasible:   true\",\"├── objective:  38.0\",\"├── x:          x_0_0=0, x_0_1=0, x_0_2=0, x_0_3=0, x_0_4=1, x_1_0=1, …  (25 variables)\",\"└── solve_time: 0.0117 s\"]","$ python assignment.py",[45,611,613],{"id":612},"find-your-problem","Find your problem",[615,616,617,633],"table",{},[618,619,620],"thead",{},[621,622,623,627,630],"tr",{},[624,625,626],"th",{},"Your brute force looks like…",[624,628,629],{},"It's probably…",[624,631,632],{},"Guide",[634,635,636,652,666,679,692,705,718,736,749,762],"tbody",{},[621,637,638,642,645],{},[639,640,641],"td",{},"permutations of stops \u002F a tour",[639,643,644],{},"Traveling salesman",[639,646,647],{},[648,649,651],"a",{"href":650},"\u002Fdeveloper\u002Fguides\u002Ftraveling-salesman","TSP",[621,653,654,657,660],{},[639,655,656],{},"routes for several vehicles",[639,658,659],{},"Vehicle routing",[639,661,662],{},[648,663,665],{"href":664},"\u002Fdeveloper\u002Fguides\u002Fvehicle-routing","VRP",[621,667,668,671,674],{},[639,669,670],{},"matching A's to B's one-to-one",[639,672,673],{},"Assignment",[639,675,676],{},[648,677,673],{"href":678},"\u002Fdeveloper\u002Fguides\u002Fassignment",[621,680,681,684,687],{},[639,682,683],{},"pick items under a budget\u002Flimit",[639,685,686],{},"Knapsack",[639,688,689],{},[648,690,686],{"href":691},"\u002Fdeveloper\u002Fguides\u002Fknapsack",[621,693,694,697,700],{},[639,695,696],{},"fewest sets\u002Foptions covering everything",[639,698,699],{},"Set cover",[639,701,702],{},[648,703,699],{"href":704},"\u002Fdeveloper\u002Fguides\u002Fset-cover",[621,706,707,710,713],{},[639,708,709],{},"pack items into fewest bins",[639,711,712],{},"Bin packing",[639,714,715],{},[648,716,712],{"href":717},"\u002Fdeveloper\u002Fguides\u002Fbin-packing",[621,719,720,723,726],{},[639,721,722],{},"split jobs across machines\u002Fworkers",[639,724,725],{},"Scheduling",[639,727,728,731,732],{},[648,729,725],{"href":730},"\u002Fdeveloper\u002Fguides\u002Fscheduling"," · ",[648,733,735],{"href":734},"\u002Fdeveloper\u002Fguides\u002Fshift-scheduling","Shifts",[621,737,738,741,744],{},[639,739,740],{},"color \u002F assign without conflicts",[639,742,743],{},"Graph coloring",[639,745,746],{},[648,747,743],{"href":748},"\u002Fdeveloper\u002Fguides\u002Fgraph-coloring",[621,750,751,754,757],{},[639,752,753],{},"split a graph in two",[639,755,756],{},"Max-cut",[639,758,759],{},[648,760,756],{"href":761},"\u002Fdeveloper\u002Fguides\u002Fmax-cut",[621,763,764,767,770],{},[639,765,766],{},"divide a budget \u002F resources",[639,768,769],{},"Allocation",[639,771,772],{},[648,773,775],{"href":774},"\u002Fdeveloper\u002Fguides\u002Fresource-allocation","Resource allocation",[45,777,779],{"id":778},"next","Next",[781,782,783,791],"ul",{},[784,785,786,787],"li",{},"Browse every guide by problem class: ",[648,788,790],{"href":789},"\u002Fdeveloper\u002Fguides","Guides",[784,792,793,794],{},"Set up the client and solve your first model: ",[648,795,797],{"href":796},"\u002Fdeveloper\u002Fgetting-started","Getting started",[799,800],"contact-cta",{"sub":801,"title":802},"Tell us what you're optimizing — paste your slow loop — and we'll point you at the right model.","Not sure which one fits?",[804,805,806],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":58,"searchDepth":72,"depth":72,"links":808},[809,810,811,812],{"id":47,"depth":72,"text":48},{"id":200,"depth":72,"text":201},{"id":612,"depth":72,"text":613},{"id":778,"depth":72,"text":779},"When nested loops or itertools over every combination blow up in Python, you have an optimization problem — model it and hand it to a solver instead. How to recognize the pattern and pick the right approach with quicopt.","md",[816,819,822],{"q":817,"a":818},"How do I know it's an optimization problem?","If you're looping over permutations, combinations, or a product of choices to find the best one, and it slows to a crawl as inputs grow, it's an optimization problem — a solver prunes that search instead of walking it.",{"q":820,"a":821},"Won't a solver be overkill for small inputs?","No — the model is a few lines and solves the small cases instantly too. The difference is it keeps working when the input grows past what brute force can handle.",{"q":823,"a":824},"Is quicopt free to use?","Yes — pip install quicopt and your first call sets up a free key, no license.",{},false,"\u002Fdeveloper\u002Fguides\u002Fbrute-force-too-slow",{"title":5,"description":813},"developer\u002Fguides\u002Fbrute-force-too-slow","N51epz0zF-ZkCA8Mrp41pfC7lzprlyrPFRfsJU3xSSI",1784110686355]