Click here to Skip to main content
15,884,041 members
Articles / Web Development / HTML

Signum Framework Tutorials Part 2 – Southwind Logic

Rate me:
Please Sign up or sign in to vote.
4.45/5 (6 votes)
15 Nov 2012LGPL325 min read 31.3K   1K   22  
In this part, we will focus on writing business logic, LINQ queries and explain inheritance
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.IO;
using System.Threading;
using System.Globalization;
using Signum.Utilities.Synchronization;
using Signum.Utilities.DataStructures;
using Signum.Utilities.Reflection;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Data;
using System.Text.RegularExpressions;
using Signum.Utilities.Properties;
using System.Collections;

namespace Signum.Utilities
{
    public static class EnumerableExtensions
    {
        public static bool IsEmpty<T>(this IEnumerable<T> collection)
        {
            foreach (var item in collection)
                return false;

            return true;
        }

        public static bool IsNullOrEmpty<T>(this IEnumerable<T> collection)
        {
            return collection == null || collection.IsEmpty();
        }

        public static IEnumerable<T> NotNull<T>(this IEnumerable<T> collection) where T : class
        {
            return collection.Where(a => a != null);
        }

        public static IEnumerable<T> NotNull<T>(this IEnumerable<T?> collection) where T : struct
        {
            return collection.Where(a => a.HasValue).Select(a => a.Value);
        }

        public static IEnumerable<T> And<T>(this IEnumerable<T> collection, T newItem)
        {
            foreach (var item in collection)
                yield return item;
            yield return newItem;
        }

        public static IEnumerable<T> PreAnd<T>(this IEnumerable<T> collection, T newItem)
        {
            yield return newItem;
            foreach (var item in collection)
                yield return item;
        }

        public static int IndexOf<T>(this IEnumerable<T> collection, T item)
        {
            int i = 0;
            foreach (var val in collection)
            {
                if (EqualityComparer<T>.Default.Equals(item, val))
                    return i;
                i++;
            }
            return -1;
        }

        public static int IndexOf<T>(this IEnumerable<T> collection, Func<T, bool> condition)
        {

            int i = 0;
            foreach (var val in collection)
            {
                if (condition(val))
                    return i;
                i++;
            }
            return -1;
        }

        public static T Single<T>(this IEnumerable<T> collection, string errorMessage)
        {
            return collection.Single<T>(errorMessage, errorMessage);
        }

        public static T Single<T>(this IEnumerable<T> collection, string errorZero, string errorMoreThanOne)
        {
            if (collection == null)
                throw new ArgumentNullException("collection");

            using (IEnumerator<T> enumerator = collection.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                    throw new InvalidOperationException(errorZero);

                T current = enumerator.Current;

                if (!enumerator.MoveNext())
                    return current;
            }

            throw new InvalidOperationException(errorMoreThanOne);
        }

        public static T SingleOrDefault<T>(this IEnumerable<T> collection, string errorMessage)
        {
            if (collection == null)
                throw new ArgumentNullException("collection");

            using (IEnumerator<T> enumerator = collection.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                    return default(T);

                T current = enumerator.Current;

                if (!enumerator.MoveNext())
                    return current;
            }

            throw new InvalidOperationException(errorMessage);
        }

        public static T First<T>(this IEnumerable<T> collection, string errorMessage)
        {
            if (collection == null)
                throw new ArgumentNullException("collection");

            using (IEnumerator<T> enumerator = collection.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                    throw new InvalidOperationException(errorMessage);

                return enumerator.Current;
            }
        }

        public static T SingleOrMany<T>(this IEnumerable<T> collection, string errorZero)
        {
            if (collection == null)
                throw new ArgumentNullException("collection");

            using (IEnumerator<T> enumerator = collection.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                    throw new InvalidOperationException(errorZero);

                T current = enumerator.Current;

                if (enumerator.MoveNext())
                    return default(T);

                return current;
            }
        }

        //Throws exception if 0, returns if one, returns default if many
        public static T SingleOrMany<T>(this IEnumerable<T> collection)
        {
            if (collection == null)
                throw new ArgumentNullException("collection");

            using (IEnumerator<T> enumerator = collection.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                    throw new InvalidOperationException("The collection has no elements");

                T current = enumerator.Current;

                if (enumerator.MoveNext())
                    return default(T);

                return current;
            }
        }

        //returns default if 0 or many, returns if one
        public static T Only<T>(this IEnumerable<T> collection)
        {
            if (collection == null)
                throw new ArgumentNullException("collection");

            using (IEnumerator<T> enumerator = collection.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                    return default(T);

                T current = enumerator.Current;

                if (enumerator.MoveNext())
                    return default(T);

                return current;
            }
        }

        public static string ToString<T>(this IEnumerable<T> collection, string separator)
        {
            StringBuilder sb = new StringBuilder();
            foreach (var item in collection)
            {
                sb.Append(item.ToString());
                sb.Append(separator);
            }
            return sb.ToString(0, Math.Max(0, sb.Length - separator.Length));  // Remove at the end is faster
        }

        public static string ToString<T>(this IEnumerable<T> collection, Func<T, string> toString, string separator)
        {
            StringBuilder sb = new StringBuilder();
            foreach (var item in collection)
            {
                sb.Append(toString(item));
                sb.Append(separator);
            }
            return sb.ToString(0, Math.Max(0, sb.Length - separator.Length));  // Remove at the end is faster
        }


        public static string CommaAnd<T>(this IEnumerable<T> collection)
        {
            return CommaString(collection.Select(a => a.ToString()).ToArray(), Resources.And);
        }

        public static string CommaAnd<T>(this IEnumerable<T> collection, Func<T, string> toString)
        {
            return CommaString(collection.Select(toString).ToArray(), Resources.And);
        }

        public static string CommaOr<T>(this IEnumerable<T> collection)
        {
            return CommaString(collection.Select(a=>a.ToString()).ToArray(), Resources.Or);
        }

        public static string CommaOr<T>(this IEnumerable<T> collection, Func<T, string> toString)
        {
            return CommaString(collection.Select(toString).ToArray(), Resources.Or);
        }

        public static string Comma<T>(this IEnumerable<T> collection, string lastSeparator)
        {
            return CommaString(collection.Select(a => a.ToString()).ToArray(), lastSeparator);
        }

        public static string Comma<T>(this IEnumerable<T> collection, Func<T, string> toString, string lastSeparator)
        {
            return CommaString(collection.Select(toString).ToArray(), lastSeparator);
        }

        static string CommaString(this string[] values, string lastSeparator)
        {            
            if (values.Length == 0)
                return "";

            StringBuilder sb = new StringBuilder();
            sb.Append(values[0]);

            for (int i = 1; i < values.Length - 1; i++)
            {
                sb.Append(", ");
                sb.Append(values[i]);
            }

            if (values.Length > 1)
            {
                sb.Append(lastSeparator);
                sb.Append(values[values.Length - 1]);
            }

            return sb.ToString();
        }

        public static void ToConsole<T>(this IEnumerable<T> collection)
        {
            ToConsole(collection, a => a.ToString());
        }

        public static void ToConsole<T>(this IEnumerable<T> collection, Func<T, string> toString)
        {
            foreach (var item in collection)
                Console.WriteLine(toString(item));
        }

        public static void ToFile(this IEnumerable<string> collection, string fileName)
        {
            using (FileStream fs = File.Create(fileName))
            using (StreamWriter sw = new StreamWriter(fs, Encoding.Default))
            {
                foreach (var item in collection)
                    sw.WriteLine(item);
            }
        }

        public static void ToFile<T>(this IEnumerable<T> collection, Func<T, string> toString, string fileName)
        {
            collection.Select(toString).ToFile(fileName);
        }

        public static DataTable ToDataTable<T>(this IEnumerable<T> collection)
        {
            DataTable table = new DataTable();

            List<MemberEntry<T>> members = MemberEntryFactory.GenerateList<T>();
            table.Columns.AddRange(members.Select(m => new DataColumn(m.Name, m.MemberInfo.ReturningType())).ToArray());
            foreach (var e in collection)
                table.Rows.Add(members.Select(m => m.Getter(e)).ToArray());
            return table;
        }

        #region String Tables
        public static string[,] ToStringTable<T>(this IEnumerable<T> collection)
        {
            List<MemberEntry<T>> members = MemberEntryFactory.GenerateList<T>();

            string[,] result = new string[members.Count, collection.Count() + 1];

            for (int i = 0; i < members.Count; i++)
                result[i, 0] = members[i].Name;

            int j = 1;
            foreach (var item in collection)
            {
                for (int i = 0; i < members.Count; i++)
                    result[i, j] = members[i].Getter(item).TryCC(a => a.ToString()) ?? "";
                j++;
            }

            return result;
        }

        public static string FormatTable(this string[,] table)
        {
            return FormatTable(table, true);
        }

        public static string FormatTable(this string[,] table, bool longHeaders)
        {
            int width = table.GetLength(0);
            int height = table.GetLength(1);
            int start = height == 1 ? 0 : (longHeaders ? 0 : 1);

            int[] lengths = 0.To(width).Select(i => Math.Max(3, start.To(height).Max(j => table[i, j].Length))).ToArray();

            return 0.To(height).Select(j => 0.To(width).ToString(i => table[i, j].PadChopRight(lengths[i]), " ")).ToString("\r\n");
        }

        public static void WriteFormatedStringTable<T>(this IEnumerable<T> collection, TextWriter textWriter, string title, bool longHeaders)
        {
            textWriter.WriteLine();
            if (title.HasText())
                textWriter.WriteLine(title);
            textWriter.WriteLine(collection.ToStringTable().FormatTable(longHeaders));
            textWriter.WriteLine();
        }

        public static void ToConsoleTable<T>(this IEnumerable<T> collection, string title = null, bool longHeader = false)
        {
            collection.WriteFormatedStringTable(Console.Out, title, longHeader);
        }

        public static string ToWikiTable<T>(this IEnumerable<T> collection)
        {
            string[,] table = collection.ToStringTable();

            string str = "{| class=\"data\"\r\n" + 0.To(table.GetLength(1))
                .Select(i => (i == 0 ? "! " : "| ") + table.Row(i).ToString(o => o == null ? "" : o.ToString(), i == 0 ? " !! " : " || "))
                .ToString("\r\n|-\r\n") + "\r\n|}";

            return str;
        }
        #endregion

        #region Min Max
        public static T WithMin<T, V>(this IEnumerable<T> collection, Func<T, V> valueSelector)
          where V : IComparable<V>
        {
            T result = default(T);
            bool hasMin = false;
            V min = default(V);
            foreach (var item in collection)
            {
                V val = valueSelector(item);
                if (!hasMin || val.CompareTo(min) < 0)
                {
                    hasMin = true;
                    min = val;
                    result = item;
                }
            }

            return result;
        }

        public static T WithMax<T, V>(this IEnumerable<T> collection, Func<T, V> valueSelector)
               where V : IComparable<V>
        {
            T result = default(T);
            bool hasMax = false;
            V max = default(V);

            foreach (var item in collection)
            {
                V val = valueSelector(item);
                if (!hasMax || val.CompareTo(max) > 0)
                {
                    hasMax = true;
                    max = val;
                    result = item;
                }
            }
            return result;
        }

        public static MinMax<T> WithMinMaxPair<T, V>(this IEnumerable<T> collection, Func<T, V> valueSelector)
        where V : IComparable<V>
        {
            T withMin = default(T), withMax = default(T);
            bool hasMin = false, hasMax = false;
            V min = default(V), max = default(V);
            foreach (var item in collection)
            {
                V val = valueSelector(item);
                if (!hasMax || val.CompareTo(max) > 0)
                {
                    hasMax = true;
                    max = val;
                    withMax = item;
                }

                if (!hasMin || val.CompareTo(min) < 0)
                {
                    hasMin = true;
                    min = val;
                    withMin = item;
                }
            }

            return new MinMax<T>(withMin, withMax);
        }

        public static Interval<T> MinMaxPair<T>(this IEnumerable<T> collection)
            where T : struct, IComparable<T>, IEquatable<T>
        {
            bool has = false;
            T min = default(T), max = default(T);
            foreach (var item in collection)
            {
                if (!has)
                {
                    has = true;
                    min = max = item;
                }
                else
                {
                    if (item.CompareTo(max) > 0)
                        max = item;
                    if (item.CompareTo(min) < 0)
                        min = item;
                }
            }

            return new Interval<T>(min, max);
        }

        public static Interval<V> MinMaxPair<T, V>(this IEnumerable<T> collection, Func<T, V> valueSelector)
            where V:struct, IComparable<V>, IEquatable<V>
        {
            bool has = false;
            V min = default(V), max = default(V);
            foreach (var item in collection)
            {
                V val = valueSelector(item);

                if (!has)
                {
                    has = true;
                    min = max = val;
                }
                else
                {
                    if (val.CompareTo(max) > 0)
                        max = val;
                    if (val.CompareTo(min) < 0)
                        min = val;
                }
            }

            return new Interval<V>(min, max);
        }


        #endregion

        #region Operation
        public static IEnumerable<T> Concat<T>(params IEnumerable<T>[] collections)
        {
            foreach (var collection in collections)
            {
                foreach (var item in collection)
                {
                    yield return item;
                }
            }
        }

        public static IEnumerable<S> BiSelect<T, S>(this IEnumerable<T> collection, Func<T, T, S> func)
        {
            return BiSelect(collection, func, BiSelectOptions.None);
        }

        public static IEnumerable<S> BiSelect<T, S>(this IEnumerable<T> collection, Func<T, T, S> func, BiSelectOptions options)
        {
            using (IEnumerator<T> enumerator = collection.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                    yield break;


                T firstItem = enumerator.Current;
                if (options == BiSelectOptions.Initial || options == BiSelectOptions.InitialAndFinal)
                    yield return func(default(T), firstItem);

                T lastItem = firstItem;
                while (enumerator.MoveNext())
                {
                    T item = enumerator.Current;
                    yield return func(lastItem, item);
                    lastItem = item;
                }

                if (options == BiSelectOptions.Final || options == BiSelectOptions.InitialAndFinal)
                    yield return func(lastItem, default(T));

                if (options == BiSelectOptions.Circular)
                    yield return func(lastItem, firstItem);
            }
        }

        //return one element more
        public static IEnumerable<S> SelectAggregate<T, S>(this IEnumerable<T> collection, S seed, Func<S, T, S> aggregate)
        {
            yield return seed;
            foreach (var item in collection)
            {
                seed = aggregate(seed, item);
                yield return seed;
            }
        }

        public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
        {
            IEnumerable<ImmutableStack<T>> emptyProduct = new[] { ImmutableStack<T>.Empty };
            var result = sequences.Aggregate(
              emptyProduct,
              (accumulator, sequence) =>
                from accseq in accumulator
                from item in sequence
                select accseq.Push(item));

            return result.Select(a => a.Reverse());
        }

        public static List<IGrouping<T, T>> GroupWhen<T>(this IEnumerable<T> collection, Func<T, bool> isGroupKey)
        {
            return GroupWhen(collection, isGroupKey, false); 
        }

        public static List<IGrouping<T, T>> GroupWhen<T>(this IEnumerable<T> collection, Func<T, bool> isGroupKey, bool includeKeyInGroup)
        {
            List<IGrouping<T, T>> result = new List<IGrouping<T, T>>();
            Grouping<T, T> group = null;
            foreach (var item in collection)
            {
                if (isGroupKey(item))
                {
                    group = new Grouping<T, T>(item);
                    if (includeKeyInGroup)
                        group.Add(item); 
                    result.Add(group);
                }
                else
                {
                    if (group != null)
                        group.Add(item);
                }
            }

            return result;
        }

        public static IEnumerable<T> Distinct<T, S>(this IEnumerable<T> collection, Func<T, S> func)
        {
            return collection.Distinct(new LambdaComparer<T, S>(func));
        }

        public static IEnumerable<T> Distinct<T, S>(this IEnumerable<T> collection, Func<T, S> func, IEqualityComparer<S> comparer)
        {
            return collection.Distinct(new LambdaComparer<T, S>(func, comparer, null));
        }

        public static IEnumerable<List<T>> GroupsOf<T>(this IEnumerable<T> collection, int groupSize)
        {
            List<T> newList = new List<T>(groupSize);
            foreach (var item in collection)
            {
                newList.Add(item);
                if (newList.Count == groupSize)
                {
                    yield return newList;
                    newList = new List<T>();
                }
            }

            if (newList.Count != 0)
                yield return newList;
        }

        public static IEnumerable<T> Slice<T>(this IEnumerable<T> collection, int firstIncluded, int toNotIncluded)
        {
            return collection.Skip(firstIncluded).Take(toNotIncluded - firstIncluded);
        }

        public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> collection) where T : IComparable<T>
        {
            return collection.OrderBy(a => a);
        }

        public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> collection) where T : IComparable<T>
        {
            return collection.OrderByDescending(a => a);
        }
        #endregion

        #region Zip
        public static IEnumerable<Tuple<A, B>> Zip<A, B>(this IEnumerable<A> colA, IEnumerable<B> colB)
        {
            using (var enumA = colA.GetEnumerator())
            using (var enumB = colB.GetEnumerator())
            {
                while (enumA.MoveNext() && enumB.MoveNext())
                {
                    yield return new Tuple<A, B>(enumA.Current, enumB.Current);
                }
            }
        }

        public static void ZipForeach<A, B>(this IEnumerable<A> colA, IEnumerable<B> colB, Action<A, B> actions)
        {
            using (var enumA = colA.GetEnumerator())
            using (var enumB = colB.GetEnumerator())
            {
                while (enumA.MoveNext() && enumB.MoveNext())
                {
                    actions(enumA.Current, enumB.Current);
                }
            }
        }

        public static IEnumerable<Tuple<A, B>> ZipStrict<A, B>(this IEnumerable<A> colA, IEnumerable<B> colB)
        {
            using (var enumA = colA.GetEnumerator())
            using (var enumB = colB.GetEnumerator())
            {
                while (AssertoTwo(enumA.MoveNext(), enumB.MoveNext()))
                {
                    yield return new Tuple<A, B>(enumA.Current, enumB.Current);
                }
            }
        }

        public static IEnumerable<R> ZipStrict<A, B, R>(this IEnumerable<A> colA, IEnumerable<B> colB, Func<A, B, R> mixer)
        {
            using (var enumA = colA.GetEnumerator())
            using (var enumB = colB.GetEnumerator())
            {
                while (AssertoTwo(enumA.MoveNext(), enumB.MoveNext()))
                {
                    yield return mixer(enumA.Current, enumB.Current);
                }
            }
        }

        public static void ZipForeachStrict<A, B>(this IEnumerable<A> colA, IEnumerable<B> colB, Action<A, B> action)
        {
            using (var enumA = colA.GetEnumerator())
            using (var enumB = colB.GetEnumerator())
            {
                while (AssertoTwo(enumA.MoveNext(), enumB.MoveNext()))
                {
                    action(enumA.Current, enumB.Current);
                }
            }
        }

        static bool AssertoTwo(bool nextA, bool nextB)
        {
            if (nextA != nextB)
                if (nextA)
                    throw new InvalidOperationException("Second collection is shorter");
                else
                    throw new InvalidOperationException("First collection is shorter");
            else
                return nextA;
        }
        #endregion

        #region Conversions


        public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
        {
            return new HashSet<T>(source);
        }

        public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer)
        {
            return new HashSet<T>(source, comparer);
        }

        public static ReadOnlyCollection<T> ToReadOnly<T>(this IEnumerable<T> collection)
        {
            return collection == null ? null :
                collection as ReadOnlyCollection<T> ?? (collection as List<T> ?? collection.ToList()).AsReadOnly();
        }

        public static IEnumerable<T> AsThreadSafe<T>(this IEnumerable<T> source)
        {
            return new TreadSafeEnumerator<T>(source);
        }

        public static IEnumerable<T> ToProgressEnumerator<T>(this IEnumerable<T> source, out IProgressInfo pi)
        {
            pi = new ProgressEnumerator<T>(source, source.Count());
            return (IEnumerable<T>)pi;
        }



        public static void PushRange<T>(this Stack<T> stack, IEnumerable<T> elements)
        {
            foreach (var item in elements)
                stack.Push(item);
        }

        public static void EnqueueRange<T>(this Queue<T> queue, IEnumerable<T> elements)
        {
            foreach (var item in elements)
                queue.Enqueue(item);
        }

        public static void AddRange<T>(this HashSet<T> hashset, IEnumerable<T> coleccion)
        {
            foreach (var item in coleccion)
            {
                hashset.Add(item);
            }
        }

        public static bool TryContains<T>(this HashSet<T> hashset, T element)
        {
            if (hashset == null)
                return false;

            return hashset.Contains(element);
        }
        #endregion

        public static IEnumerable<R> JoinStrict<K, O, N, R>(
           IEnumerable<O> oldCollection,
           IEnumerable<N> newCollection,
           Func<O, K> oldKeySelector,
           Func<N, K> newKeySelector,
           Func<O, N, R> resultSelector, string action)
        {

            var oldDictionary = oldCollection.ToDictionary(oldKeySelector);
            var newDictionary = newCollection.ToDictionary(newKeySelector);

            var oldOnly = oldDictionary.Keys.Where(k => !newDictionary.ContainsKey(k)).ToList();
            var newOnly = newDictionary.Keys.Where(k => !oldDictionary.ContainsKey(k)).ToList();

            if (oldOnly.Count != 0)
                if (newOnly.Count != 0)
                    throw new InvalidOperationException("Error {0}\r\n Extra: {1}\r\n Lacking: {2}".Formato(action, oldOnly.ToString(", "), newOnly.ToString(", ")));
                else
                    throw new InvalidOperationException("Error {0}\r\n Extra: {1}".Formato(action, oldOnly.ToString(", ")));
            else
                if (newOnly.Count != 0)
                    throw new InvalidOperationException("Error {0}\r\n Lacking: {1}".Formato(action, newOnly.ToString(", ")));

            return oldDictionary.Select(p => resultSelector(p.Value, newDictionary[p.Key]));
        }

        public static IEnumerable<Iteration<T>> Iterate<T>(this IEnumerable<T> collection)
        {
            using (IEnumerator<T> enumerator = collection.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                {
                    yield break;
                }
                bool isFirst = true;
                bool isLast = false;
                int index = 0;
                while (!isLast)
                {
                    T current = enumerator.Current;
                    isLast = !enumerator.MoveNext();
                    yield return new Iteration<T>(current, isFirst, isLast, index++);
                    isFirst = false;
                }
            }
        }
    }

    public enum BiSelectOptions
    {
        None,
        Initial,
        Final,
        InitialAndFinal,
        Circular,
    }

    public class Iteration<T>
    {
        readonly T value;
        readonly bool isFirst;
        readonly bool isLast; 
        readonly int position;

        internal Iteration(T value, bool isFirst, bool isLast, int position)
        {
            this.value = value;
            this.isFirst = isFirst;
            this.isLast = isLast;
            this.position = position;
        }

        public T Value { get { return value; } }
        public bool IsFirst { get { return isFirst; } }
        public bool IsLast { get { return isLast; } }
        public int Position { get { return position; } }
        public bool IsEven { get { return position % 2 == 0; } }
        public bool IsOdd { get { return position % 1 == 0; } }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer (Senior) Signum Software
Spain Spain
I'm Computer Scientist, one of the founders of Signum Software, and the lead developer behind Signum Framework.

www.signumframework.com

I love programming in C#, Linq, Compilers, Algorithms, Functional Programming, Computer Graphics, Maths...

Comments and Discussions