Task: Greedy algorithm for Egyptian fractions



C#: using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;

namespace EgyptianFractions {
    class Program {
        class Rational : IComparable<Rational>, IComparable<int> {
            public BigInteger Num { get; }
            public BigInteger Den { get; }

            public Rational(BigInteger n, BigInteger d) {
                var c = Gcd(n, d);
                Num = n / c;
                Den = d / c;
                if (Den < 0) {
                    Num = -Num;
                    Den = -Den;
                }
            }

            public Rational(BigInteger n) {
                Num = n;
                Den = 1;
            }

            public override string ToString() {
                if (Den == 1) {
                    return Num.ToString();
                } else {
                    return string.Format("{0}/{1}", Num, Den);
                }
            }

            public Rational Add(Rational rhs) {
                return new Rational(Num * rhs.Den + rhs.Num * Den, Den * rhs.Den);
            }

            public Rational Sub(Rational rhs) {
                return new Rational(Num * rhs.Den - rhs.Num * Den, Den * rhs.Den);
            }

            public int CompareTo(Rational rhs) {
                var ad = Num * rhs.Den;
                var bc = Den * rhs.Num;
                return ad.CompareTo(bc);
            }

            public int CompareTo(int rhs) {
                var ad = Num * rhs;
                var bc = Den * rhs;
                return ad.CompareTo(bc);
            }
        }

        static BigInteger Gcd(BigInteger a, BigInteger b) {
            if (b == 0) {
                if (a < 0) {
                    return -a;
                } else {
                    return a;
                }
            } else {
                return Gcd(b, a % b);
            }
        }

        static List<Rational> Egyptian(Rational r) {
            List<Rational> result = new List<Rational>();

            if (r.CompareTo(1) >= 0) {
                if (r.Den == 1) {
                    result.Add(r);
                    result.Add(new Rational(0));
                    return result;
                }
                result.Add(new Rational(r.Num / r.Den));
                r = r.Sub(result[0]);
            }

            BigInteger modFunc(BigInteger m, BigInteger n) {
                return ((m % n) + n) % n;
            }

            while (r.Num != 1) {
                var q = (r.Den + r.Num - 1) / r.Num;
                result.Add(new Rational(1, q));
                r = new Rational(modFunc(-r.Den, r.Num), r.Den * q);
            }

            result.Add(r);
            return result;
        }

        static string FormatList<T>(IEnumerable<T> col) {
            StringBuilder sb = new StringBuilder();
            var iter = col.GetEnumerator();

            sb.Append('[');
            if (iter.MoveNext()) {
                sb.Append(iter.Current);
            }
            while (iter.MoveNext()) {
                sb.AppendFormat(", {0}", iter.Current);
            }
            sb.Append(']');

            return sb.ToString();
        }

        static void Main() {
            List<Rational> rs = new List<Rational> {
                new Rational(43, 48),
                new Rational(5, 121),
                new Rational(2014, 59)
            };
            foreach (var r in rs) {
                Console.WriteLine("{0} => {1}", r, FormatList(Egyptian(r)));
            }

            var lenMax = Tuple.Create(0UL, new Rational(0));
            var denomMax = Tuple.Create(BigInteger.Zero, new Rational(0));

            var query = (from i in Enumerable.Range(1, 100)
                         from j in Enumerable.Range(1, 100)
                         select new Rational(i, j))
                         .Distinct()
                         .ToList();
            foreach (var r in query) {
                var e = Egyptian(r);
                ulong eLen = (ulong) e.Count;
                var eDenom = e.Last().Den;
                if (eLen > lenMax.Item1) {
                    lenMax = Tuple.Create(eLen, r);
                }
                if (eDenom > denomMax.Item1) {
                    denomMax = Tuple.Create(eDenom, r);
                }
            }

            Console.WriteLine("Term max is {0} with {1} terms", lenMax.Item2, lenMax.Item1);
            var dStr = denomMax.Item1.ToString();
            Console.WriteLine("Denominator max is {0} with {1} digits {2}...{3}", denomMax.Item2, dStr.Length, dStr.Substring(0, 5), dStr.Substring(dStr.Length - 5, 5));
        }
    }
}



Java: import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class EgyptianFractions {
    private static BigInteger gcd(BigInteger a, BigInteger b) {
        if (b.equals(BigInteger.ZERO)) {
            return a;
        }
        return gcd(b, a.mod(b));
    }

    private static class Frac implements Comparable<Frac> {
        private BigInteger num, denom;

        public Frac(BigInteger n, BigInteger d) {
            if (d.equals(BigInteger.ZERO)) {
                throw new IllegalArgumentException("Parameter d may not be zero.");
            }

            BigInteger nn = n;
            BigInteger dd = d;
            if (nn.equals(BigInteger.ZERO)) {
                dd = BigInteger.ONE;
            } else if (dd.compareTo(BigInteger.ZERO) < 0) {
                nn = nn.negate();
                dd = dd.negate();
            }
            BigInteger g = gcd(nn, dd).abs();
            if (g.compareTo(BigInteger.ZERO) > 0) {
                nn = nn.divide(g);
                dd = dd.divide(g);
            }
            num = nn;
            denom = dd;
        }

        public Frac(int n, int d) {
            this(BigInteger.valueOf(n), BigInteger.valueOf(d));
        }

        public Frac plus(Frac rhs) {
            return new Frac(
                num.multiply(rhs.denom).add(denom.multiply(rhs.num)),
                rhs.denom.multiply(denom)
            );
        }

        public Frac unaryMinus() {
            return new Frac(num.negate(), denom);
        }

        public Frac minus(Frac rhs) {
            return plus(rhs.unaryMinus());
        }

        @Override
        public int compareTo(Frac rhs) {
            BigDecimal diff = this.toBigDecimal().subtract(rhs.toBigDecimal());
            if (diff.compareTo(BigDecimal.ZERO) < 0) {
                return -1;
            }
            if (BigDecimal.ZERO.compareTo(diff) < 0) {
                return 1;
            }
            return 0;
        }

        @Override
        public boolean equals(Object obj) {
            if (null == obj || !(obj instanceof Frac)) {
                return false;
            }
            Frac rhs = (Frac) obj;
            return compareTo(rhs) == 0;
        }

        @Override
        public String toString() {
            if (denom.equals(BigInteger.ONE)) {
                return num.toString();
            }
            return String.format("%s/%s", num, denom);
        }

        public BigDecimal toBigDecimal() {
            BigDecimal bdn = new BigDecimal(num);
            BigDecimal bdd = new BigDecimal(denom);
            return bdn.divide(bdd, MathContext.DECIMAL128);
        }

        public List<Frac> toEgyptian() {
            if (num.equals(BigInteger.ZERO)) {
                return Collections.singletonList(this);
            }
            List<Frac> fracs = new ArrayList<>();
            if (num.abs().compareTo(denom.abs()) >= 0) {
                Frac div = new Frac(num.divide(denom), BigInteger.ONE);
                Frac rem = this.minus(div);
                fracs.add(div);
                toEgyptian(rem.num, rem.denom, fracs);
            } else {
                toEgyptian(num, denom, fracs);
            }
            return fracs;
        }

        public void toEgyptian(BigInteger n, BigInteger d, List<Frac> fracs) {
            if (n.equals(BigInteger.ZERO)) {
                return;
            }
            BigDecimal n2 = new BigDecimal(n);
            BigDecimal d2 = new BigDecimal(d);
            BigDecimal[] divRem = d2.divideAndRemainder(n2, MathContext.UNLIMITED);
            BigInteger div = divRem[0].toBigInteger();
            if (divRem[1].compareTo(BigDecimal.ZERO) > 0) {
                div = div.add(BigInteger.ONE);
            }
            fracs.add(new Frac(BigInteger.ONE, div));
            BigInteger n3 = d.negate().mod(n);
            if (n3.compareTo(BigInteger.ZERO) < 0) {
                n3 = n3.add(n);
            }
            BigInteger d3 = d.multiply(div);
            Frac f = new Frac(n3, d3);
            if (f.num.equals(BigInteger.ONE)) {
                fracs.add(f);
                return;
            }
            toEgyptian(f.num, f.denom, fracs);
        }
    }

    public static void main(String[] args) {
        List<Frac> fracs = List.of(
            new Frac(43, 48),
            new Frac(5, 121),
            new Frac(2014, 59)
        );
        for (Frac frac : fracs) {
            List<Frac> list = frac.toEgyptian();
            Frac first = list.get(0);
            if (first.denom.equals(BigInteger.ONE)) {
                System.out.printf("%s -> [%s] + ", frac, first);
            } else {
                System.out.printf("%s -> %s", frac, first);
            }
            for (int i = 1; i < list.size(); ++i) {
                System.out.printf(" + %s", list.get(i));
            }
            System.out.println();
        }

        for (Integer r : List.of(98, 998)) {
            if (r == 98) {
                System.out.println("\nFor proper fractions with 1 or 2 digits:");
            } else {
                System.out.println("\nFor proper fractions with 1, 2 or 3 digits:");
            }

            int maxSize = 0;
            List<Frac> maxSizeFracs = new ArrayList<>();
            BigInteger maxDen = BigInteger.ZERO;
            List<Frac> maxDenFracs = new ArrayList<>();
            boolean[][] sieve = new boolean[r + 1][];
            for (int i = 0; i < r + 1; ++i) {
                sieve[i] = new boolean[r + 2];
            }
            for (int i = 1; i < r; ++i) {
                for (int j = i + 1; j < r + 1; ++j) {
                    if (sieve[i][j]) continue;
                    Frac f = new Frac(i, j);
                    List<Frac> list = f.toEgyptian();
                    int listSize = list.size();
                    if (listSize > maxSize) {
                        maxSize = listSize;
                        maxSizeFracs.clear();
                        maxSizeFracs.add(f);
                    } else if (listSize == maxSize) {
                        maxSizeFracs.add(f);
                    }
                    BigInteger listDen = list.get(list.size() - 1).denom;
                    if (listDen.compareTo(maxDen) > 0) {
                        maxDen = listDen;
                        maxDenFracs.clear();
                        maxDenFracs.add(f);
                    } else if (listDen.equals(maxDen)) {
                        maxDenFracs.add(f);
                    }
                    if (i < r / 2) {
                        int k = 2;
                        while (true) {
                            if (j * k > r + 1) break;
                            sieve[i * k][j * k] = true;
                            k++;
                        }
                    }
                }
            }
            System.out.printf("  largest number of items = %s\n", maxSize);
            System.out.printf("fraction(s) with this number : %s\n", maxSizeFracs);
            String md = maxDen.toString();
            System.out.printf("  largest denominator = %s digits, ", md.length());
            System.out.printf("%s...%s\n", md.substring(0, 20), md.substring(md.length() - 20, md.length()));
            System.out.printf("fraction(s) with this denominator : %s\n", maxDenFracs);
        }
    }
}