Task: Greedy algorithm for Egyptian fractions





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);
        }
    }
}




VB: Imports System.Numerics
Imports System.Text

Module Module1

    Function Gcd(a As BigInteger, b As BigInteger) As BigInteger
        If b = 0 Then
            If a < 0 Then
                Return -a
            Else
                Return a
            End If
        Else
            Return Gcd(b, a Mod b)
        End If
    End Function

    Function Lcm(a As BigInteger, b As BigInteger) As BigInteger
        Return a / Gcd(a, b) * b
    End Function

    Public Class Rational
        Dim num As BigInteger
        Dim den As BigInteger

        Public Sub New(n As BigInteger, d As BigInteger)
            Dim c = Gcd(n, d)
            num = n / c
            den = d / c
            If den < 0 Then
                num = -num
                den = -den
            End If
        End Sub

        Public Sub New(n As BigInteger)
            num = n
            den = 1
        End Sub

        Public Function Numerator() As BigInteger
            Return num
        End Function

        Public Function Denominator() As BigInteger
            Return den
        End Function

        Public Overrides Function ToString() As String
            If den = 1 Then
                Return num.ToString()
            Else
                Return String.Format("{0}/{1}", num, den)
            End If
        End Function

        
        Public Shared Operator +(lhs As Rational, rhs As Rational) As Rational
            Return New Rational(lhs.num * rhs.den + rhs.num * lhs.den, lhs.den * rhs.den)
        End Operator

        Public Shared Operator -(lhs As Rational, rhs As Rational) As Rational
            Return New Rational(lhs.num * rhs.den - rhs.num * lhs.den, lhs.den * rhs.den)
        End Operator

        

        Public Shared Operator =(lhs As Rational, rhs As Rational) As Boolean
            Return lhs.num = rhs.num AndAlso lhs.den = rhs.den
        End Operator

        Public Shared Operator <>(lhs As Rational, rhs As Rational) As Boolean
            Return lhs.num <> rhs.num OrElse lhs.den <> rhs.den
        End Operator

        Public Shared Operator <(lhs As Rational, rhs As Rational) As Boolean
            
            
            Dim ad = lhs.num * rhs.den
            Dim bc = lhs.den * rhs.num
            Return ad < bc
        End Operator

        Public Shared Operator >(lhs As Rational, rhs As Rational) As Boolean
            
            
            Dim ad = lhs.num * rhs.den
            Dim bc = lhs.den * rhs.num
            Return ad > bc
        End Operator

        Public Shared Operator <=(lhs As Rational, rhs As Rational) As Boolean
            Return lhs < rhs OrElse lhs = rhs
        End Operator

        Public Shared Operator >=(lhs As Rational, rhs As Rational) As Boolean
            Return lhs > rhs OrElse lhs = rhs
        End Operator

        
        Public Shared Widening Operator CType(ByVal bi As BigInteger) As Rational
            Return New Rational(bi)
        End Operator
        Public Shared Widening Operator CType(ByVal lo As Long) As Rational
            Return New Rational(lo)
        End Operator
    End Class

    Function Egyptian(r As Rational) As List(Of Rational)
        Dim result As New List(Of Rational)

        If r >= 1 Then
            If r.Denominator() = 1 Then
                result.Add(r)
                result.Add(New Rational(0))
                Return result
            End If
            result.Add(New Rational(r.Numerator / r.Denominator))
            r -= result(0)
        End If

        Dim modFunc = Function(m As BigInteger, n As BigInteger)
                          Return ((m Mod n) + n) Mod n
                      End Function

        While r.Numerator() <> 1
            Dim q = (r.Denominator() + r.Numerator() - 1) / r.Numerator()
            result.Add(New Rational(1, q))
            r = New Rational(modFunc(-r.Denominator(), r.Numerator()), r.Denominator * q)
        End While

        result.Add(r)
        Return result
    End Function

    Function FormatList(Of T)(col As List(Of T)) As String
        Dim iter = col.GetEnumerator()
        Dim sb As New StringBuilder

        sb.Append("[")
        If iter.MoveNext() Then
            sb.Append(iter.Current)
        End If
        While iter.MoveNext()
            sb.Append(", ")
            sb.Append(iter.Current)
        End While
        sb.Append("]")
        Return sb.ToString()
    End Function

    Sub Main()
        Dim rs = {New Rational(43, 48), New Rational(5, 121), New Rational(2014, 59)}
        For Each r In rs
            Console.WriteLine("{0} => {1}", r, FormatList(Egyptian(r)))
        Next

        Dim lenMax As Tuple(Of ULong, Rational) = Tuple.Create(0UL, New Rational(0))
        Dim denomMax As Tuple(Of BigInteger, Rational) = Tuple.Create(New BigInteger(0), New Rational(0))

        Dim query = (From i In Enumerable.Range(1, 100)
                     From j In Enumerable.Range(1, 100)
                     Select New Rational(i, j)).Distinct().ToList()
        For Each r In query
            Dim e = Egyptian(r)
            Dim eLen As ULong = e.Count
            Dim eDenom = e.Last().Denominator()
            If eLen > lenMax.Item1 Then
                lenMax = Tuple.Create(eLen, r)
            End If
            If eDenom > denomMax.Item1 Then
                denomMax = Tuple.Create(eDenom, r)
            End If
        Next

        Console.WriteLine("Term max is {0} with {1} terms", lenMax.Item2, lenMax.Item1)
        Dim 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))
    End Sub

End Module