Hello I need some help in my library. I'm trying to implement a binary search from my List<> but its not working so well.
This is my Library class.
private class Library
{
List<object> library = new List<object>();
public void AddBook(string bookName, string bookAuthor, int bookIDNum)
{
//Add books to the library.
string bookEntry = bookName + " " + bookAuthor + " " + bookIDNum;
library.Add(bookEntry);
library.TrimExcess();
}
public void SearchLibrary(string bookName)
{
//Searches the library by title
library.Sort();
int low = 0;
int high = library.Count;
int mid = 0;
int steps = 0;
while(!bookName)
{
steps++;
mid = (low + high)/2;
if(bookName == library[mid])
{
return true;
}
else if(bookName < library[mid])
{
high = mid;
}
else
{
low = mid;
}
if(low > high-1 || high < low+1)
{
return false;
}
}
}
}
}
If there's a better way I can make a search method I would appreciate it, thank you.
Without commenting on the details of your algorithm, there are some issues with your code. You could've found all these issues by looking at the compiler's error messages.
You return true and false, yet your method specifies it returns void. Change that to bool instead.
public bool SearchLibrary(string bookName)
You do !bookName, where I presume you want to check whether it is null or not. You must do that explicitly in C#.
while (bookName != null)
You compare two strings, but the < operator is not overloaded for strings. Use CompareTo instead:
else if (bookName.CompareTo(library[mid]) < 0)
Not all code paths return a value. You'll have to return a value no matter what execution path it takes. For example, end your method with this:
return false;
Then there is an issue with your algorithm: it will run forever when there is no match. As I suspect this may be homework, I'll leave it an an exercise for the OP to solve this issue.
And if this isn't a homework exercise, you could've saved yourself some trouble:
The List<T> class has a method BinarySearch that uses the default comparer to compare the objects in the list.
library.Sort();
bool found = (library.BinarySearch(bookName) >= 0);
Related
Firstly I understand that there are several ways to do this and I do have some code which runs, but what I just wanted to find out was if anyone else has a recommended way to do this. Say I have a string which I already know that would have contain a specific character (a ‘,’ in this case). Now I just want to validate that this comma is used only once and not more. I know iterating through each character is an option but why go through all that work because I just want to make sure that this special character is not used more than once, I’m not exactly interested in the count per se. The best I could think was to use the split and here is some sample code that works. Just curious to find out if there is a better way to do this.
In summary,
I have a certain string in which I know has a special character (‘,’ in this case)
I want to validate that this special character has only been used once in this string
const char characterToBeEvaluated = ',';
string myStringToBeTested = "HelloWorldLetus,code";
var countOfIdentifiedCharacter = myStringToBeTested.Split(characterToBeEvaluated).Length - 1;
if (countOfIdentifiedCharacter == 1)
{
Console.WriteLine("Used exactly once as expected");
}
else
{
Console.WriteLine("Used either less than or more than once");
}
You can use string's IndexOf methods:
const char characterToBeEvaluated = ',';
string myStringToBeTested = "HelloWorldLetus,code";
string substringToFind = characterToBeEvaluated.ToString();
int firstIdx = myStringToBeTested.IndexOf(substringToFind, StringComparison.Ordinal);
bool foundOnce = firstIdx >= 0;
bool foundTwice = foundOnce && myStringToBeTested.IndexOf(substringToFind, firstIdx + 1, StringComparison.Ordinal) >= 0;
Try it online
You could use the LINQ Count() method:
const char characterToBeEvaluated = ',';
string myStringToBeTested = "HelloWorldLetus,code";
var countOfIdentifiedCharacter = myStringToBeTested.Count(x => x == characterToBeEvaluated);
if (countOfIdentifiedCharacter == 1)
{
Console.WriteLine("Used exactly once as expected");
}
else
{
Console.WriteLine("Used either less than or more than once");
}
This is the most readable and simplest approach and is great if you need to know the exact count but for your specific case #ProgrammingLlama's answer is better in terms of efficiency.
Adding another answer using a custom method:
public static void Main()
{
const char characterToBeEvaluated = ',';
string myStringToBeTested = "HelloWorldLetus,code";
var characterAppearsOnlyOnce = DoesCharacterAppearOnlyOnce(characterToBeEvaluated, myStringToBeTested);
if (characterAppearsOnlyOnce)
{
Console.WriteLine("Used exactly once as expected");
}
else
{
Console.WriteLine("Used either less than or more than once");
}
}
public static bool DoesCharacterAppearOnlyOnce(char characterToBeEvaluated, string stringToBeTested)
{
int count = 0;
for (int i = 0; i < stringToBeTested.Length && count < 2; ++i)
{
if (stringToBeTested[i] == characterToBeEvaluated)
{
++count;
}
}
return count == 1;
}
The custom method DoesCharacterAppearOnlyOnce() performs better than the method using IndexOf() for smaller strings - probably due to the overhead calling IndexOf. As the strings get larger the IndexOf method is better.
I'm working on application that's need search method I have listbox full with items every item have singer name and song title , I need be able to search the song title or singer name on same method that's what's I tried so far :
public void search_song()
{
for (int i = listbox_titles.Items.Count - 1; i >= 0; i--)
{
int char_count = listbox_titles.Items[i].ToString().Length;
if (listbox_titles.Items[i].ToString().ToLower().Contains(txt_to_search.Text) || listbox_titles.Items[i].ToString().StartsWith(txt_to_search.Text, StringComparison.Ordinal) || listbox_titles.Items[i].ToString().ToLower().Substring(0, char_count).Contains(txt_to_search.Text)) ;
{
//listbox_titles.SetSelected(i, true);
MessageBox.Show(listbox_titles.Items[i].ToString());
}
}
its work but only search from the beginning of items not middle
any ideas ??
this example of what's I want if item is **avicii waiting for love ** if I search waiting for love is should give me the item .
You just need to find the list box item that contains what you search for, so you don't need for StartsWith method, but since you're saying that your search method only works on the starting string, I can find that you're not converting the text to lower in StartsWith as in Contains, and that might what make the issue. So if your check is case insensitive you can just use the following:
public void search_song()
{
for (int i = listbox_titles.Items.Count - 1; i >= 0; i--)
{
int char_count = listbox_titles.Items[i].ToString().Length;
if (listbox_titles.Items[i].ToString().IndexOf(txt_to_search.Text, StringComparison.OrdinalIgnoreCase) >= 0)
{
//listbox_titles.SetSelected(i, true);
MessageBox.Show(listbox_titles.Items[i].ToString());
}
}
Make sure your code isn't like your original when you do get it working because you will always get the message box:
if (listbox_titles.Items[i].ToString().ToLower().Contains(txt_to_search.Text) ||
listbox_titles.Items[i].ToString().StartsWith(txt_to_search.Text, StringComparison.Ordinal) ||
listbox_titles.Items[i].ToString().ToLower().Substring(0, char_count).Contains(txt_to_search.Text)
) ;
{
//listbox_titles.SetSelected(i, true);
MessageBox.Show(listbox_titles.Items[i].ToString());
}
That green squiggle on the syntax highlighter is pointing you to a warning that it is an empty statement - you have a semi-colon at the end of the if, so your block of code is not conditional at all.
Edit:
public void search_song(string txt_to_search)
{
foreach(var t in listbox_titles.Items)
{
String s = t.ToString().ToLower();
if(s.Contains(txt_to_search.ToLower()))
{
//listbox_titles.SetSelected(i, true);
MessageBox.Show(s);
}
}
}
This works for me because it keeps the size of the lines down to a manageable level - obviously, you would need to index using a variable rather than foreach.
Edit:
If you need to know where the occurrences are you can always define an extension helper:
public void search_song(string txt_to_search)
{
foreach (var t in listbox_titles.Items)
{
if(txt_to_search.Occurrences(t.ToString(), false).Count > 0)
MessageBox.Show(t.ToString());
}
}
}
static class StringHelpers
{
public static List<int> Occurrences(this string pattern, string source, bool caseSensitive = true)
{
List<int> occurs = new List<int>();
if (String.IsNullOrEmpty(pattern) || String.IsNullOrWhiteSpace(pattern))
return occurs;
int index = 0;
if (!caseSensitive)
{
pattern = pattern.ToLower();
source = source.ToLower();
}
while (index < source.Length) // was (index < source.Length - 1)
{
if ((index = source.IndexOf(pattern, index)) < 0)
break;
occurs.Add(index);
++index;
}
return occurs;
}
}
Just capture the list and interrogate it.
Edit: just noticed that I had the scan stop short of the end (no idea why, old age brain fade perhaps). It probably won't make a major difference unless you are searching for single characters (which is what I happened to do!)
I add an indexer to my class. Is it proper coding to return null when the index doesn't exist? Then the consuming class would always have to check for null too. Is there a more elegant way?
public class ObjectList
{
private readonly ArrayList _objectList = new ArrayList();
public void Add(object value)
{
_objectList.Add(value);
}
public object this[int index]
{
get
{
if (CheckIndex(index))
return _objectList[index];
else
return null;
}
set
{
if (CheckIndex(index))
_objectList[index] = value;
}
}
private bool CheckIndex(int index)
{
if (index >= 0 && index <= _objectList.Count - 1 )
return true;
else
return false;
}
public int IndexOf(object value)
{
return _objectList.IndexOf(value);
}
}
class Program
{
static void Main(string[] args)
{
var oList = new ObjectList();
oList.Add("Some String");
oList.Add("new string");
//oList[1] = "Changed String";
Console.WriteLine("Index of new string = " + oList.IndexOf("new string"));
Console.WriteLine("Index of Some String = " + oList.IndexOf("Some String"));
Console.WriteLine("index 0 = {0} and index 5 = {1}", oList[0], oList[1]);
Console.WriteLine("Non-existing index 5 doesn't lead to crash when written to the console = {0} ", oList[5]);
if(oList[5]!=null)
Console.WriteLine("But when GetType is called on non-existing index then it crashes if not intercepted.", oList[5].GetType());
}
}
Also I wonder why doesn't the program crash when I write the value of an element to the console when it is null.
If I do not check for null when calling GetType() on it then it crashes, however.
How come?
the is "It depend"
1- when you build a libarary (for example DLL) and this dll is used by software that you do not know at all, or it is not already exits.
then the better is to throw an expcetion as the following
public object this[int index]
{
get {
if (index >= 0 && index <= _objectList.Count - 1 )
throw new IndexOutOfRangeException();
// your logic here .....
}
}
2- but if you build just a small class to use from anther place.
so you can follow the first way or you can return null.
public object this[int index]
{
get {
if (index >= 0 && index <= _objectList.Count - 1 )
return null;
// your logic here .....
}
}
and at that time you have to check the reference which returned by the indexer
but ( i prefer the first way, it is more clean)
The most normal way would be to throw an IndexOutOfRangeException. This is what the .NET builtin containers do, and many might expect the same behavior from a similar interface. As one might say, "it is better to ask for forgiveness than for permission."
However, the ArrayList object you use internally will already throw this if you try to access an invalid item. In addition, this overhead for bounds checking is already automatically done by the ArrayList itself, and so, in this situation, the index accessors should be simple wrappers around the ArrayList.
Another situation might be: What if I add null to your object? How could I then tell if an object I try to get in the future doesn't exist, or is just the value null? Exceptions fix this by interrupting the code flow so you can write code with the assumption that the code will work, but that you know what to do in the case of failure.
I am trying to extract the number out of the last part of the string, I have wrote a function to do this but am having problems with out of range index.
Here is the string
type="value" cat=".1.3.6.1.4.1.26928.1.1.1.2.1.2.1.1" descCat=".1.3.6.1.4.1.26928.1.1.1.2.1.2.1.3"
and here is my function
private static string ExtractDescOID(string property)
{
string result = "";
int startPos = property.LastIndexOf("descOid=\"") + "descOid=\"".Length;
int endPos = property.Length - 1;
if (endPos - startPos != 1)
{
//This now gets rid of the first . within the string.
startPos++;
result = property.Substring(startPos, endPos);
}
else
{
result = "";
}
if (startPos == endPos)
{
Console.WriteLine("Something has gone wrong");
}
return result;
}
I want to be able to get 1.3.6.1.4.1.26928.1.1.1.2.1.2.1.3 this part of the string. I have stepped through the code, the string length is 99 however when AND MY startPos becomes 64 and endPos becomes 98 which is actually within the range.
The second argument to Substring(int, int) isn't the "end position", but the length of the substring to return.
result = property.Substring(startPos, endPos - startPos);
Read the documentation again, the second value is the length, not the index.
As found on MSDN:
public string Substring(
int startIndex,
int length
)
A different approach to this problem could be through using string.Split() to take care of the parsing for you. The only reason why I would propose this (other than that I like to present additional options to what's already there, plus this is the lazy man's way out) is that from a code perspective, the code is easier IMHO to decompose, and when decomposed, is easier to comprehend by others.
Here's the sample program with some comments to illustrate my point (tested, btw).
class Program
{
static void Main(string[] args)
{
var someAttributesFromAnXmlNodeIGuess =
"type=\"value\" cat=\".1.3.6.1.4.1.26928.1.1.1.2.1.2.1.1\" descCat=\".1.3.6.1.4.1.26928.1.1.1.2.1.2.1.3\"";
var descCat = GetMeTheAttrib(someAttributesFromAnXmlNodeIGuess, "descCat");
Console.WriteLine(descCat);
Console.ReadLine();
}
// making the slightly huge assumption that you may want to
// access other attribs in the string...
private static string GetMeTheAttrib(string attribLine, string attribName)
{
var parsedDictionary = ParseAttributes(attribLine);
if (parsedDictionary.ContainsKey(attribName))
{
return parsedDictionary[attribName];
}
return string.Empty;
}
// keeping the contracts simple -
// i could have used IDictionary, which might make sense
// if this code became LINQ'd one day
private static Dictionary<string, string> ParseAttributes(string attribLine)
{
var dictionaryToReturn = new Dictionary<string, string>();
var listOfPairs = attribLine.Split(' '); // items look like type=value, etc
foreach (var pair in listOfPairs)
{
var attribList = pair.Split('=');
// we were expecting a type=value pattern... if this doesn't match then let's ignore it
if (attribList.Count() != 2) continue;
dictionaryToReturn.Add(attribList[0], attribList[1]);
}
return dictionaryToReturn;
}
}
I am trying to create a Collection with properties and their respective accessors.
Here is my code:
class SongCollection : List<Song>
{
private string playedCount;
private int totalLength;
public string PlayedCount
{
get
{
foreach (Song s in this)
{
if (s.TimesPlayed > 0)
{
return s.ToString();
}
}
}
}
public int TotalLength
{
get
{
foreach (Song s in this)
{
int total = 0;
total += s.LengthInSeconds;
}
return total;
}
}
}
I'm receiving the error at the "get" point. It tells me that not all code paths return a value... What exactly does this mean, and what am I missing?
Firstly, the reason you're getting that message is that if this is empty, then the code within the foreach block (which is where the required return statement is) would never be executed.
However, your TotalLength() function would always return the length of the first Song, as you're declaring your variable, setting its value, then returning it within the foreach block. Instead, you'd need to do something like this:
int totalLength = 0;
foreach(Song s in this)
{
total += s.LengthInSeconds;
}
return totalLength;
Your PlayedCount function suffers from similar issues (if the collection is empty or contains no elements whose TimesPlayed property is greater than 0, then there would be no way for it to return a value), so judging by your comment you could write it this way:
public int PlayedCount()
{
int total = 0;
foreach(Song s in this)
{
if (s.TimesPlayed > 0)
{
total++;
}
}
return total;
}
It means just as it says, not all code paths return a value.
In this case, if your list is empty, then it cannot call return. In a foreach, there must be at least one item for the code to execute. Now, maybe you know that the list will always contain a value, but the compiler can't know that
What would your method return if this did not evaluate?
if (s.TimesPlayed > 0)
{
return s.ToString();
}
try using an else to return an empty string or something
The fact that 'this' could have no songs- in that case the loops will not execute at all and there is no implicit return value in C#.
Furthermore, your getters don't really make sense unless you only ever had one song in the collection. You need something more like this:
public int TotalLength()
{
get
{
int total = 0;
foreach (Song s in this)
{
total += s.LengthInSeconds;
}
return total;
}
}
Finally, without knowing how you keep track of TimesPlayed for each individual song, I wouldn't know how to implement that getter, but I am sure you can figure it out with this much.