How to count multiple items in SteamBot - c#

In Jassecar's SteamBot, is there a way to count items of different defindexes and add them up?
I tried this:
switch(message.ToLower())
{
case "ticket":
foreach (ulong id in Trade.OtherOfferedItems)
{
int totalScrap = 0;
Trade.SendMessage("Please pay 3.44 ref");
var items = Trade.OtherOfferedItems;
var itemType = Trade.OtherInventory.GetItem(id);
if (itemType.Defindex == 5002)
{
totalScrap = items.Count * 9;
}
else if (itemType.Defindex == 5001)
{
totalScrap = items.Count * 3;
}
else if (itemType.Defindex == 5000)
{
totalScrap = items.Count;
}
Trade.RemoveAllItems();
if (totalScrap > 31)
{
Trade.AddItemByDefindex(725);
int Change = 31 - totalScrap;
while(Change > 0)
{
Trade.AddItemByDefindex(5000);
Change - 1;
}
}
else
{
Trade.SendMessage("You have only added a total of " + totalScrap + " Scrap, please put up the correct amount and type ticket again");
}
}
break;
But it will count 1 Scrap (item Defindex of 5000) and 1 Refined Metal (item Defindex of 5002) as both 9 and say to the user he has added a total of 18 scrap where he as only added 10. (1 refined = 9 Scrap)

You're iterating over id's in Trade.OtherOfferedItems.
Error#1
On every iteration of the loop you're clearing totalScrap, by setting it to 0.
Error#2
In here:
`totalScrap = items.Count * 9`
you're saying "the total amount of scrap is the ammount of my items multiplied by 9" which is wrong, because if you've got 2 items with possibly different Defindexes (5002 and 5000 in your case), it gives you 18.
Error#3
Then you've got:
Trade.RemoveAllItems();
which I suppose will remove all the items from the collection you're actually iterating over - I'm really amazed that your loop doesn't crash.
Put the totalScrap outside. Iterate over every item and THEN do all the clearing, removing and checking whether the sum is>31 or not.

Related

Problem sorting objects based on value of its properties

I'm sitting here with a school project I can't seem to figure out; I am to create a console application that let's the user enter a number of salesmen for a hypothetical company. The information about the salesmen includes their name, district and number of sold items. The salesmen are then to be sorted into different levels according to the number of sold items. Finally, the salesmen are to be printed to the console. The printing should be done one level at the time, if for instance two salesmen reached level one and one reached level 2, the console should look something like:
John Johnsson, someDistrict, 33 items sold
Mary Mara, someOtherDistrict, 40 items sold
2 salesmen reached level 1
Judy Juggernut, anotherDistrict, 67 items sold
1 salesmen reached level 2
And it's the printing part in question that gives me trouble. When the user enters information a new object of a salesman-class is created and stored in an array of salesmen. The number of items sold for each salesman is then checked and each salesman is assigned a level. The array is then sorted using bubblesort, to have the salesman with the least amount of sales on salesmanArray[0] and so on.
Everything works fine until its time to print the results to the console. I tried to write a method for it:
public static void sortering(Salesman[] salesmenArray)
{
Salesman[] level1 = new Salesman[salesmenArray.Length];
Salesman[] level2 = new Salesman[salesmenArray.Length];
Salesman[] level3 = new Salesman[salesmenArray.Length];
Salesman[] level4 = new Salesman[salesmenArray.Length];
for (int i = 0; i < salesmenArray.Length - 1; i++)
{
if (salesmenArray[i].level == 1)
{
level1[i] = salesmenArray[i];
} else if (salesmenArray[i].level == 2)
{
level2[i] = salesmenArray[i];
} else if (salesmenArray[i].level == 3)
{
level3[i] = salesmenArray[i];
} else if (salesmenArray[i].level == 4)
{
level4[i] = salesmenArray[i];
}
}
if (level1.Length != 0)
{
for (int i = 0; i < level1.Length - 1; i++)
{
Console.WriteLine("Name: " + level1[i].name);
Console.WriteLine("District: " + level1[i].district);
Console.WriteLine("Items sold: " + level1[i].itemsSold);
}
Console.WriteLine("" + (level1.Length - 1) + " sellers have reached level 1");
}
//Same thing for level 2, 3 and 4
}
What I'm trying to do is 4 new arrays for the different levels. I then loop through the array with all the salesmen and place the salesmen into the arrays in accordance to the number of sold items. I then check if the level-arrays are empty. If they aren't, I loop through them printing out the name, district and items sold for each salesman. Finally also printing out how many sellers there are in each level. When running the program, I get an error on the line
Console.WriteLine("Name: " + level1[i].name);
Saying "System.NullReferenceException has been thrown "Object reference not set to an instance if an object".
I would assume that means level1[i].name isn't referencing to an object but I don't really know how to go from there... Any advice or pointers would be greatly appriciated!
You are getting a System.NullReferenceException because you are initializing the level arrays with the same length as the salesmen array, but you are only adding salesmen to the level arrays based on their level.
So there will be not initialized null elements in the level arrays, and when you try to access the name property of a null element, you get the exception because you try to read property of absent element.
To fix this, you may use List<Salesman> instead of Salesman[]. List<T> is a generic dynamic array and you can iterate over its items in the same way:
public static void sortering(Salesman[] salesmenArray)
{
var level1 = new List<Salesman>();
var level2 = new List<Salesman>();
var level3 = new List<Salesman>();
var level4 = new List<Salesman>();
for (int i = 0; i < salesmenArray.Length; i++)
{
if (salesmenArray[i].level == 1)
{
level1.Add(salesmenArray[i]);
}
else if (salesmenArray[i].level == 2)
{
level2.Add(salesmenArray[i]);
}
else if (salesmenArray[i].level == 3)
{
level3.Add(salesmenArray[i]);
}
else if (salesmenArray[i].level == 4)
{
level4.Add(salesmenArray[i]);
}
}
if (level1Count > 0)
{
for (int i = 0; i < level1.Count; i++)
{
Console.WriteLine("Name: " + level1[i].name);
Console.WriteLine("District: " + level1[i].district);
Console.WriteLine("Items sold: " + level1[i].itemsSold);
}
Console.WriteLine("" + level1Count + " sellers have reached level 1");
}
//Same thing for level 2, 3 and 4
}
Here is some other improvments then you can do with your code. For example if Salesman.level may contains only values form the list [1, 2, 3, 4] you can store levels in the List of List<Salesman> or in the array of List<Salesman> and add items in the easier way. Also string interpolation is an easier, faster, and more readable string concatenation syntax.
// here we creates a new array of lists and initialize it with 4 empty lists of Salesman
var levels = new List<Salesman>[]
{
new List<Salesman>(),
new List<Salesman>(),
new List<Salesman>(),
new List<Salesman>()
};
foreach(var salesmen in salesmenArray)
{
// (salesmen.level - 1)-th list stores salesmen with that level
levels[salesmen.level - 1].Add(salesmen);
}
// you can iterate salesmen of all levels with nested loops
for(int level = 0; level < levels.Lenth; level++)
{
foreach(var salesman in levels[level])
{
Console.WriteLine($"Name: {salesman.name}");
Console.WriteLine($"District: {salesman.district}");
Console.WriteLine($"Items sold: {salesman.itemsSold}");
}
// Count property gets the number of elements contained in the List<T> so you don't need to decrement this value for display the number of salesmen with this level
Console.WriteLine($"{levels[level].Count} sellers have reached level {level + 1}");
}
Finally there is an interesting mechanism to manipulate collections in .NET called LINQ. You can use LINQ syntax to select, filter, group and aggregate data. LINQ is readable efficiency and powerful tool. Here's a sample of your code rewritten with LINQ:
foreach(var group in salesmenArray
.GroupBy(salesman => salesman.level)
.OrderBy(groups => groups.Key))
{
foreach(var salesman in group)
{
Console.WriteLine($"Name: {salesman.name}");
Console.WriteLine($"District: {salesman.district}");
Console.WriteLine($"Items sold: {salesman.itemsSold}");
}
Console.WriteLine($"{group.Count()} sellers have reached level {group.Key}");
}
Where is the bubble sort? Sort the array first, then loop through the array with counters to count each level and print the output from the same loop.
// bubble sort
for (int i = 0; i < salesmenArray.Length; i++)
for (int j = 0; j < salesmenArray.Length - 1; j++)
if(salesmenArray[j].itemsSold > salesmenArray[j+1].itemsSold)
{
//swap positions
//...
}
int counter = 0;
int lastLevel = 1; //if 1 is the min level
for (int i = 0; i < salesmenArray.Length; i++)
{
if(salesmenArray[j].level != lastLevel)
{
//print summary
//...
counter = 0; //reset counter
}
// print detail lines
Console.WriteLine("Name: " + level1[i].name);
Console.WriteLine("District: " + level1[i].district);
Console.WriteLine("Items sold: " + level1[i].itemsSold);
counter++;
}
//print final summary for last level
//...
The ... are lines for you to fill.
Vadim's answer details why your code is failing. He proposed a way to solve the problem via Lists. I would also follow that road.
On the other hand, your approach was valid, but not very efficient and has a few traps for yourself (as Vadim mentioned, you are creating 4 level arrays with the same size of the total of salesmen, then you assign them to each level via i, leaving some null gaps). If you want your approach to work, in the printing for-loop, before getting level1[i].name, check that level1[i] is not null.
If you are using an IDE, I would recommend you to put a breakpoint inside the for-loop and see the contents of level1.
Good luck learning!

How to tell if I reach the beginning of a list when using a reverse For Loop?

I am fairly new to C#, so I'm sorry if this is a simple question
--
I have a list of stock objects and I am using a for loop to go backwards through the list. Each property of the stock object is set to a textbox to display the value and there is a button that allows the user to cycle through each object, changing the values of the textboxes.
I need to tell if I reach the beginning of the list so that I can deactivate the button that allows the user to go backwards through the list.
Note -
The Count is > 1 because I had to skip the first item in the list.
Here is my code:
if (stockList.Count > 1)
{
for (int i = stockList.Count - 1; i >= 0; i--)
{
txtName.Text = stockList[i].Name;
numLastPrice.Value = stockList[i].LastPrice;
numOpeningPrice.Value = stockList[i].OpeningPrice;
numLowPrice.Value = stockList[i].LowPrice;
numHighPrice.Value = stockList[i].HighPrice;
if (i == ???)
{
txtName.Text = stockList[i].Name;
numLastPrice.Value = stockList[i].LastPrice;
numOpeningPrice.Value = stockList[i].OpeningPrice;
numLowPrice.Value = stockList[i].LowPrice;
numHighPrice.Value = stockList[i].HighPrice;
btnBack.Enabled = false;
}
}
If you have 10 items in that list, then you're going backwards from 9 to 0 (indexes are zero based by default)
In your case, 0 indicates the first item in your list, so just check the index is 0
if (i == 0)
(edit after reading comments)
in your for loop you declare i as an int with the value of .Count - 1:
for (int i = stockList.Count - 1; i >= 0; i--)
so in your loop i is just a variable, but as a result of the way you declared it, it will also be the index value of your list as you iterate through the loop.
Hope that helps.
I hope that the following snippet helps you see the relationship between i and the list:
var myList = new List<string>() { "A", "B", "C", "D", "E" };
for (int i = myList.Count() - 1; i >= 0; i--)
{
Console.WriteLine($"i:{i}, myList[{i}]={myList[i]}");
if (i == 3)
{
//I can access the elements at an index different than `i`
Console.WriteLine($"i:{i}, Seaky peek at the 5th element (index 4): {myList[4]}");
}
}
// This would cause a compilation error because `i` is being used outside of `for`
//i = 100; // Error: The name 'i' does not exist in the current context
Console.WriteLine($"First item is myList[0] and is '{myList[0]}'");
Console.WriteLine($"Last item is myList[myLIst.Count()-1] ans is '{myList[myList.Count() - 1]}'");
// Let's go through the list again
for (int someNameForIndex = myList.Count() - 1; someNameForIndex >= 0; someNameForIndex--)
{
Console.WriteLine($"i:{someNameForIndex}, myList[{someNameForIndex}]={myList[someNameForIndex]}");
}
This generates the following output
i:4, myList[4]=E
i:3, myList[3]=D
i:3, Seaky peek at the 5th element (index 4): E
i:2, myList[2]=C
i:1, myList[1]=B
i:0, myList[0]=A
First item is myList[0] and is 'A'
Last item is myList[myLIst.Count()-1] ans is 'E'
i:4, myList[4]=E
i:3, myList[3]=D
i:2, myList[2]=C
i:1, myList[1]=B
i:0, myList[0]=A

Detecting spikes and drops in a long list of integers C#

Hi there I'm trying to write a method that reads every number in a list and detects where it spikes and drops. This is what I have so far:
I basically figure if I loop through the list, loop through it again to get the next number in the list, then detecting if it's more or less. If it's more it'll save to one list, vice versa.
What I want this method to do is determine where there's a spike of 100 or more, save the point that it does this (which is 'counter') and also save the points where the numbers drop.
This so far notices only a drop and it will save every number in the list until it spikes again and once it has spiked it shows no numbers, until it drops again and so on.
I've put 'check' and 'check2' to try and counteract it saving every number after it notices a drop and only save it once but no luck.
Any ideas?
public void intervalDetection()
{
//Counter is the point in the list
int counter = 0;
int spike = 0;
int drop = 0;
//Loop through power list
for (int i = 0; i < powerList.Count(); i++)
{
counter++;
int firstNumber = powerList[i];
//Loop again to get the number after??
for (int j = 1; j < 2; j++)
{
//Detect Spike
spike = firstNumber + 100;
drop = firstNumber - 100;
if (powerList[j] > spike)
{
if (check2 == false)
{
intervalStartList.Add(counter);
check2 = true;
check = false;
}
}
//Detect Drop
else if (powerList[j] < drop)
{
if (check == false)
{
intervalEndList.Add(counter);
check = true;
check2 = false;
}
}
}
Create integer "average"
Loop through List/Array and add each value to average
Divide average by the count of the List/Array
Loop through List/Array and check deviation to the average integer
derp
Code example:
public class DSDetector {
public static List<int>[] getDropsnSpikes(List<int> values, int deviation) {
List<int> drops = new List<int>();
List<int> spikes = new List<int>();
int average = 0;
foreach (int val in values) {
average += val;
}
average = average/values.Count;
foreach (int val in values) {
if (val < average - deviation) {
drops.add(val);
}
if (val > average + deviation) {
spikes.add(val);
}
}
//derp.
return new List<int>{drops, spikes};
}
}
not tested but I think it works. Just try it.
What exactly do you mean saying "peaks" and "drops"?
Let's say you have following list of integers
112, 111, 113, 250, 112, 111, 1, 113
In this case value 250 is peak and 1 drop relative to average value and you can get it using Kai_Jan_57 answer.
But also 250 is peak to previous value 113 and 112 is drop for 250.
If you want to find local peaks and drops you can check each value relative to previous and next: find average as avg=(val[i-1]+val[i+1])/2 and check if val[i]>avg + 100 (peak) or val[i]

Descending Numbers C#

I was able to write this tiny segment of this program to display the numbers in Ascending order but i still cant get it to display it in Descending order.
Basics of what this program should do is takes in two number values from the user in the form of "From" and "To" and displays it as a list in the listbox. The users choice of either ascending or descending order depends on which of the two Radio buttons he has selected.
private void btnCalc_Click(object sender, EventArgs e)
{
double fromNum, toNum, total = 0;
fromNum = double.Parse(txtFrom.Text);
toNum = double.Parse(txtTo.Text);
lstResult.Items.Clear();
lblResult.Text = "";
if (radAsc.Checked)
{
while (fromNum <= toNum)
{
lstResult.Items.Add(fromNum);
total = total + fromNum;
fromNum++;
}
}
else
{
while (fromNum >= toNum)
{
lstResult.Items.Add(fromNum);
total = total + toNum;
toNum--;
}
}
lblResult.Text = total.ToString();
}
Here's an image to what the program looks like.
http://imgur.com/SVwN3Tx
Note:- I am completely new to C# and I've just started taking it in College.
I suggest using for loop instead of while which makes the code easy to implement:
if (radAsc.Checked)
{
// num += 1: - I've seen odds/even switch on the screenshot
// so you may want to change/add num += 1 into num += 2
for (double num = fromNum; num <= toNum; num += 1) {
lstResult.Items.Add(num);
total = total + num;
}
}
else
{
// Descending order:
// - start from the bottom (toNum)
// - loop until the top (fromNum)
// - descend by 1 (num -= 1)
for (double num = toNum; num >= fromNum; num -= 1) {
lstResult.Items.Add(num);
total = total + num;
}
}
You're decrementing the wrong value
while (fromNum >= toNum)
{
lstResult.Items.Add(fromNum);
total = total + toNum;
toNum--;
}
So, here's what you're doing:
Say fromNum is 10, and toNum is 1.
After your first iteration, fromNum is still 10 but toNum is 0. Decrement the fromNum instead of toNum and it should work accordingly.
EDIT
Couple things to take note. If total is collecting the sum of all numbers, a neat way to write:
total = total + value;
is
total += value;.
You should also verify that the numbers will actually work before going into your logic. So if the radio button is selected for Ascending order, you want to make sure fromNum is less than toNum, and maybe throw up a message box if they're not:
if(fromNum < toNum)
{ run logic .... }
else
{ alert user ... }

Data Aggregations over time, when time is variable

I am in the process of developing an application which calculates the shared acquired in a product over a specified time period (Term).
After the calculations have been performed, it is necessary for me to aggregate the data into groups based on a predefined review period (for example if the time required to gain 100% ownership of the product is 25 years, and the review period value is 5 years, I would have 5 sets of data aggregations for the agreement).
I perform the aggregations as shown by looping through my calculation result set:
if (Year% ReviewPeriod == 0)
{
// Perform Aggregations
}
This works fine in most scenarios.
However I do have a number of scenarios where the product reaches 100% ownership before the end of term.
What I need to be able to do is aggregate the calculations performed based on the ReviewPeriod variable, but if the final number of values in the calculations is not equal to the review period, aggregate the items based on the number of items remaining.
For example, given a 22 year term, data would be aggregated based on the Review Period variable, however if there is a remainder, then the remainder should be aggregated based on the value of the remainder.
Worked Example
Year 0 - 5 = 5 Aggregations
Year 6 - 10 = 5 Aggregations
Year 11 - 15 = 5 Aggregations
Year 16 - 20 = 5 Aggregations
Year 21 - 22 = 2 Aggregations
Could anyone help me with the logic to aggregate the data as I have described.
Probably the simplest way would be something like:
for ( int year = 0; year <= max_year; year++ ) {
if ( year % reviewPeriod == 0 ) {
// start a new aggregation
}
// add year to current aggregation
}
You could keep a list of aggregations and add a new one at the start of each period.
Here is a working example that just groups years in lists:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Aggregations
{
class Program
{
static void Main(string[] args)
{
int maxYear = 22;
int period = 5;
int year = 1985;
List<List<int>> aggregations = new List<List<int>>();
int i = -1;
for (int y = 0; y <= maxYear; y++)
{
if (y % period == 0)
{
aggregations.Add(new List<int>());
i++;
}
aggregations.ElementAt(i).Add(year);
year++;
}
foreach ( List<int> l in aggregations )
{
foreach (int yy in l)
{
Console.Write(yy + " ");
}
Console.WriteLine();
}
}
}
}
You've not really given enough of your code to go on. Hopefully you should be able to use this however your loop is currently set up. It "leaks" the mod value to the outside of the loop; after the loop is over, you can check the final mod value to see how many aggregations are left.
int modValue = 0;
for //foreach/while/... - your loop here
{
...
modValue = Year % ReviewPeriod;
if (modValue == 0)
{
// Perform Aggregations
}
...
} // end of your loop
if (modValue != 0)
{
// Perform final aggregation. There are modValue items to aggregate.
}
I think my suggestion is not worth 300rep bounty, and either I misunderstood your problem, or you've overshot the bounty..
Do your existing code that calculates the final aggregations works well? If so, then to determine the ranges yo umay just use modulo (%) and simple math:
int minYear = ...the first year // inclusive, i.e. 1970
int maxYear = ...the last year // inclusive, i.e. 2012
int span = maxYear - minYear + 1; // 1970..2012->43, 2001..2006->6
int fullFives = span / 5; // 1970..2012->8, 2001..2006->1
int remainder = span % 5; // 2001..2006->3, 2001..2006->1
for(int i=0; i<fullFives; ++i)
{
int yearFrom = minYear + 5*i
int yearTo = minYear + 5*(i+1) - 1
// 1970..2012 -> 1970-1974, 1975-1979,1980-1984,1985-1989,1990-1994,1995-1999,2000-2004,2005-2009
// 2001..2006 -> 2001-2005
aggregate(yearFrom, yearTo);
}
if(remainder > 0)
{
int yearFrom = minYear + 5*fullFives
int yearTo = minYear + maxYear
// 1970..2012 -> 2010-2012
// 2001..2006 -> 2006-2006
aggregate(yearFrom, yearTo);
}
This is written "out of thin air", I've not checked/compiled it - it is just to sketch the idea.
Note: you've said that everything works but sometimes "a number of scenarios where the product reaches 100% ownership before the end of term." - that would suggest that you rather have an error in the calculations, not in the looping. If the error were in the loop or year boundary detection, then probably almost all would be off. It's hard to say without more of the calculating code is revealed.
The code sample will fire on years 0, 5, 10 etc rather than for every year.
If you just need the number of years to aggregate when that code fires, and the term can be set in advance when a product reaches 100% ownership early, I think this would work:
int term = 22;
int reviewperiod = 5;
for (int year = 0; year < term; year++)
{
if (year % reviewperiod == 0)
{
var endyear = Math.Min(year + reviewperiod, term);
Console.WriteLine("Aggregate years {0} to {1}, {2} Aggregations ", year, endyear, endyear - year);
}
}
Do you think of something like
private int reviewPeriod = 5;
public void Aggregate(int term)
{
Enumerable.Range(0, term)
.ToList()
.Foreach(this.AggregateYear);
}
when this.AggregateYear is defined as follows
public void AggregateYear(int year)
{
var currentRemainder = year % this.reviewPeriod;
var aggregatePeriod = (currentRemainder == 0)
? this.reviewPeriod
: currentRemainder;
this.PerformAggregation(aggregatePeriod);
}
and this.PerformAggregation is defined as follows
private void PerformAggregation(int aggregatePeriod)
{
//...
}
Assuming this data is in memory (since you have not specified otherwise), then you can just use the GroupBy function from Linq:
struct YearValue
{
public int Year, Value;
}
static void Main()
{
// Create some data, hopefully representative of what you are dealing with...
Random r = new Random();
YearValue[] dataValues = new YearValue[22];
for (int i = 0; i < dataValues.Length; i++)
dataValues[i] = new YearValue {Year = i, Value = r.Next(200)};
// Average of values across 'ReviewPeriod' of five:
foreach (var item in dataValues.AsEnumerable().GroupBy(i => i.Year / 5))
{
YearValue[] items = item.ToArray();
Console.WriteLine("Group {0} had {1} item(s) averaging {2}",
item.Key,
items.Length,
items.Average(i => i.Value)
);
}
}
This program then outputs the following text:
Group 0 had 5 item(s) averaging 143.6
Group 1 had 5 item(s) averaging 120.4
Group 2 had 5 item(s) averaging 83
Group 3 had 5 item(s) averaging 145.2
Group 4 had 2 item(s) averaging 98.5

Categories

Resources