How to match exact numerical pattern using FlexSearch - javascript

Is there a way to have FlexSearch (https://github.com/nextapps-de/flexsearch) find only results that contains exact character sequence including numerical characters ?
The documentation is there : https://flexsearch.net/docs/flexsearch-manual.pdf
There is a section page 35 called Analyzer that seems to give hints on how to do, but there is a TODO note at the end when is expected the list of Analyzer that we could try alternatively.
The following thread works fine only for plain characters : https://stackoverflow.com/a/69677055/2143734
Or if you know any equivalent efficient and browser compatible search API, it is just that I seem to miss something there !
If you input C3, 1, C0 or 4, you get results although none of these numbers appears...
var data = Array.from(Array(1000000).keys()).map(String);
data.unshift("CA", "VIS-CD", "CATDIR-U", "UE5", "GAE");
(function() {
const index = new FlexSearch.Index({
tokenize: "full",
matcher: "default",
cache: true
});
for (var i = 0; i < data.length; i++) {
index.add(i, data[i]);
}
var suggestions = document.getElementById("suggestions");
var userinput = document.getElementById("userinput");
userinput.addEventListener("input", show_results, true);
function show_results() {
var value = this.value;
var results = index.search(value);
var entry, childs = suggestions.childNodes;
var i = 0,
len = results.length;
for (; i < len; i++) {
entry = childs[i];
if (!entry) {
entry = document.createElement("div");
suggestions.appendChild(entry);
}
entry.textContent = data[results[i]];
}
while (childs.length > len) {
suggestions.removeChild(childs[i])
}
}
}());
<!doctype html>
<html>
<head>
<title>FlexSearch Sample</title>
<script src="https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch#master/dist/flexsearch.compact.js"></script>
</head>
<body>
<input type="text" id="userinput" placeholder="Search by keyword...">
<br></br>
<div id="suggestions"></div>
</body>
</html>

find only results that contains exact character sequence
ok, so instead of Array.prototype.includes logic, I used a cache system for the kind of search you want :D
var data = Array.from(Array(1000000).keys()).map(String);
data.unshift("CA", "VIS-CD", "CATDIR-U", "UE5", "GAE");
//in essence, you can just change the condition(match_condition) to append a result based on whatever other condition you want
//the edit has me changing the match condition :D
//the edit also has me attempting a cache block(for speed)
(function() {
var suggestions = document.getElementById("suggestions");
var userinput = document.getElementById("userinput");
var cacheText={}, resultLimit=100, length=Symbol(null) //I'm doing caching based on how you want results(as in your snippet this part will lag a bit)
for(let i=0;i<data.length;i++){
if(typeof data[i]!="string"){continue}
for(let j=0;j<data[i].length;j++){
let string="" //for caching all direct text indexes
for(let k=0;k<=j;k++){string+=data[i][k]}
if(!cacheText[string]){
cacheText[string]={[data[i]]:true}
cacheText[string][length]=1 //length measurement
}
else{
if(cacheText[string][length]==resultLimit){continue}
cacheText[string][data[i]]=true
cacheText[string][length]++ //adding to length
}
}
}
userinput.addEventListener("input", show_results, true);
function show_results() {
var {value}=this, values={}
let match_condition=(text)=> cacheText[value]?cacheText[value][text]:false && value!="" //match condition(that you can change to whatever other logic)
for(let i=0; i<suggestions.children.length; i++){
//the purpose of this loop is to only remove elements that won't be on the new result list
let matchCondition=()=>
!suggestions.children[i]? true: //test(if child still exists)
match_condition(suggestions.children[i].innerText) //condition(in this case if data exactly includes user input)
while(!matchCondition()){ suggestions.children[i].remove() } //remove only those which won't match up to the condition
if(!suggestions.children[i]){break} //end loop(since if this is true, there is no child left)
values[suggestions.children[i].innerText]=true //to indicate that this value already exists when doing the second loop below
}
for(let i=0; i<data.length; i++){
//the purpose of this loop is to append UNIQUE results
if(match_condition(data[i]) && !values[data[i]]){
var child=document.createElement('div')
child.innerText=data[i]
suggestions.appendChild(child)
}
}
}
}());
<body>
<input type="text" id="userinput" placeholder="Search by keyword..." />
<br></br>
<div id="suggestions"></div>
</body>

Related

JavaScript Search and Loop - doesn't return correct values

Please, can you check my code where is the error? It should loop trough 1 array to choose each string and then loop through second array and check, if the value from second string contains value of first string.
for (var i = 0; i < oldLines.length; i++){
var subStringEach = oldLines[i];
var subStringEachNoDash = subStringEach.replace(/[^a-z0-9]/g,'');
// read New URLs and line by line save them as an object
var newLines = $('#newUrl').val().split(/\n/);
var newUrlResult = [];
for (var j = 0; j < newLines.length; j++){
var newUrlString = newLines[j];
var newUrlStringNoDash = newUrlString.replace(/[^a-z0-9]/g,'');
var isThere = newUrlStringNoDash.search(subStringEachNoDash);
if (isThere !== -1 ) {
newUrlResult[i] = newLines[j];
}
else {
newUrlResult[i] = "";
}
}
stockData.push({OldURL:oldLines[i],SearchSubstring:subStringEach,NewURL:newUrlResult[i]});
}
Now it finds only part of the results.. I place to first array:
anica-apartment
casa-calamari-real
ostrovni-apartman
and to the second array:
http://tempweb3.datastack.cz/be-property/anica-apartment/
http://tempweb3.datastack.cz/be-property/ostrovni-apartman/
http://tempweb3.datastack.cz/be-property/st-michael-apartment/
http://tempweb3.datastack.cz/be-property/casa-calamari-real/
and it will only find and return casa-calamari-real, http://tempweb3.datastack.cz/be-property/casa-calamari-real/ and the others returns empty..
Any ideas please?
Here is the full code on Codepen: https://codepen.io/vlastapolach/pen/VWRRXX
Once you find a match you should exit the inner loop, otherwise the next iteration of that loop will clear again what you had matched.
Secondly, you should use push instead of accessing an index, as you don't know how many results you will have. And as a consequence you will need to relate the find string with it (because i will not be necessary the same in both arrays)
So replace:
if (isThere !== -1 ) {
newUrlResult[i] = newLines[j];
}
else {
newUrlResult[i] = "";
}
with this:
if (isThere !== -1 ) {
newUrlResult.push({
searchSubstring: subStringEach,
newURL: newUrlString
});
break; // exit loop
}
At the end, just output newUrlResult.
NB: If you want to leave the possibility that a search string matches with more than one URL, then you don't need the break. The push will then still prevent you from overwriting a previous result.
I see that you solved already) But maybe you will like this code too)
newUrlResult variable could be a string I guess, because loop breaks when one value is found. If no values where found there will be just empty string. And I'm not sure you need to call newLines = $('#newUrl').val().split(/\n/) on every iteration.
var stockData = [];
oldLines.map(function(oldLine){
var cleanOldLine = oldLine.replace(/[^a-z0-9]/g,''),
newLines = $('#newUrl').val().split(/\n/),
newUrlResult = '';
for (var j = 0; j < newLines.length; j++){
var newLine = newLines[j],
cleanNewLine = newLine.replace(/[^a-z0-9]/g,''),
ifExists = cleanNewLine.search(cleanOldLine);
if (ifExists !== -1) {
newUrlResult = newLine;
break;
}
}
stockData.push({OldURL:oldLine, SearchSubstring:cleanOldLine, NewURL:newUrlResult});
});

Overwrite results of one function with another

I am trying to create a simple encoder-decoder game which encodes and decodes user-entered word as shown below:
eg. overflow --> ofvleorw --> overflow (userword --> encodedword
--> decodedword).
The issue I'm having is that the encoder part works fine but when clicking decode button, the developer tools crashes after few seconds. I am unsure what the real issue is as I see no error messages on console so my only guess is the title heading.
Thank You and Happy New Year 2017
function encoder(){
var userWord = document.getElementById("userinputbox").value; // gets user entered word
var letterArray = userWord.split("");
var encodedWord = [];
for (var i=0; i<letterArray.length/2; i++) {
encodedWord.push(letterArray[i],letterArray[letterArray.length/2 + i]);
} // pushing elements sequentially from first and second half of original array to new empty array
displayResult.value = encodedWord.join("");
}
function decoder() {
var encodedWord = displayResult.value; // extracts the encoded text from display box
var letterArray = encodedWord.split("");
var half1 = [], half2 = []; // array for storing even and odd index elements from letterArray
for (var i=0; i<letterArray.length; i+2) {
half1.push(letterArray[i]);
half2.push(letterArray[i+1]);
}
var decodedWord = half1.concat(half2);
displayResult.value = decodedWord.join(""); // overwriting the displaybox with new string
}
var displayResult = document.getElementById('displaybox'); // box to show results
var encodeBtn = document.getElementById('encode'); // event-listener 'Encode' btn
encodeBtn.addEventListener("click", encoder);
var decodeBtn = document.getElementById('decode'); // event-listener 'Decode' btn
decodeBtn.addEventListener("click", decoder);
<body>
Enter word (even number of letters) :<input type="text" id="userinputbox"><br>
<input type="button" value="Encode" id="encode">
<input type="button" value="Decode" id="decode">
Results :<input type="text" id="displaybox" value="">
</body>
Your for loop is infinite: for (var i=0; i<letterArray.length; i+2)
You probably wanted something like i = i + 2 in the end.
Whenever JS dies and you don't see an error, it's likely an infinite loop happening somewhere.
Happy new year !

How to process a text in order to create a new array with some sub strings?

I am writing a script on html, the idea is that I want to process a text that looks like this:
"TW|223SDSDr33|Archive" "Yes"
"TW|ASFFSDFSFASDFS|Name" "LOCALggr"
"TW|AFFSFSFSDFSFASDFS|AFFAckAssocCd" ""
"TW|12AFFFSDFASFSFASDFS|AFFAckCommID" "fsdf"
"TW|FSFASFFSDFSFASDFS|AFFAckLevel" "fsdf Supported"
"TW|AFFSDFAASFSA|AFFAckRqst" "No Requedfst"
"TW|AFFSDFSFASDFS|AFFAckTestInd" "Test"
"TW|sfasfsSFSAFAS|AFFAckVersion" "fsdfs"
I want to process the text to create an array called words, that contains substrings of the previous text using the pipe as separator as follows:
words=["TW,223SDSDr33,Archive" "Yes",...,"TW,sfasfsSFSAFAS,AFFAckVersion" "fsdfs"]
In order to achieve this, I tried:
var stringArray = document.getElementById("texto").value.split('\n');
document.write(stringArray.toString());
var arrayLength = stringArray.length;
for (var i = 0; i < arrayLength; i++) {
//Process every line;
}
This save my textarea to then process it, but the problem is that I don't know how to process every line in order to extract the sub strings that I want, In order to be more clear this is the complete code, I would like to appreciate any suggestion to achieve this, thanks thanks anyhow:
<!DOCTYPE html>
<html>
<body>
<p id="demo"></p>
<textarea cols=150 rows=10 id="texto">
"TW|223SDSDr33|Archive" "Yes"
"TW|ASFFSDFSFASDFS|Name" "LOCALggr"
"TW|AFFSFSFSDFSFASDFS|AFFAckAssocCd" ""
"TW|12AFFFSDFASFSFASDFS|AFFAckCommID" "fsdf"
"TW|FSFASFFSDFSFASDFS|AFFAckLevel" "fsdf Supported"
"TW|AFFSDFAASFSA|AFFAckRqst" "No Requedfst"
"TW|AFFSDFSFASDFS|AFFAckTestInd" "Test"
"TW|sfasfsSFSAFAS|AFFAckVersion" "fsdfs"
</textarea>
<script>
var words = [];
var stringArray = document.getElementById("texto").value.split('\n');
document.write(stringArray.toString());
var arrayLength = stringArray.length;
for (var i = 0; i < arrayLength; i++) {
//Do something;
}
</script>
</body>
</html>
I used both a regular expression and Array.prototype.split. You can uncomment depending what you want to put into words.
for (var i = 0; i < arrayLength; i++) {
var line = stringArray[i];
var quotes = /"(.*?)" "(.*?)"/.exec(line);
if (quotes) {
var first = quotes[1];
var last = quotes[2];
var separated = first.split("|");
// If you want to put the array of words
words.push(separated);
// In case you want them joined with a colon
// words.push(separated.join(","));
// If you want to add the second word that was in double quotes
// words.push(last);
}
}
// Uncomment to see results
// console.log(words);

Please assist me with this simple script

I am new to JavaScript and would like to ask for some help with my simple script.
What I am trying to do is to retrieve and display the values of all list item elements in the unordered list with the help of the (for) loop. I was able to get the script display all list items in the alert window one by one. But the problem is that I need values of all list elements displayed in a table row way. Like this:
Monday
Tuesday
Wednesday
.......
Here is what I have in my script:
<script language="JavaScript">
<!--
function process() {
a = document.getElementsByTagName('li')
for (i = 0; i < a.length; i++) {
alert(a[i].childNodes[0].nodeValue);
}
}
//-->
</script>
And here is HTML code:
<body>
<ul>
<li>Monday</li>
<li>Tuesday</li>
<li>Wednesday</li>
</ul>
<input type="button" value="Submit" onclick="process()" />
</body>
If that's possible at all would anyone please also explain where I am wrong in my script? Why all 3 list item values can't be shown in the alert window at once?
Thanks a lot!
First, create a string variable: var all_at_once = "". Then, add the contents of the nodeValue. Finally, alert this variable:
function process(){
var a = document.getElementsByTagName('li')
var all_at_once = "";
for(i=0;i<a.length;i++){
all_at_once += a[i].childNodes[0].nodeValue + " ";
}
alert(all_at_once);
}
The alert shows repeatedly because that is what a for loop does... it loops! The loop will iterate over the array of elements returned by getElementsByTagName, executing the loop body once for each element in that array.
If you wanted to display one alert, an option would be to build up a string containing the appropriate text, and alert it afterwards:
var yourString = "";
for(i=0;i<a.length;i++){
yourString += a[i].childNodes[0].nodeValue;
}
alert(yourString);
Some other notes on your code... you should almost always declare variables with the var keyword to prevent them leaking into the global scope. You should also always end lines with semi-colons:
function process(){
var a = document.getElementsByTagName('li'),
yourString = "";
for(i=0;i<a.length;i++){
yourString += a[i].childNodes[0].nodeValue;
}
alert(yourString);
}
<script language="JavaScript">
<!--
function process(){
var data = '';
a=document.getElementsByTagName('li')
for(i=0;i<a.length;i++){
data = data + '\n' +(a[i].childNodes[0].nodeValue);
}
alert(data);
}
//-->
</script>
You need to call alert only once if you need 1 popup with all the text.
function process()
{
var a = getElementsByTagName('li'),
text = '';
for( i = 0; i < a.length; i++ )
{
text += a[i].childNodes[0].nodeValue + '\n';
}
alert( text );
}
You can process the days in whatever manner you like by storing them in an array first, and then iterating:
var days = new Array();
var a = document.getElementsByTagName('li')
for(var i = 0; i < a.length; i++) {
days.push(a[i].childNodes[0].nodeValue);
}
for (i=0; i < days.length; i++) {
// process the day
}
See: http://jsfiddle.net/jkeyes/Cfg4k/ for a working example.
These few adjustments to your function should produce the result you want. Good luck!
What changed: 1) Set up an empty string var 2) Instead of alerting each value, just append them to the string var you created earlier 3) Finally, alert the newly created (concatenated) string!
function process() {
a = document.getElementsByTagName('li');
var days = new String("");
for (i = 0; i < a.length; i++) {
days = days+(a[i].childNodes[0].nodeValue)+"\n";
}
alert(days);
}
Now I see there have been tons of answers since opening this thread... but maybe all the different solutions will help you in different ways.

Get value of JSON object with inner objects by HTML form field name without eval

I have a problem like this Convert an HTML form field to a JSON object with inner objects but in to the other direction.
This is the JSON Object response from the server:
{
company : "ACME, INC.",
contact : {
firstname : "Daffy",
lastname : "Duck"
}
}
And this is the HTML form:
<form id="myform">
Company: <input type="text" name="company" />
First Name: <input type="text" name="contact.firstname" />
Last Name: <input type="text" name="contact.lastname" />
</form>
And this is the (pseudo)code:
var aFormFields;
for (var i = 0, iMax = aFormFields.length; i < iMax; i++) {
var sFieldName = aFormFields[i].getAttribute('name');
eval("sFieldValue = oResponse."+sFieldName);
}
Ok my solution works, but i looking for a good way to remove the evil eval from the code.
And the solution should also work for form fields with any count of dots in the field name.
Instead of:
eval("sFieldValue = oResponse."+sFieldName);
Use for single dotted fields:
sFieldValue = oResponse[sFieldName];
This will retrieve the value via its key.
Now if you need more than that you need to do the following:
Split sFieldName on .
Loop over that array and go down in oResponse till you reach the value that you desire
Code could look like this:
var node = oResponse, parts = sFieldName.split('.');
while(parts.length > 0) {
node = node[parts.shift()];
}
// node will now have the desired value
Further information on "Member Operators":
https://developer.mozilla.org/en/JavaScript/Reference/Operators/Member_Operators
This works for a single property:
sFieldValue = oResponse[sFieldName]
But it won't work for nested data like contact.firstname.
For that, split the name by dots, and use loop through each name:
var aFormFields;
for (var i = 0, iMax = aFormFields.length; i < iMax; i++) {
var aFieldNameParts = aFormFields[i].getAttribute('name').split(".");
var oFieldValue = oResponse;
for(var j=0; j<aFieldNameParts.length; j++) {
oFieldValue = oFieldValue[aFieldNameParts[j]];
}
var sFieldValue = oFieldValue;
}
Note: if a property does not exist, an error will occur. You might want to check whether oFieldValue[ aFieldNameParts[j] ] exists or not.
While it is possible, I wouldn't loop over the input fields, but over the JSON object:
function fillForm (form, data, prefix) {
prefix = prefix ? prefix + "." : "";
for (var x in data) {
if (typeof data[x] === "string") {
var input = form.elements[prefix + x];
if (input)
input.value = data[x];
} else
fillForm(form, data[x], prefix + x);
}
}
fillForm(document.getElementById("myform"), oResponse);
(untested)
Assuming your naming scheme is consistent, you can convert the dot-notation into subscripts. You'd have to split the field name on the period and iterate or recurse over the tokens, converting each into a subscript. Of course this assumes that oResponse always contains a value for every field.
for (var i = 0; i < aFormFields.length; i++) {
var sFieldName = aFormFields[i].getAttribute('name');
var tokens = sFieldName.split('.');
var cur = oResponse;
for (var j = 0; j < tokens.length; j++) {
cur = cur[tokens[j]];
}
sFieldValue = cur;
}
please treat this as a combination of answer and question :)
i am currently trying to get my server to jsonify the data that i get sent from a form just like you...
in my case the form will in the end create a json object with multiple subobjects that can have subobjects which can have... as well.
the depth is up to the user so i should be able to support infinite recursion.
my "solution" so far just feels wrong, but it correctly does the job,
the function getRequestBody gets fed a req.body object from expressjs,
this is basically an object with the following mapping:
{
"ridic-ulously-deep-subobject": "value",
"ridic-ulously-deep-subobject2": "value",
"ridic-ulously-deep2-subobject3": "value",
}
the following html is in use:
<form>
<input name="ridic-ulously-long-class-string" value="my value" />
</form>
and the javascript function (that should work genericly, feed it a req.body object like above and it will return a json object):
function getRequestBody(reqB){
var reqBody = {};
for(var keys in reqB) {
var keyArr = keys.split('-');
switch(keyArr.length){
case 1:
if(!reqBody[keyArr[0]]) reqBody[keyArr[0]] = {};
reqBody[keyArr[0]] = reqB[keys];
break;
case 2:
if(!reqBody[keyArr[0]]) reqBody[keyArr[0]] = {};
if(!reqBody[keyArr[0]][keyArr[1]]) reqBody[keyArr[0]][keyArr[1]] = {};
reqBody[keyArr[0]][keyArr[1]] = reqB[keys];
break;
case 3:
if(!reqBody[keyArr[0]]) reqBody[keyArr[0]] = {};
if(!reqBody[keyArr[0]][keyArr[1]]) reqBody[keyArr[0]][keyArr[1]] = {};
if(!reqBody[keyArr[0]][keyArr[1]][keyArr[2]]) reqBody[keyArr[0]][keyArr[1]][keyArr[2]] = {};
reqBody[keyArr[0]][keyArr[1]][keyArr[2]] = reqB[keys];
break;
case 4:
// ...
//and so on, always one line longer
}
return reqBody;
}
this just feels wrong and its only covering 5 levels of subobjects right now,
it might happen that an application has enough functionality to reach seven or even ten levels though.
this should be a common problem, but my search effort turned up nothing within 10 minutes,
which usually means that i am missing some keywords
or
that there is no viable solution [yet] (which i cant really imagine in this case).
is there someone out there who has imagination and logic sufficient enough to unspaghettify this or will i just have to expand this function with even more clutter to get me down to 10 possible sublevels?
i think that in the end it wont make a big difference performance wise,
but i would really like NOT to create this awful behemoth :D
have fun
jascha

Categories

Resources