rank items in list of string in dart - algorithm

Let's consider a list:
List<String> recipeNamesList = [
'Burger',
'French Fries',
'Pizza',
'Bengali Lamb Curry',
'Chingri Malai Curry',
]
If the user searches Bengali Lamb Fries I need to return Bengali lamb curry and French Fries.
Benagli Lamb Curry will have the highest rank since it has 2 words matching and French Fries has only one word that matches.
So the returned list will be something like this:
List<String> result = [
'Bengali Lamb Curry',
'French Fries'
]
My current code:
Future<List<String>> getSuggestions(String search) async {
List<String> results = [];
List<String> searchSplit =
search.toLowerCase().split(" "); // split the search query
for (int i = 0; i < searchSplit.length; i++) {
// iterate over search query
for (int j = 0; j < recipeNamesList.length; j++) {
// iterate over the recipe names list
List<String> recipeNamesListSplit =
recipeNamesList[j].split(" "); // split the recipe names
for (int k = 0; k < recipeNamesListSplit.length; k++) {
// iterate over the list of splitted name
if (recipeNamesListSplit[k]
.toLowerCase()
.startsWith(searchSplit[i])) {
// convert to lower case and check if the query is present in splitted name
results
.add(recipeNamesList[j]); // if contains == true add to results
}
}
}
}
// Avoid repeated values
results = results.toSet().toList();
return results;
}
It's a completely ad-hoc that only matches if the query word is present in the recipeNamesList. And if so adds them to the results list. It does not rank which recipe has the most matched words from the search query.
How am I supposed to rank? Is it possible with my current code with modifications? Or do I need to completely change my code?

You need to modify your method as
Future<List<String>> getSuggestions(String search) async {
List<String> result = [];
recipeNamesList.forEach((word){
int value = getMatching(search,word);
if(value > 0){
if(result.isNotEmpty){
if(getMatching(search,result[0]) > value){
//only insert the maximum matched value at starting. it is some type of sorting
result.add(word);
}else{
result.insert(0,word);
}
}else{
result.add(word);
}
}
});
}
A basic function to count the exact matched words
int getMatching(String input, String word){
List<String> inn = input.split(' ');
List<String> words = word.split(' ');
int temp = 0;
inn.forEach((inWord){
if(words.contains(inWord)){
temp++;
}
});
return temp;
}
Output with the input getSuggestions('Bengali Lamb Fries')

Related

How to chech if the string characters have equal duplicates

I want to Check if the characters of a string have equal duplicate
if I enter :"abaababb"
returns true because I have 4 'a' and 4 'b' in that string
if I enter : "addda"
returns false because I have 2 'a' and 3 'd'
I tryed to check the duplicates but I found out I have to do it for some characters
just need to create an empty object and loop through each character and increment the value associated with the key. Keep track of the highest value so we can easily use every on the array from Object.values and check if all character counts match this value
let check = "abaababb";
let check2 = "addda";
function hasEqualCharacters( input ) {
let characters = {};
let highestCount = 0;
for( let i =0; i < input.length; i++) {
if( characters[input[i]]) {
characters[input[i]]++;
if( highestCount < characters[input[i]]) {
highestCount = characters[input[i]];
}
} else {
characters[input[i]] = 1;
}
}
return Object.values(characters).every( (charCount) => {
return charCount === highestCount;
});
}
console.log(hasEqualCharacters(check));
console.log(hasEqualCharacters(check2));
Can see it working here
https://playcode.io/1024243

Algo: replace chars of string to find the correct word

I have a string from OCR which contains some errors.
For example "2SQ41S" in place of "250415", i have a dictionary for the possible replacements:
O/Q can be replaced by 0,
S can be replaced by 5...
I can calculate the checksum to be sure that the good word is found.
Here is the function recursive which doesn't work, it will be stopped when startPosition>=6, it's before the correct word was found:
public void CombinaisonTest()
{
string date = "2SO41S";
Dictionary<char, String[]> replaceDictionary= new Dictionary<char, String[]>()
{
{'O', new []{"Q", "0"}},
{'S', new []{"8", "5", "B"}}
};
String result = "";
var r = combinations2(date, 0, replaceDictionary);
Console.WriteLine("Date: " + date);
Console.WriteLine("R: " + r);
}
public string combinations2(string date, int startPosition, Dictionary<char, String[]> dictionary)
{
Console.WriteLine("Call function " + date + ", " + startPosition);
if (string.Join("", date).Equals("250415")) //need to calculate checksum
{
Console.WriteLine("Found: " + date);
return date;
}
if (startPosition >= date.Length)
{
Console.WriteLine("Not Found: ");
return "";
}
for (int i = startPosition; i < date.Length; i++)
{
if (dictionary.ContainsKey(date.ToCharArray()[i]))
{
foreach (var value in dictionary[date.ToCharArray()[i]])
{
return combinations2(date.Remove(i, 1).Insert(i, value), startPosition + 1, dictionary);
}
}
else
{
return combinations2(date, i + 1, dictionary);
}
}
return combinations2(date, startPosition + 1, dictionary);
}
Do you have any ideas for the corrections, please?
Thank you.
There are a couple of issues with the code. The first is that when iterating through the values in the dictionary, it returns after checking the first one, so it will only ever try and substitute Q for 0 and 8 for S. The second is that you are attempting two methods of processing the characters in the string: iterative AND recursive. You don't need to iterate over the index i with a for loop and also use recursion.
Another issue (which isn't a problem in your use case but stops the algorithm being more generic) is that the algorithm attempts to do a substitution in every case where an ambiguous character is encountered, as well as iterating over each value in the dictionary you should also consider the case where the character is left unmodified.
The function can be changed to remove the outer for loop and iterate over the values in the dictionary, test each one (recursing over the remainder of the string) and only return if a match is found. A simple way to do this is to store the result in a string and only return it if it is not the empty string (since your function returns the empty string when no match is found). If all the values in the dictionary have been tried and no match has been found, then it tries recursing without modifying the string.
public string combinations2(string date, int startPosition, Dictionary<char, String[]> dictionary)
{
Console.WriteLine("Call function " + date + ", " + startPosition);
if (string.Join("", date).Equals("250415")) //need to calculate checksum
{
Console.WriteLine("Found: " + date);
return date;
}
if (startPosition >= date.Length)
{
Console.WriteLine("Not Found: ");
return "";
}
if (dictionary.ContainsKey(date.ToCharArray()[startPosition]))
{
foreach (var value in dictionary[date.ToCharArray()[startPosition]])
{
string result = combinations2(date.Remove(startPosition, 1).Insert(startPosition, value), startPosition + 1, dictionary);
if(result != "")
return result;
}
}
return combinations2(date, startPosition + 1, dictionary);
}

splitting up the contents of a single line

I just went through a problem, where input is a string which is a single word.
This line is not readable,
Like, I want to leave is written as Iwanttoleave.
The problem is of separating out each of the tokens(words, numbers, abbreviations, etc)
I have no idea where to start
The first thought that came to my mind is making a dictionary and then mapping accordingly but I think making a dictionary is not at all a good idea.
Can anyone suggest some algorithm to do it ?
First of all, create a dictionary which helps you to identify if some string is a valid word or not.
bool isValidString(String s){
if(dictionary.contains(s))
return true;
return false;
}
Now, you can write a recursive code to split the string and create an array of actually useful words.
ArrayList usefulWords = new ArrayList<String>; //global declaration
void split(String s){
int l = s.length();
int i,j;
for(i = l-1; i >= 0; i--){
if(isValidString(s.substr(i,l)){ //s.substr(i,l) will return substring starting from index `i` and ending at `l-1`
usefulWords.add(s.substr(i,l));
split(s.substr(0,i));
}
}
}
Now, use these usefulWords to generate all possible strings. Maybe something like this:
ArrayList<String> splits = new ArrayList<String>[10]; //assuming max 10 possible outputs
ArrayList<String>[] allPossibleStrings(String s, int level){
for(int i = 0; i < s.length(); i++){
if(usefulWords.contains(s.substr(0,i)){
splits[level].add(s.substr(0,i));
allPossibleStrings(s.substr(i,s.length()),level);
level++;
}
}
}
Now, this code gives you all possible splits in a somewhat arbitrary manner. eg.
dictionary = {cat, dog, i, am, pro, gram, program, programmer, grammer}
input:
string = program
output:
splits[0] = {pro, gram}
splits[1] = {program}
input:
string = iamprogram
output:
splits[0] = {i, am, pro, gram} //since `mer` is not in dictionary
splits[1] = {program}
I did not give much thought to the last part, but I think you should be able to formulate a code from there as per your requirement.
Also, since no language is tagged, I've taken the liberty of writing the code in JAVA-like syntax as it is really easy to understand.
Instead of using a Dictionary, I'd suggest you use a Trie with all your valid words (the whole English dictionary?). Then you can start moving one letter at a time in your input line and the trie at the same time. If the letter leads to more results in the trie, you can continue expanding the current word, and if not, you can start looking for a new word in the trie.
This won't be a forward only search for sure, so you'll need some sort of backtracking.
// This method Generates a list with all the matching phrases for the given input
List<string> CandidatePhrases(string input) {
Trie validWords = BuildTheTrieWithAllValidWords();
List<string> currentWords = new List<string>();
List<string> possiblePhrases = new List<string>();
// The root of the trie has an empty key that points to all the first letters of all words
Trie currentWord = validWords;
int currentLetter = -1;
// Calls a backtracking method that creates all possible phrases
FindPossiblePhrases(input, validWords, currentWords, currentWord, currentLetter, possiblePhrases);
return possiblePhrases;
}
// The Trie structure could be something like
class Trie {
char key;
bool valid;
List<Trie> children;
Trie parent;
Trie Next(char nextLetter) {
return children.FirstOrDefault(c => c.key == nextLetter);
}
string WholeWord() {
Debug.Assert(valid);
string word = "";
Trie current = this;
while (current.Key != '\0')
{
word = current.Key + word;
current = current.parent;
}
}
}
void FindPossiblePhrases(string input, Trie validWords, List<string> currentWords, Trie currentWord, int currentLetter, List<string> possiblePhrases) {
if (currentLetter == input.Length - 1) {
if (currentWord.valid) {
string phrase = ""
foreach (string word in currentWords) {
phrase += word;
phrase += " ";
}
phrase += currentWord.WholeWord();
possiblePhrases.Add(phrase);
}
}
else {
// The currentWord may be a valid word. If that's the case, the next letter could be the first of a new word, or could be the next letter of a bigger word that begins with currentWord
if (currentWord.valid) {
// Try to match phrases when the currentWord is a valid word
currentWords.Add(currentWord.WholeWord());
FindPossiblePhrases(input, validWords, currentWords, validWords, currentLetter, possiblePhrases);
currentWords.RemoveAt(currentWords.Length - 1);
}
// If either the currentWord is a valid word, or not, try to match a longer word that begins with current word
int nextLetter = currentLetter + 1;
Trie nextWord = currentWord.Next(input[nextLetter]);
// If the nextWord is null, there was no matching word that begins with currentWord and has input[nextLetter] as the following letter.
if (nextWord != null) {
FindPossiblePhrases(input, validWords, currentWords, nextWord, nextLetter, possiblePhrases);
}
}
}

Finding highest number in text file

I have a text file that contains 50 student names and scores for each student in the format.
foreName.Surname:Mark
I have figured out how to split up each line into a forename, surname and mark using this code.
string[] Lines = File.ReadAllLines(#"StudentExamMarks.txt");
int i = 0;
var items = from line in Lines
where i++ != 0
let words = line.Split(' ', '.', ':')
select new
{
foreName = words[0],
Surname = words[1],
Mark = words[2]
};
I am unsure of how i would incorporate a findMax algorithm into to find the highest mark and display the pupil with the highest mark. this as i have not used text files that often.
You can use any sorting algorithm there is a Pseudo Code available to find maximum number in any list or array..
Try this code, required just parse all files.
string[] lines = File.ReadAllLines(#"StudentExamMarks.txt");
string maxForeName = null;
string maxSurName = null;
var maxMark = 0;
for (int i = 0; i < lines.Length; i++)
{
var tmp = lines[i].Split(new char[] { ' ', '.', ':' }, StringSplitOptions.RemoveEmptyEntries);
if (tmp.Length == 3)
{
int value = int.Parse(tmp[2]);
if (i == 0 || value > maxMark)
{
maxMark = value;
maxForeName = tmp[0];
maxSurName = tmp[1];
}
}
}

Algorithm to generate all variants of a word

i would like to explain my problem by the following example.
assume the word: abc
a has variants: ä, à
b has no variants.
c has variants: ç
so the possible words are:
abc
äbc
àbc
abç
äbç
àbç
now i am looking for the algorithm that prints all word variantions for abritray words with arbitray lettervariants.
I would recommend you to solve this recursively. Here's some Java code for you to get started:
static Map<Character, char[]> variants = new HashMap<Character, char[]>() {{
put('a', new char[] {'ä', 'à'});
put('b', new char[] { });
put('c', new char[] { 'ç' });
}};
public static Set<String> variation(String str) {
Set<String> result = new HashSet<String>();
if (str.isEmpty()) {
result.add("");
return result;
}
char c = str.charAt(0);
for (String tailVariant : variation(str.substring(1))) {
result.add(c + tailVariant);
for (char variant : variants.get(c))
result.add(variant + tailVariant);
}
return result;
}
Test:
public static void main(String[] args) {
for (String str : variation("abc"))
System.out.println(str);
}
Output:
abc
àbç
äbc
àbc
äbç
abç
A quickly hacked solution in Python:
def word_variants(variants):
print_variants("", 1, variants);
def print_variants(word, i, variants):
if i > len(variants):
print word
else:
for variant in variants[i]:
print_variants(word + variant, i + 1, variants)
variants = dict()
variants[1] = ['a0', 'a1', 'a2']
variants[2] = ['b0']
variants[3] = ['c0', 'c1']
word_variants(variants)
Common part:
string[] letterEquiv = { "aäà", "b", "cç", "d", "eèé" };
// Here we make a dictionary where the key is the "base" letter and the value is an array of alternatives
var lookup = letterEquiv
.Select(p => p.ToCharArray())
.SelectMany(p => p, (p, q) => new { key = q, values = p }).ToDictionary(p => p.key, p => p.values);
A recursive variation written in C#.
List<string> resultsRecursive = new List<string>();
// I'm using an anonymous method that "closes" around resultsRecursive and lookup. You could make it a standard method that accepts as a parameter the two.
// Recursive anonymous methods must be declared in this way in C#. Nothing to see.
Action<string, int, char[]> recursive = null;
recursive = (str, ix, str2) =>
{
// In the first loop str2 is null, so we create the place where the string will be built.
if (str2 == null)
{
str2 = new char[str.Length];
}
// The possible variations for the current character
var equivs = lookup[str[ix]];
// For each variation
foreach (var eq in equivs)
{
// We save the current variation for the current character
str2[ix] = eq;
// If we haven't reached the end of the string
if (ix < str.Length - 1)
{
// We recurse, increasing the index
recursive(str, ix + 1, str2);
}
else
{
// We save the string
resultsRecursive.Add(new string(str2));
}
}
};
// We launch our function
recursive("abcdeabcde", 0, null);
// The results are in resultsRecursive
A non-recursive version
List<string> resultsNonRecursive = new List<string>();
// I'm using an anonymous method that "closes" around resultsNonRecursive and lookup. You could make it a standard method that accepts as a parameter the two.
Action<string> nonRecursive = (str) =>
{
// We will have two arrays, of the same length of the string. One will contain
// the possible variations for that letter, the other will contain the "current"
// "chosen" variation of that letter
char[][] equivs = new char[str.Length][];
int[] ixes = new int[str.Length];
for (int i = 0; i < ixes.Length; i++)
{
// We start with index -1 so that the first increase will bring it to 0
equivs[i] = lookup[str[i]];
ixes[i] = -1;
}
// The current "workin" index of the original string
int ix = 0;
// The place where the string will be built.
char[] str2 = new char[str.Length];
// The loop will break when we will have to increment the letter with index -1
while (ix >= 0)
{
// We select the next possible variation for the current character
ixes[ix]++;
// If we have exausted the possible variations of the current character
if (ixes[ix] == equivs[ix].Length)
{
// Reset the current character to -1
ixes[ix] = -1;
// And loop back to the previous character
ix--;
continue;
}
// We save the current variation for the current character
str2[ix] = equivs[ix][ixes[ix]];
// If we are setting the last character of the string, then the string
// is complete
if (ix == str.Length - 1)
{
// And we save it
resultsNonRecursive.Add(new string(str2));
}
else
{
// Otherwise we have to do everything for the next character
ix++;
}
}
};
// We launch our function
nonRecursive("abcdeabcde");
// The results are in resultsNonRecursive
Both heavily commented.

Resources