I have a function called ValidColumns that, when finished will contain a string of id values joined by the pipe character "|."
private bool ValidColumns(string[] curGlobalAttr, int LineCount)
{
//test to see if there is more than 1 empty sku mod field in the imported file
string SkuMod = GetValue(curGlobalAttr, (int)GlobalAttrCols.SKU);
if(SkuMod =="")
ids += string.Join("|", OptionId);
}
What I want to do is take the ids string and pass it as a reference into another function to check to see if it contains duplicate values:
protected bool CheckForDuplicates(ref string ids)
{
bool NoDupes = true;
string[] idSet = ids.Split('|');
for (int i = 1; i <= idSet.Length - 1; i++)
{
if (idSet[i].ToString() == idSet[i - 1].ToString()) { NoDupes = false; }
}
return NoDupes;
}
But I am not sure how to do it properly? It seems so easy but I feel like I'm making this out to be much harder than it needs to be.
if (idSet[i].ToString() == idSet[i - 1].ToString())
You're only checking each value against the previous value. That would work if the values were sorted, but an easier method would just be to get a distinct list and check the lengths:
return (idSet.Length == idSet.Distinct().Count());
The second part of D Stanley's answer is what you want
public bool CheckForDuplicates(string value)
{
string[] split = value.Split('|');
return split.Length != split.Distinct().ToArray().Length;
}
You need a nested loop to do this which will look at each item and compare it to another item. This also will eliminate checking it against itself so it does not always return false.
In this we will track what we have already checked and only compare the new items against items after that slowly shrinking the second loop down in size to eliminate duplicate checks.
bool noDupes = true;
int currentItem = 0;
int counter = 0;
string[] idSet = ids.Split('|');
while(currentItem < idSet.Count)
{
counter = currentItem + 1;
while(counter < idSet.Count)
{
if(idSet[currentItem].ToUpper() == idSet[counter].ToUpper())
{
noDupes = false;
return noDupes;
}
counter ++;
}
currentItem ++;
}
return noDupes;
Edit:
It was pointed out an option I posted as an answer would always return false so I removed that option and adjusted this option to be more robust and easier to step through logically.
Hope this helps :)
Related
I have a button which should take a 'surname' string input, search a directory array for the 'record' structure correlating to that surname, and output that record to a listview. The directory consists of many rows of 3 string record structures: surname, forename, extcode.
After days of scouring similar SO questions and other sources, I've pieced together the methods which seem to fit my problem best, but my primary search method is not, for some reason, referencing my secondary method. Can you tell me why you think this is, whether you think it will work if I manage to get the reference working, and if not, any alternative suggestions?
FYI my my if statement is a little unruly as my project requires that the surname be case insensitive and accept partial string matches.
private void SearchSurname()
{
Array.Sort(directory, (x, y) => String.Compare(x.surname, y.surname));
ClearForm();
int surnameIndex = Array.BinarySearch(directory, txtSurname.Text);
if (directory[surnameIndex].surname.ToUpper().Substring(0, txtSurname.Text.Length).Contains(txtSurname.Text.ToUpper()))
{
ListViewItem record = new ListViewItem();
// Send each field in current record to single listview item
record.Text = (Convert.ToString(txtSurname.Text));
record.SubItems.Add(Convert.ToString(txtForename.Text));
record.SubItems.Add(Convert.ToString(txtExtCode.Text));
// Display new record listview item in listview
lvDirectory.Items.Add(record);
}
}
public int BinarySearch(string[] directory, string searchTerm)
{
int first = 0;
int last = directory.Length - 1;
int position = -1;
bool found = false;
int compCount = 0;
searchTerm = txtSurname.Text;
while (found != true && first <= last)
{
int middle = (first + last) / 2;
if (string.Compare(directory[middle], searchTerm, true) == 0)
{
found = true;
position = middle;
compCount++;
}
else if (string.Compare(directory[middle], searchTerm, true) > 0)
{
last = middle;
compCount++;
}
else
{
first = middle;
compCount++;
}
}
return position;
}
Edit: Updated code per Olivier Jacot-Descombes' answer:
private void SearchSurname()
{
// Sort directory alphabetically by surname
Array.Sort(directory, (x, y) => String.Compare(x.surname, y.surname));
ClearForm();
// In directory, find line index of search term
int surnameIndex = BinarySearch(directory, txtSurname.Text);
ListViewItem record = new ListViewItem();
// Send each field in current record to single listview item
record.Text = (Convert.ToString(directory[surnameIndex].surname));
record.SubItems.Add(Convert.ToString(directory[surnameIndex].forename));
record.SubItems.Add(Convert.ToString(directory[surnameIndex].extCode));
// Display new record listview item in listview
lvDirectory.Items.Add(record);
}
private int BinarySearch(record[] directory, string searchTerm)
{
int first = 0;
int last = directory.Length - 1;
int surnameIndex = -1;
bool indexFound = false;
// While index not found and there are still points in the array to check
while (indexFound != true && first < last)
{
int middle = (first + last) / 2;
// If surname field in middle record of directory array matches the search term
if (string.Compare(directory[middle].surname, searchTerm, true) == 0)
{
// Index found!
indexFound = true;
surnameIndex = middle;
MessageBox.Show("If 1");
}
// If surname field in middle record of directory array is higher, alphabetically, than the search term
else if(string.Compare(directory[middle].surname, searchTerm, true) > 0)
{
// The next search will be between the first and the current middle records of the array
last = middle;
MessageBox.Show("If 2");
}
// If surname field in middle record of directory array is lower, alphabetically, than the search term
else
{
// The next search will be between the current middle and the highest records of the array
first = middle;
MessageBox.Show("If 3");
}
}
return surnameIndex;
}
Your SearchSurname method calls Array.BinarySearch, which is a static method of the Array Class. If you wanted to call your own method, you would have to write:
int surnameIndex = BinarySearch(directory, txtSurname.Text); // Without "Array."
You are using case-insensitive comparison through the 3rd parameter of String.Compare being true.
You could use
if (string.StartsWith(directory[middle], searchTerm,
StringComparison.CurrentCultureIgnoreCase))
To search only for beginning of names. But there is a problem if several names match. E.g. You are searching for "smit" and you have "Smith" and "Smithy" in the array. Therefore, you would have to test the entries before and after the found match, and return all matching ones as possible results.
I am working with two lists. The first contains a large sequence of strings. The second contains a smaller list of strings. I need to find where the second list exists in the first list.
I worked with enumeration, and due to the large size of the data, this is very slow, I was hoping for a faster way.
List<string> first = new List<string>() { "AAA","BBB","CCC","DDD","EEE","FFF" };
List<string> second = new List<string>() { "CCC","DDD","EEE" };
int x = SomeMagic(first,second);
And I would need x to = 2.
Ok, here is my variant with old-good-for-each-loop:
private int SomeMagic(IEnumerable<string> source, IEnumerable<string> target)
{
/* Some obvious checks for `source` and `target` lenght / nullity are ommited */
// searched pattern
var pattern = target.ToArray();
// candidates in form `candidate index` -> `checked length`
var candidates = new Dictionary<int, int>();
// iteration index
var index = 0;
// so, lets the magic begin
foreach (var value in source)
{
// check candidates
foreach (var candidate in candidates.Keys.ToArray()) // <- we are going to change this collection
{
var checkedLength = candidates[candidate];
if (value == pattern[checkedLength]) // <- here `checkedLength` is used in sense `nextPositionToCheck`
{
// candidate has match next value
checkedLength += 1;
// check if we are done here
if (checkedLength == pattern.Length) return candidate; // <- exit point
candidates[candidate] = checkedLength;
}
else
// candidate has failed
candidates.Remove(candidate);
}
// check for new candidate
if (value == pattern[0])
candidates.Add(index, 1);
index++;
}
// we did everything we could
return -1;
}
We use dictionary of candidates to handle situations like:
var first = new List<string> { "AAA","BBB","CCC","CCC","CCC","CCC","EEE","FFF" };
var second = new List<string> { "CCC","CCC","CCC","EEE" };
If you are willing to use MoreLinq then consider using Window:
var windows = first.Window(second.Count);
var result = windows
.Select((subset, index) => new { subset, index = (int?)index })
.Where(z => Enumerable.SequenceEqual(second, z.subset))
.Select(z => z.index)
.FirstOrDefault();
Console.WriteLine(result);
Console.ReadLine();
Window will allow you to look at 'slices' of the data in chunks (based on the length of your second list). Then SequenceEqual can be used to see if the slice is equal to second. If it is, the index can be returned. If it doesn't find a match, null will be returned.
Implemented SomeMagic method as below, this will return -1 if no match found, else it will return the index of start element in first list.
private int SomeMagic(List<string> first, List<string> second)
{
if (first.Count < second.Count)
{
return -1;
}
for (int i = 0; i <= first.Count - second.Count; i++)
{
List<string> partialFirst = first.GetRange(i, second.Count);
if (Enumerable.SequenceEqual(partialFirst, second))
return i;
}
return -1;
}
you can use intersect extension method using the namepace System.Linq
var CommonList = Listfirst.Intersect(Listsecond)
There could be up to four items in my ListBox control. I want to assign the text displayed to string variables but I am running into problems with the index being out of range as soon as it gets to string Two. I know why this is happening (only one item in the listbox...) but I can't seem to get my head around how to do it.
I could possibly construct some kind of if statement to deal with this but I think there may be a more efficient method of doing it, rather than checking if it's less than 2 items, more than 1, etc.
Simple version of my code:
public void button1_Click(object sender, EventArgs e)
{
if (listBox1.Items.Count < 4) {
listBox1.Items.Add(comboBox1.Text);
} else {
System.Windows.Forms.MessageBox.Show("Too many");
}
string One = listBox1.Items[0].ToString();
string Two = listBox1.Items[1].ToString();
string Three = listBox1.Items[2].ToString();
string Four = listBox1.Items[3].ToString();
}
Note that I cannot use an array for this, as I need to access the variables in another application which does not have the ability to iterate through an array or access its indexes.
you could create a temporary array to handle the copying for you:
if (listBox1.Items.Count < 4) {
listBox1.Items.Add(comboBox1.Text);
} else {
System.Windows.Forms.MessageBox.Show("Too many");
}
string[] ListItemsArray= new string[4];
listBox1.Items.CopyTo(ListItemsArray, 0);
//Then you can leave this here:
string One = ListItemsArray[0] ?? "";
string Two = ListItemsArray[1] ?? "";
string Three = ListItemsArray[2] ?? "";
string Four = ListItemsArray[3] ?? "";
The listBox1.Items.CopyTo() method is explained here.
The ?? operator is used if you don't want any value to be null
If its really just up to 4 items, then manually knock it out?
string One = "";
string Two = "";
string Three = "";
string Four = "";
if (listBox1.Items.Count >= 1)
{
One = listBox1.Items[0];
}
if (listBox1.Items.Count >= 2)
{
Two = listBox1.Items[1];
}
if (listBox1.Items.Count >= 3)
{
Three = listBox1.Items[2];
}
if (listBox1.Items.Count >= 4)
{
Four = listBox1.Items[3];
}
Its not elegant, but the job is done.
You could use a for loop over the listBox.Items so you never try to access indices that are out of range.
You could first create an array of strings like so:
string[] names = new string[] { "", "", "", "" };
Then you could use a for loop over the items of the listbox and set the strings of the array of strings you just created to be equal to whatever is in the listBox[index].ToString() like:
for (int i = 0; i < listBox.Items.Count; i++)
{
names[i] = listBox.Items[i].ToString();
}
Finally, you set your original variables with the :
string one = names[0];
string two = names[1];
string three = names[2];
string four = names[3];
If any of the strings above is an empty string, it means you didn't have any item on that position in the listbox or the item you had in the listbox implements ToString() to return an empty string.
Not sure if I understood your problem, but I'd do something like that.
I recently got asked in a interview to create an method where the following checks are to be made:
Code to check if ArrayList is null
Code to loop through ArrayList objects
Code to make sure object is an integer
Code to check if it is null, and if not then to compare it against a variable containing the smallest integer from the list and if smaller then
overwrite it.
Return the smallest integer in the list.
So I created the following method
static void Main(string[] args)
{
ArrayList list = new ArrayList();
list.Add(1);
list.Add(2);
list.Add(3);
list.Add(4);
list.Add(5);
Program p = new Program();
p.Min(list);
}
private int? Min(ArrayList list)
{
int value;
//Code to check if ArrayList is null
if (list.Count > 0)
{
string minValue = GetMinValue(list).ToString();
//Code to loop through ArrayList objects
for(int i = 0; i < list.Count; i++)
{
//Code to make sure object is an integer
//Code to check if it is null, and if not to compare it against a variable containing the
//smallest integer from the list and if smaller overwrite it.
if (Int32.TryParse(i.ToString(), out value) || i.ToString() != string.Empty)
{
if (Convert.ToInt32(list[i]) < Convert.ToInt32(minValue))
{
minValue = list[i];
}
}
}
}
return Convert.ToInt32(GetMinValue(list));
}
public static object GetMinValue(ArrayList arrList)
{
ArrayList sortArrayList = arrList;
sortArrayList.Sort();
return sortArrayList[0];
}
I think the above is somewhat correct, however am not entirely sure about 4?
I think The following logic may help you. It is simpler than the current and are using int.TryParse() for parsing, which is better than Convert.To..() and int.Parse() Since it has some internal error handling and hence it will will not throw any exception for invalid input. If the input is invalid then it gives 0 to the out variable and returns false, From that we can assume the conversion failed. See the code for this:
var arrayMin = listOfInt;
int currentNum = 0;
int yourNum = int.MaxValue;
bool isSuccess = true;
foreach (var item in listOfInt)
{
if (int.TryParse(item.ToString(), out currentNum) && currentNum <= yourNum)
{
yourNum = currentNum;
}
else
{
isSuccess = false;
break;
}
}
if(isSuccess)
Console.WriteLine("Minimum Number in the array is {0}",yourNum);
else
Console.WriteLine("Invalid input element found");
Simplistic version:
private int? Min(ArrayList list)
{
if (list == null || list.Count == 0) return null;
return list.Cast<int>().Min();
}
I am making a list of unique "set of 3 strings" from some data, in a way that if the 3 strings come together they become a set, and I can only have unique sets in my list.
A,B,C
B,C,D
D,E,F and so on
And I keep adding sets to the list if they do not exist in the list already, so that if I encounter these three strings together {A,B,C} I wont put it in the list again. So I have 2 questions. And the answer to second one actually depends on the answer of the first one.
How to store this set of 3 string, use List or array or concatenate them or anything else? (I may add it to a Dictionary to record their count as well but that's for later)
How to compare a set of 3 strings with another, irrespective of their order, obviously depending on the structure used? I want to know a proper solution to this rather than doing everything naively!
I am using C# by the way.
Either an array or a list is your best bet for storing the data, since as wentimo mentioned in a comment, concatenating them means that you are losing data that you may need. To steal his example, "ab" "cd "ef" concatenated together is the same as "abcd" "e" and "f" concatenated, but shouldn't be treated as equivalent sets.
To compare them, I would order the list alphabetically, then compare each value in order. That takes care of the fact that the order of the values doesn't matter.
A pseudocode example might look like this:
Compare(List<string> a, List<string> b)
{
a.Sort();
b.Sort();
if(a.Length == b.Length)
{
for(int i = 0; i < a.Length; i++)
{
if(a[i] != b[i])
{
return false;
}
}
return true;
}
else
{
return false;
}
}
Update
Now that you stated in a comment that performance is an imporatant consideration since you may have millions of these sets to compare and that you won't have duplicate elements in a set, here is a more optimized version of my code, note that I no longer have to sort the two lists, which will save quite a bit of time in executing this function.
Compare(List<string> a, List<string> b)
{
if(a.Length == b.Length)
{
for(int i = 0; i < a.Length; i++)
{
if(!b.Contains(a[i]))
{
return false;
}
}
return true;
}
else
{
return false;
}
}
DrewJordan's approach of using a hashtable is still probably than my approach, since it just has to sort each set of three and then can do the comparison to your existing sets much faster than my approach can.
Probably the best way is to use a HashSet, if you don't need to have duplicate elements in your sets. It sounds like each set of 3 has 3 unique elements; if that is actually the case, I would combine a HashSet approach with the concatenation that you already worked out, i.e. order the elements, combine with some separator, and then add the concatenated elements to a HashSet which will prevent duplicates from ever occuring in the first place.
If your sets of three could have duplicate elements, then Kevin's approach is what you're going to have to do for each. You might get some better performance from using a list of HashSets for each set of three, but with only three elements the overhead of creating a hash for each element of potentially millions of sets seems like it would perform worse then just iterating over them once.
here is a simple string-wrapper for you:
/// The wrapper for three strings
public class StringTriplet
{
private List<string> Store;
// accessors to three source strings:
public string A { get; private set; }
public string B { get; private set; }
public string C { get; private set; }
// constructor (need to feel internal storage)
public StringTriplet(string a, string b, string c)
{
this.Store = new List<string>();
this.Store.Add(a);
this.Store.Add(b);
this.Store.Add(c);
// sort is reqiured, cause later we don't want to compare all strings each other
this.Store.Sort();
this.A = a;
this.B = b;
this.C = c;
}
// additional method. you could add IComparable declaration to the entire class, but it is not necessary in your task...
public int CompareTo(StringTriplet obj)
{
if (null == obj)
return -1;
int cmp;
cmp = this.Store.Count.CompareTo(obj.Store.Count);
if (0 != cmp)
return cmp;
for (int i = 0; i < this.Store.Count; i++)
{
if (null == this.Store[i])
return 1;
cmp = this.Store[i].CompareTo(obj.Store[i]);
if ( 0 != cmp )
return cmp;
}
return 0;
}
// additional method. it is a good practice : override both 'Equals' and 'GetHashCode'. See below..
override public bool Equals(object obj)
{
if (! (obj is StringTriplet))
return false;
var t = obj as StringTriplet;
return ( 0 == this.CompareTo(t));
}
// necessary method . it will be implicitly used on adding values to the HashSet
public override int GetHashCode()
{
int res = 0;
for (int i = 0; i < this.Store.Count; i++)
res = res ^ (null == this.Store[i] ? 0 : this.Store[i].GetHashCode()) ^ i;
return res;
}
}
Now you could just create hashset and add values:
var t = new HashSet<StringTriplet> ();
t.Add (new StringTriplet ("a", "b", "c"));
t.Add (new StringTriplet ("a", "b1", "c"));
t.Add (new StringTriplet ("a", "b", "c")); // dup
t.Add (new StringTriplet ("a", "c", "b")); // dup
t.Add (new StringTriplet ("1", "2", "3"));
t.Add (new StringTriplet ("1", "2", "4"));
t.Add (new StringTriplet ("3", "2", "1"));
foreach (var s in t) {
Console.WriteLine (s.A + " " + s.B + " " + s.C);
}
return 0;
You can inherit from List<String> and override Equals() and GetHashCode() methods:
public class StringList : List<String>
{
public override bool Equals(object obj)
{
StringList other = obj as StringList;
if (other == null) return false;
return this.All(x => other.Contains(x));
}
public override int GetHashCode()
{
unchecked
{
int hash = 19;
foreach (String s in this)
{
hash = hash + s.GetHashCode() * 31;
}
return hash;
}
}
}
Now, you can use HashSet<StringList> to store only unique sets