Evaluate if a list of integers is triangular in python

1

By means of a python script I want to evaluate if a list is triangular, that is to say if it is increasing up to a certain element, and from that element it is decreasing. For example, the list [2, 4, 5, 7, 4, 3] is triangular, while the list [2, 4, 5, 7, 5, 8, 4, 3, 1] is not.

Here is my test code:

def listaTriangular(listaEv):
    lista_final = []
    if (len(listaEv) - 1) != 0:
        for i in range(len(listaEv) ):
            if listaEv[i] > listaEv[i - 1]:
                return print("La lista crece")
            else:
                return print("La lista decrece")

Obviously the logic of the program allows reccurrer the list, but does not compare the elements as requested by the slogan, so I am thinking badly the solution.

    
asked by jsuarezbaron 26.09.2018 в 00:39
source

3 answers

3

The basic idea is, as you already outline, to compare one item with the next, but the key is to detect the peak to change the meaning of the comparison from it. On the other hand, if there are two equal contiguous values, they invalidate the triangle in principle and this is not taken into account in the idea that you propose. Finally, to compare each item with the next you must start from the element with index 1, not with the one from position 0, this makes the first comparison be the first element with the last ( lista[0] > lista[-1] ) and not the first with the second: range(1, len(listaEv)) .

There are many ways to do this, for example:

def es_triangular(lista):
    # Si la lista tiene menos de 3 elementos no puede ser triangular
    if len(lista) < 3:
        return False

    # Iterador sobre la lista
    list_iter = iter(lista)

    # Usar los dos primeros elementos para determinar si es ascendente o descendente
    prim, ultimo = next(list_iter), next(list_iter)
    if prim < ultimo:
        ascendente = True
    elif prim > ultimo:
        ascendente = False
    else:
        return False

    pico = False
    for n in list_iter:
        if n == ultimo:
            return False
        elif not pico:
            if (ascendente and n < ultimo) or (not ascendente and n > ultimo):
                pico = True
        else:
            if (ascendente and n > ultimo) or (not ascendente and n < ultimo):
                return False
        ultimo = n

    # Retornamos True si hay pico, False en caso contrario
    return True if pico else False 

Some characteristics of this approach, possibly improved, are:

  • It is considerably efficient in terms of execution time, it is only iterated once on the complete list in the worst case. At the time an incongruence occurs, the function returns False and cuts the iteration at that point. For example, in the list [1, 2, 1, 4, 5, 6] the cycle for performs a single iteration.

  • The memory usage is insignificant and independent of the length of the list. An iterator is used to scroll through the list and apart from this one no more objects are created, the variables used are limited to pointing to references of already existing objects.

  • Validates both ascending and descending triangular lists. If you just want to validate ascending lists, the function can be simplified a lot.

You can use indexing instead of an iterator, but it is slightly more inefficient. However, you only have to modify a couple of lines:

def es_triangular(lista):
    if len(lista) < 3:
        return False

    prim, ultimo = lista[0], lista[1]
    if prim < ultimo:
        ascendente = True
    elif prim > ultimo:
        ascendente = False
    else:
        return False

    pico = False
    for i in range(2, len(lista)):
        n = lista[i]
        if n == ultimo:
            return False
        elif not pico:
            if (ascendente and n < ultimo) or (not ascendente and n > ultimo):
                pico = True
        else:
            if (ascendente and n >= ultimo) or (not ascendente and n <= ultimo):
                return False
        ultimo = n

    return True if pico else False 

Some tests:

print(es_triangular([2, 4, 5, 7, 4, 3]))          # Triangular ascendente
print(es_triangular([2, 4, 5, 7, 5, 8, 4, 3, 1])) # No triangular
print(es_triangular([7, 6, 5, 4]))                # No triangular
print(es_triangular([8, 6, 5, 1, 7, 9]))          # Triangular descendente
print(es_triangular([2, 4, 5, 7, 8, 3]))          # Triangular ascendente
print(es_triangular([2, 4, 5, 6, 7, 8]))          # No Triangular
print(es_triangular([6, 7, 6]))                   # Triangular ascendente
print(es_triangular([2, 4, 5]))                   # No triangular
print(es_triangular([8, 4, 7]))                   # Triangular descendente
print(es_triangular([2, 3]))                      # No triangular
print(es_triangular([2, 5, 5, 7, 4, 3]))          # No triangular
print(es_triangular([2, 4, 5, 7, 7, 3]))          # No triangular
print(es_triangular([2, 5, 7, 4, 3, 3]))          # No triangular

Edit

The return line uses the syntax of the "ternary operator", it would be equivalent to:

if pico:
    return True
else:
    return False

The else is not really necessary since the return causes the execution to end:

if pico:
    return True
return False
    
answered by 26.09.2018 / 09:19
source
1

This function tells you if a list is triangular, but only ascending. I do not know if it will be the most "pythonic" way to do it, but it occurred to me to split the list by the maximum value. First compare the list from the beginning to the largest number (list1), with the same list1 ordered ascending, if it is the same then it passes to the next comparison. Then compare that the list from the first element down to the last (list2) is the same as the same list2 ordered descending. If it also meets, then it's triangular.

def listaTriangular(listaEv):
    max_elem_idx = listaEv.index(max(listaEv))
    lista1 = listaEv[0 : max_elem_idx+1]
    lista2 = listaEv[max_elem_idx+1 : ]
    message = "La lista no es triangular"
    if (len(lista1) > 1) and (lista1 == sorted(lista1)):
        if (len(lista2) != 0) and (lista2 == sorted(lista2, reverse=True)):
            message = "La Lista es triangular"
    return message

print(listaTriangular([2, 4, 5, 7, 4, 3]))
print(listaTriangular([2, 4, 5, 7, 5, 8, 4, 3, 1]))
print(listaTriangular([1, 2]))
print(listaTriangular([1, 2, 3]))
print(listaTriangular([3, 2, 1]))
print(listaTriangular([2, 3, 1]))
print(listaTriangular([1]))

Exit:

La Lista es triangular
La lista no es triangular
La lista no es triangular
La lista no es triangular
La lista no es triangular
La Lista es triangular
La lista no es triangular
    
answered by 26.09.2018 в 02:20
1

First of all:

for i in range(len(listaEv) ):
    if listaEv[i] > listaEv[i - 1]:
       return print("La lista crece")
    else:
       return print("La lista decrece")

The verification you do within the for is only verified once, because whatever the result, you exit the function with a return and on the other hand, in python, when you do listaEv[i - 1] e i = 0 you end up with listaEv[-1] that is totally valid, it just represents the last element and I do not think that's what you're looking for.

I think the idea is: verify the existence of a growth pattern in the list and then a decrement pattern or vice versa, no matter the length of each pattern, but only two unique patterns should exist , if it first grows, then it decreases and it grows back we have three different patterns and that invalidates the requirement, or even if we only have one pattern we would be in the same situation.

In practice we will have three patterns in any list, either grow (+1) or decrease (-1) or stay the same (0), what interests us is that there are only two patterns (+1) and (- one).

To detect the patterns, we can do something like this:

def patrones(lista):
  return [1 if lista[i] > lista[i-1] else -1 if lista[i] < lista[i-1] else 0 for i in range(1,len(lista))]

print(patrones([2, 4, 5, 7, 4, 3]))
print(patrones([1, 2, 5, 7, 4, 3, 6]))
print(patrones([7, 5, 3]))
print(patrones([1, 2]))

[1, 1, 1, -1, -1]
[1, 1, 1, -1, -1, 1]
[-1, -1]
[1]

the function patrones returns the mentioned patterns, it simply uses an understanding of lists to go comparing each element of the list with the previous one, the peculiarity is that we must begin to compare from the second element that is why we do range(1,len(lista)) the verification is not complex, simply 1 if lista[i] > lista[i-1] else -1 if lista[i] < lista[i-1] else 0 , that is, we will obtain a +1 if the list grows, a -1 if it decreases and a 0 if it is maintained with the same value.

The output gives us an idea of which of the lists is actually a triangular list, only the first case is, as we said, we only have two patterns and these are +1 and -1. The second case we have the patterns +1 and -1, but we have three patterns, which is not a triangular list either.

Having the built pattern, it is easy to verify the requirements:

from itertools import groupby

def patrones(lista):
      return [1 if lista[i] > lista[i-1] else -1 if lista[i] < lista[i-1] else 0 for i in range(1,len(lista))]

def listaTriangular(lista):
  p = [e[0] for e in list(groupby(patrones(lista)))]
  return len(p) == 2 and (p == [-1,1] or p == [1,-1])


print(listaTriangular([1]))
print(listaTriangular([1,2,3]))
print(listaTriangular([1,1,1]))
print(listaTriangular([2, 4, 5, 7, 4, 3]))
print(listaTriangular([2, 4, 5, 7, 5, 8, 4, 3, 1]))

False
False
False
True
False

We use groupby() of the module itertools to obtain the groups, the only possibility that does not work is that they are 2 and are [-1,1] or [1,-1]

    
answered by 26.09.2018 в 04:34