/// <summary> /// Produces a hash for the paths of adjacent bnodes for a bnode, /// incorporating all information about its subgraph of bnodes. /// </summary> /// <remarks> /// Produces a hash for the paths of adjacent bnodes for a bnode, /// incorporating all information about its subgraph of bnodes. This method /// will recursively pick adjacent bnode permutations that produce the /// lexicographically-least 'path' serializations. /// </remarks> /// <param name="id">the ID of the bnode to hash paths for.</param> /// <param name="bnodes">the map of bnode quads.</param> /// <param name="namer">the canonical bnode namer.</param> /// <param name="pathNamer">the namer used to assign names to adjacent bnodes.</param> /// <param name="callback">(err, result) called once the operation completes.</param> private static HashResult HashPaths(string id, IDictionary <string, IDictionary <string, object> > bnodes, UniqueNamer namer, UniqueNamer pathNamer) { #if !PORTABLE MessageDigest md = null; try { // create SHA-1 digest md = MessageDigest.GetInstance("SHA-1"); var groups = new JObject(); IList <string> groupHashes; var quads = (IList <RdfDataset.Quad>)bnodes[id]["quads"]; for (var hpi = 0;; hpi++) { if (hpi == quads.Count) { // done , hash groups groupHashes = new List <string>(groups.GetKeys()); ((List <string>)groupHashes).Sort(StringComparer.CurrentCultureIgnoreCase); for (var hgi = 0;; hgi++) { if (hgi == groupHashes.Count) { var res = new HashResult(); res.hash = EncodeHex(md.Digest()); res.pathNamer = pathNamer; return(res); } // digest group hash var groupHash = groupHashes[hgi]; md.Update(JavaCompat.GetBytesForString(groupHash, "UTF-8")); // choose a path and namer from the permutations string chosenPath = null; UniqueNamer chosenNamer = null; var permutator = new Permutator((JArray)groups[groupHash]); while (true) { var contPermutation = false; var breakOut = false; var permutation = permutator.Next(); var pathNamerCopy = pathNamer.Clone(); // build adjacent path var path = string.Empty; var recurse = new JArray(); foreach (string bnode in permutation) { // use canonical name if available if (namer.IsNamed(bnode)) { path += namer.GetName(bnode); } else { // recurse if bnode isn't named in the path // yet if (!pathNamerCopy.IsNamed(bnode)) { recurse.Add(bnode); } path += pathNamerCopy.GetName(bnode); } // skip permutation if path is already >= chosen // path if (chosenPath != null && path.Length >= chosenPath.Length && string.CompareOrdinal(path, chosenPath) > 0) { // return nextPermutation(true); if (permutator.HasNext()) { contPermutation = true; } else { // digest chosen path and update namer md.Update(JavaCompat.GetBytesForString(chosenPath, "UTF-8")); pathNamer = chosenNamer; // hash the nextGroup breakOut = true; } break; } } // if we should do the next permutation if (contPermutation) { continue; } // if we should stop processing this group if (breakOut) { break; } // does the next recursion for (var nrn = 0;; nrn++) { if (nrn == recurse.Count) { // return nextPermutation(false); if (chosenPath == null || string.CompareOrdinal(path, chosenPath) < 0) { chosenPath = path; chosenNamer = pathNamerCopy; } if (!permutator.HasNext()) { // digest chosen path and update namer md.Update(JavaCompat.GetBytesForString(chosenPath, "UTF-8")); pathNamer = chosenNamer; // hash the nextGroup breakOut = true; } break; } // do recursion var bnode_1 = (string)recurse[nrn]; var result = HashPaths(bnode_1, bnodes, namer, pathNamerCopy); path += pathNamerCopy.GetName(bnode_1) + "<" + result.hash + ">"; pathNamerCopy = result.pathNamer; // skip permutation if path is already >= chosen // path if (chosenPath != null && path.Length >= chosenPath.Length && string.CompareOrdinal(path, chosenPath) > 0) { // return nextPermutation(true); if (!permutator.HasNext()) { // digest chosen path and update namer md.Update(JavaCompat.GetBytesForString(chosenPath, "UTF-8")); pathNamer = chosenNamer; // hash the nextGroup breakOut = true; } break; } } // do next recursion // if we should stop processing this group if (breakOut) { break; } } } } // get adjacent bnode var quad = (IDictionary <string, object>)quads[hpi]; var bnode_2 = GetAdjacentBlankNodeName((IDictionary <string, object>)quad["subject" ], id); string direction = null; if (bnode_2 != null) { // normal property direction = "p"; } else { bnode_2 = GetAdjacentBlankNodeName((IDictionary <string, object>)quad["object"], id ); if (bnode_2 != null) { direction = "r"; } } if (bnode_2 != null) { // get bnode name (try canonical, path, then hash) string name; if (namer.IsNamed(bnode_2)) { name = namer.GetName(bnode_2); } else { if (pathNamer.IsNamed(bnode_2)) { name = pathNamer.GetName(bnode_2); } else { name = HashQuads(bnode_2, bnodes, namer); } } // hash direction, property, end bnode name/hash using (var md1 = MessageDigest.GetInstance("SHA-1")) { // String toHash = direction + (String) ((Map<String, // Object>) quad.get("predicate")).get("value") + name; md1.Update(JavaCompat.GetBytesForString(direction, "UTF-8")); md1.Update(JavaCompat.GetBytesForString((string)((IDictionary <string, object>)quad["predicate"])["value"], "UTF-8")); md1.Update(JavaCompat.GetBytesForString(name, "UTF-8")); var groupHash = EncodeHex(md1.Digest()); if (groups.ContainsKey(groupHash)) { ((JArray)groups[groupHash]).Add(bnode_2); } else { var tmp = new JArray(); tmp.Add(bnode_2); groups[groupHash] = tmp; } } } } } catch { // TODO: i don't expect that SHA-1 is even NOT going to be // available? // look into this further throw; } finally { md?.Dispose(); } #else throw new PlatformNotSupportedException(); #endif }
/// <summary> /// Returns the Threat Level after assigning <see cref="Force"/> to /// <see cref="CityBlock"/>. No work is done here to remember state of any sort, /// only the allocated threat level is calculated and returned. It is not the most /// optimized thing that can be done, but given the timeframe and problem domain, /// this is pretty close to being good enough. /// </summary> /// <returns></returns> private int Allocate() { //var comparer = new ForceComparer(); var threatLevel = Blocks.Sum(x => x.ThreatLevel); /* Really, really (REALLY) avoid the R# help here: that could * be potentially very, very (VERY) expensive. */ var permutator = new Permutator<Force>(Forces); do { var localForces = (from x in permutator.Values select x.Clone()) .OfType<Force>().ToList(); // ReSharper disable once PossibleMultipleEnumeration var localBlocks = (from x in Blocks.ToArray() select (CityBlock) x.Clone()).ToArray(); // Assign all the possible forces we can to the best possible locations. foreach (var force in localForces .TakeWhile(x => x.TryPatrol(localBlocks)) .Where(x => localBlocks.All(y => y.IsPatrolled))) { break; } threatLevel = Math.Min(threatLevel, localBlocks.Sum(x => x.ThreatLevel)); // This is as low as we can go, no point in continuing through the (many) other permutations. if (threatLevel == 0) break; } while (permutator.Next()); //// Permute the forces themselves. //var forcePermutations = Forces.Permute().ToArray(); //// ReSharper disable once LoopCanBePartlyConvertedToQuery //foreach (var forces in forcePermutations) //{ // var localForces = (from x in forces // select x.Clone()).OfType<Force>().ToArray(); // // ReSharper disable once PossibleMultipleEnumeration // var localBlocks = (from x in Blocks.ToArray() // select (CityBlock) x.Clone()).ToArray(); // // Assign all the possible forces we can to the best possible locations. // foreach (var force in localForces // .TakeWhile(x => x.TryPatrol(localBlocks)) // .Where(x => localBlocks.All(y => y.IsPatrolled))) // { // break; // } // threatLevel = Math.Min(threatLevel, // localBlocks.Sum(x => x.ThreatLevel)); // // This is as low as we can go, no point in continuing through the (many) other permutations. // if (threatLevel == 0) break; //} return threatLevel; }