Conditional ForEach<T>T
Balássy György tegnapi blogbejegyzésében láttam meg a következő problémát. Ha szeretjük a LINQ-t és egy kollekciónak csak bizonyos elemein szeretnénk végrehajtani egy műveletet, akkor ahhoz először ki kell választanunk az adott elemeket (1. ciklus), majd végre kell rajtuk hajtani a kívánt műveletet (2. ciklus). Ez 2 darab ciklust jelent, azonban ehhez elég lenne csak egy.
Van egy listánk:
List<int> numbers = new List<int>() { 1, 2, 3, 4, 5, 6, 7 };
Irassuk ki csak a páros számokat az első módszerrel:
numbers.FindAll(n => n % 2 == 0).ForEach(n => Console.WriteLine(n));
Nézzünk egy sokkal szebb és teljesítménykímélőbb megoldás, egy új kiegészítő metódust:
public static void ForEach<T>(this List<T> list, Predicate<T> predicate, Action<T> action) { Contract.Requires(list != null); Contract.Requires(predicate != null); Contract.Requires(action != null); foreach (T item in list) { if (predicate(item)) action(item); } }
Én szivem szerint IEnumerable<T>-t írtam volna a List<T> helyett, de a Framework tervezését kell követni. Valaki nem tudja, hogy miért közvetlen a List<T> kapta meg ezt a metódust és miért nem kiegészítőként egy IEnumerable<T> vagy legalább IList<T>?
Abstract operators
Nem csak az osztályok, hanem a metódusok egymásból származtatása is érdekes tud lenni. A Framework belsejében rengeteg ilyet találhatunk. A kódunk mégjobban struktúrált és használható lesz.
A bemutatást egy nagyon egyszerű, általános problémával szeretném szemléltetni. Célunk a következő megvalósítása lenne:
int sum = BigOperators.Sum(1, 100); // 5050
Ehhez nem is kellene többet írni, mint:
public static void Sum(int start, int end) { int result = 0; for (int i = start; i <= end; i++) result += i; return result; }
Valószínűleg majdnem mindenki így oldalá meg a feladatot. De közelítsük meg a témát jóval mélyebbről. Írjunk egy olyan metódust, amely nem csak int, hanem bármilyen típusú változókkal tud műveleteket végezni. Nem csak összeadni, hanem szorozni is, illetve bármilyen műveletet. Bonyolítsuk meg teljesen! Íme a bázismetódusunk, amely teljesen testreszabható és amelyre az összes többi visszavezet majd:
internal static T Aggregate<T>(T start, Func<T, bool> stop, Func<T, T> step, Func<T, T, T> accumulate) { T result = start; for (T current = step(start); !stop(current); current = step(current)) result = accumulate(result, current); return result; }
Vigyük el ezt az egymást követő értékek, a számok irányába. A stop paraméter egy másik metódust vár, amely eldönti a pillanatnyi értékről, hogy az az utolsó-e és így a ciklus futása befejeződjön-e. Ezt kicseréljük egy lambda kifejezésre, amely lehetővé teszi egy pontos felső korlát megadását:
internal static T RangeAggregate<T>(T start, T end, Func<T, T> step, Func<T, T, T> accumulate) where T : IComparable<T> { return Aggregate<T>(start, v => v.CompareTo(end) > 0, step, accumulate); }
Így már megadható egy alsó és felső korlát is. Az értékeket a step paraméterként megadott metódus lépteti, tehát teljesen tetszőleges. Most írjunk be egy típust és készítsünk egy olyan metódust, amely a számokat összegzi.
public static int Sum(int start, int end, int step) { return RangeAggregate<int>(start, end, v => v + step, (r, v) => r + v); }
Végül az utolsó simítás:
public static int Sum(int start, int end) { return Sum(start, end, 1); }
Készen vagyunk. Nem csak a típust, de a műveleteken át, minden mást absztrakttá tettünk.
Visual Studio 2010 Beta 1
Kora este elérhetővé vált mindenki számára a várva-várt Visual Studio 2010 első beta verziója.
Többféle letöltési lehetőséget kínálnak, köztük egy web installert is:
http://blogs.msdn.com/onoj/archive/2009/05/19/visual-studio-2010-beta-1-download-options.aspx
Első benyomás:
Az új felület nagyon szép lett, illik a témához. A kódszerkesztőben is van jópár új feature; azonban még nagyon lassú, illetve a kiegészítő listán is sokat kell még dolgozniuk.
Most, hogy már van .NET Framework 4 beta, hamarosan jönnek C# 4.0 kódok is.
FunctionCache<T, TResult>
A maximális teljesítmény eléréséhez általában elengedhetetlen a gyorsítótár. Nézzük meg, hogyan lehet ezt megoldani úgy, hogy ne csak a megoldás, hanem a használat is a lehető legegyszerűbb legyen.
/// <summary> /// Represents a class, that caches the return value of a specified function. /// </summary> public class FunctionCache<T, TResult> { /// <summary> /// Initializes a new instance of the FunctionCache<T, TResult> class. /// </summary> /// <param name="func"></param> public FunctionCache(Func<T, TResult> func) { this.Function = func; this.Cache = new Dictionary<T, TResult>(); } protected IDictionary<T, TResult> Cache; /// <summary> /// Gets the function to be cached. /// </summary> public Func<T, TResult> Function { get; protected set; } /// <summary> /// Invokes the function and adds its return value to the cache. /// </summary> /// <param name="arg"></param> /// <returns></returns> public TResult Invoke(T arg) { if (!this.Cache.ContainsKey(arg)) this.Cache.Add(arg, this.Function(arg)); return this.Cache[arg]; } /// <summary> /// Clears cache. /// </summary> public void Reset() { this.Cache.Clear(); } }
Lambda expressions and Fibonacci
Ma arra szeretnék kitérni, hogy C#-ban mennyire hasznosak a Lambda kifejezések és mennyire lerövidíthető egyes feladatok megoldása velük. A példánkban a Fibonacci számsor segítségével mutatom be, hogy hogy lehet meghatározni az n-edik számot mindössze egy sorból.
Kezdjük a hagyományos módszerrel, ahogy a többség csinálná:
static uint Fibonacci(uint n) { if (n <= 1) return n; else return Fibonacci(n - 1) + (n - 2); }
Most ezt alakítsuk át egy kicsit más formába, hogy jobban hasonlítson egy matematikai függvényre:
Func<uint, uint> Fibonacci = delegate(uint n) { if (n <= 1) return n; else return Fibonacci(n - 1) + Fibonacci(n - 2); };
Na ha ez nem is rövidebb, de már jobb a formája. A következő lépésben Fibonacci számait egy Lambda kifejezéssel fogom megadni (és az if-et átírom más alakra):
Func<uint, uint> Fibonacci = n => n <= 1 ? n : Fibonacci(n - 1) + Fibonacci(n - 2);
Ez lenne a legtömörebb és legszebb alakja… de sajnos csak lenne. Ugyanis a kifejezés definíciójában egy olyan változóra hivatkozok (saját magára) rekurzívan, ami még nincsen definiálva. Ezért sajnos hozzá kell adni még egy sort, amiben null kezdőértékkel bevezetjük a Fibonacci nevű "változót".
Végezetül a 7 sorból lett 2, ami valójában csak 1.
Teljesítmény szempontjából persze ajánlottabb nem rekurzív képletet (Binet’s formula) használni a számok meghatározásához.
Tree transversal
Az előző bejegyzésben konstruált TreeNode<T> nagyon egyszerű módon bejárható, ha kiegészítjük az alábbi metódussal:
/// <summary> /// Transverse a tree node. /// </summary> /// <param name="action">Performs the specified action on each node.</param> public void TraversePreorder(Action<TreeNode<T>> action) { action(this); foreach (TreeNode<T> node in this) node.TraversePreorder(action); }
Példakód, amely a konzolra kirajzolja a fánkat:
myTreeNode.TransversePreorder( (t) => { Console.WriteLine(new string(' ', t.Depth) + t.Value.ToString()); } );
Depth of a TreeNode<T> (recursive)
Hogyan határozzuk meg egy faág mélységét egy fában a legegyszerűbb módon? A megoldás egy rekurzív tulajdonság:
/// <summary> /// Represents a tree node. /// </summary> /// <typeparam name="T"></typeparam> public class TreeNode<T> : IEnumerable<TreeNode<T>> { // ... /// <summary> /// Gets the parent node. /// </summary> public TreeNode<T> Parent { get; internal set; } /// <summary> /// Gets a value indicating whether is this the root of the tree. /// </summary> public bool IsRoot { get { return this.Parent == null; } } /// <summary> /// Gets the depth of the node. /// </summary> public int Depth { get { return (this.IsRoot ? 0 : 1 + this.Parent.Depth); } } // ... }
CyclicList<T>
Az alábbi osztály egy ciklikus listát valósít meg. Az elemek periodikusan ismétlődnek a végtelenségig (System.Int32 típusú indexer határain belül). Az index lehet negatív szám is.
/// <summary> /// Represents a strongly typed cyclic list of objects that can be accessed by index. /// </summary> /// <typeparam name="T"></typeparam> public class CyclicList<T> : List<T> { /// <summary> /// Initializes a new instance of the CyclicList<T>. /// </summary> public CyclicList() : base() { } public CyclicList(int capacity) : base(capacity) { } public CyclicList(IEnumerable<T> collection) : base(collection) { } /// <summary> /// Gets or sets the element at the specified index. /// </summary> /// <param name="index"></param> /// <returns></returns> public new T this[int index] { get { return base[this.Mod(index)]; } set { base[this.Mod(index)] = value; } } #region Methods /// <summary> /// Removes the element at the specified index. /// </summary> /// <param name="index"></param> /// <returns></returns> public new void RemoveAt(int index) { base.RemoveAt(this.Mod(index)); } // ... #endregion private int Mod(int index) { int rem = index % base.Count; return (rem < 0 ? rem + base.Count : rem); } }
Min, Max, Clamp
Kezdjük néhány egyszerű metódus generikus változatával…
Melyik a kisebb?
public static T Min<T>(T a, T b) where T : IComparable<T> { return (a.CompareTo(b) < 0 ? a : b); }
Melyik a nagyobb?
public static T Max<T>(T a, T b) where T : IComparable<T> { return (a.CompareTo(b) > 0 ? a : b); }
Egy adott érték határok közé szorítása:
public static T Clamp<T>(T min, T max, T value) where T : IComparable<T> { return ( value.CompareTo(min) < 0 ? min : (value.CompareTo(max) > 0 ? max : value) ); } public static T Clamp<T>(Range<T> range, T value) where T : IComparable<T> { return Clamp<T>(range.Start, range.End, value); }
(A Range osztály definícióját mindenki képzelje hozzá!)
Első post
Ebben a blogban .NET fejlesztésről lesz szó. Reflektorfényben a C# nyelv képességei lesznek; egyszerű és bonyolult problémák gyönyőrű megoldásai.