How to iterate through multiple variables? - c#

I'd like to create a short program to download several pictures from a website.
On a form, I would like to enter a root-link to a website with placeholders.
The placeholders can be defined with Start/End value and asc/desc.
For example: the original link is
google.de/1236-01.jpg
and I'd like to generate all links from
google.de/1236-1.jpg
up to
google.de/9955-12.jpg
So my input would be "google.de/[0]-[1].jpg" and placeholders are set to:
[0] = start 1236|end 9955|asc
[1] = start 1|end 12|asc
Via GetValidCharacters() I get a String-List of valid combinations for each entered placeholder (can be selected via ascending/descending + start&end).
The goal I'm struggling with is to build all combinations of this link, because I need to determine while runtime, how much placeholders I have.
My idea was to loop over an queue and enquueue each new build line, until there is none left with placeholders, but I don't know how to do this.
I need to make sure that all combinations are entered and they are entered only once.
private static void CreateDownloadList()
{
Queue<string> tmpQueue = new Queue<string>(); //temp queue
tmpQueue.Enqueue(DL_path); //DL_Path = google.de/[0]-[1].jpg
string line = "";
while ((line = tmpQueue.Dequeue()) != null) //not empty
{
if (line.Contains("[")) //placeholder
{
string tmpLine = line;
//how to determine, which placeholder is next?? need to know this and replace this with every combination, I get from GetValidCharacters(start, end, DESC)
}
else //done
{
_urlList.Add(line);
}
}
}

how about a simple for loop?
for (int i = 1236; i <= 9955; i++)
{
for (int j = 1; j <= 12; j++)
{
tmpQueue.Enqueue(string.Format("google.de/{0}-{1}.jpg", i, j));
}
}

I'm not going give you the full code but here is some pseudo code that would solve the problem.
given :
todostack -- stack object that holds a list of unresolved items
replace_map -- map object that holds marker string and map of all values
marker_list -- list of all markers
final_list -- list object that holds the results
(note you can probably use marker_list and replace_map in one object -- I have them separate to make my code clearer)
init :
push todostack with your starting string
set marker_list and replace_map to correct values (from parameters I assume)
clear final_list
algorithm :
while (there are any items in todostack)
{
curitem = todostack.pop
if (curitem contains a marker in marker_list)
{
loop for each replacement in replace_map
{
new_item = curitem replaced with replacement
todostack.push(new_item)
}
}
else
add curitem to final_list
}

#Hogan this was the hint to the correct way.
solution is this
private void CreateDownloadList()
{
Queue<string> tmpQueue = new Queue<string>();
tmpQueue.Enqueue(downloadPathWithPlaceHolders);
while(tmpQueue.Count > 0)
{
string currentItem = tmpQueue.Dequeue();
bool test = false;
if(currentItem.Contains("["))
{
foreach(Placeholder p in _placeholders)
{
if(currentItem.Contains(p.PlaceHolder))
{
foreach(string s in p.Replacements)
{
tmpQueue.Enqueue(currentItem.Replace(p.PlaceHolder, s));
}
test = true;
}
if(test)
break;
}
}
else
{
_downloadLinkList.Add(currentItem);
}
}
}

Related

Making a simple change function for a database app C#

Sorry if this is an ultra beginner question but, I need to be able to change all field values except the item number by giving its item number not array index. I already have a add and delete feature.
using System;
struct ItemData
{
public int ItemNumber;
public string Description;
public double PricePerItem;
public int QuantityOnHand;
public double OurCostPerItem;
public double ValueOfItem;
}
class Program
{
public static void Main()
{
int invItems = 0;
var items = new ItemData[10];
while (true)
{
Console.Write("1. Add, 2. Change, 3. Delete, 4. List:");
string strx = Console.ReadLine();
var choice = int.Parse(strx);
Console.WriteLine();
switch (choice)
{
This is what I have so far but not sure where to start
case 2: //change items
{
Console.Write("Please enter an item ID No:");
string input = Console.ReadLine();
int changeItemNumber = int.Parse(input);
bool foundItem = false;
for (int x = 0; x < invItems; x++)
{
if (items[x].ItemNumber == changeItemNumber)
{
//code
}
}
if (!foundItem)
{
Console.WriteLine("Item {0} not found", changeItemNumber);
}
break;
}
It's still a little unclear what you're asking so if this is wide of the mark then feel free to let me know.
Start by mapping out the flow of the process and then implement one piece at a time. Here's a basic series of operations you'll want to do, not necessarily in this order:
Input ItemNumber.
Find index of matching record in array.
If not found report error and stop.
Get record from user.
Set ItemNumber in record to entered value.
Store record in array at same index as original.
If you think about the way the rest of the program works you should see that some of those parts - maybe most of them - are already done somewhere in your code. Your add operation probably already has the Get record from user code. Your delete operation probably has Input ItemNumber and Find index of matching record in array done already.
So pull those parts out into methods and reuse them where it makes sense. Anywhere you're writing the same code multiple times with minor changes try to see if you can extract the code into a small method you can call with maybe some parameters. Your "input a number" code for instance - used for operation selection, item number entry and presumably whatever you're doing for delete - can be pulled out to a supporting method like this one:
static int InputNumber(string prompt)
{
Console.Write($"{prompt}: ");
var inputText = Console.ReadLine();
return int.Parse(inputText);
}
Now when you want to change the way you're doing number input you have a central place to make the changes and you don't have to worry about tracking down all the places you wrote the same code.
The same goes for the rest of the pieces. When two operations share some of the same code then pull that code out to supporting methods. Finding the index of an item in the array by some criteria can - and should - be one of those:
// Return array index of matching item or -1 if not found
int ItemIndex(int itemNumber)
{
for (int i = 0; i < invItems; i++)
{
if (items[i].ItemNumber == itemNumber)
return i;
}
return -1;
}
Likewise the code - whatever that looks like - that your add operation uses to get a record from the user.
Once you do that your change item case looks a lot simpler:
case 2: // change items
var itemNumber = InputNumber("Please enter an item ID");
var index = ItemIndex(itemNumber);
if (index == -1)
{
Console.WriteLine($"Item {itemNumber} not found.");
}
else
{
var newItem = InputItemData();
newItem.ItemNumber = itemNumber;
items[index] = newItem;
}
break;
For bonus points you can actually provide the current values to the InputItemData method so the user can re-use them instead of just asking for a complete new record each time.

Convert user entered string into an object in c#

I have a battleship like terminal game, the user enters a coordinate like e2, and the program checks one of the instance variables of my object Box, it checks whether hasShip is true, if its true then it will make the coordinate e2 false, and give the output "Ship destroyed"
The problem is that all my objects are called a1,a2,a3,a4,a5,b1,b2 and so on.
I have created 25 instances of the Box class. All names as such.
Once the program gets input, either e4 ,e5 etc. I want to convert that string into an object.
For example( I want to do something like this )
target = Console.ReadLine();
target.hasShip == true;
I want to convert target into an object, then use target to use the methods of the Box class.
Because the other approach requires me to make loads of if statements, which isn't clean code, doesn't look good, and is a waste if you ask me.
Thanks in advance,
New Answer: use an Array
I am slow. I did not pay attention that you are making a battleship-like game, and that we know that the "boxes" make a rectangle. We can store this efficiently in an array.
Why I did not catch up to this fact earlier? I guess I need to wake up properly.
So, use an array:
var board = new Box[5, 5];
Now, to populate it, we can do a double for loop:
for(var indexRow = 0; indexRow < 5; indexRow++)
{
for(var indexCol = 0; indexCol < 5; indexCol++)
{
board[indexRow, indexCol] = new Box();
}
}
Note: pay attention that the indexes go from 0 to 4. For a total of 5 values: {0, 1, 2, 3, 5}.
And to query from it, we will need the indexes...
Addendum on populating the array
In comments, OP has said that each Box has an id and the ship positions are picked at random.
We can give the id in the loop:
for(var indexRow = 0; indexRow < 5; indexRow++)
{
for(var indexCol = 0; indexCol < 5; indexCol++)
{
var box = new Box();
box.vhID = (((char)(((int)'a') + indexRow))).ToString() + ((char)(((int)'1') + indexCol)).ToString();
board[indexRow, indexCol] = box;
}
}
What I am doing here is constructing the id from the indexes. Basically taking the value of 'a' and adding the indexRow will give us 'a' when indexRow is 0, 'b' when it is 1 and so on. Similarly, we get the digit that represents the column.
Note: We convert the char to int, do the addition, then convert back to char... and then from char to string. Once we have string, we can concatenate them.
I do not think we need this id. But, hey, you can do it like this.
OP also mentions that he will pick 4 ship positions at random. Fair enough:
var random = new Random();
for (var ships = 0; ships < 4; ships++)
{
board[random.Next(0, 4), random.Next(0, 4)].hasShip = true;
}
Since the user inputs an string, I suggest to create a function to convert it to the index pair:
var input = Console.ReadLine();
if (TryGetCoordinates(input, out int irow, out int icol))
{
var target = board[irow, icol];
}
else
{
Console.WriteLine("The cell {0} does not exist.", input);
}
// ...
bool TryGetCoordinates(string cell, out int indexRow, out int indexCol)
{
// ...
}
Start by validating null:
bool TryGetCoordinates(string cell, out int indexRow, out int indexCol)
{
indexRow = -1;
indexCol = -1;
if (cell == null)
{
return false;
}
// ...
}
Note: Feel free to use Trim, ToUpper or ToUpperInvariant.
We know that must be a letter followed by a digit, we can validate the length:
bool TryGetCoordinates(string cell, out int indexRow, out int indexCol)
{
indexRow = -1;
indexCol = -1;
if (cell == null)
{
return false;
}
if (cell.Length != 2)
{
return false;
}
// ...
}
We extract the characters and from them the coordinates. Noting that the first one is a letter, and the other a digit. We can also validate they are withing bounds.
bool TryGetCoordinates(string cell, out int indexRow, out int indexCol)
{
indexRow = -1;
indexCol = -1;
if (cell == null)
{
return false;
}
if (cell.Length != 2)
{
return false;
}
indexRow = (int)cell[0] - (int)'a';
indexCol = (int)cell[1] - (int)'1';
return indexRow < 5 && indexRow >= 0 && indexCol < 5 && indexCol >= 0;
}
And of course, you can do a loop of the validation similar to what was explained in the old answer.
Note: the issue with value types I describe in the old answer still applies with the array.
Old Answer: Use a Dictionary
I believe you do not want to convert the string to an object (the string is an object by the way), you want to pick the Box object you previously created based on the string. And you want to do it without using if statements. What you need is a dictionary.
So, you would have Dictionary<string, Box> meaning that it is a dictionary that you can query by string and stores Box.
Addendums:
In this case, string is the key type, by which we will access the dictionary. When we add an object to the dictionary we identify it with a key, and when we retrieve it, we also use the key. The key does not have to be string, you can choose a different type. string is convenient in this case because it is what you get from Console.ReadLine().
You can create the dictionary to store whatever type you need. If you do not need Box, you can create a dictionary that stores something else.
Creating and populating the Dictionary
Then, you add to the Dictionary all your Box objects, like this:
var dict = new Dictionary<string, Box>();
// ...
dict.Add("a1", CreateBoxA1());
Where CreateBoxA1 represents whatever means you have to create the object. No, you do not need to create a method for each Box... you can do it like this:
dict.Add("a1", new Box());
Or whatever. I do not know how you create them, so consider that a placeholder, ok? ok.
Querying and retrieving values from the Dictionary
Once you have all your Box instances in your dictionary, you can get the one you need using the string:
Console.WriteLine("Enter the name of the Box:");
var name = Console.ReadLine();
var target = dict[name];
Addendum: The value you get from dict[name] is the value that you added to the dictionary with that key. So, if the user typed "a1" it dict[name] will be the value that we added with "a1" (dict.Add("a1", new Box());). Again, if what you need is not Box you can create a dictionary to store a different type.
Input validation
You can also use the Dictionary to validate if the string corresponds to a Box that exists, for example:
Console.WriteLine("Enter the name of the Box:");
var name = Console.ReadLine();
if (dict.KeyExists(name))
{
var target = dict[name];
// ...
}
else
{
Console.WriteLine("The Box {0} does not exist", name);
}
It goes without saying, but... you can make a loop based on that, for example:
Box target = null;
while(true)
{
Console.WriteLine("Enter the name of the Box:");
var name = Console.ReadLine();
if (dict.KeyExists(name))
{
target = dict[name];
break;
}
Console.WriteLine("The Box {0} does not exist", name);
}
Also, it goes without saying, but... you can add your own validations and sanitation steps. For example using ToUpper, ToUpperInvariant or Trim. And I would remind you that changing strings to lower or upper case is culture sensitive.
See also: Best Practices for Using Strings in .NET.
Editing an removing objects from the dictionary
Once you have the object you retrieved from the Dictionary...
var target = dict[name];
We can use it, and even modify it:
var target = dict[name];
if (target.hasShip) // no need for "== true" if hasShip bool
{
target.hasShip = false;
Console.WriteLine("Ship Destroyed");
}
An special note must be done if Box is value type. For a custom type that means that it is not a class but a struct. The problem with value types is that they are copied on assignment, meaning that when you do var target = dict[name]; with a value type, you get a copy. You must then update the dictionary once you manipulated it:
var target = dict[name];
if (target.hasShip) // no need for "== true" if hasShip bool
{
target.hasShip = false;
dict[name] = target;
Console.WriteLine("Ship Destroyed");
}
Note: As I said above, this is only needed for value types.
And you can even remove the Box from the dictionary if that is necesary:
dict.Remove(name);

search string inside string

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!)

How to handle values of values of values

I'm reading in a text file that contains data for 3D elements and store them in a dictionary dict in C#. The main objects are OPEN_SHELLs and CLOSED_SHELLs. These contain multiple ADVANCED_FACEs. These again contain a single FACE_OUTER_BOUND and multiple FACE_BOUNDs. These again contain more values and so on until there are finally numerical values.
For now I have a class Step that contains
List<List>string>> closedShell; //contains all closed shells with their values
List<List<string>> openShell; //contains all open shells with their values
List<List<string>> closedShellAdvFace; //contains all closed advanced faces...
List<List<string>> openShellAdvFace; //contains all open advanced faces...
...
I iterate through each list to get the next values and so on. Now this doesn't seem really efficient as I'm using duplicate code for closed and open lists.
An examplary code for this:
string closedShellKey = "";
string closedShellValue = "";
string openShellKey = "";
string openShellValue = "";
// For CLOSED_SHELLs
for (int shellListIndex = 0; shellListIndex < stepObj.GetClosedShells().Count; shellListIndex++)
{
for (int valuesCount = 1; valuesCount < stepObj.GetClosedShells()[shellListIndex].Count - 1; valuesCount++)
{
if (dict.ContainsKey(stepObj.GetClosedShells()[shellListIndex][valuesCount]))
{
closedShellKey = stepObj.GetClosedShells()[shellListIndex][valuesCount];
dict.TryGetValue(closedShellKey, out closedShellValue);
stepObj.SetCsAdvFace(SplitValues(closedShellValue));
} else
{
//Throw Exception
}
}
}
// For OPEN_SHELLs
for (int shellListIndex = 0; shellListIndex < stepObj.GetOpenShells().Count; shellListIndex++)
{
for (int valuesCount = 1; valuesCount < stepObj.GetOpenShells()[shellListIndex].Count - 1; valuesCount++)
{
if (dict.ContainsKey(stepObj.GetOpenShells()[shellListIndex][valuesCount]))
{
openShellKey = stepObj.GetOpenShells()[shellListIndex][valuesCount];
dict.TryGetValue(openShellKey, out openShellValue);
stepObj.SetOsAdvFace(SplitValues(openShellValue));
} else
{
//Throw Exception
}
}
}
This goes on for the next values, etc.
What would be a really good and efficient way to implement each of these steps?
Maybe create an openShellObject and a closedShellObject to further seperate?
How would I handle data that contains different data that again contains further different data, etc.?
Hope this is clear enough
First, note that Dictionary.TryGetValue already does the work of Dictionary.ContainsKey so you only need the former.
If I understand this, you need to iterate over multiple collections, applying an operation that varies only in one step, according to each collection category, e.g. closed face, open face, etc. How to separate that step from the iteration code? I'd suggest either Template Method pattern or Method As Parameter (MAP). For this problem I'd probably choose MAP because the collection items vary by category not data type, and probably it means less coding.
In the pseudocode below I've assumed that the final step in each iteration always involves a method like stepObj.SetCsAdvFace that takes a string[] value returned by SplitValues. This is why, in the Apply method below, the parameter method is a delegate that takes a string[] parameter, so it matches either stepObj.SetCsAdvFace or stepObj.SetOsAdvFace, whichever is required for the relevant collection.
private void Apply(List<List<string>> list, Action<string[]> method)
{
foreach (var items in list)
{
for (int valuesIndex = 1; valuesIndex < items.Count - 1; valuesIndex++)
{
var key = items[valuesIndex];
string values;
if (dict.TryGetValue(key, out values))
{
method(SplitValues(values));
}
else
{
//Throw Exception
}
}
}
}
Apply(stepObj.GetClosedShells(), stepObj.SetCsAdvFace);
Apply(stepObj.GetOpenShells(), stepObj.SetOsAdvFace);
...
First get rid of two for loops, use IEnumerable instead:
var allShellKeys = stepObj.GetClosedShells().Union(stepObj.GetOpenShells()).SelectMany(i => i.Skip(1).Take(i.Count() - 2))
Then you can iterate over all values in one loop:
string anyShellValue;
foreach (var anyShellKey in allShellKeys)
{
if (dict.TryGetValue(anyShellKey, out anyShellValue))
{
stepObj.SetCsAdvFace(SplitValues(anyShellValue));
}
else
{
//Throw Exception
}
}

Two lists with a relationship, how to display them to the user?

My problem is that I want to make a program that uses two lists, which is almost impossible for me to understand. Okay, so the deal is that I want to make a program where you first type in a city name and then the temperature for the city. This is where the relationship comes from.
I have started by making a "list class", which looks like this:
class citytemp
{
private string city;
private double temp;
public citytemp(string city, double temp)
{
this.city = city;
this.temp = temp;
}
public string City
{
get { return city; }
set { city = value; }
}
public double Temp
{
get { return temp; }
set { temp = value; }
}
}
Then I make the list in the program like this
List<citytemp> temps = new List<citytemp>();
Which all looks good to me. But when I'm trying to show the user the list nothing shows up. I use these lines to show it:
for (int i = 0; i > temps.Count; i++)
{
Console.WriteLine(temps[i].City, temps[i].Temp);
}
BTW: I add "things" to the list by these rows:
temps.Add(new citytemp(tempcity, temptemp));
...where tempcity and temptemp are temporary variables. They are only there to make it more simple for me to add them to the list, since I'm using a switch statement to add them to the list.
To make things more clear, my problem is that I don't know how I'm suppose to show the list to the user in the program.
Your problem is in the for loop. Change it to this
for (int i = 0; i < temps.Count; i++)
i.e. change the greater than > operator to a less than <
You have an error in your for loop.
for (int i = 0; i > temps.Count; i++)
it should be:
for (int i = 0; i < temps.Count; i++)
First of all, I'm not sure what you mean by "2 lists" as you only have one list in your code.
However, the problem you're having with "nothing shows up" is easy to fix.
This line of code:
for (int i = 0; i > temps.Count; i++)
Should be read as follows:
i = 0;
while (i > temps.Count)
{
... rest of your loop body here
i++;
}
if you read this, you'll notice that the second part of the for statement is not when to terminate but how long to keep going.
Change it to this, and you should be good:
for (int i = 0; i < temps.Count; i++)
^
+-- changed from >
I think a hashtable, specifically a dictionary would help you here:
var cityTemps = new Dictionary<string, double>();
cityTemps.Add("TestCity", 56.4);
foreach (var kvp in cityTemps)
Console.WriteLine("{0}, {1}", kvp.Key, kvp.Value);
In addition to the loop that has been mentioned, be careful withConsole.WriteLine because it takes in a String as first argument which it assumes is a format and object[] params as a second parameter. When you pass temps[i].City to it since its String it will think that its the format and temps[i].Temp is the parameter and won't display correctly.
What you want instead:
Console.WriteLine("City: {0} Temp: {1}", temps[i].City, temps[i].Temp);
Here I am using "City: {0} Temp: {1}" as the format for the string and the proper parameters.
This answer is to save you a headache later on wondering why only the city name is being displayed.

Categories

Resources