In developing search for a site I am building, I decided to go the cheap and quick way and use Microsoft Sql Server's Full Text Search engine instead of something more robust like Lucene.Net.
One of the features I would like to have, though, is google-esque relevant document snippets. I quickly found determining "relevant" snippets is more difficult than I realized.
I want to choose snippets based on search term density in the found text. So, essentially, I need to find the most search term dense passage in the text. Where a passage is some arbitrary number of characters (say 200 -- but it really doesn't matter).
My first thought is to use .IndexOf() in a loop and build an array of term distances (subtract the index of the found term from the previously found term), then ... what? Add up any two, any three, any four, any five, sequential array elements and use the one with the smallest sum (hence, the smallest distance between search terms).
That seems messy.
Is there an established, better, or more obvious way to do this than what I have come up with?
Although it is implemented in Java, you can see one approach for that problem here:
http://rcrezende.blogspot.com/2010/08/smallest-relevant-text-snippet-for.html
I know this thread is way old, but I gave this a try last week and it was a pain in the back side. This is far from perfect, but this is what I came up with.
The snippet generator:
public static string SelectKeywordSnippets(string StringToSnip, string[] Keywords, int SnippetLength)
{
string snippedString = "";
List<int> keywordLocations = new List<int>();
//Get the locations of all keywords
for (int i = 0; i < Keywords.Count(); i++)
keywordLocations.AddRange(SharedTools.IndexOfAll(StringToSnip, Keywords[i], StringComparison.CurrentCultureIgnoreCase));
//Sort locations
keywordLocations.Sort();
//Remove locations which are closer to each other than the SnippetLength
if (keywordLocations.Count > 1)
{
bool found = true;
while (found)
{
found = false;
for (int i = keywordLocations.Count - 1; i > 0; i--)
if (keywordLocations[i] - keywordLocations[i - 1] < SnippetLength / 2)
{
keywordLocations[i - 1] = (keywordLocations[i] + keywordLocations[i - 1]) / 2;
keywordLocations.RemoveAt(i);
found = true;
}
}
}
//Make the snippets
if (keywordLocations.Count > 0 && keywordLocations[0] - SnippetLength / 2 > 0)
snippedString = "... ";
foreach (int i in keywordLocations)
{
int stringStart = Math.Max(0, i - SnippetLength / 2);
int stringEnd = Math.Min(i + SnippetLength / 2, StringToSnip.Length);
int stringLength = Math.Min(stringEnd - stringStart, StringToSnip.Length - stringStart);
snippedString += StringToSnip.Substring(stringStart, stringLength);
if (stringEnd < StringToSnip.Length) snippedString += " ... ";
if (snippedString.Length > 200) break;
}
return snippedString;
}
The function which will find the index of all keywords in the sample text
private static List<int> IndexOfAll(string haystack, string needle, StringComparison Comparison)
{
int pos;
int offset = 0;
int length = needle.Length;
List<int> positions = new List<int>();
while ((pos = haystack.IndexOf(needle, offset, Comparison)) != -1)
{
positions.Add(pos);
offset = pos + length;
}
return positions;
}
It's a bit clumsy in its execution. The way it works is by finding the position of all keywords in the string. Then checking that no keywords are closer to each other than the desired snippet length, so that snippets won't overlap (that's where it's a bit iffy...). And then grabs substrings of the desired length centered around the position of the keywords and stitches the whole thing together.
I know this is years late, but posting just in case it might help somebody coming across this question.
public class Highlighter
{
private class Packet
{
public string Sentence;
public double Density;
public int Offset;
}
public static string FindSnippet(string text, string query, int maxLength)
{
if (maxLength < 0)
{
throw new ArgumentException("maxLength");
}
var words = query.Split(' ').Where(w => !string.IsNullOrWhiteSpace(w)).Select(word => word.ToLower()).ToLookup(s => s);
var sentences = text.Split('.');
var i = 0;
var packets = sentences.Select(sentence => new Packet
{
Sentence = sentence,
Density = ComputeDensity(words, sentence),
Offset = i++
}).OrderByDescending(packet => packet.Density);
var list = new SortedList<int, string>();
int length = 0;
foreach (var packet in packets)
{
if (length >= maxLength || packet.Density == 0)
{
break;
}
string sentence = packet.Sentence;
list.Add(packet.Offset, sentence.Substring(0, Math.Min(sentence.Length, maxLength - length)));
length += packet.Sentence.Length;
}
var sb = new List<string>();
int previous = -1;
foreach (var item in list)
{
var offset = item.Key;
var sentence = item.Value;
if (previous != -1 && offset - previous != 1)
{
sb.Add(".");
}
previous = offset;
sb.Add(Highlight(sentence, words));
}
return String.Join(".", sb);
}
private static string Highlight(string sentence, ILookup<string, string> words)
{
var sb = new List<string>();
var ff = true;
foreach (var word in sentence.Split(' '))
{
var token = word.ToLower();
if (ff && words.Contains(token))
{
sb.Add("[[HIGHLIGHT]]");
ff = !ff;
}
if (!ff && !string.IsNullOrWhiteSpace(token) && !words.Contains(token))
{
sb.Add("[[ENDHIGHLIGHT]]");
ff = !ff;
}
sb.Add(word);
}
if (!ff)
{
sb.Add("[[ENDHIGHLIGHT]]");
}
return String.Join(" ", sb);
}
private static double ComputeDensity(ILookup<string, string> words, string sentence)
{
if (string.IsNullOrEmpty(sentence) || words.Count == 0)
{
return 0;
}
int numerator = 0;
int denominator = 0;
foreach(var word in sentence.Split(' ').Select(w => w.ToLower()))
{
if (words.Contains(word))
{
numerator++;
}
denominator++;
}
if (denominator != 0)
{
return (double)numerator / denominator;
}
else
{
return 0;
}
}
}
Example:
highlight "Optic flow is defined as the change of structured light in the image, e.g. on the retina or the camera’s sensor, due to a relative motion between the eyeball or camera and the scene. Further definitions from the literature highlight different properties of optic flow" "optic flow"
Output:
[[HIGHLIGHT]] Optic flow [[ENDHIGHLIGHT]] is defined as the change of structured
light in the image, e... Further definitions from the literature highlight diff
erent properties of [[HIGHLIGHT]] optic flow [[ENDHIGHLIGHT]]
Well, here's the hacked together version I made using the algorithm I described above. I don't think it is all that great. It uses three (count em, three!) loops an array and two lists. But, well, it is better than nothing. I also hardcoded the maximum length instead of turning it into a parameter.
private static string FindRelevantSnippets(string infoText, string[] searchTerms)
{
List<int> termLocations = new List<int>();
foreach (string term in searchTerms)
{
int termStart = infoText.IndexOf(term);
while (termStart > 0)
{
termLocations.Add(termStart);
termStart = infoText.IndexOf(term, termStart + 1);
}
}
if (termLocations.Count == 0)
{
if (infoText.Length > 250)
return infoText.Substring(0, 250);
else
return infoText;
}
termLocations.Sort();
List<int> termDistances = new List<int>();
for (int i = 0; i < termLocations.Count; i++)
{
if (i == 0)
{
termDistances.Add(0);
continue;
}
termDistances.Add(termLocations[i] - termLocations[i - 1]);
}
int smallestSum = int.MaxValue;
int smallestSumIndex = 0;
for (int i = 0; i < termDistances.Count; i++)
{
int sum = termDistances.Skip(i).Take(5).Sum();
if (sum < smallestSum)
{
smallestSum = sum;
smallestSumIndex = i;
}
}
int start = Math.Max(termLocations[smallestSumIndex] - 128, 0);
int len = Math.Min(smallestSum, infoText.Length - start);
len = Math.Min(len, 250);
return infoText.Substring(start, len);
}
Some improvements I could think of would be to return multiple "snippets" with a shorter length that add up to the longer length -- this way multiple parts of the document can be sampled.
This is a nice problem :)
I think I'd create an index vector: For each word, create an entry 1 if search term or otherwise 0. Then find the i such that sum(indexvector[i:i+maxlength]) is maximized.
This can actually be done rather efficiently. Start with the number of searchterms in the first maxlength words. then, as you move on, decrease your counter if indexvector[i]=1 (i.e. your about to lose that search term as you increase i) and increase it if indexvector[i+maxlength+1]=1. As you go, keep track of the i with the highest counter value.
Once you got your favourite i, you can still do finetuning like see if you can reduce the actual size without compromising your counter, e.g. in order to find sentence boundaries or whatever. Or like picking the right i of a number of is with equivalent counter values.
Not sure if this is a better approach than yours - it's a different one.
You might also want to check out this paper on the topic, which comes with yet-another baseline: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72.4357&rep=rep1&type=pdf
I took another approach, perhaps it will help someone...
First it searches if it word appears in my case with IgnoreCase (you change this of course yourself).
Then I create a list of Regex matches on each separators and search for the first occurrence of the word (allowing partial case insensitive matches).
From that index, I get the 10 matches in front and behind the word, which makes the snippet.
public static string GetSnippet(string text, string word)
{
if (text.IndexOf(word, StringComparison.InvariantCultureIgnoreCase) == -1)
{
return "";
}
var matches = new Regex(#"\b(\S+)\s?", RegexOptions.Singleline | RegexOptions.Compiled).Matches(text);
var p = -1;
for (var i = 0; i < matches.Count; i++)
{
if (matches[i].Value.IndexOf(word, StringComparison.InvariantCultureIgnoreCase) != -1)
{
p = i;
break;
}
}
if (p == -1) return "";
var snippet = "";
for (var x = Math.Max(p - 10, 0); x < p + 10; x++)
{
snippet += matches[x].Value + " ";
}
return snippet;
}
If you use CONTAINSTABLE you will get a RANK back , this is in essence a density value - higher the RANK value, the higher the density. This way, you just run a query to get the results you want and dont have to result to massaging the data when its returned.
Wrote a function to do this just now. You want to pass in:
Inputs:
Document text
This is the full text of the document you're taking a snippet from. Most likely you will want to strip out any BBCode/HTML from this document.
Original query
The string the user entered as their search
Snippet length
Length of the snippet you wish to display.
Return Value:
Start index of the document text to take the snippet from. To get the snippet simply do documentText.Substring(returnValue, snippetLength). This has the advantage that you know if the snippet is take from the start/end/middle so you can add some decoration like ... if you wish at the snippet start/end.
Performance
A resolution set to 1 will find the best snippet but moves the window along 1 char at a time. Set this value higher to speed up execution.
Tweaks
You can work out score however you want. In this example I've done Math.pow(wordLength, 2) to favour longer words.
private static int GetSnippetStartPoint(string documentText, string originalQuery, int snippetLength)
{
// Normalise document text
documentText = documentText.Trim();
if (string.IsNullOrWhiteSpace(documentText)) return 0;
// Return 0 if entire doc fits in snippet
if (documentText.Length <= snippetLength) return 0;
// Break query down into words
var wordsInQuery = new HashSet<string>();
{
var queryWords = originalQuery.Split(' ');
foreach (var word in queryWords)
{
var normalisedWord = word.Trim().ToLower();
if (string.IsNullOrWhiteSpace(normalisedWord)) continue;
if (wordsInQuery.Contains(normalisedWord)) continue;
wordsInQuery.Add(normalisedWord);
}
}
// Create moving window to get maximum trues
var windowStart = 0;
double maxScore = 0;
var maxWindowStart = 0;
// Higher number less accurate but faster
const int resolution = 5;
while (true)
{
var text = documentText.Substring(windowStart, snippetLength);
// Get score of this chunk
// This isn't perfect, as window moves in steps of resolution first and last words will be partial.
// Could probably be improved to iterate words and not characters.
var words = text.Split(' ').Select(c => c.Trim().ToLower());
double score = 0;
foreach (var word in words)
{
if (wordsInQuery.Contains(word))
{
// The longer the word, the more important.
// Can simply replace with score += 1 for simpler model.
score += Math.Pow(word.Length, 2);
}
}
if (score > maxScore)
{
maxScore = score;
maxWindowStart = windowStart;
}
// Setup next iteration
windowStart += resolution;
// Window end passed document end
if (windowStart + snippetLength >= documentText.Length)
{
break;
}
}
return maxWindowStart;
}
Lots more you can add to this, for example instead of comparing exact words perhaps you might want to try comparing the SOUNDEX where you weight soundex matches less than exact matches.
Related
I want to make a simple generator of strings.
User inputs a "template" for string. Template can have placeholders in any place in it.
Then he inputs possible characters that can fit into any placeholder in string.
How it should work:
INPUT:
a.b.
123
OUTPUT:
[
"a1b1", "a1b2", "a1b3",
"a2b1", "a2b2", "a2b3",
"a3b1", "a3b2", "a3b3"
]
I found some of my old python code, but i don't understand it at all.
I split the input string to array of strings and array of dots.
Then I tried to increment just dots and each time just concat those two arrays in the right way.
But I found a new trouble.
string[] splitted = kt_NonCur.Split('.'); // array of constant strings
char[] nch = new char[splitted.Length - 1]; // array of new chars (generated)
char lgc = goodLetters.Last( ); // last good char
for( int i = 0; i < nch.Length - 1; i++ ) // set up all nch to first letter
nch[i] = goodLetters[0];
while( nch.Last( ) != lgc ) { // until last nch is set to last good char
outputData.Add($"{concatsplit(splitted, nch)}"); // concatsplit(s,n) concatenates two arrays into string
nch[0] = up(nch[0]); // up(char) gets next character from goodLetters. If there is no next, it returns first letter.
if( nch[0] == goodLetters[0] ) {
nch[1] = up(nch[1]);
if(nch[1] == goodLetters[0]){
nch[2] = up(nch[2]);
// .
// .
// .
}
}
}
And the problem is: I am facing a dilemma. Either find better way, or limit number of placeholders so that the code ladder is not too long. Of course I would add then some code that checks if it is the last and stop executing code for others, but I still would have to make
You can look at your problem this way: if you have P placeholders in your input string and the number of replacement characters is R, to construct every possibe output string you need at each step P numbers [0...R-1] (which can then serve as index into the replacement character list). Well, this is the definition of an integer with P digits in base R.
So let's write a helper class representing such integers:
class NDigitNumber
{
int[] _digits;
int _base;
// construct an integer with the specified numer of digits in the specified base
public NDigitNumber(int digits, int #base)
{
_digits = new int[digits];
_base = #base;
}
// get the digit at the specified position
public int this[int index] => _digits[index];
// increment the number, returns false on overflow
public bool Increment()
{
for (var pos = 0; pos < _digits.Length; pos++)
{
if (++_digits[pos] < _base)
break;
if (pos == _digits.Length-1)
return false;
for (var i = 0; i <= pos; i++)
_digits[i] = 0;
}
return true;
}
}
The Increment methods works like these mechanical counter devices where each digit wheel, when rotated from its maximum digit to the next, resets itself and all lower wheels to 0 and increments the next higher wheel.
Then we only have to iterate over all possible such integers to get the desired output:
var input = "a.b.";
var placeholder = '.';
var replacements = new[] { '1', '2', '3' };
// determine positions of placeholder in string
var placeholderPositions = new List<int>();
for (var i = 0; i < input.Length; i++)
{
if (input[i] == placeholder)
placeholderPositions.Add(i);
}
// iterate over all possible integers with
// placeholderPositions.Count digits
// in base replacements.Length
var number = new NDigitNumber(placeholderPositions.Count, replacements.Length);
do
{
var result = new StringBuilder(input);
for (var i = 0; i < placeholderPositions.Count; i++)
result[placeholderPositions[i]] = replacements[number[i]];
Console.WriteLine(result.ToString());
} while(number.Increment());
Output:
a1b1
a2b1
a3b1
a1b2
a2b2
a3b2
a1b3
a2b3
a3b3
Based on accepted answer of this post:
public static IEnumerable<string> Combinations(string template, string str, char placeholder)
{
int firstPlaceHolder = template.IndexOf(placeholder);
if (firstPlaceHolder == -1)
return new string[] { template };
string prefix = template.Substring(0, firstPlaceHolder);
string suffix = template.Substring(firstPlaceHolder + 1);
var recursiveCombinations = Combinations(suffix, str, placeholder);
return
from chr in str
from recSuffix in recursiveCombinations
select prefix + chr + recSuffix;
}
Usage:
List<string> combinations = Combinations("a.b.", "123", '.').ToList();
I'm trying to cycle through chars in a string.
string cycleMe = "Hi StackOverflow! Here is my string."
However, I want to skip over certain ranges of indexes. The ranges I want to skip over are stored in a List of objects, delims.
List<Delim> delims = delimCreator();
To retrieve each starting index and ending index for a range, I have to write a loop that accesses each "delim":
delims[0].getFirstIndex() //results in, say, index 2
delims[0].getLastIndex() //results in, say, index 4
delims[1].getFirstIndex() //results in, say, index 5
delims[1].getLastIndex() //results in, say, index 7
(there can be infinitely many "delim" objects in play)
If the above were my list, I'd want to print the string cycleMe, but skip all the chars between 2 and 4 (inclusive) and 5 and 7 (inclusive).
Expected output using the numbers above:
HiOverflow! Here is my string.
Here is the code I have written so far. It loops far more often than I'd expect (it loops ~x2 the number of characters in the string). Thanks in advance! =)
List<Delim> delims = delimAggregateInator(displayTextRaw);
for (int x = 0; x < cycleMe.Length;x++){
for (int i = 0; i < delims.Count; i++){
if (!(x >= delims[i].getFirstIndex() && x <= delims[i].getLastIndex())){
Debug.Log("test");
}
}
I assume that by skipping you meant you want to omit those characters from the original string. If that is the case, you can try Aggregate extension method like below.
string result = delims.Aggregate<Delim, string>(cycleMe, (str, d) => cycleMe = cycleMe.Remove(d.FirstIndex, (d.LastIndex - d.FirstIndex) + 1));
Make sure that the delim list is in the proper order.
Solution might be converting the string to char array, replacing the desired parts to spaces, and converting the output back to string.
Here is the modified version of your code:
string cycleMe = "Hi StackOverflow! Here is my string."
var charArray = cycleMe.ToCharArray(); // Converting to char array
List<Delim> delims = delimAggregateInator(displayTextRaw);
for (int x = 0; x < cycleMe.Length;x++){
for (int i = 0; i < delims.Count; i++){
// ORIGINAL: if (!(x >= delims[i].getFirstIndex() && x <= delims[i].getLastIndex())){
if (x >= delims[i].getFirstIndex() && x <= delims[i].getLastIndex()){
Debug.Log("test");
charArray[x] = ' '; // Replacing the item with space
}
}
string output = new string(charArray); // Converting back to string
P.S. This is probably not the most optimal solution but at least it should work.
You should use LINQ for that
struct Delim
{
public int First { get; set; }
public int Last { get; set; }
}
static void Main(string[] args)
{
string cycleMe = "Hi StackOverflow! Here is my string.";
var delimns = new List<Delim> { new Delim { First=2, Last=4}, new Delim { First = 5, Last = 7 } };
var cut = cycleMe.Where((c, i) =>
!delimns.Any(d => i >= d.First && i <= d.Last));
Console.WriteLine(new string(cut.ToArray());
}
That means I am basically only selecting letters, at positions which are not part of any cutting range.
Also: Fix your naming. A delimiter is a character, not a position (numeric)
Currently I am trying to find the thirteen adjacent digits in a 1000-digit number that will have the greatest product.Now I written a function which is supposed to multiply the desired number of adjacent digits to be multiplied and later store the product in a list . The two parameters the functions takes are the desired number of adjacent digits and string which contains the number. But for some reason it wont stop running.
public static void giveProduct(int quantity, string word)
{
int product = 1;
int place1 = 0;
int place2 = quantity - 1;
int temp = 1;
string temp2;
while (place2 < word.Length)
{
for (int i = place1; i < place2; i++)
{
temp2 = word[i].ToString();
temp = Int32.Parse(temp2);
product = product * i;
}
products.Add(product);
product = 1;
place1 += quantity;
place2 += quantity;
}
}
Can't reproduce your issue, the method terminates "correctly" for any sensible input.
But anyway, that is far from the only issue in your implementation. Your method is not calculating correctly the maximum product because you are skipping through the string quantity characters at a time. You should be skipping one character at a time and taking the quantity long substring starting at that position.
For string 123456 and quantity 3 you are evaluating 123 and 456. You should be checking 123, 234, 345, etc.
Also, get into the habit of:
Validating inputs
Writing helper methods. The shorter a method, the harder to introduce a bug in it.
Consider all possible values that word can represent? Have you considered { 1234 }? (note the leading and trailing spaces). How about -1234?
Prepare for the worse. Make your code robust so its able to handle incorrect data; your program will crash if the input is 123$5.
With all that in mind, consider the following implementation:
First a simple helper method that evaluates the product of all the digits of a given string representing a number.
private static bool TryMultiplyDigits(string number, out int product)
{
Debug.Assert(number != null && number.Length > 0);
product = 1;
foreach (var c in number)
{
int digit;
if (int.TryParse(c.ToString(), out digit))
{
product *= digit;
}
else
return false;
}
return true;
}
Ok great, this method will give us the correct product or simply tell us it can't evaluate it for any input.
Now, a method that will create all the possible subtrings and return the maximum product found:
public static int GetMaximumProduct(string number, int quantity)
{
if (number == null)
throw new ArgumentNullException(nameof(number));
if (quantity < 1)
throw new ArgumentOutOfRangeException(nameof(quantity));
if (quantity > number.Length)
throw new ArgumentException($"{nameof(quantity)} can not be greater than the length of {nameof(number)}.");
var normalizedNumber = number.Trim();
normalizedNumber = normalizedNumber.StartsWith("-") ? normalizedNumber.Substring(1) : normalizedNumber;
if (string.IsEmpty(normalizedNumber))
{
product = 0;
return true;
}
var maximumProduct = 0;
for (var i = 0; i < normalizedNumber.Length - (quantity - 1); i++)
{
int currentProduct;
if (TryMultiplyDigits(normalizedNumber.Substring(i, quantity), out currentProduct))
{
if (currentProduct > maximumProduct)
{
maximumProduct = currentProduct;
}
}
else
{
throw new FormatException("Specified number does not have the correct format.");
}
}
return maximumProduct;
}
And we're done!
I am having the numbers follows taken as strings
My actual number is 1234567890123456789
from this i have to separate it as s=12 s1=6789 s3=3456789012345
remaining as i said
I would like to add as follows
11+3, 2+4, 6+5, 7+6, 8+7, 9+8 such that the output should be as follows
4613579012345
Any help please
public static string CombineNumbers(string number1, string number2)
{
int length = number1.Length > number2.Length ? number1.Length : number2.Length;
string returnValue = string.Empty;
for (int i = 0; i < length; i++)
{
int n1 = i >= number1.Length ? 0 : int.Parse(number1.Substring(i,1));
int n2 = i >= number2.Length ? 0 : int.Parse(number2.Substring(i,1));
int sum = n1 + n2;
returnValue += sum < 10 ? sum : sum - 10;
}
return returnValue;
}
This sounds an awful lot like a homework problem, so I'm not giving code. Just think about what you need to do. You are saying that you need to take the first character off the front of two strings, parse them to ints, and add them together. Finally, take the result of the addition and append them to the end of a new string. If you write code that follows that path, it should work out fine.
EDIT: As Ralph pointed out, you'll also need to check for overflows. I didn't notice that when I started typing. Although, that shouldn't be too hard, since you're starting with a two one digit numbers. If the number is greater than 9, then you can just subtract 10 to bring it down to the proper one digit number.
How about this LINQish solution:
private string SumIt(string first, string second)
{
IEnumerable<char> left = first;
IEnumerable<char> right = second;
var sb = new StringBuilder();
var query = left.Zip(right, (l, r) => new { Left = l, Right = r })
.Select(chars => new { Left = int.Parse(chars.Left.ToString()),
Right = int.Parse(chars.Right.ToString()) })
.Select(numbers => (numbers.Left + numbers.Right) % 10);
foreach (var number in query)
{
sb.Append(number);
}
return sb.ToString();
}
Tried something:
public static string NumAdd(int iOne, int iTwo)
{
char[] strOne = iOne.ToString().ToCharArray();
char[] strTwo = iTwo.ToString().ToCharArray();
string strReturn = string.Empty;
for (int i = 0; i < strOne.Length; i++)
{
int iFirst = 0;
if (int.TryParse(strOne[i].ToString(), out iFirst))
{
int iSecond = 0;
if (int.TryParse(strTwo[i].ToString(), out iSecond))
{
strReturn += ((int)(iFirst + iSecond)).ToString();
}
}
// last one, add the remaining string
if (i + 1 == strOne.Length)
{
strReturn += iTwo.ToString().Substring(i+1);
break;
}
}
return strReturn;
}
You should call it like this:
string strBla = NumAdd(12345, 123456789);
This function works only if the first number is smaller than the second one. But this will help you to know how it is about.
In other words, you want to add two numbers treating the lesser number like it had zeroes to its right until it had the same amount of digits as the greater number.
Sounds like the problem at this point is simply a matter of finding out how much you need to multiply the smaller number by in order to reach the number of digits of the larger number.
This sounds like homework, yes it is (of someone else), I asked a friend of mine who is learning C# to lend me some of his class exercises to get the hang of it.
So as the title says: How can I check if a number is a Palindrome?
I'm not asking for source code (although its very useful), but rather that someone explained how should the code should work, so that it can be applied to many different languages.
The Solution:
#statikfx searched SO for this and found the solution.
n = num;
while (num > 0)
{
dig = num % 10;
rev = rev * 10 + dig;
num = num / 10;
}
// If (n == rev) then num is a palindrome
I check for palindromes by converting the integer to a string, then reversing the string, then comparing equality. This will be the best approach for you since you're just starting out.
Since you're working in C# and this is homework, I'll use very obscure-looking Python that won't help you:
def is_palindrome(i):
s = str(i)
return s[::-1] == s
Convert that to C# and you'll have your answer.
Main idea:
Input number: 12321
Splitting the digits of the number, put them into an array
=> array [1, 2, 3, 2, 1]
Check if array[x] = array[arr_length - x] for all x = 0..arr_length / 2
If check passed => palindrome
There are many ways. Probably the simplest is to have 2 indexes, i at beginning and j at end of number. You check to see if a[i] == a[j]. If so, increment i and decrement j. You stop when i > j. When looping if you ever reach a point where a[i] != a[j], then it's not a palindrome.
Here's some working code. The first function tests if a number is palidromic by converting it to a string then an IEnumerable and testing if it is equal to its reverse. This is enough to answer your question. The main function simply iterates over the integers testing them one by one.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
public static bool IsPalindromic(long l)
{
IEnumerable<char> forwards = l.ToString().ToCharArray();
return forwards.SequenceEqual(forwards.Reverse());
}
public static void Main()
{
long n = 0;
while (true)
{
if (IsPalindromic(n))
Console.WriteLine("" + n);
n++;
}
}
}
Update: Here is a more direct method of generating palindromes. It doesn't test numbers individually, it just generates palindromes directly. It's not really useful for answering your homework, but perhaps you will find this interesting anyway:
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
public static void Main()
{
bool oddLength = true;
ulong start = 1;
while (true)
{
for (ulong i = start; i < start * 10; ++i)
{
string forwards = i.ToString();
string reverse = new string(forwards.ToCharArray()
.Reverse()
.Skip(oddLength ? 1 : 0)
.ToArray());
Console.WriteLine(forwards + reverse);
}
oddLength = !oddLength;
if (oddLength)
start *= 10;
}
}
}
My solution:
bool IsPalindrome(string str)
{
if(str.Length == 1 || str.Length == 0) return true;
return str[0] == str[str.Length-1] && IsPalindrome(str.Substring(1,str.Length-2));
}
Here's some pseudocode:
function isPalindrome(number) returns boolean
index = 0
while number != 0
array[index] = number mod 10
number = number div 10
index = index + 1
startIndex = 0;
endIndex = index - 1
while startIndex > endIndex
if array[endIndex] != array[startIndex]
return false
endIndex = endIndex - 1
startIndex = startIndex + 1
return true
Note that that's for base 10. Change the two 10s in the first while loop for other bases.
The following function will work for both numbers as well as for strings.
public bool IsPalindrome(string stringToCheck)
{
char[] rev = stringToCheck.Reverse().ToArray();
return (stringToCheck.Equals(new string(rev), StringComparison.OrdinalIgnoreCase));
}
zamirsblog.blogspot.com
in theory you want to convert the number to a string. then convet the string to an array of characters and loop the array comparing character (i) with character (array length - i) if the two characters are not equal exit the loop and return false. if it makes it all the way through the loop it is a Palindrome.
Interesting. I'd probably convert the number to a string, and then write a recursive function to decide whether any given string is a palendrome.
int n = check_textbox.Text.Length;
int check = Convert.ToInt32(check_textbox.Text);
int m = 0;
double latest=0;
for (int i = n - 1; i>-1; i--)
{
double exp = Math.Pow(10, i);
double rem = check / exp;
string rem_s = rem.ToString().Substring(0, 1);
int ret_rem = Convert.ToInt32(rem_s);
double exp2 = Math.Pow(10, m);
double new_num = ret_rem * exp2;
m=m+1;
latest = latest + new_num;
double my_value = ret_rem * exp;
int myvalue_int = Convert.ToInt32(my_value);
check = check - myvalue_int;
}
int latest_int=Convert.ToInt32(latest);
if (latest_int == Convert.ToInt32(check_textbox.Text))
{
MessageBox.Show("The number is a Palindrome number","SUCCESS",MessageBoxButtons.OK,MessageBoxIcon.Information);
}
else
{
MessageBox.Show("The number is not a Palindrome number","FAILED",MessageBoxButtons.OK,MessageBoxIcon.Exclamation);
}
public class Main {
public static boolean Ispalindromic(String word) {
if (word.length() < 2) {
return true;
}
else if (word.charAt(0) != word.charAt(word.length() - 1)) {
return false;
} else {
Ispalindromic(word.substring(1, word.length() - 1));
}
return true;
}
public static void main(String args[]) {
Scanner sc = new Scanner(System.in);
String word = sc.nextLine();
System.out.println(Ispalindromic(word) ? "it is palidromic" : "it is not palidromic");
}
}
This is my solution coming from a beginner:
Console.Write("Enter a number to check if palindrome: ");
bool palindrome = true;
int x = int.Parse(Console.ReadLine());
/* c is x length minus 1 because when counting the strings
length it starts from 1 when it should start from 0*/
int c = x.ToString().Length - 1;
string b = x.ToString();
for (int i = 0; i < c; i++)
if (b[i] != b[c - i])
palindrome = false;
if (palindrome == true)
Console.Write("Yes");
else Console.Write("No");
Console.ReadKey();
You need to reverse the number then compare the result to the original number.
If it matches, you have a palindrome. It should work irrespective of the number being even, odd or symmetric.
public static bool IsNumberAPalindrome(long num)
{
return long.Parse(string.Join("", num.ToString().ToCharArray().Reverse().ToArray())) == num ? true : false;
}
The implementation is bellow:
public bool IsPalindrome(int x) {
string test = string.Empty;
string res = string.Empty;
test = x.ToString();
var reverse = test.Reverse();
foreach (var c in reverse)
{
res += c.ToString();
}
return test == res;
}
You have a string, it can have integers, it can have characters, does not matter.
You convert this string to an array, depending on what types of characters the strings consist of, this may use to toCharArray method or any other related method.
You then use the reverse method that .NET provides to reverse your array, now you have two arrays, the original one and the one you reversed.
You then use the comparison operator (NOT THE ASSIGNMENT OPERATOR!) to check if the reversed one is the same as the original.
something like this
bool IsPalindrome(int num)
{
var str = num.ToString();
var length = str.Length;
for (int i = 0, j = length - 1; length/2 > i; i++, j-- ){
if (str[i] != str[j])
return false;
}
return true;
}
you could even optimise it