From 455bb2acf097b9e3436a6139b4f3fe3e562fba0f Mon Sep 17 00:00:00 2001 From: Jason Wright Date: Sun, 5 Jul 2026 16:08:37 -0600 Subject: [PATCH] fix(vertexcover): structured parse errors + self-describing formats Bring VERTEXCOVER in line with the parse-error handling used elsewhere (e.g. 3SAT) so malformed input yields a diagnostic rather than an unhandled exception: - VERTEXCOVER constructor throws ProblemParseException on empty input and wraps any parse/format failure (StringParser, int.Parse, graph build) so the controller layer can surface a 400 instead of a 500. - VCVerifier throws CertificateParseException on empty/malformed certificates instead of letting parser exceptions escape. - Add self-describing instanceFormat/certificateFormat so the parse-error body can report the expected shape. - Remove dead code (getEdges/getK, commented-out verify logic). - Add tests covering invalid instances, malformed certificates, and the presence of the format strings. Co-Authored-By: Claude Opus 4.8 --- .../NPC_VERTEXCOVER/VERTEXCOVER_Class.cs | 28 ++++-- .../NPC_VERTEXCOVER/Verifiers/VCVerifier.cs | 96 ++++++------------- .../NPC_VERTEXCOVER/VERTEXCOVER_Tests.cs | 46 ++++++++- 3 files changed, 92 insertions(+), 78 deletions(-) diff --git a/Problems/NPComplete/NPC_VERTEXCOVER/VERTEXCOVER_Class.cs b/Problems/NPComplete/NPC_VERTEXCOVER/VERTEXCOVER_Class.cs index f744344b..db5ee378 100644 --- a/Problems/NPComplete/NPC_VERTEXCOVER/VERTEXCOVER_Class.cs +++ b/Problems/NPComplete/NPC_VERTEXCOVER/VERTEXCOVER_Class.cs @@ -18,6 +18,8 @@ class VERTEXCOVER : IGraphProblem _nodes = new List(); private List> _edges = new List>(); @@ -65,19 +67,27 @@ public VERTEXCOVER() : this(_defaultInstance) { } public VERTEXCOVER(string instanceInput) { + if (string.IsNullOrWhiteSpace(instanceInput)) { + throw new ProblemParseException("VERTEXCOVER", instanceInput, "instance is empty"); + } + instance = instanceInput; StringParser vertexCover = new("{((N,E),K) | N is set, E subset N unorderedcross N, K is int}"); - vertexCover.parse(instanceInput); - nodes = vertexCover["N"].ToList().Select(node => node.ToString()).ToList(); - edges = vertexCover["E"].ToList().Select(edge => - { - List cast = edge.ToList(); - return new KeyValuePair(cast[0].ToString(), cast[1].ToString()); - }).ToList(); - _K = int.Parse(vertexCover["K"].ToString()); + try { + vertexCover.parse(instanceInput); + nodes = vertexCover["N"].ToList().Select(node => node.ToString()).ToList(); + edges = vertexCover["E"].ToList().Select(edge => + { + List cast = edge.ToList(); + return new KeyValuePair(cast[0].ToString(), cast[1].ToString()); + }).ToList(); + _K = int.Parse(vertexCover["K"].ToString()); - graph = new UtilCollectionGraph(vertexCover["N"], vertexCover["E"]); + graph = new UtilCollectionGraph(vertexCover["N"], vertexCover["E"]); + } catch (Exception ex) when (ex is not ProblemParseException) { + throw new ProblemParseException("VERTEXCOVER", instanceInput, ex.Message); + } } } diff --git a/Problems/NPComplete/NPC_VERTEXCOVER/Verifiers/VCVerifier.cs b/Problems/NPComplete/NPC_VERTEXCOVER/Verifiers/VCVerifier.cs index 20e0d422..6be2b83f 100644 --- a/Problems/NPComplete/NPC_VERTEXCOVER/Verifiers/VCVerifier.cs +++ b/Problems/NPComplete/NPC_VERTEXCOVER/Verifiers/VCVerifier.cs @@ -26,7 +26,7 @@ public string certificate { // --- Methods Including Constructors --- public VCVerifier() { - + } /// @@ -36,91 +36,51 @@ public VCVerifier() { /// /// public bool verify(VERTEXCOVER problem, string certificate){ - //{{a,b,c,d,e,f,g} : {(a,b) & (a,c) & (c,d) & (c,e) & (d,f) & (e,f) & (e,g)} : 3} - //{{a,d,e} : {(a,b) & (a,c) & (c,d) & (c,e) & (d,f) & (e,f) & (e,g)} } - List certificateNodes = getNodes(certificate); - // List> edges = getEdges(c); + if (string.IsNullOrWhiteSpace(certificate)) + { + throw new CertificateParseException(problem, certificate, "certificate is empty"); + } + + List certificateNodes; + try + { + certificateNodes = getNodes(certificate); + } + catch (Exception ex) + { + throw new CertificateParseException(problem, certificate, ex.Message); + } + if (certificateNodes.Count == 0 || certificateNodes.Any(string.IsNullOrWhiteSpace)) + { + throw new CertificateParseException(problem, certificate, + "certificate did not parse to a non-empty list of node names"); + } List GNodes = problem.nodes; List> Gedges = problem.edges; - - //var list = nodes.Except(GNodes); - - // bool result1 = list?.Any() != true; - - // int count = 0; - // foreach (KeyValuePair edge in edges) - // { - // foreach (KeyValuePair Gedge in Gedges){ - // if (edge.Key.Equals(Gedge.Key) && edge.Value.Equals(Gedge.Value)){ - // count += 1; - // } - // if (edge.Key.Equals(Gedge.Value) && edge.Value.Equals(Gedge.Key)){ - // count += 1; - // } - // } - // } - // int size = Gedges.Count; - // bool result2 = false; - // if (size == count){ - // result2 = true; - // } - - // return (result1 == true) && (result2 == true) ? true : false; - //Step one of the verify method. Check if the input graph contains all the nodes in the certificate. If not, reject. - foreach(string cNode in certificateNodes){ - if(!GNodes.Contains(cNode)){ + foreach (string cNode in certificateNodes) + { + if (!GNodes.Contains(cNode)){ return false; //reject } } //Step two of the verify method. Test whether the set of all edges incident to nodes in c equals the set of edges in G //A node being incident to an edge means that that edge has the node as one of its two endpoints. - + //To test incidence, we will ask the graph if it has any edges that don't have an endpoint contained in the certificate set. - foreach(KeyValuePair kvp in Gedges){ - if(!certificateNodes.Contains(kvp.Key) && !certificateNodes.Contains(kvp.Value)){ //if a kvp doesnt have a key or value found in the nodeset + foreach (KeyValuePair kvp in Gedges) + { + if (!certificateNodes.Contains(kvp.Key) && !certificateNodes.Contains(kvp.Value)){ //if a kvp doesnt have a key or value found in the nodeset return false; //reject } - } return true; } public List getNodes(string nodesInput) { - return GraphParser.parseNodeListWithStringFunctions(nodesInput); - - } - - public List> getEdges(string Ginput) { - - List> allGEdges = new List>(); - - string strippedInput = Ginput.Replace("{", "").Replace("}", "").Replace(" ", "").Replace("(", "").Replace(")",""); - - // [0] is nodes, [1] is edges, [2] is k. - string[] Gsections = strippedInput.Split(':'); - string[] Gedges = Gsections[1].Split('&'); - - foreach (string edge in Gedges) { - string[] fromTo = edge.Split(','); - string nodeFrom = fromTo[0]; - string nodeTo = fromTo[1]; - - KeyValuePair fullEdge = new KeyValuePair(nodeFrom, nodeTo); - allGEdges.Add(fullEdge); - } - - return allGEdges; - } - - public int getK(string Ginput) { - string strippedInput = Ginput.Replace("{", "").Replace("}", "").Replace(" ", "").Replace("(", "").Replace(")",""); - - // [0] is nodes, [1] is edges, [2] is k. - string[] Gsections = strippedInput.Split(':'); - return Int32.Parse(Gsections[2]); + return GraphParser.parseNodeListWithStringFunctions(nodesInput); } } \ No newline at end of file diff --git a/redux-tests/Problems/NPC_VERTEXCOVER/VERTEXCOVER_Tests.cs b/redux-tests/Problems/NPC_VERTEXCOVER/VERTEXCOVER_Tests.cs index cafaff4c..97bd699c 100644 --- a/redux-tests/Problems/NPC_VERTEXCOVER/VERTEXCOVER_Tests.cs +++ b/redux-tests/Problems/NPC_VERTEXCOVER/VERTEXCOVER_Tests.cs @@ -1,4 +1,5 @@ using Xunit; +using API.Interfaces; using API.Interfaces.Graphs; using API.Problems.NPComplete.NPC_VERTEXCOVER.ReduceTo.NPC_ARCSET; using API.Problems.NPComplete.NPC_VERTEXCOVER.Verifiers; @@ -46,7 +47,50 @@ public void VCSolver_Test() } - [Theory] //tests with default graph string Certificates of this test represent junk or empty data. + // ------------------------------------------------------------------------- + // Self-describing formats (§1.5) + // ------------------------------------------------------------------------- + + [Fact] + public void VERTEXCOVER_Declares_Formats() + { + VERTEXCOVER vCov = new VERTEXCOVER(); + Assert.False(string.IsNullOrWhiteSpace(vCov.instanceFormat)); + Assert.False(string.IsNullOrWhiteSpace(vCov.certificateFormat)); + } + + // ------------------------------------------------------------------------- + // Constructor — invalid instances (all must throw ProblemParseException) + // ------------------------------------------------------------------------- + + [Theory] + [InlineData("")] // empty + [InlineData(" ")] // whitespace only + [InlineData("{{a,b,c} : {(a,b)} : 3}")] // old colon format + [InlineData("abc")] // bare string + [InlineData("(({a,b,c},{{a,b}}),x)")] // non-integer K + [InlineData("(({a,b,c},{{a,b}})")] // unbalanced / truncated + public void VERTEXCOVER_Constructor_Throws_On_Invalid_Instance(string instance) + { + Assert.Throws(() => new VERTEXCOVER(instance)); + } + + // ------------------------------------------------------------------------- + // Verifier — malformed certificates (must throw CertificateParseException) + // ------------------------------------------------------------------------- + + [Theory] + [InlineData("")] // empty + [InlineData(" ")] // whitespace only + [InlineData("{}")] // parses to a single empty token + public void VERTEXCOVER_Verifier_Throws_On_Malformed_Certificate(string certificate) + { + VERTEXCOVER testVert = new VERTEXCOVER(); + VCVerifier verifier = testVert.defaultVerifier; + Assert.Throws(() => verifier.verify(testVert, certificate)); + } + + [Theory] //tests with default graph string Certificates of this test represent junk or empty data. [InlineData("(({a,b,c,d},{{a,b},{a,c},{a,d}}),1)", "{a}")] //four node graph dependent on a with a in cert [InlineData("(({a,b,c,d},{{a,b},{a,c},{a,d}}),1)", "{b,c,d}")] //four node graph dependent on a with all nodes except a in cert [InlineData("(({a,b,c,d,e},{{a,b},{a,c},{a,d},{a,e},{b,c},{b,d},{b,e},{c,e},{c,d},{d,e}}),5)","{a,b,c,d}}")] //five node connected graph, test four nodes