Trovare la lettera successiva ad un’altra

Lavorando con le griglie capita spesso di trovarsi in una situazione in cui le righe vengono rappresentate con dei numeri e le colonne con delle lettere. In un processo di automazione, mentre è semplice numerare i numeri appare più complessa l’operazione sulle lettere. Infatti, ad esempio, dopo la lettera Z si trova AB e così via…
Un procedimento come questo non funzionerà, poichè nella tabella ASCII il numero 27 corrisponde al simbolo “[“:

int number = 27;
Console.WriteLine((char)('A' + number));

Una soluzione semplice

Le varie soluzioni che si possono proporre si dividono fra quelle che includono un dizionario e quelle matematiche. Propongo la soluzione matematica scritta in C# che utilizza il concetto di resto:

		private string IntToLetters(int num)
		{
			string result = string.Empty;
			while (num >= 0)
			{
				result = (char)('A' + num % 26) + result;
				num /= 26; num--;
			}
			return num;
		}

Il metodo parte da 0 (dove A => 0) e procede all’infinito. Ovviamente è possibile anche effettuare l’operazione inversa, anche se la logica è un po’ più complessa:

		private static int LettersToInt(string value)
		{
			int result = 0;
			List<char> digits = new List<char>("ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray());
			char[] chars = value.ToUpper().ToCharArray();
			int weight = chars.Length;
			for (int position = 0; position < chars.Length; position++)
			{
				int currentVal = digits.FindIndex(x => x == chars[position]);
				weight = chars.Length - position;
				int r = (weight - 1) * (currentVal + 1) * 26 + currentVal;
				result += r;
			}
			return result;
		}

…Devo ammettere che ci ho messo un bel po’ per ricavare la funzione qui sopra 🙂

Bubbling degli eventi in JavaScript

Events Bubbling. Un concetto con il quale prima o poi tutti gli sviluppatori che utilizzano un qualche linguaggio con supporto agli eventi si devono confrontare. Ma mentre il concetto è chiaro quando di parla di linguaggi orientati ad oggetti con supporto all’ereditarietà, la faccenda si fa più oscura quando di parla di Javascript.

Cos’è il bubbling di un evento

Possiamo relazionare il DOM della nostra pagina HTML come un elenco padre-figlio in cui il primo elemento è il tag html. Quando iscriviamo un evento ad un certo livello del DOM, sebbene l’evento sia effettivamente iscritto al solo elemento scelto, questo si propagherà verso l’alto in tutta la catena fino al tag più in cima.

<html onclick="console.log('html-click')">
	<body onclick="console.log('body-click')">
	<div onclick="console.log('div-1 clicked')">
		<div onclick="console.log('div-2 clicked')">
			<a onclick="console.log('last-child clicked')">clicca qui</a>
		</div>
	</div>
	</body>
</html>

La pressione di “clicca qui” nell’esempio sopra produrrà il seguente output:

last-child clicked
div-2 clicked
div-1 clicked
body-click
html-click

Bloccare la propagazione

Javascript mette a disposizione diversi strumenti per modificare il comportamento di un evento rispetto al contesto. In particolare per bloccare la propagazione dell’evento, ovvero per fare in modo che venga scatenato il solo “last-child clicked” occorre fare riferimento al metodo event.stopPropagation():

<html onclick="console.log('html-click')">
	<body onclick="console.log('body-click')">
	<div onclick="console.log('div-1 clicked')">
		<div onclick="console.log('div-2 clicked')">
			<a onclick="console.log('last-child clicked'); event.stopPropagation();">clicca qui</a>
		</div>
	</div>
	</body>
</html>

L’output sarà:

last-child clicked

Modificare la risposta del DOM all’evento

In effetti l’evento su un elemento del DOM agisce su un canale separato rispetto alle modifiche che un elemento è in grado di produrre sul DOM. In effetti se il nostro codice diventa:

...
<a onclick="console.log('last-child clicked'); event.stopPropagation();" href="http://www.google.it">clicca qui</a>
...

Si vede subito che stopPropagation non interrompe il caricamento del sito web inserito nell’attributo href. Per impedire qualsiasi risposta del DOM all’azione:

...
<a onclick="console.log('last-child clicked'); event.preventDefault();" href="http://www.google.it">clicca qui</a>
...

Che però non blocca la propagazione dell’evento. Combinando insieme:

...
<a onclick="console.log('last-child clicked'); event.stopPropagation(); event.preventDefault();" href="http://www.google.it">clicca qui</a>
...

In alcuni browser la somma di questi due metodi corrisponde a:

...
<a onclick="console.log('last-child clicked'); return false;" href="http://www.google.it">clicca qui</a>
...

Ma questa forma ridotta non mi sento di consigliarla poiché non supportata da tutti i browser (incluso Microsoft Edge).

Un piccolo scherzetto…

Cosa produce questo codice?

<html>
	<body>
		<a href="http://www.yahoo.it" onclick="console.log('yahoo clicked')">
			<a onclick="console.log('google clicked'); event.preventDefault();" href="http://www.google.it">motore di ricerca</a>
		</a>
	</body>
</html>

Verrebbe da pensare che al click il browser venga mandato su Yahoo… Eppure non succede niente!
Il motivo è semplice: non è possibile nidificare un anchor element dentro un altro.
E non solo non verrà cambiata pagina, ma non inizierà neppure la risalita dell’evento verso l’alto. Quindi occhio a formattare bene l’HTML 😉

Utilizzo dei Generics per un FilteredElementCollector universale

Scenario

Quando scriviamo un componente per le RevitAPI capita spesso di dover selezionare da un documento tutti gli elementi di un certo tipo, o eventualmente un sottoinsieme di questi – senza dover passare per un PickBox.

Utilizzo dei Generics per una soluzione universale

Essendomi trovato in moltissimi casi ad affrontare la selezione di elementi di un certo tipo non conosciuto, ho scritto una piccola classe che consente di selezionare tutti le istanze di un certo tipo rapidamente. La classe fornisce accesso ad un solo metodo statico.

	public static class ElementsOfType<T> where T : Element
	{
		public static List<T> GetElements(Document doc, BuiltInCategory category) 
		{
			FilteredElementCollector collector = new FilteredElementCollector(doc);
			ElementCategoryFilter filter = new ElementCategoryFilter(category);
			IList<Element> elements = collector.WherePasses(filter).ToElements();
			List<T> passes = new List<T>();
			foreach (Element element in elements)
			{
				if (element is T)
				{
					passes.Add(element as T);
				}
			}
			return passes;
		}
	}

Questo è proprio uno di quei casi in cui è utilissimo ottimizzare il codice tramite Generics 🙂

ISelectionFilter per selezionare elementi via RevitAPI

Scenario

Uno scenario base nella progettazione di un plugin di Revit consiste nella selezione di elementi appartenenti a determinate categorie.

Implementazione di un nuovo ISelection Filter

Per creare un box di selezione, semplicemente:

public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
		{
			UIApplication uiapp = commandData.Application;
			UIDocument uidoc = uiapp.ActiveUIDocument;
			Application app = uiapp.Application;
			Document doc = uidoc.Document;

			Selection sel = uidoc.Selection;
			var pickedColumns = sel.PickObjects(ObjectType.Element, new OnlyColumns(doc), "Scegli Colonne");
...

Come si vede dal codice il mio obiettivo è, in questo caso, selezionare solo elementi che siano del tipo “Pilastr strutturale”. Potremmo con questa tecnica selezionare qualsiasi FamilySymbol o qualsiasi altro tipo.
Il metodo PickObject di document supporta istanze che implementino ISelectionFilter – che a sua volta richiede due metodi: AllowElement e AllowReference. La mia implementazione per selezionare solo pilastri strutturali:

	public class OnlyColumns : ISelectionFilter
	{
		private Document document { get; set; }
		public bool AllowElement(Element elem)
		{
			if (elem is FamilyInstance)
			{
				Category c = (elem as FamilyInstance).Category;
				if(c.Name == "Pilastri strutturali") { return true; }
			}
			return false;
		}

		public bool AllowReference(Reference reference, XYZ position)
		{
			var e = document.GetElement(reference.ElementId);
			return AllowElement(e);
		}

		public OnlyColumns(Document document)
		{
			this.document = document;
		}
	}

Una tecnica base che utilizzo sempre per ottenere informazioni sull’elemento che voglio selezionare tramite un filtro consiste nel passare al costruttore del nostro ISelectionFilter un’istanza del documento. Solo in questo modo possiamo indagare sulla natura dell’elemento selezionato andandolo a ripescare dal documento attivo. Facile, no?

Ottimizzazione

Il codice presentato presenta almeno una bruttura: fa riferimento al nome-stringa “Pilastri strutturali”. Cosa succede con componenti esterni, magari con parametri in inglese? Semplice: non funzionerà.
Per risolvere il problema bisogna convertire le BuiltInCategory in Category. Un banale casting non è di aiuto.
Le categorie vengono gestite da Revit all’intero dei DocumentSettings:

public class OnlyColumns : ISelectionFilter
	{
		private Document document { get; set; }
		private Categories categories { get; set; }
		public bool AllowElement(Element elem)
		{
			if (elem is FamilyInstance)
			{
				Category c = (elem as FamilyInstance).Category;
				Category builtInCategory = categories.get_Item(BuiltInCategory.OST_StructuralColumns);
				if (c.Id == builtInCategory.Id) { return true; } // c == builtInCategory darà FALSE!
			}
			return false;
		}

		public bool AllowReference(Reference reference, XYZ position)
		{
			var e = document.GetElement(reference.ElementId);
			return AllowElement(e);
		}

		public OnlyColumns(Document document)
		{
			this.document = document;
			this.categories = document.Settings.Categories;
		}
	}

E questa è la versione più pulita del codice.

Utilità del prodotto cartesiano

Definizione

Il prodotto cartesiano non è altro che l’insieme delle coppie ordinate di due insiemi. Ad esempio se ho due insiemi:
A = {a,b,c}
B = {d,e,f}
AxB = {(a,d),(b,d),(c,d),(c,e),(c,f)

Per tre, invece:
A = {a,b}
B = {c,d}
C = {e,f}
AxBxC = {(a,c,e),(a,d,e),(a,c,f),(a,d,f),(b,c,e),(b,d,e),(b,c,f),(b,d,f)}

Per i database il prodotto cartesiano dei dati è il modo naturale per unire due set differenti.

cartesianoLa caratteristica del prodotto cartesiano risiede nel modo di determinare la griglia: è sufficiente conoscerne gli elementi degli assi. L’immagine seguente, realizzata in Python, mostra proprio il prodotto cartesiano fra i punti di 3 assi, appunto, cartesiani.

Il prodotto cartesiano non voluto

Un esempio di prodotto cartesiano non desiderato è facilmente riscontrabile nell’esempio del negozio, riporto un mock semplificato di due tabelle:

Account:
+-----------------+--------+
+ NOME		  + ACCOID +
+-----------------+--------+
+ Cristiano 	  + 1      +
+ Mario		  + 2      +
++++++++++++++++++++++++++++
Ordini:
+-----------------+---------+
+ ORDINI	  + ACCOID  +
+-----------------+---------+
+ 100€		  + 1	    +
+ 50€		  + 1 	    +
+ 70€		  + 1	    +
+ 50€		  + 1 	    +
+ 30€		  + 2	    +
+ 10€		  + 2 	    +
+++++++++++++++++++++++++++++

Eseguendo una query SQL del tipo

SELECT * FROM Account, Ordini

si ottiene proprio il prodotto cartesiano fra le due, che non è probabilmente il risultato che si vuole ottenere! (Per intenderci otterremo 12 righe).

+-----------------+-------------+
+ ORDINI	  + NOME	+
+-----------------+-------------+
+ 100€		  + Cristiano   +
+ 50€		  + Cristiano   +
+ 70€		  + Cristiano   +
+ 50€		  + Cristiano   +
+ 30€		  + Cristiano   +
+ 10€		  + Cristiano   +
+ 100€		  + Mario	+
+ 50€		  + Mario	+
+ 70€		  + Mario	+
+ 50€		  + Mario	+
+ 30€		  + Mario	+
+ 10€		  + Mario	+
+++++++++++++++++++++++++++++++++

Il motivo per cui la query non produce il risultato che vogliamo, è che viene valutato di default il prodotto cartesiano fra le tabelle interrogate (operazione di CROSS JOIN): l’output è proprio l’insieme di tutte le possibili combinazioni di account e ordini, e non un insieme con il nome dell’account all’intero del db. Si dice che il JOIN cerca il percorso più lungo (o globale) per unire le due tabelle.

SELECT * FROM Account NATURAL JOIN Ordini

In questo modo si otterrà invece il risultato desiderato: i parametri con lo stesso nome e valore verranno utilizzati come fonte per l’unione. La colonna ACCOID verrà riprodotta una sola volta.

+-----------------+-----------------+
+ ORDINI	  + ACCOID	    +
+-----------------+-----------------+
+ 100€		  + Cristiano	    +
+ 50€		  + Cristiano	    +
+ 70€		  + Cristiano	    +
+ 50€		  + Cristiano 	    +
+ 30€		  + Mario	    +
+ 10€		  + Mario	    +
+++++++++++++++++++++++++++++++++++++

Lo scopo di questo articolo non è presentare le JOIN, motivo per cui non mi soffermerò ulteriormente su altri esempi di questo tipo o su come modificare la query nel caso in cui ci siano colonne da unire con nomi diversi.

Quando è utile

In realtà le applicazioni del prodotto cartesiano sono moltissime. L’esempio più chiaro è nella creazione di grafici e report, ovvero ogni qual volta la combinazione di due valori deve dare un unica coppia. Ad esempio:

SELECT Negozi.Negozio, Prodotti.Prodotto FROM Negozi CROSS JOIN Prodotti

In questo modo avremo per ogni negozio i prodotti disponibili in una matrice.

Per fare un altro esempio, la prossima volta che dovete creare tutte le combinazioni di data e ora, potreste fare il prodotto cartesiano fra ore e minuti invece che innestare un ciclo dentro un altro.