range () para carros alegóricos


140

Existe um range()equivalente para carros alegóricos em Python?

>>> range(0.5,5,1.5)
[0, 1, 2, 3, 4]
>>> range(0.5,5,0.5)

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    range(0.5,5,0.5)
ValueError: range() step argument must not be zero

1
Essas não são frações, mas flutuam. E carros alegóricos são ... bem, propensos a dar resultados diferentes do que você espera.

6
Uma solução rápida seria inteiros tratar como decimais, por exemplo: range(5, 50, 5)e, em seguida, basta dividir cada número por 10.
NullUserException

@delnan - atualizado. Eu estou disposto a aceitar imprecisões minuto para a conveniência de ter um intervalo de flutuação
Jonathan

2
possível duplicata do valor da etapa range () decimal
Jonathan

@NullUserException - este é apenas um exemplo - o código real é claro paramétrico :)
Jonathan

Respostas:


97

Não conheço uma função interna, mas escrever uma como essa não deve ser muito complicado.

def frange(x, y, jump):
  while x < y:
    yield x
    x += jump

Como os comentários mencionam, isso pode produzir resultados imprevisíveis, como:

>>> list(frange(0, 100, 0.1))[-1]
99.9999999999986

Para obter o resultado esperado, você pode usar uma das outras respostas nesta pergunta ou, como @Tadhg mencionado, você pode usar decimal.Decimalcomo jumpargumento. Certifique-se de inicializá-lo com uma string em vez de um float.

>>> import decimal
>>> list(frange(0, 100, decimal.Decimal('0.1')))[-1]
Decimal('99.9')

Ou até:

import decimal

def drange(x, y, jump):
  while x < y:
    yield float(x)
    x += decimal.Decimal(jump)

E depois:

>>> list(drange(0, 100, '0.1'))[-1]
99.9

34
O lema do Python é realmente Deve haver uma - e preferencialmente apenas uma - maneira óbvia de fazê-lo . Mas é incrível de qualquer maneira :) Python
Jonathan

3
>>> print list(frange(0,100,0.1))[-1]==100.0seráFalse
Volodimir Kopey

frangepode funcionar inesperadamente. Devido à maldição da aritmética de ponto flutuante , por exemplo, frange(0.0, 1.0, 0.1)produz 11 valores, sendo o último valor 0.9999999999999999. Uma melhoria prática seria, while x + sys.float_info.epsilon < y:embora mesmo isso possa provavelmente falhar com grandes números .
Akseli Palen

10
-1 Por favor, não use este código , pelo menos não em software que possa afetar minha vida. Não há como fazê-lo funcionar de maneira confiável. Também não use a resposta de Akseli Palén. Use a resposta de Xaerxess ou wim (exceto ignore a parte sobre arange).
21916 benrg

3
isso funciona muito bem se você usardecimal.Decimal como etapa em vez de carros alegóricos.
Tadhg McDonald-Jensen

112

Você pode usar:

[x / 10.0 for x in range(5, 50, 15)]

ou use lambda / map:

map(lambda x: x/10.0, range(5, 50, 15))

1
E matriz (gama (5,50,15)) / 10.0 como matrizes numpy ter operadores para o manuseamento de divisão, multiplicação e assim por diante
edvaldig

2
@edvaldig: você está certo, eu não sabia disso ... Não obstante, acho que o arange(0.5, 5, 1.5)IMO é mais legível.
Xaerxess 01/09

2
Prefiro essa resposta à aceita, porque as duas primeiras soluções apresentadas são baseadas na iteração sobre números inteiros e na derivação das flutuações finais dos números inteiros. Isso é mais robusto. Se você fizer isso diretamente com carros alegóricos, corre o risco de ter erros pontuais estranhos devido à forma como os carros alegóricos são representados internamente. Por exemplo, se você tentar list(frange(0, 1, 0.5)), funcionará bem e 1 será excluído, mas se você tentar list(frange(0, 1, 0.1)), o último valor obtido será próximo de 1,0, o que provavelmente não é o que você deseja. As soluções apresentadas aqui não têm esse problema.
precisa saber é o seguinte

3
Nunca use numpy.arange (a própria documentação numpy recomenda isso). Use numpy.linspace conforme recomendado por wim ou uma das outras sugestões nesta resposta.
23416 benrg

79

Eu costumava usar, numpy.arangemas tive algumas complicações ao controlar o número de elementos que ele retorna, devido a erros de ponto flutuante. Então agora eu uso linspace, por exemplo:

>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([  0.        ,   3.33333333,   6.66666667,  10.        ])

No entanto, ainda existem erros de ponto flutuante, sem o uso de decimal, por exemplo:np.linspace(-.1,10,num=5050)[0]
TNT

2
@ TNT Não, isso não é um erro. Você encontrará que np.linspace(-.1,10,num=5050)[0] == -.1é verdadeiro. É só que os repr(np.float64('-0.1'))shows mostram mais dígitos.
wim

1
Embora esse exemplo em particular não mostre excesso de erro de arredondamento, há casos de falha. Por exemplo, print(numpy.linspace(0, 3, 148)[49])imprime 0.9999999999999999quando o resultado ideal seria 1.0. linspacefaz um trabalho muito melhor que arange, mas não é garantido que produza o erro de arredondamento mínimo possível.
user2357112 suporta Monica

Ele é garantido para executar manipulação de endpoint correta, e sempre produzir exatamente o número solicitado de elementos.
user2357112 suporta Monica

40

O Pylab possui frange(um invólucro, na verdade, para matplotlib.mlab.frange):

>>> import pylab as pl
>>> pl.frange(0.5,5,0.5)
array([ 0.5,  1. ,  1.5,  2. ,  2.5,  3. ,  3.5,  4. ,  4.5,  5. ])

4
A franquia está obsoleta desde o matplotlib versão 2.2. numpy.arange deve ser usado.
Kuzavas

13

Ansiosamente avaliado (2.x range):

[x * .5 for x in range(10)]

Preguiçosamente avaliado (2.x xrange, 3.x range):

itertools.imap(lambda x: x * .5, xrange(10)) # or range(10) as appropriate

Alternativamente:

itertools.islice(itertools.imap(lambda x: x * .5, itertools.count()), 10)
# without applying the `islice`, we get an infinite stream of half-integers.

4
+1; mas por que não (x * .5 for x in range(10))como expressão geradora de avaliação preguiçosa?
Tim Pietzcker

2
Porque isso seria muito fácil, eu acho? :)
Karl Knechtel

11

usando itertools: faixa de ponto flutuante avaliada preguiçosamente:

>>> from itertools import count, takewhile
>>> def frange(start, stop, step):
        return takewhile(lambda x: x< stop, count(start, step))

>>> list(frange(0.5, 5, 1.5))
# [0.5, 2.0, 3.5]

3
+1 para usar itertools.takewhile. No entanto, itertools.count(start, step)sofre de erros acumulados de ponto flutuante. (Avalie, takewhile(lambda x: x < 100, count(0, 0.1))por exemplo.) Em takewhile(lambda x: x < stop, (start + i * step for i in count()))vez disso, eu escreveria .
Musiphil

6

Ajudei a adicionar a função numeric_range ao pacote more-itertools .

more_itertools.numeric_range(start, stop, step) age como o intervalo de funções interno, mas pode lidar com tipos de flutuação, decimal e fração.

>>> from more_itertools import numeric_range
>>> tuple(numeric_range(.1, 5, 1))
(0.1, 1.1, 2.1, 3.1, 4.1)

4

Não existe essa função interna, mas você pode usar o seguinte (código Python 3) para fazer o trabalho tão seguro quanto o Python permitir.

from fractions import Fraction

def frange(start, stop, jump, end=False, via_str=False):
    """
    Equivalent of Python 3 range for decimal numbers.

    Notice that, because of arithmetic errors, it is safest to
    pass the arguments as strings, so they can be interpreted to exact fractions.

    >>> assert Fraction('1.1') - Fraction(11, 10) == 0.0
    >>> assert Fraction( 0.1 ) - Fraction(1, 10) == Fraction(1, 180143985094819840)

    Parameter `via_str` can be set to True to transform inputs in strings and then to fractions.
    When inputs are all non-periodic (in base 10), even if decimal, this method is safe as long
    as approximation happens beyond the decimal digits that Python uses for printing.


    For example, in the case of 0.1, this is the case:

    >>> assert str(0.1) == '0.1'
    >>> assert '%.50f' % 0.1 == '0.10000000000000000555111512312578270211815834045410'


    If you are not sure whether your decimal inputs all have this property, you are better off
    passing them as strings. String representations can be in integer, decimal, exponential or
    even fraction notation.

    >>> assert list(frange(1, 100.0, '0.1', end=True))[-1] == 100.0
    >>> assert list(frange(1.0, '100', '1/10', end=True))[-1] == 100.0
    >>> assert list(frange('1', '100.0', '.1', end=True))[-1] == 100.0
    >>> assert list(frange('1.0', 100, '1e-1', end=True))[-1] == 100.0
    >>> assert list(frange(1, 100.0, 0.1, end=True))[-1] != 100.0
    >>> assert list(frange(1, 100.0, 0.1, end=True, via_str=True))[-1] == 100.0

    """
    if via_str:
        start = str(start)
        stop = str(stop)
        jump = str(jump)
    start = Fraction(start)
    stop = Fraction(stop)
    jump = Fraction(jump)
    while start < stop:
        yield float(start)
        start += jump
    if end and start == stop:
        yield(float(start))

Você pode verificar tudo executando algumas asserções:

assert Fraction('1.1') - Fraction(11, 10) == 0.0
assert Fraction( 0.1 ) - Fraction(1, 10) == Fraction(1, 180143985094819840)

assert str(0.1) == '0.1'
assert '%.50f' % 0.1 == '0.10000000000000000555111512312578270211815834045410'

assert list(frange(1, 100.0, '0.1', end=True))[-1] == 100.0
assert list(frange(1.0, '100', '1/10', end=True))[-1] == 100.0
assert list(frange('1', '100.0', '.1', end=True))[-1] == 100.0
assert list(frange('1.0', 100, '1e-1', end=True))[-1] == 100.0
assert list(frange(1, 100.0, 0.1, end=True))[-1] != 100.0
assert list(frange(1, 100.0, 0.1, end=True, via_str=True))[-1] == 100.0

assert list(frange(2, 3, '1/6', end=True))[-1] == 3.0
assert list(frange(0, 100, '1/3', end=True))[-1] == 100.0

Código disponível no GitHub


4

Por que não há implementação de intervalo de ponto flutuante na biblioteca padrão?

Como ficou claro por todas as postagens aqui, não há versão de ponto flutuante range(). Dito isso, a omissão faz sentido se considerarmos que a range()função é frequentemente usada como um gerador de índice (e, claro, isso significa um acessador ). Portanto, quando chamamos range(0,40), estamos dizendo que queremos 40 valores começando em 0, até 40, mas não inclusivos em 40.

Quando consideramos que a geração de índice é tanto sobre o número de índices quanto sobre seus valores, o uso de uma implementação de float range()na biblioteca padrão faz menos sentido. Por exemplo, se chamarmos a função frange(0, 10, 0.25), esperaríamos que 0 e 10 fossem incluídos, mas isso renderia um vetor com 41 valores.

Assim, uma frange()função que depende de seu uso sempre exibirá um comportamento contra-intuitivo; possui valores demais percebidos da perspectiva da indexação ou não inclui um número que razoavelmente deve ser retornado da perspectiva matemática.

O caso de uso matemático

Com isso dito, conforme discutido, numpy.linspace()executa a geração com a perspectiva matemática bem:

numpy.linspace(0, 10, 41)
array([  0.  ,   0.25,   0.5 ,   0.75,   1.  ,   1.25,   1.5 ,   1.75,
         2.  ,   2.25,   2.5 ,   2.75,   3.  ,   3.25,   3.5 ,   3.75,
         4.  ,   4.25,   4.5 ,   4.75,   5.  ,   5.25,   5.5 ,   5.75,
         6.  ,   6.25,   6.5 ,   6.75,   7.  ,   7.25,   7.5 ,   7.75,
         8.  ,   8.25,   8.5 ,   8.75,   9.  ,   9.25,   9.5 ,   9.75,  10.
])

O caso de uso de indexação

E para a perspectiva da indexação, escrevi uma abordagem um pouco diferente com algumas mágicas complicadas de string que nos permitem especificar o número de casas decimais.

# Float range function - string formatting method
def frange_S (start, stop, skip = 1.0, decimals = 2):
    for i in range(int(start / skip), int(stop / skip)):
        yield float(("%0." + str(decimals) + "f") % (i * skip))

Da mesma forma, também podemos usar a roundfunção interna e especificar o número de casas decimais:

# Float range function - rounding method
def frange_R (start, stop, skip = 1.0, decimals = 2):
    for i in range(int(start / skip), int(stop / skip)):
        yield round(i * skip, ndigits = decimals)

Uma rápida comparação e desempenho

Obviamente, dada a discussão acima, essas funções têm um caso de uso bastante limitado. No entanto, aqui está uma comparação rápida:

def compare_methods (start, stop, skip):

    string_test  = frange_S(start, stop, skip)
    round_test   = frange_R(start, stop, skip)

    for s, r in zip(string_test, round_test):
        print(s, r)

compare_methods(-2, 10, 1/3)

Os resultados são idênticos para cada um:

-2.0 -2.0
-1.67 -1.67
-1.33 -1.33
-1.0 -1.0
-0.67 -0.67
-0.33 -0.33
0.0 0.0
...
8.0 8.0
8.33 8.33
8.67 8.67
9.0 9.0
9.33 9.33
9.67 9.67

E alguns horários:

>>> import timeit

>>> setup = """
... def frange_s (start, stop, skip = 1.0, decimals = 2):
...     for i in range(int(start / skip), int(stop / skip)):
...         yield float(("%0." + str(decimals) + "f") % (i * skip))
... def frange_r (start, stop, skip = 1.0, decimals = 2):
...     for i in range(int(start / skip), int(stop / skip)):
...         yield round(i * skip, ndigits = decimals)
... start, stop, skip = -1, 8, 1/3
... """

>>> min(timeit.Timer('string_test = frange_s(start, stop, skip); [x for x in string_test]', setup=setup).repeat(30, 1000))
0.024284090992296115

>>> min(timeit.Timer('round_test = frange_r(start, stop, skip); [x for x in round_test]', setup=setup).repeat(30, 1000))
0.025324633985292166

Parece que o método de formatação de strings vence por um fio no meu sistema.

As limitações

E, finalmente, uma demonstração do ponto da discussão acima e uma última limitação:

# "Missing" the last value (10.0)
for x in frange_R(0, 10, 0.25):
    print(x)

0.25
0.5
0.75
1.0
...
9.0
9.25
9.5
9.75

Além disso, quando o skipparâmetro não é divisível pelo stopvalor, pode haver uma lacuna de bocejo devido ao último problema:

# Clearly we know that 10 - 9.43 is equal to 0.57
for x in frange_R(0, 10, 3/7):
    print(x)

0.0
0.43
0.86
1.29
...
8.14
8.57
9.0
9.43

Existem maneiras de resolver esse problema, mas no final do dia, a melhor abordagem provavelmente seria usar o Numpy.


Este é um argumento bastante distorcido. range () deve ser simplesmente analisado no gerador de iterações e se ele é usado no loop for ou no índice, deve ser deixado para os chamadores. As pessoas têm usado carros alegóricos há milênios e as justificativas acima não fazem sentido. As pessoas nos comitês de Python se atrapalharam aqui muito tempo e um bom argumento provavelmente foi abafado por algumas justificativas distorcidas como acima. É tão claro e simples. Agora, existem muitas decisões, como acima, consagradas na linguagem Python.
Shital Shah 01/03/19

3

Uma solução sem dependências numpy etc foi fornecida pelo kichik, mas devido à aritmética do ponto flutuante , ela geralmente se comporta inesperadamente. Conforme observado por mim e pelo blubberdiblub , elementos adicionais se infiltram facilmente no resultado. Por exemplo naive_frange(0.0, 1.0, 0.1), renderia 0.999...como seu último valor e, portanto, renderia 11 valores no total.

Uma versão robusta é fornecida aqui:

def frange(x, y, jump=1.0):
    '''Range for floats.'''
    i = 0.0
    x = float(x)  # Prevent yielding integers.
    x0 = x
    epsilon = jump / 2.0
    yield x  # yield always first value
    while x + epsilon < y:
        i += 1.0
        x = x0 + i * jump
        yield x

Devido à multiplicação, os erros de arredondamento não se acumulam. O uso de epsiloncuida de um possível erro de arredondamento da multiplicação, mesmo que as questões possam, obviamente, aumentar nos extremos muito pequeno e muito grande. Agora, como esperado:

> a = list(frange(0.0, 1.0, 0.1))
> a[-1]
0.9
> len(a)
10

E com números um pouco maiores:

> b = list(frange(0.0, 1000000.0, 0.1))
> b[-1]
999999.9
> len(b)
10000000

O código também está disponível como GistHub Gist .


Isso falha com franja (2.0, 17.0 / 6.0, 1.0 / 6.0). De maneira alguma ele pode ser robusto.
21916 benrg

@benrg Obrigado por apontar isso! Isso me levou a perceber que o epsilon deveria depender do salto, então revi o algoritmo e reparei o problema. Esta nova versão é muito mais robusta, não é?
Akseli Palén

2

Uma versão mais simples sem biblioteca

Aw, heck - vou jogar em uma versão simples sem biblioteca. Sinta-se livre para melhorar [*]:

def frange(start=0, stop=1, jump=0.1):
    nsteps = int((stop-start)/jump)
    dy = stop-start
    # f(i) goes from start to stop as i goes from 0 to nsteps
    return [start + float(i)*dy/nsteps for i in range(nsteps)]

A idéia principal é que nstepsé o número de etapas para você começar do começo ao fim e range(nsteps)sempre emite números inteiros, para que não haja perda de precisão. O passo final é mapear [0..nsteps] linearmente para [start..stop].

editar

Se, como alancalvitti, você gostaria que a série tivesse uma representação racional exata, sempre poderá usar Frações :

from fractions import Fraction

def rrange(start=0, stop=1, jump=0.1):
    nsteps = int((stop-start)/jump)
    return [Fraction(i, nsteps) for i in range(nsteps)]

[*] Em particular, frange()retorna uma lista, não um gerador. Mas bastava para as minhas necessidades.


Se você deseja incluir o valor de parada na saída, adicionando stop + jump, esse método reverte para o resultado ingênuo com pontos flutuantes ruins no meio, tente frange(0,1.1,0.1)e ainda mais daqueles com uma opção comofrange(0,1.05,0.1)
alancalvitti

@alancalvitti: Qual é a sua definição de ponto flutuante "ruim"? Sim, os resultados podem não ser impressos corretamente, mas frange () fornece o conjunto mais próximo de valores espaçados uniformemente dentro dos limites da representação de ponto flutuante. Como você melhoraria isso?
Fearless_fool

bom ponto, estou tão acostumado a linguagem de alto nível, onde você poderia estender números sobre números racionais para tal tarefa, que Py parece uma montagem.
precisa saber é o seguinte

Montagem? Hrrumph! ;) É claro Python pode fornecer representação exata com frações: docs.python.org/3/library/fractions.html
fearless_fool

Certo, obrigado, mas por exemplo, a linguagem que eu gosto converte automaticamente esses tipos, portanto 1/2 é racional, enquanto 1 / 2.0 é flutuante, não há necessidade de declará-los como tal - deixe declarações para Java, que é ainda mais inferior / montagem que Py.
precisa saber é o seguinte

2

Isso pode ser feito com numpy.arange (iniciar, parar, tamanho da etapa)

import numpy as np

np.arange(0.5,5,1.5)
>> [0.5, 2.0, 3.5, 5.0]

# OBS you will sometimes see stuff like this happening, 
# so you need to decide whether that's not an issue for you, or how you are going to catch it.
>> [0.50000001, 2.0, 3.5, 5.0]

Nota 1: Na discussão na seção de comentários aqui, "nunca use numpy.arange()(a própria documentação numpy recomenda isso). Use numpy.linspace conforme recomendado por wim ou uma das outras sugestões nesta resposta"

Nota 2: Li a discussão em alguns comentários aqui, mas depois de voltar a esta pergunta pela terceira vez, sinto que essa informação deve ser colocada em uma posição mais legível.


2

Como escreveu Kichik , isso não deve ser muito complicado. No entanto, este código:

def frange(x, y, jump):
  while x < y:
    yield x
    x += jump

É inadequado devido ao efeito cumulativo de erros ao trabalhar com carros alegóricos. É por isso que você recebe algo como:

>>>list(frange(0, 100, 0.1))[-1]
99.9999999999986

Enquanto o comportamento esperado seria:

>>>list(frange(0, 100, 0.1))[-1]
99.9

Solução 1

O erro cumulativo pode simplesmente ser reduzido usando uma variável de índice. Aqui está o exemplo:

from math import ceil

    def frange2(start, stop, step):
        n_items = int(ceil((stop - start) / step))
        return (start + i*step for i in range(n_items))

Este exemplo funciona conforme o esperado.

Solução 2

Nenhuma função aninhada. Apenas um tempo e uma variável de contador:

def frange3(start, stop, step):
    res, n = start, 1

    while res < stop:
        yield res
        res = start + n * step
        n += 1

Essa função também funcionará bem, exceto nos casos em que você deseja a faixa invertida. Por exemplo:

>>>list(frange3(1, 0, -.1))
[]

A solução 1 neste caso funcionará conforme o esperado. Para fazer essa função funcionar nessas situações, você deve aplicar um hack, semelhante ao seguinte:

from operator import gt, lt

def frange3(start, stop, step):
    res, n = start, 0.
    predicate = lt if start < stop else gt
    while predicate(res, stop):
        yield res
        res = start + n * step
        n += 1

Com este hack, você pode usar estas funções com etapas negativas:

>>>list(frange3(1, 0, -.1))
[1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.3999999999999999, 0.29999999999999993, 0.19999999999999996, 0.09999999999999998]

Solução 3

Você pode ir ainda mais longe com a biblioteca padrão simples e compor uma função de intervalo para a maioria dos tipos numéricos:

from itertools import count
from itertools import takewhile

def any_range(start, stop, step):
    start = type(start + step)(start)
    return takewhile(lambda n: n < stop, count(start, step))

Este gerador é adaptado do livro Fluent Python (capítulo 14. Iteráveis, iteradores e geradores). Não funcionará com intervalos decrescentes. Você deve aplicar um hack, como na solução anterior.

Você pode usar este gerador da seguinte maneira, por exemplo:

>>>list(any_range(Fraction(2, 1), Fraction(100, 1), Fraction(1, 3)))[-1]
299/3
>>>list(any_range(Decimal('2.'), Decimal('4.'), Decimal('.3')))
[Decimal('2'), Decimal('2.3'), Decimal('2.6'), Decimal('2.9'), Decimal('3.2'), Decimal('3.5'), Decimal('3.8')]

E é claro que você pode usá-lo com float e int também.

Seja cuidadoso

Se você deseja usar essas funções com etapas negativas, adicione uma verificação do sinal de etapa, por exemplo:

no_proceed = (start < stop and step < 0) or (start > stop and step > 0)
if no_proceed: raise StopIteration

A melhor opção aqui é aumentar StopIteration, se você deseja imitar a rangeprópria função.

Intervalo de imitação

Se você deseja imitar a rangeinterface da função, pode fornecer algumas verificações de argumento:

def any_range2(*args):
    if len(args) == 1:
        start, stop, step = 0, args[0], 1.
    elif len(args) == 2:
        start, stop, step = args[0], args[1], 1.
    elif len(args) == 3:
        start, stop, step = args
    else:
        raise TypeError('any_range2() requires 1-3 numeric arguments')

    # here you can check for isinstance numbers.Real or use more specific ABC or whatever ...

    start = type(start + step)(start)
    return takewhile(lambda n: n < stop, count(start, step))

Eu acho que você entendeu. Você pode usar qualquer uma dessas funções (exceto a primeira) e tudo o que precisa é da biblioteca padrão do python.


1

eu escrevi uma função que retorna uma tupla de um intervalo de números de ponto flutuante de precisão dupla sem casas decimais além dos centésimos. era simplesmente uma questão de analisar os valores do intervalo, como seqüências de caracteres, e dividir o excesso. Eu o uso para exibir intervalos para selecionar em uma interface do usuário. Espero que alguém ache útil.

def drange(start,stop,step):
    double_value_range = []
    while start<stop:
        a = str(start)
        a.split('.')[1].split('0')[0]
        start = float(str(a))
        double_value_range.append(start)
        start = start+step
    double_value_range_tuple = tuple(double_value_range)
   #print double_value_range_tuple
    return double_value_range_tuple

1

Uso

# Counting up
drange(0, 0.4, 0.1)
[0, 0.1, 0.2, 0.30000000000000004, 0.4]

# Counting down
drange(0, -0.4, -0.1)
[0, -0.1, -0.2, -0.30000000000000004, -0.4]

Para arredondar cada etapa para N casas decimais

drange(0, 0.4, 0.1, round_decimal_places=4)
[0, 0.1, 0.2, 0.3, 0.4]

drange(0, -0.4, -0.1, round_decimal_places=4)
[0, -0.1, -0.2, -0.3, -0.4]

Código

def drange(start, end, increment, round_decimal_places=None):
    result = []
    if start < end:
        # Counting up, e.g. 0 to 0.4 in 0.1 increments.
        if increment < 0:
            raise Exception("Error: When counting up, increment must be positive.")
        while start <= end:
            result.append(start)
            start += increment
            if round_decimal_places is not None:
                start = round(start, round_decimal_places)
    else:
        # Counting down, e.g. 0 to -0.4 in -0.1 increments.
        if increment > 0:
            raise Exception("Error: When counting down, increment must be negative.")
        while start >= end:
            result.append(start)
            start += increment
            if round_decimal_places is not None:
                start = round(start, round_decimal_places)
    return result

Por que escolher esta resposta?

  • Muitas outras respostas serão interrompidas quando solicitadas a contagem regressiva.
  • Muitas outras respostas fornecerão resultados arredondados incorretamente.
  • Outras respostas baseadas em np.linspaceacerto-e-erro, podem ou não funcionar devido à dificuldade em escolher o número correto de divisões. np.linspacerealmente enfrenta incrementos decimais de 0,1, e a ordem das divisões na fórmula para converter o incremento em várias divisões pode resultar em código correto ou quebrado.
  • Outras respostas baseadas np.arangeestão obsoletas.

Em caso de dúvida, tente os quatro casos de teste acima.


0
def Range(*argSequence):
    if len(argSequence) == 3:
        imin = argSequence[0]; imax = argSequence[1]; di = argSequence[2]
        i = imin; iList = []
        while i <= imax:
            iList.append(i)
            i += di
        return iList
    if len(argSequence) == 2:
        return Range(argSequence[0], argSequence[1], 1)
    if len(argSequence) == 1:
        return Range(1, argSequence[0], 1)

Observe que a primeira letra do intervalo é maiúscula. Esse método de nomeação não é incentivado para funções no Python. Você pode alterar Range para algo como drange ou frange, se quiser. A função "Range" se comporta exatamente como você deseja. Você pode verificar o manual aqui [ http://reference.wolfram.com/language/ref/Range.html ].


0

Eu acho que existe uma resposta muito simples que realmente emula todos os recursos do range, mas para float e integer. Nesta solução, você apenas supõe que sua aproximação por padrão seja 1e-7 (ou a que você escolher) e poderá alterá-la quando chamar a função.

def drange(start,stop=None,jump=1,approx=7): # Approx to 1e-7 by default
  '''
  This function is equivalent to range but for both float and integer
  '''
  if not stop: # If there is no y value: range(x)
      stop= start
      start= 0
  valor= round(start,approx)
  while valor < stop:
      if valor==int(valor):
          yield int(round(valor,approx))
      else:
          yield float(round(valor,approx))
      valor += jump
  for i in drange(12):
      print(i)

0

É claro que haverá alguns erros de arredondamento, portanto isso não é perfeito, mas é o que geralmente uso para aplicativos, que não exigem alta precisão. Se você quiser tornar isso mais preciso, adicione um argumento extra para especificar como lidar com erros de arredondamento. Talvez a passagem de uma função de arredondamento possa torná-lo extensível e permitir que o programador especifique como lidar com erros de arredondamento.

arange = lambda start, stop, step: [i + step * i for i in range(int((stop - start) / step))]

Se eu escrever:

arange(0, 1, 0.1)

Ele produzirá:

[0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9]

-1

Existe um intervalo () equivalente para carros alegóricos em Python? NÃO Use isto:

def f_range(start, end, step):
    a = range(int(start/0.01), int(end/0.01), int(step/0.01))
    var = []
    for item in a:
        var.append(item*0.01)
    return var

3
Solução muito ruim, tente f_range(0.01,0.02,0.001)... Para os propósitos mais práticos, a arangeNumpy é uma solução simples, segura e rápida.
Bart Bart

Você está certo. Com numpy é 1,8 mais rápido que o meu código.
Grigor Kolev

Você está certo. Com numpy é 1,8 mais rápido que o meu código. Mas o sistema em que trabalho está completamente fechado. Apenas Python e pyserial não mais.
Grigor Kolev

-2

Existem várias respostas aqui que não lidam com casos simples de borda, como etapa negativa, início errado, parada etc. Aqui está a versão que lida com muitos desses casos, dando corretamente o mesmo comportamento que o nativo range():

def frange(start, stop=None, step=1):
  if stop is None:
    start, stop = 0, start
  steps = int((stop-start)/step)
  for i in range(steps):
    yield start
    start += step  

Observe que isso resultaria em erro na etapa = 0, como na versão nativa range . Uma diferença é que o intervalo nativo retorna um objeto indexável e reversível, enquanto o anterior não.

Você pode jogar com esse código e testar casos aqui.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.