m, r = 10, 0.3 fil = [[int(random()<r) for k in range(m)] for j in range(m)] matin = matrix(fil) TAREA D El código anterior genera la matriz de incidencia de un grafo dirigido con m vértices: los '1's de su fila j dicen a qué otros vértices k se puede pasar desde el j en "un paso"; hay un '1' en el lugar j,j, si se puede "parar" allí, pero eso es irrelevante para lo que sigue: Se trata de producir una función que: * reciba como argumentos esa matriz 'matin' y el índice j (< m) de un vértice; * devuelva una lista de tuplas (k,p) , donde los k sean todos los vértices a los que se puede llegar desde el j en uno o más "pasos", y el 'p' que acompaña a k, el MINIMO número de pasos con el que se puede llegar desde j hasta k . Observaciones: * no olvidemos que, si es posible llegar de j a k, lo será con < m pasos; * quien haya entendido qué información dan las potencias de la matriz, podrá hacer un código muy simple; pero aún sin eso, se puede (y se debe) hacer muy simple; * y naturalmente, probarlo con ejemplos de 'matin' y asegurarse de que da lo correcto. def dista(matin,j): m = matin.nrows() matk, vistos, kps = identity_matrix(m), [j], [] for p in range(1,m): matk *= matin # las sucesivas potencias matin^p for k in range(m): if matk[j,k] and k not in vistos: kps.append((k,p)) vistos.append(k) return kps dista(matin,3); matin [(0, [0 0 [1 0 [0 1 [1 0 [1 0 [1 0 [0 1 [0 0 [0 0 [0 0 1), 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 (7, 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 1), 0 0 1 1 0 0 0 1 0 0 0 1 0 1 1 1 0 1 0 0 (8, 1), (2, 2), (4, 2), (6, 2), (9, 2), (1, 3)] 0 0] 1 0] 0 1] 1 0] 0 0] 1 0] 0 1] 1 0] 0 1] 1 0] def distas(matin,j): # versión que NO usa potencias de 'matin' m = matin.nrows() sumafilas, vistos, kps = matin[j], [j], [] for p in range(1,m): ks = [k for k in range(m) if sumafilas[k] and k not in vistos] for k in ks: kps.append((k,p)) vistos.append(k) sumafilas += matin[k] # suma de filas de los 'ya vistos' return kps distas(matin,3) [(0, 1), (7, 1), (8, 1), (2, 2), (4, 2), (6, 2), (9, 2), (1, 3)] Una versión más, sugerida por una idea que se apuntaba en una de las respuestas que he leído. La aprovecho para dar un ejemplo de cómo diseñar y explicar el plan, antes de entrar en detalles de código. PLAN DEL PROCESO: además de los argumentos (matriz de incidencia M, vértice inicial j), una variable 'p' para el "número de pasos", y tres listas: ' ya, a1p, kps', para los "ya anotados", los que están "a 1 paso", y las tuplas '(k,p)' a devolver. Para cada valor de 'p', el proceso será: para cada 'j' en 'ya' y cada k con M[j,k]>0, se añade k a los 'a1p' , se añade (k,p) a los 'kps', <---(la IDEA mencionada) y se hace = 0 la columna k, para que el vértice k "no se vuelva a encontrar". Y si hay algo en 'a1p' : ya, a1p = a1p, [] , y se aumenta p. PLAN DEL CODIGO: Se puede usar por lo tanto un while a1p: ya, a1p, p = a1p, [], p+1 inicializando antes: p, a1p, kps = 0, [j], [] Detalle motivado por el código Sage: es más fácil "anular" filas que columnas, trabajando para ello con la matriz traspuesta de M. def distra(matin,j): # versión que usa la traspuesta de 'matin' m, tra = matin.nrows(), matin.transpose() p, a1p, kps, z = 0, [j], [], [0]*m tra[j] = z while a1p: ya, a1p, p = a1p, [], p+1 for j in ya: ks = [k for k in range(m) if tra[k,j]] a1p += ks for k in ks: kps.append((k,p)) tra[k] = z return kps # como se ve, no son menos lineas, sino operaciones "más baratas" distra(matin,3) [(0, 1), (7, 1), (8, 1), (4, 2), (6, 2), (2, 2), (9, 2), (1, 3)] m = 5 # m puntos del plano puntos = [[random(),random()] for _ in range(m)] TAREA C La pregunta de si dos segmentos dados en el plano: [A,B],[C,D] se cortan, tiene una respuesta muy simple: se cortan si la ecuación que da el punto intersección de ambas rectas A + x*u = C + y*v , con u = AB, v = CD, tiene solución con x,y en [0,1]. La tarea propuesta es producir una función que: * reciba como argumentos una lista de m puntos distintos del plano (pares de números), como por ejemplo las que genera el código del cuadro precedente; * devuelva el número de intersecciones entre segmentos con extremos en dos de esos puntos; * conviene añadir la opción de que produzca un gráfico con todos esos segmentos; será útil para comprobar (con m pequeño!!) que el resultado es correcto. Observaciones: * claro que se cortarán si tienen un extremo común, pero no queremos contar esos casos; * hay un generador de Sage: Combinations(lista,j) , que puede ser muy útil. def corte(A,B,C,D): a,b,c,d = vector(A), vector(B), vector(C), vector(D) xy = (c-a)/matrix([b-a, c-d]) # la solución (x,y) del SEL return all(0<=c<=1 for c in xy) # True si ambos están en [0,1] def cortes(puntos, ver=False): pares = [c for c in Combinations(range(len(puntos)),2)] if ver: # produce lista y gráfico for p in puntos: print (round(p[0],2),round(p[1],2)), segmentos = point(puntos) for c in pares: segmentos += line([puntos[k] for k in c]) show(segmentos, axes=False, figsize=[3,2]) print 'no. de cortes:', ncortes = 0 for [p,q] in Combinations(pares,2): # para cada dos pares de índices, if not(set(p) & set(q)): # ... que sean disjuntos [a,b,c,d] = [puntos[k] for k in p+q] ncortes += corte(a,b,c,d) # si se cortan, sube el contador return ncortes A,B,C,D = ([random(),random()] for _ in range(4)) print corte(A,B,C,D) # comprobando 'corte' line([A,B])+line([C,D],color='red', aspect_ratio=1/4, axes=False) True m = 5 # ... y ahora 'cortes' puntos = [[random(),random()] for _ in range(m)] cortes(puntos, ver=True) (0.82, 0.72) (0.77, 0.47) (0.22, 0.7) (0.06, 0.09) (0.11, 0.84) no. de cortes: 3 def corto(puntos): # pero ahora, la versión BREVE, que NO usa 'corte'!! ncortes = 0 for [a,b,c,d] in Combinations(puntos,4): barysa = vector(a+[1]) / matrix([b+[1],c+[1],d+[1]]) ncortes += (prod(barysa) <= 0) return ncortes corto(puntos) 3 La idea de esta BREVE versión: para cada grupo de 4 puntos hay 3 posibles "pares de segmentos", pero sólo dos posibilidades: * si son los vértices de un convexo, habrá justo UN corte: el de los segmentos "diagonales", * y si no, uno de ellos, D, queda dentro del triángulo ABC, y el segmento que lo une a cada vértice, NO corta al "lado opuesto"; y la traducción algebraica de este caso es: las coordenadas baricéntricas de D respecto de A, B, C, son todas > 0; pero eso equivale a que las coordenadas baricéntricas de cada uno respecto de los otros tres, o bien son todas > 0, o dos de ellas son < 0; que equivale a que su PRODUCTO sea > 0 . Y esa es la idea que usa el código para aumentar el contador. Y un COMENTARIO IMPORTANTE: tanto en 'cortes' como en 'corto', el código está preparado para recibir puntos en "posición general", que es como los produce, casi-seguramente, el 'random()'; pero si tres de los puntos estuviesen alineados, o los segmentos AB, CD fuesen paralelos, ambas funciones necesitarían un par de lineas más, que verifiquen si son regulares las matrices por las que dividimos, y en caso contrario anote un corte más (si están alineados tres de los puntos), o infinitos (y return) si estuviesen alineados cuatro de ellos (=> hay todo un segmento intersección). Lo que sigue es esa "versión segura" para 'corto'. def cortos(puntos): # ahora admite 'puntos' (distintos) en cualquier posición ncortes = 0 for [a,b,c,d] in Combinations(puntos,4): ref = matrix([b+[1],c+[1],d+[1]]) if rank(ref) < 3: # si están b,c,d alineados if rank(matrix([a+[1],c+[1],d+[1]])) < 3: return 'infinitos' # están los 4 alineados else: ncortes += 1 # el central de los 3 alineados else: barysa = vector(a+[1]) / ref ncortes += (prod(barysa) <= 0) return ncortes cortos(puntos) 3