How to write code to autocomplete words and sentences?
(I'm aware this isn't exactly what you're asking for, but) If you're happy with the auto-completion/suggestions appearing on TAB (as used in many shells), then you can quickly get up and running using the readline module.
Here's a quick example based on Doug Hellmann's PyMOTW writeup on readline.
import readline
class MyCompleter(object): # Custom completer
def __init__(self, options):
self.options = sorted(options)
def complete(self, text, state):
if state == 0: # on first trigger, build possible matches
if text: # cache matches (entries that start with entered text)
self.matches = [s for s in self.options
if s and s.startswith(text)]
else: # no text entered, all matches possible
self.matches = self.options[:]
# return match indexed by state
try:
return self.matches[state]
except IndexError:
return None
completer = MyCompleter(["hello", "hi", "how are you", "goodbye", "great"])
readline.set_completer(completer.complete)
readline.parse_and_bind('tab: complete')
input = raw_input("Input: ")
print "You entered", input
This results in the following behaviour (<TAB>
representing a the tab key being pressed):
Input: <TAB><TAB>
goodbye great hello hi how are you
Input: h<TAB><TAB>
hello hi how are you
Input: ho<TAB>ow are you
In the last line (HOTAB entered), there is only one possible match and the whole sentence "how are you" is auto completed.
Check out the linked articles for more information on readline
.
"And better yet would be if it would complete words not only from the beginning ... completion from arbitrary part of the string."
This can be achieved by simply modifying the match criteria in the completer function, ie. from:
self.matches = [s for s in self.options
if s and s.startswith(text)]
to something like:
self.matches = [s for s in self.options
if text in s]
This will give you the following behaviour:
Input: <TAB><TAB>
goodbye great hello hi how are you
Input: o<TAB><TAB>
goodbye hello how are you
Updates: using the history buffer (as mentioned in comments)
A simple way to create a pseudo-menu for scrolling/searching is to load the keywords into the history buffer. You will then be able to scroll through the entries using the up/down arrow keys as well as use Ctrl+R to perform a reverse-search.
To try this out, make the following changes:
keywords = ["hello", "hi", "how are you", "goodbye", "great"]
completer = MyCompleter(keywords)
readline.set_completer(completer.complete)
readline.parse_and_bind('tab: complete')
for kw in keywords:
readline.add_history(kw)
input = raw_input("Input: ")
print "You entered", input
When you run the script, try typing Ctrl+r followed by a. That will return the first match that contains "a". Enter Ctrl+r again for the next match. To select an entry, press ENTER.
Also try using the UP/DOWN keys to scroll through the keywords.
Sentence Auto-Complete with Java
If you've only got 1000 sentences, you probably don't need a powerful indexer like lucene. I'm not sure whether you want to do "complete the sentence" suggestions or "suggest other queries that have the same keywords" suggestions. Here are solutions to both:
Assuming that you want to complete the sentence input by the user, then you could put all of your strings into a SortedSet
, and use the tailSet
method to get a list of strings that are "greater" than the input string (since the string comparator considers a longer string A
that starts with string B
to be "greater" than B
). Then, iterate over the top few entries of the set returned by tailSet
to create a set of strings where the first inputString.length()
characters match the input string. You can stop iterating as soon as the first inputString.length()
characters don't match the input string.
If you want to do keyword suggestions instead of "complete the sentence" suggestions, then the overhead depends on how long your sentences are, and how many unique words there are in the sentences. If this set is small enough, you'll be able to get away with a HashMap<String,Set<String>>
, where you mapped keywords to the sentences that contained them. Then you could handle multiword queries by intersecting the sets.
In both cases, I'd probably convert all strings to lower case first (assuming that's appropriate in your application). I don't think either solution would scale to hundreds of thousands of suggestions either. Do either of those do what you want? Happy to provide code if you'd like it.
Combination of word as a autocomplete suggest once that word is selected
You can add any words to the autocomplete suggestion by pushing them to the suggestion array (a
in your code). So what you would need to do is:
- Check if the last character is a blank space.
- If it is, get the last word from the
#parameter
input. - Search that word in the list of words that you use as source (
docWords
). - If it is in the source, add the next word to the suggestion.
A simple demo would be something like this (that would be added to your function before the responseFn( a );
part):
// get the value in the input
var textValue = $("#parameter").val();
// if the last character is a space
if (textValue.slice(-1) == " ") {
// get the last word in the sentence
var start = textValue.lastIndexOf(" ", textValue.length-2);
var lastWord = textValue.substring(start + 1, textValue.length-1);
// check if the word is in the source list
var pos = docWords.indexOf(lastWord);
if (lastWord != " " && docWords.length > pos) {
// if it is, suggest the next word too as a sentence
a.push($("#parameter").val() +docWords[pos+1]);
}
}
Notice that this is really basic example, it doesn't check for word duplicates, or case, or anything else. You would need to extend it to make it more "complete" and adjust it to your needs.
Here is a running demo:
var curDocParaText = $('.docTextFull').text();var docWords = curDocParaText.replace(/\s{2,}/g, ' ').split(" ");$( "#parameter" ).autocomplete({ source: function(req, responseFn) { var re = $.ui.autocomplete.escapeRegex(req.term); var matcher = new RegExp( "^" + re, "i" ); var a = $.grep( docWords, function(item,index){ return matcher.test(item); }); var textValue = $("#parameter").val(); if (textValue.slice(-1) == " ") { var start = textValue.lastIndexOf(" ", textValue.length-2); var lastWord = textValue.substring(start + 1, textValue.length-1); var pos = docWords.indexOf(lastWord); if (lastWord != " " && docWords.length > pos) { a.push($("#parameter").val() +docWords[pos+1]); } } responseFn( a ); }});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script><script type="text/javascript" src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script><link rel="stylesheet" type="text/css" href="http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css">
<div><b>Demo Sentence:</b></div><div class="docTextFull">"The quick brown fox jumps over the lazy dog"</div><hr/><input type="text" id="parameter">
How to make a python, command-line program autocomplete arbitrary things NOT interpreter
Use Python's readline
bindings. For example,
import readline
def completer(text, state):
options = [i for i in commands if i.startswith(text)]
if state < len(options):
return options[state]
else:
return None
readline.parse_and_bind("tab: complete")
readline.set_completer(completer)
The official module docs aren't much more detailed, see the readline docs for more info.
React - Material-ui : Autocomplete on words that aren't the first in a sentence
Here is how you can do it with the material-ui components:
<div style={{position:'relative'}}>
<AutoComplete
hintText="Type anything"
dataSource={['#hello','#how','#are','#you']}
ref={ref=>this.autocompleteRef=ref}
searchText={this.state.searchText}
onNewRequest={this.onNewRequest}
textFieldStyle={{visibility:'hidden'}}
style={{position:'absolute',top:'0',left:'0'}}
/>
<TextField
value={this.state.fullText || ''}
hintText="fullText"
onChange={this.myTextChange}
type="text"
/>
</div>
And the functions should look something like:
myTextChange=(event)=>{
let fullText = event.target.value,searchText;
let hashTaggingIndex = this.hashTaggingIndex;
if(hashTaggingIndex && hashTaggingIndex>-1){
if(fullText[fullText.length-1]===' ') this.hashTaggingIndex = -1;
else {
searchText = fullText.substring(hashTaggingIndex+1,fullText.length);
}
} else if(fullText[fullText.length-1]==='#') {
this.hashTaggingIndex=fullText.length-1;
}
this.setState({fullText,searchText});
if(this.autocompleteRef && hashTaggingIndex && hashTaggingIndex>-1){
this.autocompleteRef.handleChange(event);
}
};
onNewRequest=(value)=>{
let fullText=this.state.fullText;
fullText = fullText.substring(0,this.hashTaggingIndex) + value;
this.setState({fullText})
};
Have fun:)
Related Topics
Generating Discrete Random Variables with Specified Weights Using Scipy or Numpy
Pygame Tic Tak Toe Logic? How Would I Do It
Putting Many Python Pandas Dataframes to One Excel Worksheet
How to Merge Multiple Lists into One List in Python
Pip Install Gives Error: Unable to Find Vcvarsall.Bat
How to Use Python Numpy.Savetxt to Write Strings and Float Number to an Ascii File
Passing Numpy Arrays to a C Function for Input and Output
Python Command Line Input in a Process
Anaconda/Conda - Install a Specific Package Version
Sorting a Dictionary with Lists as Values, According to an Element from the List
Is There a Difference Between Using a Dict Literal and a Dict Constructor
Getting Standard Errors on Fitted Parameters Using the Optimize.Leastsq Method in Python
How to Merge Images into a Canvas Using Pil/Pillow
Passing Command Line Arguments to Argv in Jupyter/Ipython Notebook
How to Install Python Packages in Google's Colab
What Is the Purpose of Subclassing the Class "Object" in Python