Pergunta

Eu gostaria de algo assim:

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

Ou, de forma mais geral, para desestruturar coisas arbitrárias na lista que você está fazendo o loop, como:

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

Geralmente você quer usar Map ou outras construções puramente funcionais e evite um estilo de programação não funcional onde você usa efeitos colaterais.Mas aqui está um exemplo em que acho que uma construção for-each é extremamente útil:

Digamos que eu tenha uma lista de opções (regras) que combinam símbolos com expressões, como

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

Agora quero fazer uma tabela hash onde faço o mapeamento óbvio desses símbolos para esses números.Eu não acho que haja uma maneira mais limpa de fazer isso do que

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

Casos de teste adicionais

Neste exemplo, transformamos uma lista de variáveis:

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

Após o acima, {a,b,c} deveria avaliar para {f[1],f[2],f[3]}.Observe que isso significa o segundo argumento para each deve ser mantido sem avaliação se for uma lista.

Se a forma não avaliada não for uma lista, ela deverá avaliar o segundo argumento.Por exemplo:

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

Isso deve imprimir os valores de b e c.

Termo aditivo:Para fazer for-each corretamente, ele deve suportar Break[] e Continue[].Não tenho certeza de como implementar isso.Talvez precise ser implementado de alguma forma em termos de For, While ou Do, já que essas são as únicas construções de loop que suportam Break[] e Continue[].

E outro problema com as respostas até agora:eles comem Return[]S.Ou seja, se você estiver usando um loop ForEach em uma função e quiser retornar da função de dentro do loop, não poderá.Emitir Return dentro do loop ForEach parece funcionar como Continue[].Isso apenas (espere) me deixou confuso.

Foi útil?

Solução 7

Graças a Pillsy e Leonid Shifrin, aqui está o que estou usando agora:

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) *)

Outras dicas

Versões mais recentes do Mathematica (6.0+) têm versões generalizadas de Do[] e Table[] que fazem quase exatamente o que você deseja, adotando uma forma alternativa de argumento do iterador.Por exemplo,

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

é exatamente igual ao seu

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

Alternativamente, se você realmente gosta da sintaxe específica do ForEach, você pode criar uma função HoldAll que a implemente, assim:

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]]];

Isso usa símbolos como nomes de variáveis, não padrões, mas é assim que funcionam as várias estruturas de controle integradas, como Do[] e For[].

As funções HoldAll[] permitem reunir uma grande variedade de estruturas de controle personalizadas.ReleaseHold[Hold[...]] geralmente é a maneira mais fácil de montar um monte de código do Mathematica para ser avaliado posteriormente, e Block[{x = #}, ...]& permite que variáveis ​​​​no corpo da sua expressão sejam vinculadas a quaisquer valores que você quiser.

Em resposta à pergunta de Dreeves abaixo, você pode modificar esta abordagem para permitir uma desestruturação mais arbitrária usando os DownValues ​​de um símbolo único.

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

Neste ponto, porém, acho que seria melhor construir algo em cima dos Casos.

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

Gosto de tornar Null explícito quando estou suprimindo o valor de retorno de uma função. EDITAR:Corrigi o bug apontado abaixo;Eu sempre gosto de usar With para interpolar expressões avaliadas em Hold* formulários.

Estou atrasado para a festa aqui, e isso talvez seja mais uma resposta para a "meta-pergunta", mas algo que muitas pessoas inicialmente têm dificuldade em quando a programação em Mathematica (ou outras linguagens funcionais) está abordando um problema de um ponto de vista funcional e não estrutural. A linguagem Mathematica possui construções estruturais, mas é funcional em sua essência.

Considere seu primeiro exemplo:

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

Como várias pessoas apontaram, isso pode ser expresso funcionalmente como Scan[Print, {1,2,3}] ou Print /@ {1,2,3} (Embora você deva favorecer Scan sobre Map Quando possível, como explicado anteriormente, mas isso pode ser irritante às vezes, pois não há operador de infix para Scan).

Em Mathematica, geralmente há uma dúzia de maneiras de fazer tudo, que às vezes é bonito e às vezes frustrante. Com isso em mente, considere seu segundo exemplo:

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

... o que é mais interessante do ponto de vista funcional.

Uma possível solução funcional é usar a substituição da lista, por exemplo:

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

... Mas se a lista fosse muito grande, isso seria desnecessariamente lento, pois estamos fazendo o chamado "correspondência de padrões" (por exemplo, procurando por instâncias de {a, b} na lista e atribuindo-os a para i e j) desnecessariamente.

Dada uma grande variedade de 100.000 pares, array = RandomInteger[{1, 100}, {10^6, 2}], podemos olhar para alguns horários:

A substituição de regras é bem rápida:

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

... mas podemos fazer um pouco melhor se aproveitarmos a estrutura de expressão onde cada par é realmente List[i,j] e aplique Times Como cabeça de cada par, girando cada {i,j} em 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

Conforme usado na implementação de ForEach[...] acima de, Cases é decididamente subótimo:

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

... desde Cases faz mais trabalho do que apenas a substituição de regras, tendo que criar uma saída de elementos correspondentes um por um. Acontece que podemos fazer um muito melhor decompor o problema de maneira diferente e aproveite o fato de que Times é Listable, e suporta operação vetorizada.

o Listable atributo significa que uma função f Avandará automaticamente os argumentos da 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]}

Então, desde então Times é Listable, se tivéssemos os pares de números como duas matrizes 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

Uau, um pouco mais rápido! Mesmo que a entrada não tenha sido fornecida como duas matrizes separadas (ou você tem mais de dois elementos em cada par), ainda podemos fazer algo ideal:

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

A moral deste épico não é assim ForEach Não é uma construção valiosa em geral, ou mesmo em Mathematica, mas que você pode obter os mesmos resultados de maneira mais eficiente e elegante quando trabalha em uma mentalidade funcional, e não estrutural.

O embutido Scan Basicamente, isso é mais feio:

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

É especialmente feio quando você quer destruir os elementos:

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

A seguinte função evita a feiura convertendo pattern para body Para cada elemento de list.

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

que pode ser usado como no exemplo da pergunta.

PS: A resposta aceita me induziu a mudar para isso, que é o que eu uso desde então e parece funcionar muito bem (exceto a ressalva que eu anexei à pergunta):

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.        *)

A função de mapa integrada faz exatamente o que você deseja. Pode ser usado em forma longa:

Mapa [Imprimir, {1,2,3}

ou de mão curta

Imprimir /@ {1,2,3}

No seu segundo caso, você usaria "Print [Times @@#] &/@{{1,10}, {2,20}, {3,30}}"

Eu recomendo a leitura da ajuda Mathematica no mapa, no MapThread, na aplicação e na função. Eles podem se acostumar, mas quando você estiver, você nunca vai querer voltar!

Aqui está uma ligeira melhoria com base na última resposta de Dreeves que permite especificar o padrão sem espaço em branco (tornando a sintaxe semelhante a outras funções como a tabela ou o faz) e que usa o argumento de nível 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
   ];

Testes:

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

Mathematica tem funções de mapa, então digamos que você tenha uma função Functomando um argumento. Então apenas escreva

Func /@ list

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

O valor de retorno é uma lista da função aplicada a cada elemento na lista.

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

retornará {False,True,False,False}

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top