Search code examples
vb.netalgorithmmathsubtraction

Is there an algorithm to find the circular subtractions of values from a number?


I have a variable called valorInicial with values from 0 to 9. This variable can have a variable length, ranging from 9 to 60 characters.

To this variable valorInicial, I select 3 values and subtract -1, -2, and -3 from them, considering that if it goes below zero, it continues at 9, for example, 1-3=8 or 0-1=9. These are circular subtractions.

Then, we create the variable valorRestado with valorInicial variable subtracted by the selected values.

Then, we shuffle valorInicial. Therefore, valorInicial is disordered with respect to valorRestado.

I would need invaluable help to create a function that detects which values of valorInicial have been subtracted and how much has been subtracted from each value, considering that the subtraction is done with the characteristic that if it goes below 0, it continues at 9, and also that valorInicial is disordered with respect to valorRestado, and that the only possible subtractions are -1, -2, and -3.

We can take this example:

valorInicial: 988735928

9 is subtracted by 1 and becomes 8

2 is subtracted by 2 and becomes 0

8 is subtracted by 3 and becomes 5

valorRestado: 858735908

9: Two in valorInicial, ONE in valorRestado

8: Three in valorInicial, three in valorRestado

7: One in valorInicial, one in valorRestado

6: Zero in valorInicial, zero in valorRestado

5: One in valorInicial, TWO in valorRestado

4: Zero in valorInicial, zero in valorRestado

3: One in valorInicial, one in valorRestado

2: One in valorInicial, ZERO in valorRestado

1: Zero in valorInicial, zero in valorRestado

0: Zero in valorInicial, ONE in valorRestado

Then valorInicial is shuffled - valorInicial: 753982898

A logic that I have thought is as follows:

First, we have to check the frequency of each value in each variable, both in valorInicial and valorRestado.

If the frequency of the value in valorRestado is less than the frequency of the value in valorInicial, we know that that value of valorInicial has been subtracted, and as many times as the difference is. If there are 3 ones in valorInicial and 1 one in valorRestado, we know that two ones have been subtracted from valorInicial, and we know then that there must be two ones with two different subtractions, for example, one 1 has been subtracted by -1 and another 1 has been subtracted by -3.

If the frequency of the value in valorRestado is greater than the frequency of the value in valorInicial, we know that the result of the subtractions from valorInicial has resulted in that value, as many times as the difference is. If in valorInicial there are 2 fives and in valorRestado there are 4 fives, we know that the result of the subtraction of two values of valorInicial has resulted in 5, for example, there could be a value 6 that has been subtracted by -1 and a value 8 that has been subtracted by -3.

With this, the code has to be capable of controlling the decreases and the increases, and with that data, and knowing that only the possibility of subtracting -1, -2, and -3 exists, it has to find the values of valorInicial that have been subtracted and how much has been subtracted from each one.

We have to take into account, and here is the key to complexity, that we have to evaluate the set, because, for example, there may be two values whose frequency has decreased and two values whose frequency has increased. And with that data, we have to be able to find the 3 values that have been subtracted, and find out how much has been subtracted from each value, whether -1, -2, or -3.

First, we have to find the values that have decreased to establish the easy-to-observe values, and then we have to observe the values that have increased so that, knowing those that have decreased and are already established, and the values that have increased, which are the result of the subtraction, find the remaining values.

There may be particularities where the values are not directly taken out, since when subtracting, they become other values present in valorInicial, which in turn have been subtracted. There may be substitutions. In the example given, that happens. We have to use a complex comparison logic to correctly find the values and their subtractions. It's not just seeing which value has decreased its frequency and that's it. We have to do a deeper and more exhaustive analysis, and the answer can only be given when it is complete. That is, it has to find the 3 values and their subtractions, and the subtractions can only be -1, -2, and -3.

Always remembering that the subtraction when it is below zero continues at 9, for example, 1-3=8, 2-5=7

In the given example, the result of the MsgBox should say something like this:

9 is subtracted by 1

2 is subtracted by 2

8 is subtracted by 3

And to find this result, it has to do a thorough and deep analysis, not just compare the frequencies and that's it. As we have discussed, it has to compare frequencies of valorInicial that have decreased with respect to valorRestado, frequencies of restado that have increased with respect to valorInicial, and then analyze the data obtained and find the 3 values and their corresponding subtractions.

The code that I have tried is the following:

Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
    Dim valorInicial As String = "982079171"
    Dim restado As String = "982979978"
    Dim resultadoFuncion As New List(Of String)

    resultadoFuncion = ReconstruirValorInicial(valorInicial, restado)

    ' Concatenar todos los elementos de la lista en una cadena
    Dim mensaje As String = String.Join(vbCrLf, resultadoFuncion)

    ' Mostrar la cadena en un MsgBox
    MsgBox(mensaje, MsgBoxStyle.OkOnly, "Valores de la lista")
End Sub

Function ReconstruirValorInicial(valorInicial As String, restado As String) As List(Of String)
    Dim frecuenciaInicial As New Dictionary(Of Char, Integer)
    Dim frecuenciaRestado As New Dictionary(Of Char, Integer)
    Dim resultados As New List(Of String)

    ' Contar frecuencia de cada dígito
    For i As Integer = 0 To valorInicial.Length - 1
        Dim c As Char = valorInicial.Substring(i, 1)
        If frecuenciaInicial.ContainsKey(c) Then
            frecuenciaInicial(c) += 1
        Else
            frecuenciaInicial(c) = 1
        End If
    Next
    For i As Integer = 0 To restado.Length - 1
        Dim c As Char = restado.Substring(i, 1)
        If frecuenciaRestado.ContainsKey(c) Then
            frecuenciaRestado(c) += 1
        Else
            frecuenciaRestado(c) = 1
        End If
    Next

    ' Identificar dígitos restados y determinar la cantidad a restar
    For i As Integer = 0 To valorInicial.Length - 1
        Dim c As Char = valorInicial.Substring(i, 1)
        Dim frecuenciaInicialDigito As Integer = frecuenciaInicial(c)
        Dim frecuenciaRestadoDigito As Integer = 0
        If frecuenciaRestado.ContainsKey(c) Then
            frecuenciaRestadoDigito = frecuenciaRestado(c)
        End If

        Dim cantidadARestar As Integer
        If frecuenciaRestadoDigito <= frecuenciaInicialDigito Then
            ' Se ha restado
            cantidadARestar = frecuenciaInicialDigito - frecuenciaRestadoDigito
        End If

        ' Almacenar resultados
        If cantidadARestar > 0 Then
            resultados.Add(c & " : " & cantidadARestar.ToString & " (Posición: " & (i + 1).ToString & ")")
        End If
    Next

    Return resultados
End Function

But it doesn't work correctly, as it doesn't have the deep and exhaustive comparison logic, nor does it control what has been subtracted from each value.

But it doesn't work correctly, as it doesn't have the deep and exhaustive comparison logic, nor does it control what has been subtracted from each value.

The logic must be looking at the frequencies that decrease and increase

InitialValue: 988735928 From 9 you subtract 1 it becomes 8 2 is subtracted from 2, it becomes 0 3 is subtracted from 8 and it becomes 5 subtracted: 858735908 9: two in initial value, ONE in subtraction 8: three in initial value, three in subtraction 7: one in initialvalue, one in subtraction 6: zero in initialvalue, zero in subtraction 5: one in initialvalue, TWO in subtraction 4: zero in initialvalue, zero in subtraction 3: one in initialvalue, one in subtracted 2: one in initialvalue, ZERO in subtraction 1: zero in initialvalue, zero in subtraction 0: zero in initialvalue, ONE in subtraction

First we see that 9 has reduced, and 2 has reduced. with which we know directly that the 9 has been subtracted and the 2 has been subtracted.

then we see that the 5 and the 0 have increased, it means that when performing the subtractions, one of them has become 5 and another has become 0, then we have to control that the possible subtractions are one -1 another -2 and another -3. so we have to look at the options available.

If -1 is subtracted from 2, it becomes a 1 -> does not fit, discarded

If -3 is subtracted from 2, it becomes a 9 -> does not fit, discarded

If -2 is subtracted from 2 it becomes a 0 -> it fits
If -3 is subtracted from 9 it becomes a 5 -> it fits, but...

there would be a number left to subtract -1 -> there is NO combination that fits, discarded

If -2 is subtracted from 2 it becomes a 0 -> it fits
If -1 is subtracted from 9 it becomes an 8-> it could fit if...
If -3 is subtracted from 8, it becomes a 5 -> it fits! found the combination!

Solution

  • My solution converts the digits from the String Chars to Integers. I also use an array with the index range 0 .. 9 to store the frequency of each digit. This is very handy as the digit is given by the array index.

    Asc(c) - Asc("0"c) gets the digit value as integer from a character.

    Dim initialFrequency = New Integer(9) {}
    Dim resultingFrequency = New Integer(9) {}
    
    ' Count frequency of each digit
    For Each c In initialValue
        initialFrequency(Asc(c) - Asc("0"c)) += 1
    Next
    For Each c In reducedValue
        resultingFrequency(Asc(c) - Asc("0"c)) += 1
    Next
    

    Then we create lists of changed initial digits and resulting digits. If the frequency is decreasing, we have an initial digit, if the frequency is increasing, we have a resulting digit.

    We also create a list of unchanged digits. Those are used later to complement sets of changed digits smaller than 3 because sometimes more subtractions than apparently changed digits might yield results.

    ' Get lists of changed and unchanged digits
    Dim initialDigits = New List(Of Integer)()
    Dim resultingDigits = New List(Of Integer)()
    Dim unchangedDigits = New List(Of Integer)()
    For i = 0 To 9
        Dim changes = resultingFrequency(i) - initialFrequency(i)
        If changes < 0 Then 'initial > resulting
            For j = 0 To -changes - 1
                initialDigits.Add(i)
            Next
            For j = 0 To initialFrequency(i) + changes - 1
                unchangedDigits.Add(i)
            Next
        ElseIf changes > 0 Then 'resulting > initial
            For j = 0 To changes - 1
                resultingDigits.Add(i)
            Next
            For j = 0 To resultingFrequency(i) - changes - 1
                unchangedDigits.Add(i)
            Next
        Else
            For j = 0 To initialFrequency(i) - 1
                unchangedDigits.Add(i)
            Next
        End If
    Next
    

    Then comes the core of the algorithm. The idea is to get all permutations of possible subtractions and to apply them to the initial changed digits and to compare the result with the list of the resulting digits. If the sequences are equivalent, we have a hit.

    The permutations depend on the number of changed digits. This helper function returns these permutations:

    ' First index is the permutation, second index is the digit affected
    Private Function GetSubtractionPermutations(count As Integer) As Integer(,)
        Select Case count
            Case 1
                Return New Integer(2, 0) { ' One digit changed
                    {-1},
                    {-2},
                    {-3}}
            Case 2
                Return New Integer(5, 1) { ' Two digits changed
                    {-1, -2},
                    {-1, -3},
                    {-2, -1},
                    {-2, -3},
                    {-3, -1},
                    {-3, -2}}
            Case 3
                Return New Integer(5, 2) { ' Three digits changed
                    {-1, -2, -3},
                    {-1, -3, -2},
                    {-2, -1, -3},
                    {-2, -3, -1},
                    {-3, -1, -2},
                    {-3, -2, -1}}
            Case Else
                Return New Integer(-1, -1) {}
        End Select
    End Function
    

    I also mentioned complementing sets smaller than 3. This is another helper method doing it. It returns a list of tuples of lists of initial and resulting digits:

    ' If the count of changing digits is less than 3, we create synthetic sets with more digits
    ' to find more intricate solutions.
    Private Function ComplementSets(ByVal initialDigits As List(Of Integer),
                                    ByVal resultingDigits As List(Of Integer),
                                    ByVal unchangedDigits As List(Of Integer)
                                    ) As List(Of (intial As List(Of Integer), resulting As List(Of Integer)))
        Dim sets = New List(Of (List(Of Integer), List(Of Integer))) From {
            (initialDigits, resultingDigits)
        }
        Select Case initialDigits.Count
            Case 1 ' Create 2-sets and from those also 3-sets (recursively)
                For Each d In unchangedDigits.Distinct()
                    ' Create 2-set
                    Dim i2 = New List(Of Integer)(initialDigits)
                    Dim r2 = New List(Of Integer)(resultingDigits)
                    i2.Add(d)
                    r2.Add(d)
                    sets.Add((i2, r2))
    
                    'Create 3-sets recursively
                    Dim u = New List(Of Integer)(unchangedDigits)
                    u.Remove(d)
                    Dim sets3 = ComplementSets(i2, r2, u)
                    sets.AddRange(sets3)
                Next
            Case 2 ' Create 3-sets
                For Each d In unchangedDigits.Distinct()
                    Dim i3 = New List(Of Integer)(initialDigits)
                    Dim r3 = New List(Of Integer)(resultingDigits)
                    i3.Add(d)
                    r3.Add(d)
                    sets.Add((i3, r3))
                Next
        End Select
        Return sets
    End Function
    

    Now, we can write the algorithm:

    Dim complementedDigitSets =
        ComplementSets(initialDigits, resultingDigits, unchangedDigits)
    Dim results = New HashSet(Of String)()
    Dim testResult = New List(Of Integer)()
    For Each complementedSet In complementedDigitSets
        Dim initial = complementedSet.intial
        Dim resulting = complementedSet.resulting
        Dim permutations = GetSubtractionPermutations(initial.Count)
        resulting.Sort()
        For p = 0 To permutations.GetLength(0) - 1
            testResult.Clear()
            For i = 0 To initial.Count - 1
                testResult.Add((initial(i) + permutations(p, i) + 10) Mod 10)
            Next
            testResult.Sort()
            If testResult.SequenceEqual(resulting) Then
                results.Add(String.Join(", ", initial.[Select](Function(d, i) $"{d}{permutations(p, i)}={(d + permutations(p, i) + 10) Mod 10}").Order()))
            End If
        Next
    Next
    

    Note that a HashSet eliminates duplicates. Since I order the strings representing the resulting subtractions, this will eliminate equivalent results appearing in a different order. (If you want to see what I mean, simply remove the .Order() part.)

    We can get the "circular subtraction" with the modulo operator: (d - x + 10) Mod 10. I am actually doing the addition of a negative number instead of a subtraction.

    Putting this together we get this function:

    Private Function FindSubtractions(initialValue As String, reducedValue As String) As List(Of String)
        Dim initialFrequency = New Integer(9) {}
        Dim resultingFrequency = New Integer(9) {}
    
        If initialValue.Length <> reducedValue.Length Then
            Throw New ArgumentException("initialValue and reducedValue must have the same length")
        End If
    
        ' Count frequency of each digit
        For Each c In initialValue
            initialFrequency(Asc(c) - Asc("0"c)) += 1
        Next
        For Each c In reducedValue
            resultingFrequency(Asc(c) - Asc("0"c)) += 1
        Next
    
        ' Get lists of changed and unchanged digits
        Dim initialDigits = New List(Of Integer)()
        Dim resultingDigits = New List(Of Integer)()
        Dim unchangedDigits = New List(Of Integer)()
        For i = 0 To 9
            Dim changes = resultingFrequency(i) - initialFrequency(i)
            If changes < 0 Then 'initial > resulting
                For j = 0 To -changes - 1
                    initialDigits.Add(i)
                Next
                For j = 0 To initialFrequency(i) + changes - 1
                    unchangedDigits.Add(i)
                Next
            ElseIf changes > 0 Then 'resulting > initial
                For j = 0 To changes - 1
                    resultingDigits.Add(i)
                Next
                For j = 0 To resultingFrequency(i) - changes - 1
                    unchangedDigits.Add(i)
                Next
            Else
                For j = 0 To initialFrequency(i) - 1
                    unchangedDigits.Add(i)
                Next
            End If
        Next
    
        If initialDigits.Count > 3 Then
            Console.WriteLine("Every subtraction -1, -2 and -3 can occur only once")
        ElseIf initialDigits.Count <> resultingDigits.Count Then
            Console.WriteLine("The size of the set of initial and resulting changed digits must be equal")
        ElseIf initialDigits.Count = 0 Then
            Return New List(Of String)
        End If
        If initialDigits.Count + unchangedDigits.Count <> initialValue.Length Then
            Console.WriteLine("There's somthing wrong with counting the digits")
        End If
    
        Dim complementedDigitSets =
        ComplementSets(initialDigits, resultingDigits, unchangedDigits)
        Dim results = New HashSet(Of String)()
        Dim testResult = New List(Of Integer)()
        For Each complementedSet In complementedDigitSets
            Dim initial = complementedSet.intial
            Dim resulting = complementedSet.resulting
            Dim permutations = GetSubtractionPermutations(initial.Count)
            resulting.Sort()
            For p = 0 To permutations.GetLength(0) - 1
                testResult.Clear()
                For i = 0 To initial.Count - 1
                    testResult.Add((initial(i) + permutations(p, i) + 10) Mod 10)
                Next
                testResult.Sort()
                If testResult.SequenceEqual(resulting) Then
                    results.Add(String.Join(", ", initial.[Select](Function(d, i) $"{d}{permutations(p, i)}={(d + permutations(p, i) + 10) Mod 10}").Order()))
                End If
            Next
        Next
    
        Return results.Order().ToList()
    End Function
    

    You will note that I also added a test, checking whether the number of changed digits is plausible.

    We can test it with this console application:

    Dim examples = {
        (initial:="982079171", resulting:="982979978"),
        (initial:="978465321", resulting:="978465118"),
        (initial:="987301037", resulting:="957191037"),
        (initial:="123456789", resulting:="123456759"),
        (initial:="123479", resulting:="123467")}
    For Each example In examples
        Console.WriteLine($"{example.initial} --> {example.resulting}")
        Dim results = FindSubtractions(example.initial, example.resulting)
        For Each result In results
            Console.WriteLine(result)
        Next
        Console.WriteLine()
    Next
    

    It prints:

    982079171 --> 982979978
    0-1=9, 1-2=9, 1-3=8
    
    978465321 --> 978465118
    1-3=8, 2-1=1, 3-2=1
    2-3=9, 3-2=1, 9-1=8
    
    987301037 --> 957191037
    0-1=9, 3-2=1, 8-3=5
    
    123456789 --> 123456759
    6-1=5, 8-2=6
    7-2=5, 8-1=7
    8-3=5
    
    123479 --> 123467
    7-1=6, 9-2=7
    9-3=6