I have a dropdown with three strings horizontally for each item. How can I alphabetically sort first by string 1 from each item, then by string 2 from each item?
Here's my snippet:
foreach (var item in list)
{
if(typeof(T).Equals(typeof(Machine)))
{
Machine machine = (item as Machine);
string title = machine.MachineName + " - " + machine.Serial + " - " + machine.MachineOwnership;
alert.AddAction(UIAlertAction.Create(title, UIAlertActionStyle.Default, action => {
button.SetTitle(title, UIControlState.Normal);
}));
}
else if(typeof(T).Equals(typeof(Person)))
{
alert.AddAction(UIAlertAction.Create((item as Person).Name, UIAlertActionStyle.Default, action => {
button.SetTitle((item as Person).Name, UIControlState.Normal);
}));
}
}
Where list contains objects of type Machine which has the following properties:
MachineName, Serial and MachineOwnshership (all strings).
So I want to do something like OrderBy(MachineName).ThenBy(Serial) but not sure how to do so correctly when I'm first checking to see what the list type is and then populating the dropdown list per item.
My dropdown list looks something like this if anyone needs clarification:
-------------------------------------------------
MachineNameStartsWithA - 01234 - OwnerStartsWithA
--------------------------------------------------
MachineNameStartsWithB - 012345 - OwnerStartsWithB
---------------------------------------------------
etc.... where it's a long list of items where the strings are separated by "-" like it's shown in the code.
Also, for what it's worth, this is currently inside a Xamarin app.
I think something like this should work. Sorting by a Tuple should be lexicographic:
list.OfType<Machine>().OrderBy(x => new Tuple<string,string>(x.MachineName,x.Serial))
The OfType call should also remove the need for the typeof check and cast - the result should be an iterable of Machines.
Per the comment regarding multiple object types:
Grouping like types together
This would be my default preference from a UI/UX perspective unless it really makes sense to mix Machines and Persons together. Depending on the length of the list it may be preferable to split on the element type first as list.OfType will enumerate the entire list each time.
foreach(var item in list.OfType<Machine>().OrderBy(x => new Tuple<string,string>(x.MachineName,x.Serial)))
{
// append item
}
foreach(var item in list.OfType<Person>().OrderBy(x => x.Name))
{
// append item
}
Interleaving various types
private Tuple<string,string> Projection(BaseClass x)
{
Machine item = x as Machine;
if(item != null)
{
return new Tuple<string,string>(item.MachineName,item.Serial);
}
Person item = x as Person;
if(item != null)
{
return new Tuple<string,string>(item.Name,"");
}
}
foreach(var item in list.OrderBy(Projection))
{
// check type of item and append as appropriate
}
Related
I am automating some website and I got stuck on such a case. I have a list created, everything is great for me, it has 24 elements. These are products from the store, containing their picture, name, price, etc. But now I need to take two things from the first element and display in the console, namely name and price. Is anyone able to suggest something? I sit and thinks but nothing comes out. All I managed to do was send everything for 1 item.
I tried resolve that with some Linq but without success.
public List<string> ListOfElements()
{
var elements = new List<string>();
IReadOnlyCollection<IWebElement> listElements = _driver.FindElements(By.CssSelector(".category-list div[class='cat-prod-row js_category-list-item js_clickHashData js_man-track-event ']");
foreach (IWebElement element in listElements)
{
elements.Add(element.Text);
}
return elements;
}
public void DisplayFirstElement()
{
var firstElement = ListOfElements();
Console.WriteLine(firstElement[0]);
}
I want get name and price of first element and then assert that price for that is greater than 10.
You´re flattening all the elements properties into a single collection of strings. Thus you´re losing any relationship between those strings. It´s hard to say what the 22rd element within thhat list actually is: is it a price? A name? Something completey different? To which item does it actually belong?
Instead you should just return a list of entities and then print the properties of the very first entitity:
public List<IWebElement> ListOfElements()
{
var elements = new List<string>();
return listElements = _driver.FindElements(By.CssSelector(".category-list div[class='cat-prod-row js_category-list-item js_clickHashData js_man-track-event ']");
}
public void DisplayFirstElement()
{
var allElements = ListOfElements();
var firstElement = allElements.First();
Console.WriteLine("FirstName: " + firstElement.Name + " Price: " + firstElement.Price);
}
Of course this assumes your IWebElement has a Name- and a Price-property.
I'm using the Linq OrderBy() function to sort a generic list of Sitecore items by display name, then build a string of pipe-delimited guids, which is then inserted into a Sitecore field. The display name is a model number of a product, generally around 10 digits. At first it seemed like this worked 100% of the time, but the client found a problem with it...
This is one example that we have found so far. The code somehow thinks IC-30R-LH comes after IC-30RID-LH, but the opposite should be true.
I put this into an online alphabetizer like this one and it was able to get it right...
I did try adding StringComparer.InvariantCultureIgnoreCase as a second parameter to the OrderBy() but it did not help.
Here's the code... Let me know if you have any ideas. Note that I am not running this OrderBy() call inside of a loop, at any scope.
private string GetAlphabetizedGuidString(Item i, Field f)
{
List<Item> items = new List<Item>();
StringBuilder scGuidBuilder = new StringBuilder();
if (i != null && f != null)
{
foreach (ID guid in ((MultilistField)f).TargetIDs)
{
Item target = Sitecore.Data.Database.GetDatabase("master").Items.GetItem(guid);
if (target != null && !string.IsNullOrEmpty(target.DisplayName)) items.Add(target);
}
// Sort it by item name.
items = items.OrderBy(o => o.DisplayName, StringComparer.InvariantCultureIgnoreCase).ToList();
// Build a string of pipe-delimited guids.
foreach (Item item in items)
{
scGuidBuilder.Append(item.ID);
scGuidBuilder.Append("|");
}
// Return string which is a list of guids.
return scGuidBuilder.ToString().TrimEnd('|');
}
return string.Empty;
}
I was able to reproduce your problem with the following code:
var strings = new string[] { "IC-30RID-LH", "IC-30RID-RH", "IC-30R-LH", "IC-30R-RH"};
var sorted = strings.OrderBy(s => s);
I was also able to get the desired sort order by adding a comparer to the sort.
var sorted = strings.OrderBy(s => s, StringComparer.OrdinalIgnoreCase);
That forces a character-by-character (technically byte-by-byte) comparison of the two strings, which puts the '-' (45) before the 'I' (73).
I am trying to compare two Sharepoint lists. I am using a C# program to add, update, and delete items, based on its ID. If the ID doesnt exist in List1, when the program is ran, I want to delete the IDs from List2. I was wondering how can I delete those items without specifying a specific number in the GetItemById function? Like in this example
using(ClientContext context = new ClientContext(siteUrl)) {
//Retrieve list items from list 1 here code here
using(ClientContext target = new ClientContext(siteUrl2)) {
foreach(ListItem oListItem2 in collListItem2) {
int exists = 0;
foreach(ListItem oListItem in collListItem) {
if (oListItem2["ID"] == oListItem["ID"]) {
exists++;
}
}
if (exists == 0) {
ListItem DeleteItem = list2.GetItemById();
DeleteItem.DeleteObject();
target.ExecuteQuery();
}
return;
}
}
}
To delete the items from the second list not in the first, just get all of the items from the first list, and filter the items in the second list based on those ids. Note you can use a hash based lookup to greatly improve performance over a linear search:
var idsFromFirstList = new HashSet<int>(
collListItem.AsEnumerable()
.Select(item => item.Id));
var itemsToDelete = collListItem2.AsEnumerable()
.Where(item => !idsFromFirstList.Contains(item.Id);
foreach(var item in itemsToDelete)
item.DeleteObject();
target.ExecuteQuery();
Note that you can basically do the exact opposite to find the items to add (create a hashset of the IDs of the items in the target into a set, find all items in the first no tin there, and then add all of those items).
To find items that match you can use a Dictionary<int, ListItem> by putting either set of items into a dictionary, with the ID as the key, and going through the other set, finding the matches. If you are going to do that, you can re-use that dictionary to check for one of the other two conditions as well, to save you one data structure:
var firstSiteItemLookup = collListItem.AsEnumerable()
.ToDictionary(item => item.Id, item => item);
foreach(var item in collListItem2)
{
ListItem match;
if(firstSiteItemLookup.TryGetValue(item.Id, out match))
UpdateItemToMatch(item, match);
else
item.DeleteObject();
}
target.ExecuteQuery();
Can you use the same code to check if an ID in collListItem has been modified then update the same ID in collListItem2? #Servy
I adapted the following code from here
foreach (String table in tablesToTouch)
{
foreach (Object selecteditem in listBoxSitesWithFetchedData.SelectedItems)
{
site = selecteditem as String;
hhsdbutils.DeleteSiteRecordsFromTable(site, table);
}
}
...but, alas, the SelectedItems member appears unavailable to me: "'System.Windows.Forms.ListBox' does not contain a definition for 'SelectedItems' and no extension method 'SelectedItems' accepting a first argument of type 'System.Windows.Forms.ListBox' could be found (are you missing a using directive or an assembly reference?)"
Another suggestion was:
foreach(ListItem listItem in listBox1.Items)
{
if (listItem.Selected == True)
{
. . .
...but I also don't have ListItem available.
What is a workaround to accomplish the same thing?
UPDATE
There are at least two (somewhat kludgy) things I could do:
0) Manually keep track of items selected in listBoxSitesWithFetchedData (as they are clicked) and loop through *that* list
1) Dynamically create checkboxes instead of adding items to the ListBox (getting rid of the ListBox altogether), and use the text value of checked checkboxes to pass to the "Delete" method
But I'm still thinking there's got to be a more straightforward way than those.
UPDATE 2
I can do this (it compiles):
foreach (var item in listBoxSitesWithFetchedData.Items)
{
hhsdbutils.DeleteSiteRecordsFromTable(item.ToString(), table);
}
...but I'm still left with the problem of only acting on the items that have been selected.
UPDATE 3
Since the CF-Whisperer said that listbox multiselection isn't possible in the murky and misty labyrinthine world of CF (cuneiform forms), I simplified the code to:
foreach (String table in tablesToTouch)
{
// Comment from the steamed coder:
// The esteemed user will have to perform this operation multiple times if they want
to delete from multiple sites
hhsdbutils.DeleteSiteRecordsFromTable(listBoxSitesWithFetchedData.SelectedItem.ToString(),
table);
}
The Compact Framework Listbox simply contains a list of object Items. It calls ToString() on each for display, but the items are there.
So let's say we have an object:
class Thing
{
public string A { get; set; }
public int B { get; set; }
public Thing(string a, int b)
{
A = a;
B = b;
}
public override string ToString()
{
return string.Format("{0}: {1}", B, A);
}
}
And we throw some into a ListBox:
listBox1.Items.Add(new Thing("One", 1));
listBox1.Items.Add(new Thing("Two", 2));
listBox1.Items.Add(new Thing("Three", 3));
They will show up as the ToString() equivalent in the list (e.g. "One: 1").
You can still iterate across them as the source objects via a cast or as operation like this:
foreach (var item in listBox1.Items)
{
Console.WriteLine("A: " + (item as Thing).A);
Console.WriteLine("B: " + (item as Thing).A);
}
I have a collection of Addresses, which has different address items. Each address item has AddressType, City, Zipcode and other columns. I am writing a validation that if some one is adding a new addressType and the collection of Addressses has already an AddressType listed then give a warning that it has already been listed. How can I do that. I have attached some code. Right now its only checking it for the "Job Address". I have three types of Addresss.
if (Addresses.Any
(a => a.AddressType =="Job Address"))
{
DialogManager.ShowMessageBox(("The type has already been listed "),
MessageBoxButton.OKCancel);
}
If you are checking this after the fact, you can use the size of a HashSet<string>:
var types = new HashSet<string>(Addresses.Select(aa => aa.AddressType));
if (types.Count < Addresses.Count)
{
// You have a duplicate...
// ...not necessarily easy to know WHO is the duplicate
}
The above works by assigning each of the AddressType instances to a set. A set is a collection which only contains the unique items added. Therefore, if you have duplicates in your input sequence, the set will contain less items than your input sequence. You can illustrate this behavior like so:
// And an ISet<T> of existing items
var types = new HashSet<string>();
foreach (string typeToAdd in Addresses.Select(aa => aa.AddressType))
{
// you can test if typeToAdd is really a new item
// through the return value of ISet<T>.Add:
if (!types.Add(typeToAdd))
{
// ISet<T>.Add returned false, typeToAdd already exists
}
}
A better way would be beforehand, perhaps through CanExecute of a command if you have it implemented in a similar fashion:
this.AddCommand = new DelegateCommand<Address>(
aa => this.Addresses.Add(aa),
aa => !this.Addresses.Any(xx => xx.AddressType == aa.AddressType));
Create a new AddressComparer class implementing IEqualityComparer interface
Now you use Contains method
if(Addresses.Contains(Address,new AddressComparer()))
{
//your code
}