11
votes

Soustrayez les chevauchements entre deux gammes sans ensembles

pas de jeu! strong>

Je ne peux pas utiliser des ensembles car: p>

  • Les gammes seront trop longues. Li>
  • Ils prendront trop de mémoire li>
  • La création des ensembles eux-mêmes prendra trop de temps. Li> ul>

    Utiliser uniquement les points d'extrémité des plages, existe-t-il un moyen optimal de soustraire deux listes de gammes? P>

    Exemple: H3>
    r1 = (1, 1000), (1100, 1200)  
    r2 = (30, 50), (60, 200), (1150, 1300)
    
    r1 - r2 = (1, 29), (51, 59), (201, 1000), (1100, 1149)
    


5 commentaires

Et si quelque chose dans r2 n'est pas dans r1 ?


Est-ce que nous traitons uniquement d'entiers? Et si nous avons r1 = (1.5, 5.1) et r2 = (2.5, 4.3) ? Comment devrions-nous faire face exactement à cela?


@Jbernardo: R2 peut contenir des gammes qui n'ont aucun chevauchement avec R1


@JMLOPEZ: Personnellement, je n'uimerai que des entiers. J'ai un FXN qui le fait maintenant pour soustraire des séquences de génome. Je suis curieux s'il y a une façon optimale de le faire.


FWIW, je travaille sur (dans le cadre d'un projet plus vaste - et cette partie n'a pas été examinée dans un moment) une implémentation d'une classe qui semble faire ce que vous voulez ...


7 Réponses :


2
votes

Je pense que j'ai mal compris la question, mais ce code fonctionne si R2 code> est un sous-ensemble de R1 code> xxx pré>

alors, vous faites. : p>

Mise à jour forte>: Remarque Le dernier intervalle de R2 code> est différent de fonctionner comme je l'entends. P>

>>> r1 = RangeSet(((1, 1000), (1100, 1200)))
>>> r2 = RangeSet([(30, 50), (60, 200), (1150, 1200)])
>>> r1 - r2
RangeSet: [(1, 29), (51, 59), (201, 1000), (1100, 1149)]
>>> r1.sub(r2)
>>> r1
RangeSet: [(1, 29), (51, 59), (201, 1000), (1100, 1149)]


3 commentaires

La réponse attendue n'inclut pas (1181, 1200)


@Dan c'est un ensemble différent. Il me semble étrange de ne pas accepter de se chevaucher au milieu, mais faites-le à la fin


Oh, vous avez utilisé un exemple qui était trop semblable à l'exemple donné et qui m'a confondu; J'ai considéré que c'était exactement la soustraction d'intervalle: (1100, 1200) - (1150, 1300) = (1100, 1149) .



0
votes

Ceci est plutôt laid, mais cela fonctionne pour l'exemple donné xxx

impressions: xxx


0 commentaires

5
votes

Une solution (en plus de toutes les autres solutions différentes présentées ici) consiste à utiliser un arbre d'intervalle / segment (ils sont vraiment la même chose):

http://fr.wikipedia.org/wiki/segment_tree p>

http://fr.wikipedia.org/wiki/interval_tree P>

Un grand avantage de le faire de cette façon est que c'est qu'il est Trivial pour faire des opérations booléennes arbitraires (pas seulement la soustraction) en utilisant le même morceau de code. Il existe un traitement standard de cette structure de données à De Berg. Pour effectuer une opération booléenne sur une paire d'arbres d'intervalle (y compris la soustraction), vous venez de les fusionner ensemble. Voici un code python (certes naïfs) pour le faire avec des arbres de plage déséquilibrés. Le fait qu'ils soient déséquilibrés n'a aucun effet sur le temps nécessaire pour fusionner les arbres, mais la construction arborescente ici est la partie vraiment idiote qui finit par être quadratique (à moins que la réduction ne soit exécutée par partitionnement, que je doute en quelque sorte). Quoi qu'il en soit, vous allez: P>

#Intersection
merge(r1, r2, lambda a, b : a and b)

#Union
merge(r1, r2, lambda a, b : a or b)

#Xor
merge(r1, r2, lambda a, b : a != b)


1 commentaires

@senderlele: merci! Techniquement, la structure de données est un arbre BSP 1D. Cet algorithme de fusion est en réalité basé sur celui que j'ai publié lorsque j'étais sous-traitant. Voici un lien PDF: Sal-CNC. me.wisc.edu/...



12
votes

Le L'intervalle package peut fournir tout ce dont vous avez besoin.

from interval import Interval, IntervalSet
r1 = IntervalSet([Interval(1, 1000), Interval(1100, 1200)])
r2 = IntervalSet([Interval(30, 50), Interval(60, 200), Interval(1150, 1300)])
print(r1 - r2)

>>> [1..30),(50..60),(200..1000],[1100..1150)


7 commentaires

Oui, même si je ne sais pas à quel point ce code semble être bien soutenu. Il dit que c'était la dernière mise à jour il y a 6 ans ...


Cela n'a peut-être pas été mis à jour en partie car il n'a pas besoin d'être: il semble être bien écrit avec de nombreux tests. Et, comme il est open source, vous pouvez le maintenir vous-même.


Merci! C'est génial et m'aurait sauvé environ deux jours de travailler à travers toutes les exceptions dans le script que j'ai écrit. Fyi à tous les autres, pour obtenir le nombre le plus bas ou le plus élevé dans un intervalle, il suffit de faire intervalle.Low_bound ou intervalle.upper_bound.


Qu'en est-il de l'exigence de non-série? Est-ce que Intervalet création O (n)?


C'est une belle solution mais malheureusement, ne fonctionne pas pour Python3 ...


Lors de l'essai de travailler avec cela dans Python 2, il donne l'erreur suivante: TypeError: '<< non pris en charge entre les instances d'intervalle "et" intervalle ". Une solution pour cela?


idem sur python3



1
votes

Voici une fonction rapide Python qui fait la soustraction, que les listes initiales soient bien formées (c.-à-d. Tournez les listes dans la liste la plus petite des plages équivalentes, triées, avant de faire la soustraction):

def condense(l):
    l = sorted(l)
    temp = [l.pop(0)]
    for t in l:
        if t[0] <= temp[-1][1]:
            t2 = temp.pop()
            temp.append((t2[0], max(t[1], t2[1])))
        else:
            temp.append(t)
    return temp

def setSubtract(l1, l2):
    l1 = condense(l1)
    l2 = condense(l2)
    i = 0
    for t in l2:
        while t[0] > l1[i][1]:
            i += 1
            if i >= len(l1):
                break
        if t[1] < l1[i][1] and t[0] > l1[i][0]:
            #t cuts l1[i] in 2 pieces
            l1 = l1[:i] + [(l1[i][0], t[0] - 1), (t[1] + 1, l1[i][1])] + l1[i + 1:]
        elif t[1] >= l1[i][1] and t[0] <= l1[i][0]:
            #t eliminates l1[i]
            l1.pop(i)
        elif t[1] >= l1[i][1]:
            #t cuts off the top end of l1[i]
            l1[i] = (l1[i][0], t[0] - 1)
        elif t[0] <= l1[i][0]:
            #t cuts off the bottom end of l1[i]
            l1[i] = (t[1] + 1, l1[i][1])
        else:
            print "This shouldn't happen..."
            exit()
    return l1

r1 = (1, 1000), (1100, 1200)
r2 = (30, 50), (60, 200), (1150, 1300)
setSubtract(r1, r2) #yields [(1, 29), (51, 59), (201, 1000), (1100, 1149)]


0 commentaires

5
votes

C'était un problème intéressant!

Je pense que cela a raison, et c'est assez compact. Il devrait fonctionner avec des gammes qui se chevauchent de toutes sortes, mais cela assume des gammes bien formées (c'est-à-dire [x, y) code> où x code>). Il utilise [x, y) code> gammes de style pour simplicité. Il est basé sur l'observation qu'il n'y a vraiment que six arrangements possibles (avec des résultats dans ()): p>

edit strong>: j'ai trouvé une représentation plus compacte: p>

>>> r1_list = [(1, 1001), (1100, 1201)]
>>> r2_list = [(30, 51), (60, 201), (1150, 1301)]
>>> print multirange_diff(r1_list, r2_list)
[(1, 30), (51, 60), (201, 1001), (1100, 1150)]


5 commentaires

Les limites de ces gammes ne sont pas correctes. Ils sont hors-tête en raison du fait que les intrants sont fermés gammes (je sais parce que j'ai fait cette erreur moi-même aussi :).


@Mikola: Eh bien, le texte de la réponse indique que les intervalles utilisés sont fermés à gauche, à droite. Il est assez simple de convertir l'entrée à l'avance à l'avance de ce que cette réponse nécessite, puis convertissez la sortie par la suite à ce que les besoins de l'OP.


Oui, @john, exactement. @Mikola, j'ai conclu qu'il était plus idiomatique (en python) d'utiliser une plage à moitié fermée, -style; Pour obtenir l'entrée et la sortie de l'OP, convertissez les gammes ci-dessus en soustraits en soustrayant un du dernier numéro dans chaque cas - une opération triviale "laissée au lecteur" :).


Je serais d'accord. En fait, c'est ainsi que j'ai initialement écrit ma solution. Honnêtement, en utilisant des gammes fermées est probablement une erreur désastreuse (d'une perspective de conception / de débogage), mais c'est ce que la puissance posée pour que je sentais que je sentais que je devrais obliger.


La représentation est absolument génie!



1
votes

Question amusante! Une autre mise en œuvre, bien que vous ayez déjà beaucoup. C'était intéressant de faire! Implique une «décoration» supplémentaire pour faire ce que je vais plus explicite.

r1 = (1, 1000), (1100, 1200)
r2 = (30, 50), (60, 200), (1150, 1300)
>>> subtract_ranges(r1,r2)
[(1, 29), (51, 59), (201, 1000), (1100, 1149)]


0 commentaires