Search CoreData background thread causes EXC_BAD_ACCESS - core-data

I'm using Swift 4, iOS 11
I'm clearly trying to do something beyond my knowledge level here, but I'm hoping someone can help.
I am implementing a table view search that fetches results from CoreData. The search became too slow, so I am now trying to run it asynchronously to avoid UI lag.
On my main VC (table view), I have the following logic that runs on textDidChange:
DispatchQueue.global(qos: .background).async {
DataManager.searchAllTables(searchText: searchText){ (returnResults) -> () in
DispatchQueue.main.async {
// LOGIC TO GIVE RETURNED RESULTS TO TABLE
self.resultsTable.reloadData()
}
}
}
Within my DataManager in the function searchAllTables, I have the following logic run on each CoreData relationship:
// Get Disease
if let disease = bug.related_disease?.allObjects {
if !disease.isEmpty {
if disease.contains(where: { (($0 as AnyObject).name?.lowercased().contains(searchTerm.lowercased()))! || (($0 as AnyObject).name?.lowercased().contains(self.convertAcronyms(searchTerm: searchTerm)))! }){
// LOGIC TO DETERMINE POINTS
}
}
}
It was working well before trying to make it async, but now it gives me the error
Thread 5: EXC_BAD_ACCESS (code=1, address=0x1a1b40cc771)
for the line of code
if let disease = bug.related_disease?.allObjects {
This error does NOT appear if the user types slowly. This makes me think that the background threads are crossing and something is happening with the object I'm trying to get from CoreData.
I know that EXC_BAD_ACCESS means that I'm "sending a message to an object that has already been released". Can anyone help me understand how to solve this problem?
UPDATE: Added searchAllTables below
static func searchAllTables(searchText:String, completion: #escaping ((_ returnResults:[BugElement])->())) {
var bugElements:[BugElement] = []
var bugElementShell:[BugElement] = []
var returnValueRank:Int = 0
var categoryRestriction:String = "all" // Search all categories by default
var numSearchTerms:Int = searchText.components(separatedBy: " ").count
let searchTerm:String = self.getSearchPhrases(searchTerms: searchText.components(separatedBy: " ")).1
numSearchTerms = self.getSearchPhrases(searchTerms: searchText.components(separatedBy: " ")).0
// Set category restriciton if needed
if self.bugFilter.category_restricted[searchTerm.lowercased()] != nil{
categoryRestriction = self.bugFilter.category_restricted[searchTerm.lowercased()]!
}
let fetchRequest: NSFetchRequest<Bugs> = Bugs.fetchRequest()
// DOES THIS HELP SPEED?
if searchTerm.count <= 3{
let predicate = NSPredicate(format:"name beginswith[c] %# OR ANY related_disease.name contains[c] %# OR ANY related_general.name beginswith[c] %# OR ANY related_gramstain.name beginswith[c] %# OR ANY related_keypoints.name beginswith[c] %# OR ANY related_laboratory.name beginswith[c] %# OR ANY related_morphology.name beginswith[c] %# OR ANY related_prevention.name beginswith[c] %# OR ANY related_signs.name beginswith[c] %# OR ANY related_treatments.name beginswith[c] %# OR ANY related_type.name beginswith[c] %#", argumentArray: [searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased()])
fetchRequest.predicate = predicate
} else{
let predicate = NSPredicate(format:"name contains[c] %# OR related_disease.name contains[c] %# OR related_general.name contains[c] %# OR related_gramstain.name contains[c] %# OR related_keypoints.name contains[c] %# OR related_laboratory.name contains[c] %# OR related_morphology.name contains[c] %# OR related_prevention.name contains[c] %# OR related_signs.name contains[c] %# OR related_treatments.name contains[c] %# OR related_type.name contains[c] %#", argumentArray: [searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased()])
fetchRequest.predicate = predicate
}
do {
let diseases = try DataManager.context.fetch(fetchRequest)
for bug in diseases{
// Points system (all matches past array are worth 1)
var matchValue_name:[Int] = [10, 8, 4]
var matchValue_disease:[Int] = [8, 4, 3]
var matchValue_general:[Int] = [5, 3, 2]
var matchValue_gramstain:[Int] = [5, 3, 2]
var matchValue_keypoints:[Int] = [5, 3, 2]
var matchValue_laboratory:[Int] = [2]
var matchValue_morphology:[Int] = [5, 3, 2]
var matchValue_prevention:[Int] = [2]
var matchValue_signs:[Int] = [1]
var matchValue_treatment:[Int] = [5, 3, 2]
var matchValue_type:[Int] = [1]
// Break down by word
var matchedNameTerms:Int = 0
var matchedDiseaseTerms:Int = 0
var matchedGeneralTerms:Int = 0
var matchedGramStainTerms:Int = 0
var matchedKeyPointsTerms:Int = 0
var matchedLaboratoryTerms:Int = 0
var matchedMorphologyTerms:Int = 0
var matchedPreventionTerms:Int = 0
var matchedSignsTerms:Int = 0
var matchedTreatmentTerms:Int = 0
var matchedTypeTerms:Int = 0
// BEGIN: By term
var matchBasis = Set<String>()
if categoryRestriction == "all" || categoryRestriction == "name"{
// FAULT
if bug.name.lowercased().contains(searchTerm.lowercased()){
matchedNameTerms += 1
// Matched based on disease name
if matchedNameTerms > (matchValue_name.count-1){
// Match beyond point assignment
returnValueRank += 1
} else {
// Matched within point assignment, add those points
returnValueRank += matchValue_name[(matchedNameTerms-1)]
}
// Append name if first match
if matchedNameTerms == 1{
matchBasis.insert("Name")
}
}
}
// Get Disease
if categoryRestriction == "all" || categoryRestriction == "disease"{
// FAULT
if let disease = bug.related_disease?.allObjects {
if !disease.isEmpty {
if disease.contains(where: { (($0 as AnyObject).name?.lowercased().contains(searchTerm.lowercased()))! || (($0 as AnyObject).name?.lowercased().contains(self.convertAcronyms(searchTerm: searchTerm)))! }){
matchedDiseaseTerms += 1
// Matched based on Disease
if matchedDiseaseTerms > (matchValue_disease.count-1){
// Match beyond point assignment
returnValueRank += 1
} else {
// Matched within point assignment, add those points
returnValueRank += matchValue_disease[(matchedDiseaseTerms-1)]
}
// Append Disease if first match
if matchedDiseaseTerms == 1{
matchBasis.insert("Disease")
}
}
}
}
}
// ADDITIONAL MATCHES JUST LIKE ABOVE DISEASE BLOCK
// END: By term
if (matchedNameTerms + matchedDiseaseTerms + matchedGeneralTerms + matchedGramStainTerms + matchedKeyPointsTerms + matchedLaboratoryTerms + matchedMorphologyTerms + matchedPreventionTerms + matchedSignsTerms + matchedTreatmentTerms + matchedTypeTerms) > 0{
// Create Element
let bugElement = BugElement(rank: returnValueRank, name: bug.name, match: matchBasis) // Initialize struct
bugElementShell.append(bugElement)
}
returnValueRank = 0
}
} catch {
if DataManager.debug{ print("Could not get Bugs!") }
}
// Handle stored search
if numSearchTerms == 0{
// No stored search
//print("None")
self.storedBugElements.append(bugElementShell)
} else if numSearchTerms > self.storedBugElements.count{
// New search term
//print("New")
self.storedBugElements.append(bugElementShell)
} else if numSearchTerms < self.storedBugElements.count{
// Deleted search term
//print("Delete")
self.storedBugElements.removeLast()
} else if numSearchTerms == self.storedBugElements.count{
// Still typing search term
//print("Still...")
self.storedBugElements.removeLast()
self.storedBugElements.append(bugElementShell)
}
// Handle stored search
if self.storedBugElements.count > 0 {
let namedElements = self.storedBugElements.joined().map { ($0.name, $0) }
// Now combine them as you describe. Add the ranks, and merge the items
let uniqueElements =
Dictionary<String, BugElement>(namedElements,
uniquingKeysWith: { (lhs, rhs) -> BugElement in
let sum = lhs.rank + rhs.rank
return BugElement(rank: sum,
name: lhs.name,
match: lhs.match.union(rhs.match))
})
// The result is the values of the dictionary
let result = uniqueElements.values
bugElements = result.sorted { $0.rank > $1.rank }
} else{
bugElements = bugElementShell.sorted { $0.rank > $1.rank }
}
completion(bugElements)
}

ManagedObjectContexts and managedObjects are not thread safe. Neither for reading or for writing. I don't need to to see what you are doing in searchAllTables to know that you are doing it wrong. There are two types of context - main queue context (that must always be accessed from the main thread) and private queue context that must only be assessed through performBlock. There is no context that is correct to run on a global background thread.
It appears to me that your problem is that you are loading all the objects into memory and testing each one instead of using a NSPredicate on a fetch request. I would recommend running the search on the main thread and fixing it so it doesn't take so long.

Related

How to find all words that "contain" or "unscramble" an input string using a Trie?

Here is a trie made to work on alphabets/scripts from any language.
class TrieNode {
constructor(key) {
// the "key" value will be the character in sequence
this.key = key;
// we keep a reference to parent
this.parent = null;
// we have hash of children
this.children = {};
// check to see if the node is at the end
this.end = false;
}
getWord() {
let output = [];
let node = this;
while (node !== null) {
output.unshift(node.key)
node = node.parent
}
return output.join('')
}
}
class Trie {
constructor() {
this.base = new TrieNode(null)
}
insert(word) {
let node = this.base
const points = Array.from(word)
for (const i in points) {
const point = points[i]
if (!node.children[point]) {
const child = node.children[point] = new TrieNode(point)
child.parent = node
}
node = node.children[point]
if (i == word.length - 1) {
node.end = true
}
}
}
contains(word) {
let node = this.base
const points = Array.from(word)
for (const i in points) {
const point = points[i]
if (node.children[point]) {
node = node.children[point]
} else {
return false
}
}
return node.end;
}
find(prefix) {
let node = this.base
let output = []
const points = Array.from(prefix)
for (const i in points) {
const point = points[i]
// make sure prefix actually has words
if (node.children[point]) {
node = node.children[point]
} else {
// there's none. just return it.
return output
}
}
const stack = [node]
while (stack.length) {
node = stack.shift()
// base case, if node is at a word, push to output
if (node.end) {
output.unshift(node.getWord())
}
// iterate through each children, call recursive findAllWords
for (var child in node.children) {
stack.push(node.children[child])
}
}
return output
}
}
After asking How to efficiently store 1 million words and query them by starts_with, contains, or ends_with? and getting some answers, I am wondering how the "contains" and "unscrambles" parts can be implemented as a trie. The prefix ("starts with") search is easily handled by the trie.
const fs = require('fs')
const Trie = require('./Trie')
const words = fs.readFileSync('tmp/scrabble.csv', 'utf-8')
.trim()
.split(/\n+/)
.map(x => x.trim())
const trie = new Trie()
words.forEach(word => trie.insert(word))
console.log(trie.find('zy'))
[
'zygodactylous', 'zygomorphies', 'zygapophysis',
'zygapophyses', 'zygomorphic', 'zymologies',
'zygospores', 'zygosities', 'zygomorphy',
'zygodactyl', 'zymurgies', 'zymograms',
'zymogenes', 'zygotenes', 'zygospore',
'zygomatic', 'zyzzyvas', 'zymosans',
'zymology', 'zymogram', 'zymogens',
'zymogene', 'zygotene', 'zygosity',
'zygomata', 'zyzzyva', 'zymurgy',
'zymotic', 'zymosis', 'zymoses',
'zymosan', 'zymogen', 'zymases',
'zygotic', 'zygotes', 'zygosis',
'zygoses', 'zygomas', 'zydecos',
'zymase', 'zygote', 'zygose',
'zygoma', 'zygoid', 'zydeco',
'zymes', 'zyme'
]
Here is the tmp/scrabble.csv I used.
The question is, can a trie be used to find all the words which "contain" some input string, or find all the words which "unscramble" the input string? I am curious how to accomplish this with a Trie. If a trie cannot do this efficiently, then knowing why not, and potentially what I should be looking at instead, will be a great answer as well.
From my initial thought-attempts at solving "contains", it seems I would have to create a trie which maps all possible combinations of substrings to the final word, but that seems like it will be an explosion of memory, so not sure how to better reason about this.
For "unscrambles", where you put in "caldku" and it finds "duck", amongst other possible words, I think possibly, similar to the linked answer to the other question, maybe sorting "duck" into "cdku", and then storing that in the trie, and then sorting the input to "acdklu", and then searching for "contains" using the previous algorithm, but hmm, no that will break in a few cases. So maybe a trie is not the right approach for these two problems? If it is, roughly how would you do it (you don't need to provide a full implementation, unless you'd like and it's straightforward enough).
Here is an answer to unscrambles that ALSO handles pagination. It actually returns [words, startOfNextPage]. So to find the next page you call it starting at startOfNextPage.
The idea is to normalize words by sorting the letters, and then storing the word in the trie once. When searching we can dig into the tree both for the next letter we want, and any letter before it (because the letter we want may come after). So we also store a lookup to know what letters might be found, so that we can break out early without having to look at a lot of bad options. And, of course, we include counts so that we can calculate how much of the trie we have explored and/or skipped over.
class Trie {
constructor(depth=0) {
this.depth = depth;
this.children = {};
this.words = [];
this.count = 0;
this.isSorted = false;
this.hasChildWith = {};
}
insert (word) {
if (word != undefined) {
this._insert(Array.from(word).sort(), word);
}
}
_insert (key, word) {
this.count++;
this.isSorted = false;
if (key.length == this.depth) {
this.words.push(word);
}
else {
for (let i = this.depth+1; i < key.length; i++) {
this.hasChildWith[key[i]] = true;
}
if (! this.children[key[this.depth]] ) {
this.children[key[this.depth]] = new Trie(this.depth+1);
}
this.children[key[this.depth]]._insert(key, word);
}
}
sort () {
if (! this.isSorted) {
this.words.sort();
let letters = Object.keys(this.children);
letters.sort();
// Keys come out of a hash in insertion order.
let orderedChildren = {};
for (let i = 0; i < letters.length; i++) {
orderedChildren[letters[i]] = this.children[letters[i]];
}
this.children = orderedChildren;
}
}
find (letters, startAt=0, maxCount=null) {
return this._find(Array.from(letters).sort(), 0, startAt, maxCount || this.count, 0);
}
_find (key, keyPos, startAt, maxCount, curPos) {
if (curPos + this.count < startAt) {
return [[], curPos + this.count];
}
this.sort(); // Make sure we are sorted.
let answer = [];
if (keyPos < key.length) {
// We have not yet found all the letters.
// tmpPos will track how much we looked at in detail
let tmpPos = curPos;
tmpPos += this.words.length;
for (const [k, v] of Object.entries(this.children)) {
if (k < key[keyPos]) {
// The next letter we want can be deeper in the trie?
if (v.hasChildWith[key[keyPos]]) {
// It is! Let's find it.
let result = v._find(key, keyPos, startAt, maxCount - answer.length, tmpPos);
answer = answer.concat(result[0]);
if (maxCount <= answer.length) {
// We finished our answer, return it and what we found.
return [answer, result[1]];
}
}
tmpPos += v.count; // Didn't find it, but track that we've seen this.
}
else if (k == key[keyPos]) {
// We found our next letter! Search deeper.
let result = v._find(key, keyPos+1, startAt, maxCount - answer.length, tmpPos);
answer = answer.concat(result[0]);
if (maxCount <= answer.length) {
// We finished the search.
return [answer, result[1]];
}
else {
// No other letter can match.
break;
}
}
else {
// Neither this or any later letter can match.
break;
}
}
// Return our partial answer and mark that we went
// through this whole node.
return [answer, curPos + this.count];
}
else {
// We have our letters and are recursively finding words.
if (startAt <= curPos + this.words.length) {
answer = this.words.slice(startAt - curPos);
if (maxCount <= answer.length) {
// No need to search deeper, we're done.
answer = answer.slice(0, maxCount);
return [answer, curPos + answer.length];
}
curPos += answer.length;
}
for (const child of Object.values(this.children)) {
let result = child._find(key, keyPos, startAt, maxCount - answer.length, curPos);
answer = answer.concat(result[0])
if (maxCount <= answer.length) {
return [answer, result[1]];
}
else {
curPos += child.count;
}
}
return [answer, curPos];
}
}
}
exports.Trie = Trie
And here is some code to test it with.
const fs = require('fs')
const AnagramTrie = require('./AnagramTrie')
const words = fs.readFileSync('tmp/scrabble.csv', 'utf-8')
.trim()
.split(/\n+/)
.map(x => x.trim())
const trie = new AnagramTrie.Trie()
words.forEach(word => trie.insert(word))
console.log(trie.find('b', 0, 12))
console.log(trie.find('b', 0, 6))
console.log(trie.find('b', 11, 6))
console.log(trie.find('zx', 0, 12))
console.log(trie.find('zx', 0, 6))
console.log(trie.find('zx', 60875, 6))
It will return words in the order of their sorted anagram, followed by the word itself. So if you search for admired you'll find unadmired first because the n in un comes before the r in admired. And you'll find that disembarrassed comes before either because it has 2 a's in it.
And here is an answer to contains. Note how, despite using a Trie both times, it needs a lot of customization to the problem we are solving.
Sample code to use it is like the other one. You can see the deduplication at work if you search for a. The answer aa (correctly) only comes up once. Also it is a little slower to start up because you have to put words into the data structure multiple times.
class Trie {
constructor(depth=0, key=[]) {
this.depth = depth;
this.children = {};
this.words = [];
this.count = 0;
this.isSorted = false;
this.path = key.slice(0, depth).join("");
this.char = key[this.depth-1];
}
insert (word) {
if (word != undefined) {
const key = Array.from(word);
for (let i = 0; i < key.length; i++) {
this._insert(key.slice(i), word);
}
}
}
_insert (key, word) {
this.count++;
if (this.depth == key.length) {
this.words.push(word);
}
else {
if (! this.children[key[this.depth]] ) {
this.children[key[this.depth]] = new Trie(this.depth+1, key);
}
this.children[key[this.depth]]._insert(key, word);
}
}
sort () {
if (! this.isSorted) {
this.words.sort();
let letters = Object.keys(this.children);
letters.sort();
// Keys come out of a hash in insertion order.
let orderedChildren = {};
for (let i = 0; i < letters.length; i++) {
orderedChildren[letters[i]] = this.children[letters[i]];
}
this.children = orderedChildren;
this.isSorted
}
}
find (letters, startAt=0, maxCount=null) {
// Defaults, special cases, etc.
if (this.count <= startAt) {
return [[], startAt];
}
if (maxCount == null) {
maxCount = this.count;
}
if (letters == "") {
// Special case.
this.sort();
answer = this.words.slice(startAt, startAt + maxCount);
return [answer, startAt + answer.length];
}
// We will do the recursive search.
const key = Array.from(letters);
// The challenge is that each word is stored multiple times.
// We want to only find them once. So we will stop searching as soon
// as we match our search string twice.
//
// This requires keeping track of where we are in trying to match our
// search string again. And if we fail, backtracking in our attempt
// to match.
//
// Here is the complication. Partial matches can overlap.
//
// Consider the following search string:
//
// search: a b a a b a c a b
// 0 1 2 3 4 5 6 7 8
//
// Now suppose that I'm looking for "c" at 6 next and it isn't an
// "c". Well I know I matched "aba" and should next check if I got
// character 3, "a". We can encode this logic in an array.
//
// a b a a b a c a b
// backtrackTo: [-1, 0,-1, 1, 0,-1, 3,-1, 0]
//
// So if I've matched through 5, I check "c", then "a", then "b"
// before deciding that I'm not still partway through a match.
//
// Let's calculate that backtrackTo. We will also calculate a
// variable, matchAtAtEnd. Which is how long our longest
// running match is at a full match.
// We start by assuming that we go back to the beginning.
let backtrackTo = [-1];
let rematches = [];
let matchAtEnd = 0;
for (let i = 1; i < key.length; i++) {
if (key[i] == key[0]) {
backtrackTo[i] = -1;
rematches.push(i);
}
else {
backtrackTo[i] = 0;
}
}
// In our example we have:
//
// backtrackTo = [-1, 0, -1, -1, 0, -1, 0, -1, 0]
// rematches = [2, 3, 5, 7]
//
// Now let `k` be the length of the current rematch.
let k = 1;
while (0 < rematches.length) {
let nextRematches = [];
for (let i = 0; i < rematches.length; i++) {
if (i + k == key.length) {
matchAtEnd = k-1;
}
else {
if (key[k] == key[i+k]) {
nextRematches.push(i);
}
else {
backtrackTo[i+k] = k;
}
}
}
rematches = nextRematches;
k++;
// In our example we get:
//
// k = 1
// backtrackTo = [-1, 0, -1, -1, 0, -1, 0, -1, 0]
// rematches = [2, 3, 5, 7]
// matchAtEnd = -1
//
// k = 2
// backtrackTo = [-1, 0, -1, 1, 0, -1, 0, -1, 0]
// rematches = [3, 7]
// matchAtEnd = -1
//
// k = 3
// backtrackTo = [-1, 0, -1, 1, 0, -1, 3, -1, 0]
// rematches = []
// matchAtEnd = 2
//
// and we see that we got the expected backtrackTo AND
// we have recorded the fact that at matching
// abaabacab we are currently also matching the ab at the
// start.
//
// Now let's find the first match.
let node = this;
for (let i = 0; i < key.length; i++) {
node = node.children[key[i]];
if (!node) {
return [[], startAt];
}
}
return node._find(key, startAt, maxCount, 0, backtrackTo, matchAtEnd)
}
_find (key, startAt, maxCount, curPos, backtrackTo, nextMatch) {
// console.log([key, startAt, maxCount, curPos, backtrackTo, nextMatch, [this.path, this.count]]);
// Skip me?
if ((curPos + this.count <= startAt) || (key.length <= nextMatch)) {
return [[], curPos + this.count];
}
this.sort();
let answer = [];
if (curPos < startAt) {
if (startAt < curPos + this.words.length) {
answer = this.words.slice(startAt-curPos, startAt-curPos+maxCount);
}
else {
curPos += this.words.length; // Count the words we are skipping.
}
}
else {
answer = this.words.slice(0, maxCount);
}
curPos += answer.length;
if (maxCount <= answer.length) {
return [answer, curPos];
}
for (const [k, v] of Object.entries(this.children)) {
let thisMatch = nextMatch;
while ((-1 < thisMatch) && (key[thisMatch] != k)) {
thisMatch = backtrackTo[thisMatch];
}
thisMatch++;
let partialAnswer = null;
[partialAnswer, curPos] = v._find(key, startAt, maxCount - answer.length, curPos, backtrackTo, thisMatch);
answer = answer.concat(partialAnswer);
if (maxCount <= answer.length) {
break; // We are done.
}
}
return [answer, curPos];
}
}
exports.Trie = Trie

I would like my bot to delete the message that contains a keyword or that contains similar characters

in my bot I have implemented a keyword filter that the bot reviews in each message that is written in the chat, until now it works, but I want to improve it, for reasons of respect I will not put words here, so I will put some others example,
The bot detects if you write for example "vulgar", "badword", "hello"
But what I want to achieve is to detect if they write "hellooo", "vuulgarr", vulg4rr"
This is my base where I have the words stored:
badwords.js
var words = ["vulgar", "vulg4r", "hello", "badword4", "badword5"]
module.exports = words;
This is my function that checks if a bad word comes on the way, split any words and then deletes the message if it finds a result, with indexOf()
index.js
const _ = require('lodash');
const badwords = require('./badwords');
/**
* Functions
*/
// compares every word to badWords array from badWords.js
function checkWord(word) {
return badwords.indexOf(word) > -1;
}
/**
* Main Module
*/
module.exports = function (self, nick, channel, message) {
'use strict';
message = message.toLowerCase();
message = message.split(' ');
nick = nick;
channel = channel.toLowerCase();
for (var i = 0, len = message.length; i < len; i++) {
if (checkWord(message[i])) {
self.send('.ban', channel, nick);
}
}
}
Any idea to improve it?, thank's
A more complicated method
We can have two pointers on both strings to compare, but skipping offsets upon duplicates:
function checkString(message, keyword) {
while(message.length > 0) {
if(checkPrefix(message, keyword)) return true
message = message.substr(1)
}
}
function checkPrefix(message, keyword) { // keyword is one of the keywords
let om = 0, ok = 0
while (true) {
if (ok >= keyword.length)
return true // we have finished reading keyword, and everything matched
if(om >= message.length)
return false // message is shorter than keyword
while (om + 1 < message.length && message.charAt(om) === message.charAt(om + 1))
om++ // skip consecutive repetitions in message
while (ok + 1 < keyword.length && keyword.charAt(ok) === keyword.charAt(ok + 1))
ok++ // skip consecutive repetitions in keyword
if (message.charAt(om) !== message.charAt(ok)) return false // encountered an inconsistent character
}
}
A simpler method
Just scan the repetitions in a string and delete them first.
function removeDuplicates(string) {
for (let i = 0; i < string.length - 1; ) {
if (string.charAt(i) === string.charAt(i + 1)) {
string = string.substr(0, i) + string.substr(i + 1) // skip string[i]
} else {
i++ // not duplicate, proceed to next pair
}
}
}
Then you can compare directly:
removeDuplicates(message).indexOf(removeDuplicates(keyword)) !== -1
You can apply it like this:
for (const part in message.split(" ")) {
for (word in words) {
if (removeDuplicates(part).indexOf(removeDuplicates(word)) !== -1)
self.send(".ban", ...)
break
}
}

Longest Substring without repeating characters issue with edge case

I was trying to solve this problem: Longest substring without repeating characters. The issue is, it's failing in couple test cases, I don't know how to fix it. I would need your help to see where I'm going wrong.
Question:
Given a string, find the length of the longest substring without
repeating characters.
Examples:
Given "abcabcbb", the answer is "abc", which the length is 3.
Given "bbbbb", the answer is "b", with the length of 1.
Given "pwwkew", the answer is "wke", with the length of 3. Note that
the answer must be a substring, "pwke" is a subsequence and not a
substring.
This is my code:
function longestSubString(arr){
let localSum=0,globalSum=0;
let set = new Set();
for(let i=0; i<arr.length; i++){
let current = arr[i];
//if the key is present in the store.
if(set.has(current)){
set.clear();
localSum = 1;
set.add(current);
} else {
localSum +=1;
set.add(current);
}
if(globalSum < localSum){
globalSum = localSum;
}
}
return globalSum;
}
Tests:
let test = "abcabc"; //returns 3 - correct
let test2 = "bbb"; //returns 1 - correct
let test5 = "dvdf"; //returns 2 - INCORRECT! it should return 3 (i.e for vdf) since I'm doing set.clear() I'm not able to store previous elements.
longestSubString(test5); //incorrect
Live:
https://repl.it/Jo5Z/10
Not fully tested!
function longestSubString(arr){
let localSum=0,globalSum=0;
let set = new Set();
for(let i=0; i<arr.length; i++){
let current = arr[i];
//if the key is present in the store.
if(set.has(current)){
let a = Array.from(set);
a.splice(0,a.indexOf(current)+1);
set = new Set(a);
set.add(current);
localSum = set.size;
} else {
localSum +=1;
set.add(current);
}
if(globalSum < localSum){
globalSum = localSum;
}
}
return globalSum;
}
The idea is that when you get duplicate, you should start from the charachter after the first duplicated character, in your case dvdf, when you reach the second d you should continue from vd not from d!
You have to consider that the substring might start from any character in the string. Erasing the set only when you're finding a duplicate makes you only consider a substring starting from characters that are equal to to the first character.
An O(logn*n^2) solution modifying yours just a bit:
function longestSubString(arr){
let globalSum=0;
for(let i=0; i<arr.length; i++){
let set = new Set();
let localSum=0;
for(let j=i; j<arr.lenght; j++){
let current = arr[j];
//if the key is present in the store.
if(set.has(current)){
break;
} else {
localSum +=1;
set.add(current);
}
}
if(globalSum < localSum){
globalSum = localSum;
}
}
return globalSum;
}
There's also a O(n + d) (almost linear) solution, d being the number of characters in the alphabet. See http://www.geeksforgeeks.org/length-of-the-longest-substring-without-repeating-characters/.
There seem to be a lot of long answers here. The implementation I've thought of is simplified due to two observations:
Whenever you encounter a duplicate character, you need to start the next substring just after the previous occurrence of the current character.
Set() creates an array in insertion order when iterated.
function longestSubstring(str) {
let maxLength = 0
let current = new Set()
for (const character of str) {
if (current.has(character)) {
const substr = Array.from(current)
maxLength = Math.max(maxLength, substr.length)
current = new Set(substr.slice(substr.indexOf(character) + 1))
}
current.add(character)
}
return Math.max(maxLength, current.size)
}
const tests = [
"abcabc",
"bbb",
"pwwkew",
"geeksforgeeks",
"dvdf"
]
tests.map(longestSubstring).forEach(result => console.log(result))
A simple edit allows us to keep the first occurrence of the largest substring instead of the maximum length.
function longestSubstring(str) {
let maxSubstr = []
let current = new Set()
for (const character of str) {
if (current.has(character)) {
const substr = Array.from(current)
maxSubstr = maxSubstr.length < substr.length ? substr: maxSubstr
current = new Set(substr.slice(substr.indexOf(character) + 1))
}
current.add(character)
}
const substr = maxSubstr.length < current.size ? Array.from(current) : maxSubstr
return substr.join('')
}
const tests = [
"abcabc",
"bbb",
"pwwkew",
"geeksforgeeks",
"dvdf"
]
tests.map(longestSubstring).forEach(result => console.log(result))
As we can see, the last test yields vdf, as expected.
Below solution gets the length in O(n+d) time and also prints the longest non repeating character substring as well:
public void longestNonRepeatingLength(String a){
a="dvdf";
int visitedIndex[] = new int[256];
int curr_len = 0, max_len = 0, prev_ind = 0, start = 0, end = 1;
for(int i =0;i<256;i++)
visitedIndex[i] = -1;
visitedIndex[a.charAt(0)] = 0;
curr_len++;
int i = 0;
for( i=1;i<a.length();i++){
prev_ind = visitedIndex[a.charAt(i)];
if(prev_ind == -1 || i > prev_ind + curr_len)
curr_len++;
else{
if(curr_len>max_len){
start = prev_ind + 1;
end = i;
max_len = curr_len;
}
curr_len = i - prev_ind;
}
visitedIndex[a.charAt(i)] = i;
}
if(curr_len>max_len){
end = i-1;
max_len = curr_len;
}
for( i = start;i<=end;i++)
System.out.print(a.charAt(i));
System.out.println("");
System.out.println("Length = "+max_len);
}
As set contains the largest set of non-repeating characters of a String ending on index i, this means that when you encounter a previously seen character, rather than starting over with an empty set as your codes does now, you should just remove all characters from your set until that duplicate one.
Say for example your input is "abXcXdef". When the second "X" is encountered, you'll want to drop "a" and "b" from your set, leaving a set of ("c","X") as the longest set up to that point. Adding all other characters (as none are duplicates) you then end up with a max length of 5.
Something like this should work:
function longestSubString(arr) {
let globalSum = 0;
let set = new Set();
for (let i=0; i<arr.length; i++) {
let current = arr[i];
if (set.has(current)) {
while (true) {
let removeChar = arr[i - set.count];
if (removeChar != current)
set.remove(removeChar);
else
break;
}
} else {
set.add(current);
if (set.count > globalSum)
globalSum = set.count;
}
}
return globalSum;
}
As every character is added at most once and deleted at most once, this is an O(N) algorithm.

Moving round content dynamically

I have an xpage, where fields are created dynamically. By default, there are 3, but you can click a button to add as many as you like, naming convention "ObjectiveDetails1", "ObjectiveDetails2" and so on.
I am trying to handle blank fields... For example, there is content in fields 1 and 2, nothing in 3 and 4, but content in 5. What I want to do, is shift content from field 5 into the first available blank, in this case 3. Likewise, if there is content in fields 1, 2, 4, 5, I then need content in 4, to go into field 3, and content in 5 to go into field 4 etc. At the moment, I'm managing to shift content up 1 only.
So if fields 2, 3, 4 are blank but 5 has content, it only moves the content into 4, where-as I need it to move into field 2.
I hope I've explained this well enough..... Current code is a little messy....
for (var i = 1; i < viewScope.rows+1; i++) {
print("Starting Array.....");
if(applicationScope.get("BreakAllCode")==true){
break;
}
var objFieldName:string = "ObjectiveDetails" +i;
print ("Field Name: " + objFieldName);
var fieldValue = document1.getItemValueString(objFieldName);
print ("Field Value: " + fieldValue);
if (fieldValue =="" || fieldValue==null){
print("EMPTY");
// We now need to delete the 3 fields related to this objective
var updFieldName:string = "ObjectiveUpdates" +i;
var SAFieldName:string = "ObjectiveSelfAssessment" +i;
// Before we delete the fields, we need to check if the next field is blank
// and if not, copy its contents into this field.
for (var n =i+1; n < viewScope.rows+1; n++) {
if(applicationScope.get("BreakAllCode")==true){
break;
}
if(document1.hasItem("ObjectiveDetails" +n)){
print("Next field: " +"ObjectiveDetails" +n);
var nextFieldValue = document1.getItemValueString("ObjectiveDetails" +n);
if (!nextFieldValue =="" || !nextFieldValue==null){
// Now copy the content into the field
var nextFieldName:string = "ObjectiveDetails" +n;
var previousNo = n-1;
var previousFieldName:string = "ObjectiveDetails" +previousNo;
print ("Previous field: " + previousFieldName);
document1.replaceItemValue(previousFieldName,nextFieldValue);
// Now clear the content from next field
document1.replaceItemValue(nextFieldName,"");
}else{
// Do nothing
}
}else{
print("Last field");
}
}
// Remove items from the document
//document1.removeItem(objFieldName);
//document1.removeItem(updFieldName);
//document1.removeItem(SAFieldName);
// Remove fields from our arrays
//viewScope.fields.splice(i-1, 1);
//viewScope.fields2.splice(i-1, 1);
//viewScope.fields3.splice(i-1, 1);
// Update the row variable
//viewScope.rows--;
//document1.replaceItemValue("rows",viewScope.rows);
//document1.save();
// We now need to "re-index" the array so that the fields are numbered corectly
}else{
print("We have a value");
}
}
Update with beforePageLoad:
viewScope.rows = document1.getItemValueInteger("rows");
var rowCount:integer = viewScope.rows;
var newCount:integer = 0;
while(newCount<rowCount){
if (!viewScope.fields) {
viewScope.fields = [];
viewScope.fields2 = [];
viewScope.fields3 = [];
}
viewScope.fields.push("ObjectiveDetails" + (viewScope.fields.length + 1));
viewScope.fields2.push("ObjectiveUpdates" + (viewScope.fields2.length + 1));
viewScope.fields3.push("ObjectiveSelfAssessment" + (viewScope.fields3.length + 1));
newCount++;
}
This should be your core algorithm:
var writeIndex = 0;
for (var readIndex = 1; readIndex <= viewScope.rows; readIndex++) {
var value = document1.getItemValueString("ObjectiveDetails" + readIndex);
if (value) {
writeIndex++;
if (readIndex !== writeIndex) {
document1.removeItem("ObjectiveDetails" + readIndex);
document1.replaceItemValue("ObjectiveDetails" + writeIndex, value);
}
} else {
document1.removeItem("ObjectiveDetails" + readIndex);
}
}
viewScope.rows = writeIndex;
The code
deletes all empty fields,
renames fields to ObjectiveDetails1, ObjectiveDetails2, ObjectiveDetails3,... and
writes the resulting number of rows back to viewScope variable "rows".
If I understood your problem correctly, then this code should work (not tested):
// define variables
var i,j,nRows,values,value,allowContinue,fieldname;
// collect values from document fields
nRows=viewScope.rows;
values=[];
for (i=0;i<nRows;i++) {
values.push(document1.getItemValueString("ObjectiveDetails"+(i+1).toFixed(0)));
}
// fill empty values
for (i=0;i<nRows-1;i++) {
if (values[i]) continue;
allowContinue=false;
for (j=i+1;j<nRows;j++) {
if (value=values[j]) {
values[i]=value;
values[j]="";
allowContinue=true;
break;
}
}
if (!allowContinue) break;
}
// write back to document and remove empty fields on the end
for (i=0;i<nRows;i++) {
fieldname="ObjectiveDetails"+(i+1).toFixed(0);
if (value=values[i]) document1.replaceItemValue(fieldname,value);
else document1.removeItem(fieldname);
}

How can I get the max value on scores from Core Data?

Is it possible to return all values for a certain person and get their max score
let barScore: String = "9.246"
let numbers = [1, 6, 3, 9.245, 4, 6]
//numbers.minElement()
let value = numbers.maxElement()
if (Float(value!) < Float(barScore)) {
print("True")
} else {
print("False")
}
That works fine, but can't seem to get equivalent from core data.
let request = NSFetchRequest(entityName: "MeetResult")
request.predicate = NSPredicate(format: "gymnast.fullName = %#", "\(gymnastNameText.text!)")
do {
let results = try AD.managedObjectContext.executeFetchRequest(request) as! [MeetResult]
let value = results.maxElement()
for result in results {
print("Gymnast Name: \(gymnastNameText.text!)")
print("Bar Score: \(result.barsScore!)")
}
} catch {
fatalError("Falied to get results")
}

Resources