Related
I am building on a project that I already created. This is my first attempt at using dictionaries/lists. This a very broad question, as the book I have does not cover using dictionaries at all, and I am having trouble finding examples of dictionaries with user input online. I have made a program using a multidimensional array that asks the user for a number of students and a number of exams, then has the user enter scores for each exam, and outputs each students average grade based on their exam scores. I now want to achieve the same thing, only using dictionaries and lists instead of arrays. I don't even know where to begin. Can anyone explain how this would work? Here is the code I already have created, though it probably will not be helpful at all:
class MainClass
{
public static void Main(string[] args)
{
int TotalStudents = 0;
int TotalGrades = 0;
Console.WriteLine("Enter the number of students: ");
TotalStudents = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Enter the number of exams: ");
TotalGrades = Convert.ToInt32(Console.ReadLine());
int[,] scoresArray = new int[TotalStudents, TotalGrades];
for (int r = 0; r < TotalStudents; r++)
for (int c = 0; c < TotalGrades; c++)
{
Console.Write("Please enter exam score {0} for student {1}: ", c + 1, r + 1);
scoresArray[r, c] = Convert.ToInt32(Console.ReadLine());
}
for (int r = 0; r < scoresArray.GetLength(0); r++)
{
int studentSum = 0;
int testCount = 0;
for (int c = 0; c < scoresArray.GetLength(1); c++)
{
studentSum += scoresArray[r, c];
testCount++;
}
string gradeLetter = "";
double average = studentSum / testCount;
Console.WriteLine("\nStudent " + (r + 1).ToString() + " Average Score: " + average.ToString());
if (average >= 90)
{
gradeLetter = "A";
}
else if (average >= 80 && average < 90)
{
gradeLetter = "B";
}
else if (average >= 70 && average < 80)
{
gradeLetter = "C";
}
else if (average >= 60 && average < 70)
{
gradeLetter = "D";
}
else
{
gradeLetter = "F";
}
Console.WriteLine("Student " + (r + 1).ToString() + " will recieve a(n) " + gradeLetter + " in the class.\n");
}
Console.Write("\nPress the [ENTER] key to exit.");
Console.ReadLine();
}
}
Dictionaries are a wonderful tool! I tried to use your original logic, but at times, I had to go another way. Also, I kept getting lost with the "c" and "r" index variables. I prefer longer names for indices.
//Let's create a gradeTranslator dictionary.
// As the grades follow the simple divisions along averages divisible by 10,
// we can just use the first digit of the average to determine the grade.
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
bool useSampleData = true;
Dictionary<string, List<double>> gradeBook = new Dictionary<string, List<double>>();
Dictionary<int, string> gradeTranslator = new Dictionary<int, string>();
for (int i = 0; i < 6; i++) {
gradeTranslator.Add(i, "F");
}
gradeTranslator.Add(6, "D");
gradeTranslator.Add(7, "C");
gradeTranslator.Add(8, "B");
gradeTranslator.Add(9, "A");
gradeTranslator.Add(10, "A");
int TotalStudents, TotalGrades;
// For testing purposes, it is a lot easier to start with some
// sample data. So, I created a query to see if the user wants
// to use sample data or to provide her own input.
Console.WriteLine("Do you want to input the data (I) or allow me to use sample data (S)?");
var inputMethod = Console.ReadLine();
if(inputMethod.ToUpper().IndexOf("I") >=0) {
useSampleData = false;
}
// User Sample Data
if (useSampleData) { // test without using the console input
gradeBook.Add("Bob", new List<double>() { 67.8, 26.3, 33.2, 33.1, 67.2 });
gradeBook.Add("Dick", new List<double>() { 88.2, 45.2, 100.0, 89.2, 91.5 });
gradeBook.Add("Jane", new List<double>() { 99.2, 99.5, 93.9, 98.2, 15.0 });
gradeBook.Add("Samantha", new List<double>() { 62, 89.5, 93.9, 98.2, 95.0 });
gradeBook.Add("Estefania", new List<double>() { 95.2, 92.5, 92.9, 98.2, 89 });
TotalStudents = gradeBook.Count();
TotalGrades = gradeBook["Bob"].Count();
TotalStudents = 5;
// user will provide their own data.
} else {
Console.WriteLine("Enter the number of students: ");
TotalStudents = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Enter the number of exams: ");
TotalGrades = Convert.ToInt32(Console.ReadLine());
for (int studentId = 0; studentId < TotalStudents; studentId++) {
Console.Write("Please enter name of student {0}: ", studentId);
var name = Console.ReadLine();
gradeBook.Add(name, new List<double>());
for (int testId = 0; testId < TotalGrades; testId++) {
Console.Write("Please enter exam score {0} for " +
"student {1}: ", testId + 1, name);
gradeBook[name].
Add(Convert.ToDouble(Console.ReadLine()));
}
}
}
// Here we will divide the grade by 10 as an integer division to
// get just the first digit of the average and then translate
// to a letter grade.
foreach (var student in gradeBook) {
Console.WriteLine("Student " + student.Key +
" scored an average of " + student.Value.Average() + ". " +
student.Key + " will recieve a(n) " +
gradeTranslator[(int)(student.Value.Average() / 10)] +
" in the class.\n");
}
Console.Write("\nPress the [ENTER] key to exit.");
Console.ReadLine();
}
}
}
Build a student class to hold the information.
public class Student
{
public string ID
public List<double> TestScores {get;set;}
// in this case average only has a getter which calculates the average when needed.
public double Average
{
get
{
if(TestScores != null && TestScores.Count >0)
{
double ave = 0;
foreach(var x in TestScores)//basic average formula.
ave += x;
// using linq this could be reduced to "return TestScores.Average()"
return ave/TestScores.Count;
}
return 0; // can't divide by zero.
}
}
public Student(string id)
{
ID = id;
TestScores = new List<double>();
}
}
Now your dictionary collection can keep the information. To get you started here are a couple ways to access the data. Definitely not all inclusive.
Adding elements to the dictionary:
Dictionary<string,Student> dict = new Dictionary<string,Student>();
Student stud = new Student("Jerry");
dict.Add(stud.ID,stud);
Pulling information:
string aveScore = dict["Jerry"].Average.ToString("#.00");
Console.Write(aveScore);
looping through the dictionary:
foreach(var s in dict.Keys)
{
dict[s].TestScores.Add(50);
}
Since this is either homework, or you're trying to learn how to use dictionaries, instead of posting code I'll just point you in the right direction.
A Dictionary in C# is a collection of Key-Value pairs. The Key must be unique while the Value doesn't have to be. So if you define a dictionary as follows:
Dictionary<int, int> dict = new Dictionary<int, int>();
It means you've created a collection of pairs, where the Key is of type int, and Value is also of type int. So, if you add a couple of items to the dictionary, it's content would look like this:
At index = 0, you have an item with Key=1, and Value=100.
At index = 1, you have an item with Key=2, and Value=200.
But, the Value of a dictionary doesn't necessarily have to be a single value. It could also be a Collection (or a class object, or even another dictionary).
Since your requirement is to have a list of scores per student, and a dictionary Value permits a Collection, it looks like using a dictionary is indeed a good idea. But what kind of a dictionary should we use?
Well, there's many to choose from.
One approach is to use a Dictionary<string, List<int>>. The string representing the student name (or an ID), and the List<int> representing test scores. So;
Dictionary<string, List<int>> dict = new Dictionary<string, List<int>>();
Now you can populate this dictionary with each student's scores. For example:
dict.Add("Arthur Dent", new List<int>() { 75, 54, 26 });
dict.Add("Zaphod Beeblebrox", new List<int>() { 12, 13, 7 });
dict.Add("Ford Prefect", new List<int>() { 99, 89, 69 });
In your case, you can get the input from user with each student name and scores.
However, like I said, you can also use a class object as a dictionary Value, and, in fact, that is a better approach for your problem. The answer by Felix shows how to do this, so I won't go into details. But you essentially want to create a dictionary as follows, assuming your class is Student.
Dictionary<string, Student> dict = new Dictionary<string, Student>();
Then create instances of Student based on user input, and add them to the dictionary. An added advantage of using a Student class is that you can have all information related to one student in one place, and also have additional helper methods, such as the Average() method in Felix's example so your code will be much cleaner and simpler.
I made some C# code that outputs a SortedDictionary <int index, list<int>values>> Where the index always starts with lowest int of the list<> that follows. Here is a short example of input (in reality the input is larger):
index- Values<>
2 - 2,3,6,7
3 - 3,5
5 - 5,7,9
11 - 11,12,12
Now I want to do some re-orderning in here. The values are linked indexes. I want to sort them so that connected indexes are grouped together, with no double indexes. That should result in output
2 - 2,3,5,6,7,9
11 - 11,12
Initially i had problems working with a sortedDictionary using a foreach while also decreasing the dictionary set size. I solved that, and now give an update of this problem description with my latest code. It doesnt use a foreach anymore, and some sorting problems seam to be fixed now, but as a side effect it became pretty complex and large. And i doubt if it should be so complex, or might be written shorter, more simple.
Each List i call a tree, where is thoe dictionary are trees
And Cursor or c i use like reading out text digit by digit from screen.
For the moment I put it in a small concept function code in a console app. Just to check if it all works. Testing is quite complex, since number sets can be complexly linked. So its not directly visible if you got lots of sets and a lots of number how they should be sorted. Therefore manually checking code validity and results isn't easy either.
While I am not yet sure if it now indeed does work for 100%. It seams to work better then before. But i think this code is far from perfect, as i walk the set of trees twice. With a pre-sort and a final-sort.
static SortedDictionary<int, List<int>> NewSort(SortedDictionary<int, List<int>> trees)
{
bool debugmode = false;
//pre sort
List<int> indexTree = new List<int>();
foreach (KeyValuePair<int, List<int>> tree in trees)
{
indexTree.Add(tree.Key);
}
for (int i = 0; i < indexTree.Count; i++)
{
int cursor = 1;
List<int> tree = new List<int>();
int index = indexTree[i];
tree = trees[index];
while ((tree !=null)&& (cursor<tree.Count) )
{
int c = tree[cursor ];
if (trees.ContainsKey(c))
{
if (trees[c] != null)
{
List<int> u = new List<int>();
u = trees[c];
tree.AddRange(u);
tree.Sort();
trees[index] = tree;
trees[c] = null;
}
}
cursor++;
}
}
for (int i = trees.Count; i > 0; i--)
{
int c = indexTree[i - 1];
if (trees[c] == null)
{ trees.Remove(indexTree[i - 1]); }
else
{
trees[c] = trees[c].Distinct().ToList(); //removing all duplicates
}
}
indexTree = new List<int>();
//resort.
foreach (KeyValuePair<int, List<int>> tree in trees)
{
indexTree.Add(tree.Key);
if(debugmode) Console.WriteLine("* " +DumpIntList(trees[tree.Key]));
}
for (int i = (indexTree.Count); i > 0; i--)
{
if (debugmode) Console.WriteLine(i.ToString());
List<int> tree = new List<int>();
tree = trees[indexTree[i-1]];
for (int j = 0; j < tree.Count; j++)
{
int c = tree[j];
for (int k = (i - 2); k > 0; k--)
{
List<int> compareTree = new List<int>();
compareTree = trees[indexTree[k]];
if (compareTree.IndexOf(c) != -1) // found !
{
if (debugmode) Console.Write("found " + c.ToString() + " from ");
if (debugmode) Console.WriteLine(DumpIntList(tree) + " in (" + DumpIntList(compareTree)+ ")");
tree.Remove(c); // or we would create a duplicate
compareTree.AddRange(tree);
compareTree = compareTree.Distinct().ToList(); //removing doubles again, doubles as side effect of merging
compareTree.Sort();
trees.Remove(indexTree[i - 1]);
trees[indexTree[k]] = compareTree;
}
}
}
}
return trees;
}
Maybe what i try to do inst that clear to some, what i try to do here is that i try to look if series have overlapping numbers, and if so merge them.
Where each series is always sorted and starts with the lowest number of that series. As I found recently this might be a version of the UnionFind problem. The problem also appears in Blob detection, and finding what web pages are link eachother in a set of webpages. (but my data is a strange set lab measurements).
The difficulty is that there are lots of series, and it might be not directly clear if they are connected
1-3-4
4-7-9
11-12
would result in 2 series :
1) 1-3-4-7-9
2) 11-12
But after you add series 12-3 then it would all become one series.
Some more test data :
2 - 2,3,5,6,7 // note my data is always ordered like this
5 - 5,7,9 // dictionary starts with lowest key
11 - 11,12,12,27,30,31 // each list inside a tree key
22 - 22,27 // is ordered low to high
23 - 23,25 // lowest int, equals the dict key.
28 - 28,30
34 - 34
Output using above function
2 - 2,3,5,6,7,9
11 - 11,12,22,27,28,30,31
23 - 23,25
34 - 34
So while the code seams to work now, I highly doubt its ideal code, i irritate the trees set twice. And so i wonder if better solutions are possible. It might also be that the code doesnt do what i hope it todo; as i'm still testing it.
Other than inverting an if to avoid 1 level of nesting, I don't yet see how LINQ can be used to improve the readability of this code block.
static SortedDictionary<int, List<int>> SortTree(SortedDictionary<int, List<int>> trees)
{
//SortedDictionary<int, List<int>> newtrees = new SortedDictionary<int, List<int>>();
if (trees.Count < 2) { return trees; } // dont process if ntrees contains 1 or 0 trees
foreach (KeyValuePair<int, List<int>> singletree in trees)
{
int cursor = 1;
bool nFinish = false;
List<int> n = singletree.Value;
if (n.Count <= 1) continue;
while (nFinish == false)
{
if (trees.ContainsKey(n[cursor]))
{
List<int> t = trees[n[cursor]]; // think of a screen cursor going over the list table
t.AddRange(n);
trees.Remove(n[cursor]);
n.Sort();
trees[singletree.Key] = n;
}
cursor++;
if (cursor != n.Count) continue;
nFinish = true;
}
}
return trees;
}
Well i decreased the size of the function and improved it.
It should now be a single irritation over all trees.
Unless someone knows a better answer, i think its "the" answer.
The code has been tested to work with larger sets, and i couldnt spot errors.
static SortedDictionary<int, List<int>> NewSort(SortedDictionary<int, List<int>> trees)
{
bool debugmode = false;
//pre sort
List<int> indexTree = new List<int>();
foreach (KeyValuePair<int, List<int>> tree in trees)
{
indexTree.Add(tree.Key);
if(debugmode) Console.WriteLine("* " +DumpIntList(trees[tree.Key]));
}
for (int i = (indexTree.Count); i > 0; i--)
{
if (debugmode) Console.WriteLine(i.ToString());
List<int> tree = new List<int>();
tree = trees[indexTree[i-1]];
for (int j = 0; j < tree.Count; j++)
{
int c = tree[j];
for (int k = (i - 2); k > -1; k--) // k can be 0 but i can minimally be 1
{
List<int> compareTree = new List<int>();
compareTree = trees[indexTree[k]]; // for loop > checking all trees
if (compareTree.IndexOf(c) != -1) // found !
{
if (debugmode) Console.Write("found " + c.ToString() + " from ");
if (debugmode) Console.WriteLine(DumpIntList(tree) + " in (" + DumpIntList(compareTree)+ ")");
// tree.Remove(c); // or we would create a duplicate
compareTree.AddRange(tree);
compareTree = compareTree.Distinct().ToList();
compareTree.Sort();
trees.Remove(indexTree[i - 1]);
trees[indexTree[k]] = compareTree;
j =tree.Count; //break from more checks. maybe dirty code but it increases speed
break; //break checking loop on all trees for current tree
}
}
}
}
return trees;
}
Here is your solution with test cases
using System;
using System.Collections.Generic;
using System.Linq;
namespace Demo
{
public class Example
{
public static void Main()
{
SortedDictionary<int, List<int>> tempRepositary = new SortedDictionary<int, List<int>>();
//test 1
tempRepositary.Add(2, new List<int>(new[] { 2, 3, 5, 6, 7 }));
tempRepositary.Add(5, new List<int>(new[] { 5, 7, 9 }));
tempRepositary.Add(11, new List<int>(new[] { 11, 12, 12, 27, 30, 31 }));
tempRepositary.Add(22, new List<int>(new[] { 22, 27 }));
tempRepositary.Add(23, new List<int>(new[] { 23, 25 }));
tempRepositary.Add(28, new List<int>(new[] { 28, 30 }));
tempRepositary.Add(34, new List<int>(new[] { 34 }));
//test 2
//tempRepositary.Add(2, new List<int>(new[] { 2,3,6,7 }));
//tempRepositary.Add(3, new List<int>(new[] { 3,5 }));
//tempRepositary.Add(5, new List<int>(new[] { 5,7,9 }));
//tempRepositary.Add(11, new List<int>(new[] { 11,12,12 }));
var refreshOne = SortTree(tempRepositary);
foreach (var item in refreshOne)
{
Console.Write("Key:" + item.Key + " ");
Console.WriteLine(string.Join(",", item.Value));
}
Console.ReadKey();
}
private static SortedDictionary<int, List<int>> SortTree(SortedDictionary<int, List<int>> trees)
{
if (trees.Count < 2) { return trees; } // dont process if ntrees contains 1 or 0 trees
SortedDictionary<int, List<int>> compressedTree
= new SortedDictionary<int, List<int>>();
var allKeys = trees.Keys.ToList();
var allValues = trees.Values.ToList();
for (int i = 0; i < allKeys.Count; i++)
{
var tempValues = allValues[i];
var tempMax = tempValues.Max();
for (int j = i + 1; j < allKeys.Count; )
{
if (tempMax >= allKeys[j])
{
tempValues.AddRange(allValues[j]);
allKeys.Remove(allKeys[j]);
allValues.Remove(allValues[j]);
//
tempMax = tempValues.Max();
continue;
}
j++;
}
compressedTree.Add(allKeys[i], tempValues.Distinct().OrderBy(i1 => i1).ToList());
}
return compressedTree;
}
}
}
I have a SortedList in Key/Value pair that so far stores 3 entries like this:
Key: "Shapes" and Value: ["Cube", "Sphere"]
Key: "Colors" and Value: ["Red", "Green"]
Key: "Sizes" and Value: ["Big", "Small"]
My goal is generate all the combination of strings and store them into another list like this:
"Shape:Cube/Colors:Red/Size:Big"
"Shape:Cube/Colors:Red/Size:Small"
"Shape:Cube/Colors:Green/Size:Big"
"Shape:Cube/Colors:Green/Size:Small"
"Shape:Sphere/Colors:Red/Size:Big"
"Shape:Sphere/Colors:Red/Size:Small"
"Shape:Sphere/Colors:Green/Size:Big"
"Shape:Sphere/Colors:Green/Size:Small"
The caveat here is that there can be N number of entries in the first SortedList so I can't really create the for-loops in my source code before hand. I know I should use recursion to tackle the trickiness of the dynamic N value.
So far I've only come up with a hard-coded solution for N=2 entries and I'm having trouble translating into a recursion that can handle any value of N entries:
for (int ns=0; ns < listFeaturesSuperblock.Values[0].Count; ns++) {
for (int nc=0; nc < listFeaturesSuperblock.Values[1].Count; nc++) {
//prefab to load
string str = "PreFabs/Objects/" + listFeaturesSuperblock.Keys[0][ns] + ":" + listFeaturesSuperblock.Values[0][ns] + "/" + listFeaturesSuperblock.Values[1][nc] + ":" + listFeaturesSuperblock.Values[1][nc];
}
}
Can somebody kindly point me towards the right direction? How should I approach this and what do I need to study to get better at coding recursion?
Thank you.
In your current method:
List<string> result = new List<string>;
ProcessItems(listFeaturesSuperblock, result);
And this is the recursive method:
void ProcessItems(SortedList<string, List<string>> data, List<string> result, int level = 0, string prefix = "PreFabs/Objects/")
{
for (int i = 0; i < data.Values[level].Count; i++)
{
string item = prefix + data.Keys[level] + ":" + data.Values[level][i] + "/";
if (level == data.Values.Count - 1)
result.Add(item);
else
ProcessItems(data, result, level + 1, item);
}
}
The 'result' variable will then contain all permutations.
To use recursion is quiet a simple way and here's how.
Let's say we have Dictionary just like in your example
public static Dictionary<string, List<string>> props = new Dictionary<string, List<string>>(){
{ "Shapes", new List<string>{"Cube", "Sphere"} },
{ "Colors", new List<string>{"Red", "Green"} },
{ "Sizes", new List<string>{"Big", "Small"} }
};
Now we take all values of first key and go through them appending their values to the source string. So for the first value we will get
/Shapes:Cube
And now we do the same for the next key Colors, resulting
/Shapes:Cube/Colors:Red
We continue it while there are more unprocessed keys. When there are no more keys we got the first result string
/Shapes:Cube/Colors:Red/Sizes:Big
now we need to go back and add another value which result
/Shapes:Cube/Colors:Red/Sizes:Small
And the code for this will be like following
public static List<string> GetObjectPropertiesPermutations(string src, string[] keys, int index) {
if(index >= keys.Length) {
return new List<string>() { src };
}
var list = new List<string>();
var key = keys[index];
foreach(var val in props[key]) {
var other = GetObjectPropertiesPermutations(src + "/" + key + ":" + val, keys, index + 1);
list.AddRange(other);
}
return list;
}
public static void Main(string[] args)
{
var perms = GetObjectPropertiesPermutations("", props.Keys.ToArray(), 0);
foreach(var s in perms) {
Console.WriteLine(s);
}
}
I have dictionary of int (Dictionary<int, int>) which has index of all parenthesis in a string (key was openStartParenthesisIndex and value was closeEndParenthesisIndex)
e.g in text
stringX.stringY(())() -> stringX.stringY$($()^)^$()^
$ = openParenthesisStartIndex
^ = closeParenthesisEndIndex
Dictionary items:
key value
(openParenthesisStartIndex) --- (closeParenthesisEndIndex)
item1 15 19
item2 16 18
item3 19 21
My problem was when I loop my dictionary and try to remove it on string, next loop the index was not valid since its already change because I remove it .
string myText = "stringX.stringY(())()";
Dictionary<int, int> myIndexs = new Dictionary<int, int>();
foreach (var x in myIndexs)
{
myText = myText.Remove(item.Key, 1).Remove(item.Value-1);
}
Question: how can i remove all index in a string (from startIndex[key] to endIndex[value])?
To prevent the index from changing, one trick is to remove the occurences starting from the end:
string myText = stringX.stringY(())();
Dictionary<int, int> myIndexs = new Dictionary<int, int>();
var allIndexes = myIndexs.Keys.Concat(myIndexs.Values);
foreach (var index in allIndexes.OrderByDescending(i => i))
{
myText = myText.Remove(index, 1);
}
Note that you probably don't need a dictionary at all. Consider replacing it by a list.
StringBuilder will be more suited to your case as you are continuously changing data. StringBuilder MSDN
Ordering the keys by descending order will work as well for removing all indexes.
Another workaround could be to place an intermediary character at required index and replace all instances of that character in the end.
StringBuilder ab = new StringBuilder("ab(cd)");
ab.Remove(2, 1);
ab.Insert(2, "`");
ab.Remove(5, 1);
ab.Insert(5, "`");
ab.Replace("`", "");
System.Console.Write(ab);
Strings when you make a change to a string a new string is always created, so what you want is to create a new string without the removed parts. This code is a little bit complicated because of how it deals with the potential overlap. Maybe the better way would be to cleanup the indexes, making a list of indexes that represent the same removals in the right order without overlap.
public static string removeAtIndexes(string source)
{
var indexes = new Tuple<int, int>[]
{
new Tuple<int, int>(15, 19),
new Tuple<int, int>(16, 18),
new Tuple<int, int>(19, 21)
};
var sb = new StringBuilder();
var last = 0;
bool copying = true;
for (var i = 0; i < source.Length; i++)
{
var end = false;
foreach (var index in indexes)
{
if (copying)
{
if (index.Item1 <= i)
{
copying = false;
break;
}
}
else
{
if (index.Item2 < i)
{
end = true;
}
}
}
if (false == copying && end)
{
copying = true;
}
if(copying)
{
sb.Append(source[i]);
}
}
return sb.ToString();
}
I have been stumped on this one for a while. I want to take a List and order the list such that the Products with the largest Price end up in the middle of the list. And I also want to do the opposite, i.e. make sure that the items with the largest price end up on the outer boundaries of the list.
Imagine a data structure like this.. 1,2,3,4,5,6,7,8,9,10
In the first scenario I need to get back 1,3,5,7,9,10,8,6,4,2
In the second scenario I need to get back 10,8,6,4,2,1,3,5,7,9
The list may have upwards of 250 items, the numbers will not be evenly distributed, and they will not be sequential, and I wanted to minimize copying. The numbers will be contained in Product objects, and not simple primitive integers.
Is there a simple solution that I am not seeing?
Any thoughts.
So for those of you wondering what I am up to, I am ordering items based on calculated font size. Here is the code that I went with...
The Implementation...
private void Reorder()
{
var tempList = new LinkedList<DisplayTag>();
bool even = true;
foreach (var tag in this) {
if (even)
tempList.AddLast(tag);
else
tempList.AddFirst(tag);
even = !even;
}
this.Clear();
this.AddRange(tempList);
}
The Test...
[TestCase(DisplayTagOrder.SmallestToLargest, Result=new[]{10,14,18,22,26,30})]
[TestCase(DisplayTagOrder.LargestToSmallest, Result=new[]{30,26,22,18,14,10})]
[TestCase(DisplayTagOrder.LargestInTheMiddle, Result = new[] { 10, 18, 26, 30, 22, 14 })]
[TestCase(DisplayTagOrder.LargestOnTheEnds, Result = new[] { 30, 22, 14, 10, 18, 26 })]
public int[] CalculateFontSize_Orders_Tags_Appropriately(DisplayTagOrder sortOrder)
{
list.CloudOrder = sortOrder;
list.CalculateFontSize();
var result = (from displayTag in list select displayTag.FontSize).ToArray();
return result;
}
The Usage...
public void CalculateFontSize()
{
GetMaximumRange();
GetMinimunRange();
CalculateDelta();
this.ForEach((displayTag) => CalculateFontSize(displayTag));
OrderByFontSize();
}
private void OrderByFontSize()
{
switch (CloudOrder) {
case DisplayTagOrder.SmallestToLargest:
this.Sort((arg1, arg2) => arg1.FontSize.CompareTo(arg2.FontSize));
break;
case DisplayTagOrder.LargestToSmallest:
this.Sort(new LargestFirstComparer());
break;
case DisplayTagOrder.LargestInTheMiddle:
this.Sort(new LargestFirstComparer());
Reorder();
break;
case DisplayTagOrder.LargestOnTheEnds:
this.Sort();
Reorder();
break;
}
}
The appropriate data structure is a LinkedList because it allows you to efficiently add to either end:
LinkedList<int> result = new LinkedList<int>();
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Array.Sort(array);
bool odd = true;
foreach (var x in array)
{
if (odd)
result.AddLast(x);
else
result.AddFirst(x);
odd = !odd;
}
foreach (int item in result)
Console.Write("{0} ", item);
No extra copying steps, no reversing steps, ... just a small overhead per node for storage.
C# Iterator version
(Very simple code to satisfy all conditions.)
One function to rule them all! Doesn't use intermediate storage collection (see yield keyword). Orders the large numbers either to the middle, or to the sides depending on the argument. It's implemented as a C# iterator
// Pass forward sorted array for large middle numbers,
// or reverse sorted array for large side numbers.
//
public static IEnumerable<long> CurveOrder(long[] nums) {
if (nums == null || nums.Length == 0)
yield break; // Nothing to do.
// Move forward every two.
for (int i = 0; i < nums.Length; i+=2)
yield return nums[i];
// Move backward every other two. Note: Length%2 makes sure we're on the correct offset.
for (int i = nums.Length-1 - nums.Length%2; i >= 0; i-=2)
yield return nums[i];
}
Example Usage
For example with array long[] nums = { 1,2,3,4,5,6,7,8,9,10,11 };
Start with forward sort order, to bump high numbers into the middle.
Array.Sort(nums); //forward sort
// Array argument will be: { 1,2,3,4,5,6,7,8,9,10,11 };
long[] arrLargeMiddle = CurveOrder(nums).ToArray();
Produces: 1 3 5 7 9 11 10 8 6 4 2
Or, Start with reverse sort order, to push high numbers to sides.
Array.Reverse(nums); //reverse sort
// Array argument will be: { 11,10,9,8,7,6,5,4,3,2,1 };
long[] arrLargeSides = CurveOrder(nums).ToArray();
Produces: 11 9 7 5 3 1 2 4 6 8 10
Significant namespaces are:
using System;
using System.Collections.Generic;
using System.Linq;
Note: The iterator leaves the decision up to the caller about whether or not to use intermediate storage. The caller might simply be issuing a foreach loop over the results instead.
Extension Method Option
Optionally change the static method header to use the this modifier public static IEnumerable<long> CurveOrder(this long[] nums) { and put it inside a static class in your namespace;
Then call the order method directly on any long[ ] array instance like so:
Array.Reverse(nums); //reverse sort
// Array argument will be: { 11,10,9,8,7,6,5,4,3,2,1 };
long[] arrLargeSides = nums.CurveOrder().ToArray();
Just some (unneeded) syntactic sugar to mix things up a bit for fun. This can be applied to any answers to your question that take an array argument.
I might go for something like this
static T[] SortFromMiddleOut<T, U>(IList<T> list, Func<T, U> orderSelector, bool largestInside) where U : IComparable<U>
{
T[] sortedArray = new T[list.Count];
bool add = false;
int index = (list.Count / 2);
int iterations = 0;
IOrderedEnumerable<T> orderedList;
if (largestInside)
orderedList = list.OrderByDescending(orderSelector);
else
orderedList = list.OrderBy(orderSelector);
foreach (T item in orderedList)
{
sortedArray[index] = item;
if (add)
index += ++iterations;
else
index -= ++iterations;
add = !add;
}
return sortedArray;
}
Sample invocations:
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] sortedArray = SortFromMiddleOut(array, i => i, false);
foreach (int item in sortedArray)
Console.Write("{0} ", item);
Console.Write("\n");
sortedArray = SortFromMiddleOut(array, i => i, true);
foreach (int item in sortedArray)
Console.Write("{0} ", item);
With it being generic, it could be a list of Foo and the order selector could be f => f.Name or whatever you want to throw at it.
The fastest (but not the clearest) solution is probably to simply calculate the new index for each element:
Array.Sort(array);
int length = array.Length;
int middle = length / 2;
int[] result2 = new int[length];
for (int i = 0; i < array.Length; i++)
{
result2[middle + (1 - 2 * (i % 2)) * ((i + 1) / 2)] = array[i];
}
Something like this?
public IEnumerable<int> SortToMiddle(IEnumerable<int> input)
{
var sorted = new List<int>(input);
sorted.Sort();
var firstHalf = new List<int>();
var secondHalf = new List<int>();
var sendToFirst = true;
foreach (var current in sorted)
{
if (sendToFirst)
{
firstHalf.Add(current);
}
else
{
secondHalf.Add(current);
}
sendToFirst = !sendToFirst;
}
//to get the highest values on the outside just reverse
//the first list instead of the second
secondHalf.Reverse();
return firstHalf.Concat(secondHalf);
}
For your specific (general) case (assuming unique keys):
public static IEnumerable<T> SortToMiddle<T, TU>(IEnumerable<T> input, Func<T, TU> getSortKey)
{
var sorted = new List<TU>(input.Select(getSortKey));
sorted.Sort();
var firstHalf = new List<TU>();
var secondHalf = new List<TU>();
var sendToFirst = true;
foreach (var current in sorted)
{
if (sendToFirst)
{
firstHalf.Add(current);
}
else
{
secondHalf.Add(current);
}
sendToFirst = !sendToFirst;
}
//to get the highest values on the outside just reverse
//the first list instead of the second
secondHalf.Reverse();
sorted = new List<TU>(firstHalf.Concat(secondHalf));
//This assumes the sort keys are unique - if not, the implementation
//needs to use a SortedList<TU, T>
return sorted.Select(s => input.First(t => s.Equals(getSortKey(t))));
}
And assuming non-unique keys:
public static IEnumerable<T> SortToMiddle<T, TU>(IEnumerable<T> input, Func<T, TU> getSortKey)
{
var sendToFirst = true;
var sorted = new SortedList<TU, T>(input.ToDictionary(getSortKey, t => t));
var firstHalf = new SortedList<TU, T>();
var secondHalf = new SortedList<TU, T>();
foreach (var current in sorted)
{
if (sendToFirst)
{
firstHalf.Add(current.Key, current.Value);
}
else
{
secondHalf.Add(current.Key, current.Value);
}
sendToFirst = !sendToFirst;
}
//to get the highest values on the outside just reverse
//the first list instead of the second
secondHalf.Reverse();
return(firstHalf.Concat(secondHalf)).Select(kvp => kvp.Value);
}
Simplest solution - order the list descending, create two new lists, into the first place every odd-indexed item, into the other every even indexed item. Reverse the first list then append the second to the first.
Okay, I'm not going to question your sanity here since I'm sure you wouldn't be asking the question if there weren't a good reason :-)
Here's how I'd approach it. Create a sorted list, then simply create another list by processing the keys in order, alternately inserting before and appending, something like:
sortedlist = list.sort (descending)
biginmiddle = new list()
state = append
foreach item in sortedlist:
if state == append:
biginmiddle.append (item)
state = prepend
else:
biginmiddle.insert (0, item)
state = append
This will give you a list where the big items are in the middle. Other items will fan out from the middle (in alternating directions) as needed:
1, 3, 5, 7, 9, 10, 8, 6, 4, 2
To get a list where the larger elements are at the ends, just replace the initial sort with an ascending one.
The sorted and final lists can just be pointers to the actual items (since you state they're not simple integers) - this will minimise both extra storage requirements and copying.
Maybe its not the best solution, but here's a nifty way...
Let Product[] parr be your array.
Disclaimer It's java, my C# is rusty.
Untested code, but you get the idea.
int plen = parr.length
int [] indices = new int[plen];
for(int i = 0; i < (plen/2); i ++)
indices[i] = 2*i + 1; // Line1
for(int i = (plen/2); i < plen; i++)
indices[i] = 2*(plen-i); // Line2
for(int i = 0; i < plen; i++)
{
if(i != indices[i])
swap(parr[i], parr[indices[i]]);
}
The second case, Something like this?
int plen = parr.length
int [] indices = new int[plen];
for(int i = 0; i <= (plen/2); i ++)
indices[i] = (plen^1) - 2*i;
for(int i = 0; i < (plen/2); i++)
indices[i+(plen/2)+1] = 2*i + 1;
for(int i = 0; i < plen; i++)
{
if(i != indices[i])
swap(parr[i], parr[indices[i]]);
}