I am trying to make my own multithreaded mergesort algorithm by using an ArrayList. I am familiar with this method in Java but trying to bring it over to c# is not working as planned. I get the following error when trying to compare two ArrayList items Error 1 Operator '<' cannot be applied to operands of type 'object' and 'object'. I know you cannot directly compare two objects like that, in Java you can use compareTo or something like that, is there any equivalent for c#?
Here is the code causing the error in case you need it, bear in mind that I copied this from one of my Java programs that worked with integer arrays.
int size = (last - first) + 1;
ArrayList temp = new ArrayList();
int mid = (first + last) / 2;
int i1 = 0;
int i2 = first;
int i3 = mid + 1;
while(i2 <= mid && i3 <= last)
{
if(list[i2] < list[i3])
temp[i1++] = list[i2++];
else temp[i1++] = list[i3++];
}
while(i2 <= mid)
temp[i1++] = list[i2++];
while(i3 <= last)
temp[i1++] = list[i3++];
i3 = first;
for(i1 = 0; i1 < temp.Count; i1++, i3++)
list[i3] = temp[i1];
I think just use a SortedList of ints.
var sl = new SortedList();
sl.Add(15, 15);
sl.Add(443, 443);
sl.Add(2, 2);
sl.Add(934, 934);
sl.Add(55, 55);
foreach (var item in sl.Values)
{
Console.WriteLine(item); // Outputs 2, 15, 55, 443, 934
}
Or else a generic List and call Sort (better perf I think).
var list = new List<int>();
list.Add(5);
list.Add(1);
list.Add(59);
list.Add(4);
list.Sort();
foreach (var element in list)
{
Console.WriteLine(element); // Outputs 1, 4, 5, 59
}
I would suggest looking into the IComparer<T> interface. You can make a version of your MergeSort algorithm that takes an IComparer<T> which can be used to compare the objects for sorting. It would probably give you similar functionality to what you are used to.
You could do this in addition to defining a version of MergeSort that restricts the type to IComparable<T>. This way, between both versions of the function, you can handle objects that implement the interface already and also allow your users to provide a comparison for objects that don't implement it.
You could put the MergeSort in as an Extension Method on the IList<T> interface as such.
public static class MergeSortExtension
{
public static IList<T> MergeSort<T>(this IList<T> list) where T : IComparable<T>
{
return list.MergeSort(Comparer<T>.Default);
}
public static IList<T> MergeSort<T>(this IList<T> list, IComparer<T> comparer)
{
// Sort code.
}
}
You could cast each item to IComparable or do an as IComparable and check for null (casting will throw an exception if the object instance doesn't implement the interface). But what #DavidG L suggested probably the way to go. But make a constraint on T that it must implement IComparable.
Taking all of your suggestions into account I have come up with the following solution:
public static void merge(List<T> list , int first, int last) {
int size = (last - first) + 1;
List<T> temp = new List<T>();
IEnumerable<IComparable> sorter = (IEnumerable<IComparable>)list;
int mid = (first + last) / 2;
int i1 = 0;
int i2 = first;
int i3 = mid + 1;
while(i2 <= mid && i3 <= last)
{
if (sorter.ElementAt(i2).CompareTo(sorter.ElementAt(i3)) < 0)
temp[i1++] = list[i2++];
else temp[i1++] = list[i3++];
}
while(i2 <= mid)
temp[i1++] = list[i2++];
while(i3 <= last)
temp[i1++] = list[i3++];
i3 = first;
for(i1 = 0; i1 < temp.Count; i1++, i3++)
list[i3] = temp[i1];
}
Thank you for all the help, I do not get any errors anymore.
The problem is that ArrayList is not a generic collection, so it allows to call only object's methods on any item. You can use LINQ to cast to generic IEnumerable<int>, then you will be able to call methods on int, including comparing and ordering:
ArrayList al = new ArrayList();
al.Add(1);
al.Add(2);
IEnumerable<int> coll = al.Cast<int>();
if (coll.ElementAt(0) < coll.ElementAt(1))
// ...
or:
var ordered = coll.OrderBy(n => n).ToList();
If your ArrayList contains objects of different types, you should use OfType<int> to filetr out ints, but the proper way would be to use a typed collection like List<int> instead of ArrayList.
Related
I've got some school work to practice some concepts like generics, delegates, and interfaces. The whole project is to build a custom list class using an array of T.
the class should also implement IEnumerable and have a struct which is an IEnumerator. I'm only focusing on the Add method here.
class MyList<T> : IEnumerable
{
private static readonly T[] arrayStarter = new T[1];
private int capacity = 1;
private int currentItems = 1;
public T[] TList { get; set; }
public MyList()
{
TList = arrayStarter;
TList[0] = default(T);
}
public T[] Add(T[] tArray, T item)
{
T[] temp = new T[++capacity];
for (int i = 0; i < tArray.Length; i++)
temp[i] = tArray[i];
temp[tArray.Length] = item;
currentItems++;
return temp;
}
}
When I'm creating an instance of my list and I want to add an item using the method it looks like this:
MyList<int> m = new MyList<int>();
m.TList = m.Add(m.TList, 5);
m.TList = m.Add(m.TList, 7);
m.TList = m.Add(m.TList, 13);
m.TList = m.Add(m.TList, 15);
I'm pretty sure there's a better way to make a custom list I hope someone out there has a good insight on the matter.
This implementation demonstrates that you are missing the point of encapsulation in general, and of using private members in particular. Since you made TList array a member of your list class, you have an ability to hide it from your users completely. Your users should be able to write
m.Add(5);
m.Add(7);
instead of
m.TList = m.Add(m.TList, 5);
m.TList = m.Add(m.TList, 7);
and not worry about m.TList's presence at all.
Fortunately, you can do it very easily: make TList a private field, rename it to something that starts in lower case letter to follow C#'s naming guidelines, remove it from the list of Add's parameters, and change Add to not return anything. This would match IList<T>'s signature, which you should consider implementing.
Once you've made this work, consider "divorcing" capacity from currentItems, letting the first one grow faster, and the other one catching up to it as more items are added. This would reduce the number of re-allocations.
As a final point, consider switching to using Array.Resize<T> to avoid manual copying of data.
If you take a look at List<>'s Add() method implementation in ReferenceSource or with ILSpy, you'll see this:
private int _size;
private T[] _items;
public void Add(T item)
{
if (this._size == this._items.Length)
{
this.EnsureCapacity(this._size + 1);
}
this._items[this._size++] = item;
this._version++;
}
private void EnsureCapacity(int min)
{
if (this._items.Length < min)
{
int num = (this._items.Length != 0) ? (this._items.Length * 2) : 4;
if (num > 2146435071)
{
num = 2146435071;
}
if (num < min)
{
num = min;
}
this.Capacity = num;
}
}
How would I use the following LINQ query correctly? I want to create a one-dimensional array that contains only values that are greater than 5. I can't understand why it can't iterate over this multidimensional array, but if I use foreach, it actually iterates.
// Create an multidimensional array, then just put numbers into it and print the array.
int[,] myArr = new int[5,6];
int rows = myArr.GetLength(0);
int cols = myArr.GetLength(1);
for (int i = 0; i < rows; i++)
{
for (int k = 0; k < cols; k++)
{
myArr[i,k] = i + k;
Console.Write(myArr[i,k]);
}
Console.WriteLine("");
}
var highList = from val in myArr where (val > 5) select val;
The error is:
Could not find an implementation of the query pattern for source type 'int[*,*]'. 'Where' not found. Are you missing a reference or a using directive for 'System.Linq'?
I thought this might fix the problem:
public static IEnumerator<int> GetEnumerator(int[,] arr)
{
foreach(int i in arr)
{
yield return i;
}
}
But it doesn't implement the iterator.
The problem is that multi-dimensional (rectangular) arrays implement IEnumerable, but not IEnumerable<T>. Fortunately, you can use Cast to fix that - and Cast gets called automatically if you explicitly specify the type of the range variable:
var highList = from int val in myArr where (val > 5) select val;
Or without the unnecessary brackets:
var highList = from int val in myArr where val > 5 select val;
Or using method calls directly, given that it's a pretty trivial query expression:
var highList = myArr.Cast<int>().Where(val => val > 5);
I think this will box each element, however. You could add your own Cast extension method to avoid that:
public static class RectangularArrayExtensions
{
public static IEnumerable<T> Cast<T>(this T[,] source)
{
foreach (T item in source)
{
yield return item;
}
}
}
I am using an extension method which shuffles a generic list. This works
public static void Shuffle<T>(this IList<T> list)
{
RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
int n = list.Count;
while (n > 1)
{
byte[] box = new byte[1];
do provider.GetBytes(box);
while (!(box[0] < n * (Byte.MaxValue / n)));
int k = (box[0] % n);
n--;
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
I am trying trying to create another extension method which would utilize Shuffle(), but would shuffle the items in a list in groups based on a defined group size. This method seems to work when debugging the extension method, but the source list in the calling code still contains the original list after the extension call:
public static void GroupRandomize<T>(this IList<T> sourceList, int groupSize)
{
List<T> shuffledList = new List<T>();
List<T> tempList = new List<T>();
int addCounter = 0;
for (int i = 0; i < sourceList.Count; i++)
{
tempList.Add(sourceList[i]);
// if we've built a full group, or we're done processing the entire list
if ((addCounter == groupSize - 1) || (i == sourceList.Count - 1))
{
tempList.Shuffle();
shuffledList.AddRange(tempList);
tempList.Clear();
addCounter = 0;
}
else
{
addCounter++;
}
}
sourceList = shuffledList;
}
How do I ensure the shuffled list is stored properly into the source list?
sourceList is actually a local variable.
Might be better to return shuffedList;
var newList = caller.GroupRandomize<T>(5) ;
sourceList = shuffledList;
This will do nothing unless you are using a ref parameter. You could change your method so that it modifies the sourceList directly:
for(int i = 0; i < sourceList.Length; i++)
sourceList[i] = shuffledList[i];
But I'd recommend changing your approach so that the extension methods return new, shuffled lists, leaving the original lists intact. So instead of:
var list = GetList();
list.Shuffle();
... you would say:
var list = GetList().Shuffle();
Make it a regular method instead of an extension so you can pass it in by reference:
public static void GroupRandomize<T>(ref IList<T> sourceList, int groupSize) {
// ... stuff
sourceList = shuffledList;
}
Or if you don't want to change the header of the method, you could do the something like:
sourceList.Clear();
sourceList.AddRange( shuffledList );
Edit:
As stated by bperniciaro, The AddRange method is not available in the IList<T> interface.
StriplingWarrior already suggested an implementation that does what AddRange would do, so instead I will just improve his answer a little by pointing to another answer, by hvostt, that implements AddRange as an extension method of IList<T>.
I wrote skip last method. When I call it with int array, I expect to only get 2 elements back, not 4.
What is wrong?
Thanks
public static class myclass
{
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int n)
{
return source.Reverse().Skip(n).Reverse();
}
}
class Program
{
static void Main(string[] args)
{
int [] a = new int[] {5, 6, 7, 8};
ArrayList a1 = new ArrayList();
a.SkipLast(2);
for( int i = 0; i <a.Length; i++)
{
Console.Write(a[i]);
}
}
}
you need to call as
var newlist = a.SkipLast(2);
for( int i = 0; i <newlist.Count; i++)
{
Console.Write(newlist[i]);
}
your method returning skipped list, but your original list will not update
if you want to assign or update same list you can set the returned list back to original as a = a.SkipLast(2).ToArray();
You should assign the result, not just put a.SkipLast(2):
a = a.SkipLast(2).ToArray(); // <- if you want to change "a" and loop on a
for( int i = 0; i <a.Length; i++) { ...
When you do a.SkipLast(2) it creates IEnumerable<int> and then discards it;
The most readable solution, IMHO, is to use foreach which is very convenient with LINQ:
...
int [] a = new int[] {5, 6, 7, 8};
foreach(int item in a.SkipLast(2))
Console.Write(item);
The other replies have answered your question, but wouldn't a more efficient implementation be this (which doesn't involve making two copies of the array in order to reverse it twice). It does iterate the collection twice (or rather, once and then count-n accesses) though:
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int n)
{
n = source.Count() - n;
return source.TakeWhile(_ => n-- > 0);
}
Actually, if source is a type that implements Count without iteration (such as an array or a List) this will only access the elements count-n times, so it will be extremely efficient for those types.
Here is a better solution that only iterates the sequence once. It's data requirements are such that it only needs a buffer with n elements, which makes it very efficient if n is small compared with the size of the sequence:
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int n)
{
int count = 0;
T[] buffer = new T[n];
var iter = source.GetEnumerator();
while (iter.MoveNext())
{
if (count >= n)
yield return buffer[count%n];
buffer[count++%n] = iter.Current;
}
}
Change your code to,
foreach (var r in a.SkipLast(2))
{
Console.Write(r);
}
for three reasons,
The SkipLast function returns the mutated sequence, it doesn't change it directly.
What is the point of using an indexer with IEnumerable? It imposes a needless count.
This code is easy to read, easier to type and shows intent.
For a more efficient generic SkipLast see Matthew's buffer with enumerator.
Your example could use a more specialised SkipLast,
public static IEnumerable<T> SkipLast<T>(this IList<T> source, int n = 1)
{
for (var i = 0; i < (source.Count - n); i++)
{
yield return source[i];
}
}
So I need to take names and ages of people, store them in two separate arrays, sort them by age, and then display the results in a listbox. I have everything down until putting it back into the listbox. It sounds simple, but the foreach loop (from my very basic understanding of C#) can only take the values of one of the arrays. I need the names and ages on the same line because they're linked together, so I decided to switch to a for loop. However, even though my sorting on the Ages works just fine, I can't make the name match the age. Here is some of my code:
const int MAX = 100;
int count = 0;
int[] Age = new int[MAX];
string[] NameEntry = new string[MAX];
Just to show you how I declared the arrays. Here is my "store" click event:
int age;
if (txtName.Text == "")
{
lblWarningMessage.Text = "There is an error with your entries. A name must be entered!";
btnClearWarn.Show();
}
else
{
NameEntry[count] = txtName.Text;
if (int.TryParse(txtAge.Text, out age))
{
Age[count] = age;
txtAge.Clear();
txtName.Clear();
txtName.Focus();
lstbxResults.Items.Add(NameEntry[count] + " is " + Age[count].ToString() + " years old.");
count++;
}
else
{
lblWarningMessage.Text = "There is an error with your entries. The Age entry must be an integer.";
btnClearWarn.Show();
}
}
And finally, the sorting operation and subsequent for loop to add it to my listbox:
Array.Resize(ref NameEntry, count);
Array.Resize(ref Age, count);
lstbxResults.Items.Clear();
int minAge = 0;
int minIndex = 0;
for (int a = 0; a < Age.Length - 1; a++)
{
minAge = Age[a];
minIndex = a;
for (int b = a + 1; b < Age.Length; b++)
{
if (Age[b] < minAge)
{
minAge = Age[b];
minIndex = b;
}
}
OrderByAges(ref Age[minIndex], ref Age[a]);
}
for (int c = 0; c < Age.Length; c++)
{
lstbxResults.Items.Add(NameEntry[c] + " is " + Age[c] + " years old.");
}
}
private void OrderByAges(ref int p1, ref int p2)
{
int temp = p2;
p2 = p1;
p1 = temp;
}
Yes, I realize Array.sort would be faster, but this serves the same purpose, and this is how I was instructed to do it. Any thoughts on how to link the element in "NameEntry" to "Age", and have it change along with the Age when it is sorted?
You really should use "Array.Sort" in this case. It is capable of co-sorting keys and values in two linked arrays, as in:
Array.Sort(Age, NameEntry, 0, count);
If your teacher really insists on a solution that does not use Array.Sort, just use a quicksort implementation that simply swaps the elements of both key and value at the same time:
Like this (not tested):
public static class CoSorter
{
public static void Sort<TKey, TValue>(this TKey[] keys, TValue[] values, int start, int count)
{
QuickCosort(keys, values, start, count - 1, Comparer<TKey>.Default);
}
public static void Sort<TKey, TValue>(this TKey[] keys, TValue[] values, int start, int count, IComparer<TKey> comparer)
{
QuickCosort(keys, values, start, count - 1, comparer);
}
private static void QuickCosort<TKey, TValue>(TKey[] keys, TValue[] values, int left, int right, IComparer<TKey> comparer)
{
int i = left, j = right;
var pivot = keys[(left + right) / 2];
while (i <= j)
{
while (comparer.Compare(keys[i], pivot) < 0)
{
i++;
}
while (comparer.Compare(keys[j], pivot) > 0)
{
j--;
}
if (i <= j)
{
// Swap
var tmpKey = keys[i];
var tmpVal = values[i];
keys[i] = keys[j];
values[i] = values[j];
keys[j] = tmpKey;
values[j] = tmpVal;
i++;
j--;
}
}
// Recursive calls
if (left < j)
{
QuickCosort(keys, values, left, j, comparer);
}
if (i < right)
{
QuickCosort(keys, values, i, right, comparer);
}
}
}
Alternatively you may want to think about this a bit harder and see if maybe you need a data structure instead of two arrays of values that belong together.
Reaction to OP's comment under Alex' answer - if this is a homework/school assignment, it's a good custom on StackOverflow to say so in the question in the first place.
If your teacher doesn't want you to use Array.Sort, is the assignment by any chance primarily about implementing a sorting algorithm? If so, it's obvious that you should implement your own and not use a library function - and again it would be best to have that in the question.
Anyway whether it's the case or not, the most sensible solution would be to create a Person class containing both Name and Age, sort array of people by age and fill the listbox by their names (or rather use a data binding, like this). Two individual arrays of related values don't make much sense in object oriented world and are harder to maintain - as is visible in your case.
If you have to use two separate parallel arrays, the elements of each array are linked by the index. But they are only linked because you happen to put the elements in at the same index. It's not automagic. So when you move something in one array, remember the indexes used, and use those to move the same thing in the other array.
Your code moves the age in the double nested loop, so move the name in that code as well.