m, r = 10, 0.3 fil = [[int(random()r) for k in range(m)] for j in range(m

Anuncio
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
Descargar