Pregunta

Me gustaría algo como esto:

each[i_, {1,2,3},
  Print[i]
]

O, más generalmente, para destruir cosas arbitrarias en la lista sobre la que estás repasando, como:

each[{i_, j_}, {{1,10}, {2,20}, {3,30}},
  Print[i*j]
]

Por lo general, desea utilizar Mapa u otras construcciones puramente funcionales y evitar un estilo de programación no funcional en el que utiliza efectos secundarios. Pero aquí hay un ejemplo donde creo que una construcción para cada uno es sumamente útil:

Diga que tengo una lista de opciones (reglas) que combinan símbolos con expresiones, como

attrVals = {a -> 7, b -> 8, c -> 9}

Ahora quiero hacer una tabla hash en la que haga la asignación obvia de esos símbolos a esos números. No creo que haya una forma más limpia de hacerlo que

each[a_ -> v_, attrVals, h[a] = v]

Casos de prueba adicionales

En este ejemplo, transformamos una lista de variables:

a = 1;
b = 2;
c = 3;
each[i_, {a,b,c}, i = f[i]]

Después de lo anterior, {a, b, c} debe evaluar a {f [1], f [2], f [3]} . Tenga en cuenta que eso significa que el segundo argumento de cada debe mantenerse sin evaluar si se trata de una lista.

Si la forma no evaluada no es una lista, debe evaluar el segundo argumento. Por ejemplo:

each[i_, Rest[{a,b,c}], Print[i]]

Eso debería imprimir los valores de b y c .

Addendum : para hacer cada uno correctamente, debe admitir Break [] y Continuar [] . No estoy seguro de cómo implementar eso. Tal vez deba implementarse de alguna manera en términos de For, While o Do, ya que esas son las únicas construcciones de bucle que admiten Break [] y Continue [] .

Y otro problema con las respuestas hasta ahora: comen Return [] s. Es decir, si está utilizando un bucle ForEach en una función y desea regresar de la función desde dentro del bucle, no puede hacerlo. La emisión de retorno dentro del bucle ForEach parece funcionar como Continuar [] . Esto solo (esperalo) me lanzó un bucle.

¿Fue útil?

Solución 7

Gracias a Pillsy y Leonid Shifrin , esto es lo que estoy usando ahora:

SetAttributes[each, HoldAll];               (* each[pattern, list, body]      *)
each[pat_, lst_List, bod_] :=               (*  converts pattern to body for  *)
  (Cases[Unevaluated@lst, pat:>bod]; Null); (*   each element of list.        *)
each[p_, l_, b_] := (Cases[l, p:>b]; Null); (* (Break/Continue not supported) *)

Otros consejos

Las versiones más recientes de Mathematica (6.0+) tienen versiones generalizadas de Do [] y Table [] que hacen casi exactamente lo que quieres, al tomar una forma alternativa de argumento de iterador. Por ejemplo,

Do[
  Print[i],
  {i, {1, 2, 3}}]

es exactamente igual a tu

ForEach[i_, {1, 2, 3,},
  Print[i]]

Alternativamente, si realmente te gusta la sintaxis específica de ForEach, puedes hacer una función HoldAll que la implemente, de este modo:

Attributes[ForEach] = {HoldAll};

ForEach[var_Symbol, list_, expr_] :=
  ReleaseHold[
    Hold[
      Scan[
        Block[{var = #},
         expr] &,
      list]]];

ForEach[vars : {__Symbol}, list_, expr_] :=
  ReleaseHold[
    Hold[
      Scan[
        Block[vars,
          vars = #;
          expr] &,
      list]]];

Esto utiliza símbolos como nombres de variables, no patrones, pero así es como funcionan las diversas estructuras de control integradas como Do [] y For [].

Las funciones

HoldAll [] le permiten armar una gran variedad de estructuras de control personalizadas. ReleaseHold [Sostener [...]] suele ser la forma más fácil de reunir un montón de código de Mathematica para ser evaluado más adelante, y Bloquear [{x = #}, ...] & amp; permite que las variables en el cuerpo de tu expresión se unan a los valores que quieras.

En respuesta a la pregunta de dreeves a continuación, puede modificar este enfoque para permitir una desestructuración más arbitraria utilizando los valores bajos de un símbolo único.

ForEach[patt_, list_, expr_] := 
  ReleaseHold[Hold[
     Module[{f}, 
       f[patt] := expr; 
       Scan[f, list]]]]

En este punto, sin embargo, creo que es mejor que construyas algo sobre Casos.

ForEach[patt_, list_, expr_] :=
  With[{bound = list},
    ReleaseHold[Hold[
       Cases[bound,
         patt :> expr]; 
       Null]]]

Me gusta hacer explícito Null cuando estoy suprimiendo el valor de retorno de una función. EDIT : solucioné el error señalado como se muestra a continuación; Siempre me gusta usar With para interpolar expresiones evaluadas en Hold * .

Llego tarde a la fiesta aquí, y esto quizás sea más una respuesta a la "meta-pregunta", pero algo con lo que muchas personas inicialmente lo pasan mal cuando la programación en Mathematica (u otros lenguajes funcionales) es Abordar un problema desde un punto de vista funcional más que estructural. El lenguaje Mathematica tiene construcciones estructurales, pero es funcional en su núcleo.

Considera tu primer ejemplo:

ForEach[i_, {1,2,3},
  Print[i]
]

Como señalaron varias personas, esto se puede expresar funcionalmente como Scan [Print, {1,2,3}] o Print / @ {1,2,3} (aunque debería favorecer Scan sobre Map cuando sea posible, como se explicó anteriormente, pero eso puede ser molesto a veces ya que no hay un operador de infijo para Scan ).

En Mathematica, usualmente hay una docena de maneras de hacer todo, lo que a veces es hermoso y otras veces frustrante. Con eso en mente, considera tu segundo ejemplo:

ForEach[{i_, j_}, {{1,10}, {2,20}, {3,30}},
  Print[i*j]
]

... lo que es más interesante desde un punto de vista funcional.

Una posible solución funcional es utilizar el reemplazo de lista, por ejemplo:

In[1]:= {{1,10},{2,20},{3,30}}/.{i_,j_}:>i*j
Out[1]= {10,40,90}

... pero si la lista es muy grande, esto sería innecesariamente lento ya que estamos haciendo lo que se conoce como " coincidencia de patrones " (por ejemplo, buscando instancias de {a, b} en la lista y asignándolas a i y j ) innecesariamente.

Dada una gran cantidad de 100,000 pares, array = RandomInteger [{1, 100}, {10 ^ 6, 2}] , podemos ver algunos horarios:

El reemplazo de reglas es bastante rápido:

In[3]:= First[Timing[array /. {i_, j_} :> i*j;]]
Out[3]= 1.13844

... pero podemos hacerlo un poco mejor si aprovechamos la estructura de expresión donde cada par es realmente List [i, j] y aplicamos Times como la cabeza de cada par, convirtiendo cada {i, j} en Times [i, j] :

In[4]:= (* f@@@list is the infix operator form of Apply[f, list, 1] *)
    First[Timing[Times @@@ array;]]
Out[4]= 0.861267

Tal como se utiliza en la implementación de ForEach [...] anterior, Casos es decididamente subóptimo:

In[5]:= First[Timing[Cases[array, {i_, j_} :> i*j];]]
Out[5]= 2.40212

... since Cases hace más trabajo que solo el reemplazo de la regla, teniendo que construir una salida de elementos coincidentes uno por uno. Resulta que podemos hacer un lote mejor descomponiendo el problema de manera diferente, y aprovechar el hecho de que Times es Listable y es compatible con vectores operación.

El atributo Listable significa que una función f se enlaza automáticamente sobre cualquier argumento de lista:

In[16]:= SetAttributes[f,Listable]
In[17]:= f[{1,2,3},{4,5,6}]
Out[17]= {f[1,4],f[2,5],f[3,6]}

Entonces, dado que Times es Listable , si en cambio tuviéramos los pares de números como dos matrices separadas:

In[6]:= a1 = RandomInteger[{1, 100}, 10^6];
        a2 = RandomInteger[{1, 100}, 10^6];

In[7]:= First[Timing[a1*a2;]]
Out[7]= 0.012661

Wow , ¡un poco más rápido! Incluso si la entrada no se proporcionó como dos arreglos separados (o si tiene más de dos elementos en cada par), todavía podemos hacer algo óptimo:

In[8]:= First[Timing[Times@@Transpose[array];]]
Out[8]= 0.020391

La moraleja de esta epopeya no es que ForEach no sea un constructo valioso en general, ni siquiera en Mathematica, sino que a menudo puede obtener los mismos resultados de manera más eficiente y elegante cuando trabaja. En una mentalidad funcional, más que estructural.

El Scan incorporado básicamente hace esto, aunque es más feo:

    Scan[Print[#]&, {1,2,3}]

Es especialmente feo cuando quieres destruir los elementos:

    Scan[Print[#[[1]] * #[[2]]]&, {{1,10}, {2,20}, {3,30}}]

La siguiente función evita la fealdad al convertir pattern a body para cada elemento de list .

SetAttributes[ForEach, HoldAll];
ForEach[pat_, lst_, bod_] :=  Scan[Replace[#, pat:>bod]&, Evaluate@lst]

que se puede usar como en el ejemplo de la pregunta.

PD: la respuesta aceptada me indujo a cambiar a esto, que es lo que he estado usando desde entonces y parece funcionar muy bien (excepto por la advertencia que agregué a la pregunta):

SetAttributes[ForEach, HoldAll];             (* ForEach[pattern, list, body]   *)
ForEach[pat_, lst_, bod_] := ReleaseHold[    (*  converts pattern to body for  *)
  Hold[Cases[Evaluate@lst, pat:>bod];]];     (*   each element of list.        *)

La función de mapa incorporada hace exactamente lo que quieres. Puede ser utilizado en forma larga:

Mapa [Imprimir, {1,2,3}]

o mano corta

Imprimir / @ {1,2,3}

En su segundo caso, usaría " Imprimir [Times @@ #] & amp; / @ {{1,10}, {2,20}, {3,30}} "

Recomiendo leer la ayuda de Mathematica en Map, MapThread, Apply y Function. Pueden tardar un poco en acostumbrarse, pero una vez que lo estés, ¡nunca querrás volver!

Aquí hay una pequeña mejora basada en la última respuesta de dreeves que permite especificar el patrón sin Blank (haciendo que la sintaxis sea similar a otras funciones como Table o Do) y que use el argumento de nivel de Casos

SetAttributes[ForEach,HoldAll];
ForEach[patt_/; FreeQ[patt, Pattern],list_,expr_,level_:1] :=
   Module[{pattWithBlanks,pattern},
      pattWithBlanks = patt/.(x_Symbol/;!MemberQ[{"System`"},Context[x]] :> pattern[x,Blank[]]);
      pattWithBlanks = pattWithBlanks/.pattern->Pattern;

      Cases[Unevaluated@list, pattWithBlanks :> expr, {level}];
      Null
   ];

Pruebas:

ForEach[{i, j}, {{1, 10}, {2, 20}, {3, 30}}, Print[i*j]]
ForEach[i, {{1, 10}, {2, 20}, {3, 30}}, Print[i], 2]

Mathematica tiene funciones de mapa, así que digamos que tienes una función Func tomando un argumento. Entonces simplemente escribe

Func /@ list

Print /@ {1, 2, 3, 4, 5}

El valor de retorno es una lista de la función aplicada a cada elemento en la lista.

PrimeQ /@ {10, 2, 123, 555}

devolverá {False,True,False,False}

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top