Generalize a method to work with various objects - c#

In a program I'm writing, I've decided to use my own data types instead of any other database ( for educational purposes).
The data is saved as csv files, and i have a method to convert from .csv to a simple string[,].
Each class X as it's own string[,] to List converter.
After the first 3 classes i started to play with generics and reflection.
I did succeed converting a general List to string[,], but doing the opposite is started to look hard.
the way i implement List to string[,] is:
public string[,] ToArray(object[] sender)
{
if (sender.Length==0)
{
return null;
}
string[,] ret;
PropertyInfo[] props;
if (sender.Length > 0)
{
props = sender[0].GetType().GetProperties();
ret = new string[props.Length, sender.Length];
}
else
{
return null;
}
for (int i=0;i<sender.Length;i++)
{
for (int p = 0; p < props.Length; p++)
{
object value=props[p].GetValue(sender[i], null);
if (value!=null) ret[p, i] = value.ToString();
}
}
return ret;
}
and for lets say class Windows( string Name, double Size, bool Blinds)
i convert array[,] to Windows ( very generally ) like this :
public static List<Windows> ToList(string[,] arra)
{
List<Windows> ret = new List<Windows>(); // change Windows to anything
int col = array.GetLength(1);
int row = array.GetLength(0);
PropertyInfo[] props=PropArray(new Windows());
int propslen=props.Length;
for (int c = 0; c < col; c++)
{
Windows entry=new Windows();
for (int r = 0; r < propslen; r++)
{
Type pt = props[r].PropertyType;
if (pt==typeof(string))
props[r].SetValue(entry,array[r,c],null);
else
if (pt==typeof(int))
{
int i=0;int.TryParse(array[r,c],out i);
props[r].SetValue(entry,i,null);
}
else
if (pt==typeof(bool))
{
bool i = false; bool.TryParse(array[r, c], out i);
props[r].SetValue(entry,i,null);
}
else
if (pt == typeof(double))
{
double i = 0; double.TryParse(array[r, c], out i);
props[r].SetValue(entry, i, null);
}
else
if (pt==typeof(DateTime))
{
DateTime i = DateTime.MinValue; DateTime.TryParse(array[r, c], out i);
props[r].SetValue(entry,i,null);
}
}
ret.Add(entry);
}
return ret;
}
All i need to do is FindReplace the word "Windows" to any other datatype and it works.
The real question is how can i generalize it to receive a type and make a list of instances it all by itself?
Is it even possible?
Thanks in advance,
Gabriel

I will not be foolproof, but you can use generics. Something like:
public static List<T> ToList<T>(string[,] arra) // T can be anything
where T : new() // as long as it has a default constructor
{
List<T> ret = new List<T>();
int col = array.GetLength(1);
int row = array.GetLength(0);
PropertyInfo[] props=PropArray(new T());
int propslen=props.Length;
for (int c = 0; c < col; c++)
{
T entry=new T();
// ...

What you are doing is essentially a serialization format. Serialization formats are relatively simple if objects are values without complex references. Simple xml or json are common formats. Perhaps you should look into using json?
If you go for "roll your own", why not try to use property names as keys? Prefix the key/value list with the class name and instantiate the objects when you deserialize. Example format
MyApplication.Person, MyAssembly
Name=Steve
Age=30
MyApplication.Person, MyAssembly
Name=Bob
Age=54
To deserialize, read the class name, instatiate using FormatterServices.GetUninitializedObject() to get an empty instance of the class. Then using the property infos of that class, set the values from the key/value pairs that follow in the file. This is essentially a simplified JSON notation (does not support lists,maps etc.)

Related

How to convert Object from UDF parameter to Excel Range in Excel-DNA?

I am developing a simple Addin using Excel-DNA. I have written a below function, but I am finding difficulties in converting it to a Range object. Tried googling and not able to figure out. Can someone please help me
[ExcelFunction(Description = "Excel Range")]
public static string Concat2([ExcelArgument(AllowReference = true)] object rng)
{
try
{
// Assuming i am calling this from Excel cell A5 as =Concat2(A1:A2)
var app = (Excel.Application)ExcelDnaUtil.Application;
var r = app.Range[rng, Type.Missing];
return r.Cells[1,1] + r.Cells[2,2]
}
catch (Exception e)
{
return "Error";
}
}
You should rather get the values directly from the input parameter, without getting the Range COM object. It's also much more efficient doing it that way.
Your simple function might then look like this:
public static object Concat2(object[,] values)
{
string result = "";
int rows = values.GetLength(0);
int cols = values.GetLength(1);
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
object value = values[i, j];
result += value.ToString();
}
}
return result;
}
Typically you'd want to check the type of the value object, and do something different based on that. The object[,] array passed from Excel-DNA could have items of the following types:
double
string
bool
ExcelDna.Integration.ExcelError
ExcelDna.Integration.ExcelEmpty
ExcelDna.Integration.ExcelMissing (if the function is called with no parameter, as =Concat2()).
If you change the signature to have a single parameter of type object (instead of object[,]), like this:
public static object Concat2(object value)
then, depending on how the function is called, you might get one of the above types as the value or you might get an object[,] array as the value, so your type checks would look a bit different before you do the iteration.
In my F# addin I have a function that does that (I use this function mainly to extract the displayed values of dates):
[<ExcelFunction(Description="Returns what is currently displayed as text.", IsMacroType=true)>]
let DISPLAYEDTEXT ([<ExcelArgument(Description="Cell", AllowReference=true)>] rng : obj) =
app().get_Range(XlCall.Excel(XlCall.xlfReftext, rng, true)).Text
where app is:
let app()= ExcelDnaUtil.Application :?> Excel.Application
How about this?
[ExcelFunction(IsMacroType = true)]
public static double GetBackColor([ExcelArgument(AllowReference=true)] object cell)
{
ExcelReference rng = (ExcelReference)cell;
Excel.Range refrng = ReferenceToRange(rng);
return refrng.Interior.Color;
}
and this is the helper function
private static Excel.Range ReferenceToRange(ExcelReference xlRef)
{
Excel.Application app = (Excel.Application)ExcelDnaUtil.Application;
string strAddress = XlCall.Excel(XlCall.xlfReftext, xlRef, true).ToString();
return app.Range[strAddress];
}

c# comparing list of IDs

I have a List<Keyword> where Keyword class is:
public string keyword;
public List<int> ids;
public int hidden;
public int live;
public bool worked;
Keyword has its own keyword, a set of 20 ids, live by default is set to 1 and hidden to 0.
I just need to iterate over the whole main List to invalidate those keywords whose number of same ids is greater than 6, so comparing every pair, if the second one has more than 6 ids repeated respect to the first one, hidden is set to 1 and live to 0.
The algorithm is very basic but it takes too long when the main list has many elements.
I'm trying to guess if there could be any method I could use to increase the speed.
The basic algorithm I use is:
foreach (Keyword main_keyword in lista_de_keywords_live)
{
if (main_keyword.worked) {
continue;
}
foreach (Keyword keyword_to_compare in lista_de_keywords_live)
{
if (keyword_to_compare.worked || keyword_to_compare.id == main_keyword.id) continue;
n_ids_same = 0;
foreach (int id in main_keyword.ids)
{
if (keyword_to_compare._lista_models.IndexOf(id) >= 0)
{
if (++n_ids_same >= 6) break;
}
}
if (n_ids_same >= 6)
{
keyword_to_compare.hidden = 1;
keyword_to_compare.live = 0;
keyword_to_compare.worked = true;
}
}
}
The code below is an example of how you would use a HashSet for your problem. However, I would not recommend using it in this scenario. On the other hand, the idea of sorting the ids to make the comparison faster still.
Run it in a Console Project to try it out.
Notice that once I'm done adding new ids to a keyword, I sort them. This makes the comparison faster later on.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace KeywordExample
{
public class Keyword
{
public List<int> ids;
public int hidden;
public int live;
public bool worked;
public Keyword()
{
ids = new List<int>();
hidden = 0;
live = 1;
worked = false;
}
public override string ToString()
{
StringBuilder s = new StringBuilder();
if (ids.Count > 0)
{
s.Append(ids[0]);
for (int i = 1; i < ids.Count; i++)
{
s.Append(',' + ids[i].ToString());
}
}
return s.ToString();
}
}
public class KeywordComparer : EqualityComparer<Keyword>
{
public override bool Equals(Keyword k1, Keyword k2)
{
int equals = 0;
int i = 0;
int j = 0;
//based on sorted ids
while (i < k1.ids.Count && j < k2.ids.Count)
{
if (k1.ids[i] < k2.ids[j])
{
i++;
}
else if (k1.ids[i] > k2.ids[j])
{
j++;
}
else
{
equals++;
i++;
j++;
}
}
return equals >= 6;
}
public override int GetHashCode(Keyword keyword)
{
return 0;//notice that using the same hash for all keywords gives you an O(n^2) time complexity though.
}
}
class Program
{
static void Main(string[] args)
{
List<Keyword> listOfKeywordsLive = new List<Keyword>();
//add some values
Random random = new Random();
int n = 10;
int sizeOfMaxId = 20;
for (int i = 0; i < n; i++)
{
var newKeyword = new Keyword();
for (int j = 0; j < 20; j++)
{
newKeyword.ids.Add(random.Next(sizeOfMaxId) + 1);
}
newKeyword.ids.Sort(); //sorting the ids
listOfKeywordsLive.Add(newKeyword);
}
//solution here
HashSet<Keyword> set = new HashSet<Keyword>(new KeywordComparer());
set.Add(listOfKeywordsLive[0]);
for (int i = 1; i < listOfKeywordsLive.Count; i++)
{
Keyword keywordToCompare = listOfKeywordsLive[i];
if (!set.Add(keywordToCompare))
{
keywordToCompare.hidden = 1;
keywordToCompare.live = 0;
keywordToCompare.worked = true;
}
}
//print all keywords to check
Console.WriteLine(set.Count + "/" + n + " inserted");
foreach (var keyword in set)
{
Console.WriteLine(keyword);
}
}
}
}
The obvious source of inefficiency is the way you calculate intersection of two lists (of ids). The algorithm is O(n^2). This is by the way problem that relational databases solve for every join and your approach would be called loop join. The main efficient strategies are hash join and merge join. For your scenario the latter approach may be better I guess, but you can also try HashSets if you like.
The second source of inefficiency is repeating everything twice. As (a join b) is equal to (b join a), you do not need two cycles over the whole List<Keyword>. Actually, you only need to loop over the non duplicate ones.
Using some code from here, you can write the algorithm like:
Parallel.ForEach(list, k => k.ids.Sort());
List<Keyword> result = new List<Keyword>();
foreach (var k in list)
{
if (result.Any(r => r.ids.IntersectSorted(k.ids, Comparer<int>.Default)
.Skip(5)
.Any()))
{
k.hidden = 1;
k.live = 0;
k.worked = true;
}
else
{
result.Add(k);
}
}
If you replace the linq with just the index manipulation approach (see the link above), it would be a tiny bit faster I guess.

"Invalid Callee" while using MLapp in C#

I'm having a strange issue while using the MLApp.GetWorkspaceData function. I noticed that this functions works properly when I do the following:
matlab = new MLApp.MLAppClass();
object myObject;
matlab.GetWorkspaceData("myVariable", "base", out myObject);
But if I then try to use the same object as an output I get an "Invalid Callee" exception. In addition this also gives the same error:
matlab = new MLApp.MLAppClass();
object myObject = new object();
matlab.GetWorkspaceData("myVariable", "base", out myObject);
This is very troublesome because I need to get a large amount of data from Matlab to Visual Studio, and I cannot practically create 52K uninitialized variables and keep them around. Is there some way to "uninitialize" a variable? Is there some concept I'm missing here?
As #wonko79 explained in the comments, if you want to reuse the out variable, you should set it to null first.
Here is a tested example calling MATLAB from C#:
using System;
namespace CSharp_matlab_com
{
class Program
{
static void Main(string[] args)
{
MLApp.MLAppClass matlab = new MLApp.MLAppClass();
// create variables: a_0, a_1, ..., a_4
for (int k = 0; k < 5; k++) {
matlab.Execute(string.Format("a_{0} = rand(2);", k));
}
// retrieve variables from MATLAB and print their contents
object a;
for (int k = 0; k < 5; k++) {
// current variable name
string varname = string.Format("a_{0}", k);
// get data array
a = null; // without this line, an exception is thrown!
matlab.GetWorkspaceData(varname, "base", out a);
// print contents
var arr = (double[,]) a;
Console.WriteLine("\nndims(a) = {0}, numel(a) = {1}", arr.Rank, arr.Length);
for (int i = 0; i < arr.GetLength(0); i++) {
for (int j = 0; j < arr.GetLength(1); j++) {
Console.WriteLine("{0}[{1},{2}] = {3}", varname, i, j, arr[i,j]);
}
}
}
}
}
}
The output:
ndims(a) = 2, numel(a) = 4
a_0[0,0] = 0.251806122472313
a_0[0,1] = 0.617090884393223
a_0[1,0] = 0.290440664276979
a_0[1,1] = 0.265280909810029
...
ndims(a) = 2, numel(a) = 4
a_4[0,0] = 0.425259320214135
a_4[0,1] = 0.16148474431175
a_4[1,0] = 0.312718886820616
a_4[1,1] = 0.178766186752368
You can create a wrapper for GetWorkspaceData method, like in the next example:
public object GetData(string name)
{
object data;
mlApp.GetWorkspaceData(name, "base", out data);
return data;
}
Or, even more useful, a generic wrapper:
public T GetData<T>(string name)
{
object data;
mlApp.GetWorkspaceData(name, "base", out data);
if (data == null)
return default(T);
if (data is T)
return (T)data;
else
throw new InvalidCastException($"The variable '{name}', of type '{data.GetType().Name}' cannot be casted to type '{typeof(T).Name}'.");
}
The solution is to set the output object to null.
I found it here.

Why can't I Selection Sort this library?

I'm trying to use Selection Sort for a library of books so that it sorts them alphabetically, however I can't get it to work.
SelectionSort(library); doesn't work but SelectionSort(titles); does, any ideas? Thanks :)
Here is the full code:
using System;
using System.Collections.Generic;
using System.Text;
namespace BookSortingEx
{
class Program
{
static void swap<T>(ref T x, ref T y)
{
//swapcount++;
T temp = x;
x = y;
y = temp;
}
static void printArray(string[] a)
{
for (int i = 0; i < a.Length; i++)
{
Console.Write(a[i] + ",");
}
Console.WriteLine();
}
static bool IsInOrder<T>(T[] a) where T : IComparable
{
for (int i = 0; i < a.Length - 1; i++)
{
if (a[i].CompareTo(a[i + 1]) > 0)
return false;
}
return true;
}
static void Main(string[] args)
{
string[] array1 = { "Fred", "Zoe", "Angela", "Umbrella", "Ben" };
string[] titles = {"Writing Solid Code",
"Objects First","Programming Gems",
"Head First Java","The C Programming Language",
"Mythical Man Month","The Art of Programming",
"Coding Complete","Design Patterns",
"Problem Solving in Java"};
string[] authors = { "Maguire", "Kolling", "Bentley", "Sierra", "Richie", "Brooks", "Knuth", "McConnal", "Gamma", "Weiss" };
string[] isbns = { "948343", "849328493", "38948932", "394834342", "983492389", "84928334", "4839455", "21331322", "348923948", "43893284", "9483294", "9823943" };
Book[] library = new Book[10];
for (int i = 0; i < library.Length; i++)
{
library[i] = new Book(isbns[i], titles[i], authors[i]);
}
**DOESNT WORK - // SelectionSort(library);**
SelectionSort(titles);
printArray(titles);
foreach (Book book in library)
{
Console.WriteLine(" {0} ", book);
}
Console.WriteLine();
Console.ReadKey();
}
static public void SelectionSort(string[] a)
{
for (int i = 0; i < a.Length - 1; i++)
{
int smallest = i;
for (int j = i + 1; j < a.Length; j++)
{
if (a[j].CompareTo(a[smallest]) < 0)
smallest = j;
}
swap(ref a[i], ref a[smallest]);
}
}
}
}
Your method expects a string[], but you're giving it a Book[]
You'll want to change the implementation of SelectionSort to support Comparable Collections, as well as have Book implement the IComparable interface.
public class Book : IComparable
{
// Implementation
public int CompareTo(Book otherBook)
{
// Implementation
}
}
static public void SelectionSort<T>(IList<T> a) where T : IComparable
{
// Implementation
}
The advantage of this approach, is that you won't have to create a different version of SelectionSort for every kind of object collection you'll want to sort.
Because SelectionSort takes as an argument an array of type string and you are passing it an array of type Book. SelectionSort(titles) works because titles is an array of type string.
You need to write a method that takes an array of type Book
static public void SelectionSort(Book[] books)
and if you haven't done this already you probably need to define a CompareTo method in your Book class, so your sorting algorithm can figure out how to sort books.

mergesort - with an insignificant change throws SystemInvalidOperationException

A very strange thing occured in my program. Here is the simplified code.
class Program
{
static void Main(string[] args)
{
ArrayList numbers = new ArrayList();
numbers.Add(1);
numbers.Add(3);
numbers.Add(4);
numbers.Add(2);
var it = Sorts.MergeSort((ArrayList)numbers.Clone());
Sorts.PrintArray(it, "mergesort");
Console.WriteLine("DONE");
Console.ReadLine();
}
}
public static class Sorts
{
public static ArrayList BubbleSort(ArrayList numbers)
{
bool sorted = true;
for (int i = 0; i < numbers.Count; i++)
{
for (int j = 1; j < numbers.Count; j++)
{
if ((int)numbers[j - 1] > (int)numbers[j])
{
int tmp = (int)numbers[j - 1];
numbers[j - 1] = numbers[j];
numbers[j] = tmp;
sorted = false;
}
}
if (sorted)
{
return numbers;
}
}
return numbers;
}
public static ArrayList MergeSort(ArrayList numbers, int switchLimit = 3)
{
//if I use this if - everything works
if (numbers.Count <= 1)
{
// return numbers;
}
//the moment I use this condition - it throws SystemInvalidOperationException in function Merge in the line of a "for"-loop
if (numbers.Count <=switchLimit)
{
return Sorts.BubbleSort(numbers);
}
ArrayList ret = new ArrayList();
int middle = numbers.Count / 2;
ArrayList L = numbers.GetRange(0, middle);
ArrayList R = numbers.GetRange(middle, numbers.Count - middle);
L = MergeSort(L);
R = MergeSort(R);
return Merge(L, R);
}
private static ArrayList Merge(ArrayList L, ArrayList R)
{
ArrayList ret = new ArrayList();
int l = 0;
int r = 0;
for (int i = 0; i < L.Count + R.Count; i++)
{
if (l == L.Count)
{
ret.Add(R[r++]);
}
else if (r == R.Count)
{
ret.Add(L[l++]);
}
else if ((int)L[l] < (int)R[r])
{
ret.Add(L[l++]);
}
else
{
ret.Add(R[r++]);
}
}
return ret;
}
//---------------------------------------------------------------------------------
public static void PrintArray(ArrayList arr, string txt = "", int sleep = 0)
{
Console.WriteLine("{1}({0}): ", arr.Count, txt);
for (int i = 0; i < arr.Count; i++)
{
Console.WriteLine(arr[i].ToString().PadLeft(10));
}
Console.WriteLine();
System.Threading.Thread.Sleep(sleep);
}
}
There is a problem with my Sorts.MergeSort function.
When I use it normally (take a look at the first if-condition in a function - all works perfectly.
But the moment when I want it to switch to bubblesort with smaller input (the second if-condition in the function) it throws me an SystemInvalidOperationException. I have no idea where is the problem.
Do you see it?
Thanks. :)
Remark: bubblesort itself works - so there shouldn't be a problem in that sort...
The problem is with your use of GetRange:
This method does not create copies of the elements. The new ArrayList is only a view window into the source ArrayList. However, all subsequent changes to the source ArrayList must be done through this view window ArrayList. If changes are made directly to the source ArrayList, the view window ArrayList is invalidated and any operations on it will return an InvalidOperationException.
You're creating two views onto the original ArrayList and trying to work with both of them - but when one view modifies the underlying list, the other view is effectively invalidated.
If you change the code to create copies of the sublists - or if you work directly with the original list within specified bounds - then I believe it'll work fine.
(As noted in comments, I'd also strongly recommend that you use generic collections.)
Here's a short but complete program which demonstrates the problem you're running into:
using System;
using System.Collections;
class Program
{
static void Main()
{
ArrayList list = new ArrayList();
list.Add("a");
list.Add("b");
ArrayList view1 = list.GetRange(0, 1);
ArrayList view2 = list.GetRange(1, 1);
view1[0] = "c";
Console.WriteLine(view2[0]); // Throws an exception
}
}
on this line R = MergeSort(R); you alter the range of numbers represented by L. This invalidates L. Sorry I have to go so can't explain any further now.

Categories