Calcular rapidamente o total de uma coluna de números

15

Estou escrevendo uma tabela de descontos que se parece com isso:

| 13/05/15 | 09:30-16:00 |  6.5 |
| 14/05/15 | 10:00-16:30 |  6.5 |
| 16/05/15 | 15:30-01:00 |  9.5 |
| 21/05/15 | 09:00-16:30 |  7.5 |
| 22/05/15 | 08:30-17:00 |  8.5 |
| 28/05/15 | 09:30-15:30 |  6   |
| 02/06/15 | 09:00-20:00 | 11   |
| 03/06/15 | 08:30-22:30 | 14   |

Estou procurando uma maneira de calcular rapidamente o total da terceira coluna e inseri-lo no buffer. A solução que eu tenho em mente seria usar o modo de bloco visual (para selecionar todos os números) e talvez o registro de expressões (para fazer as contas).

Isso seria possível usando comandos nativos do Vim? Caso contrário, existe um plugin que pode me ajudar?

zool
fonte
11
Você pode dar uma olhada neste artigo: vim.wikia.com/wiki/Using_vim_as_calculator
nobe4

Respostas:

15

Eu escrevi um plugin: https://github.com/sk1418/HowMuch, que suporta seleção visual e faz cálculos matemáticos.

Por padrão, o plugin suporta três mecanismos de avaliação de expressões matemáticas: Gnu bc, python e vimscript. Você pode fazer os cálculos em um determinado ou permitir que o plugin escolha automaticamente um para você.

Funciona com o seu exemplo assim:

insira a descrição da imagem aqui

Para detalhes, leia o README no github.

Kent
fonte
Seria útil se você incluísse as teclas necessárias para selecionar, somar e inserir sua resposta.
precisa saber é o seguinte
@ pdoherty926 For details please read the README on github.Mesmo se eu pressionar as teclas pressionadas para esse problema aqui, não vejo como isso pode ser útil, são apenas 3 ou 4 combinações de teclas. Se meu script for realmente necessário para alguém, ele / ela verificará os detalhes de qualquer maneira.
Kent
11

Se você não deseja usar plug-ins ou soltar em um script bash, pode fazer algo como o seguinte:

  • c-V {motions} "ay copiar coluna para "a
  • :let @a = substitute(@a, 'c-V c-J', '+', 'g') substitua as novas linhas da coluna por +
  • ic-R=c-Raexecute o substituído "aatravés do registro de expressão

Como alternativa: torne a entrada do histórico de expressões reutilizável para somas adicionais de colunas

  • ctrl-V {motions} y colocar coluna no registro de arranco ""
  • ictrl-R=eval(substitute(@", '\n', '+', 'g'))

Repetindo para outra coluna:

  • ctrl-V {motion} y (inalterado)
  • ictrl-R=<CR>ou se você fez outra coisa com o registro de expressão, percorra o histórico com a tecla de seta para cima (ou ctrl-Pse você o remapeava):
    ictrl-R=<up>...<up><CR>
Hovercouch
fonte
11
Por alguma razão, só consegui usar sua solução com aspas duplas, em "vez de aspas simples 'no substitutecomando. Você sabe se existe alguma razão para isso?
vappolinario 10/09/2015
@vappolinario funciona nos dois sentidos para mim, então eu tenho medo de não saber, desculpe.
Hovercouch 10/09/2015
@Hovercouch Você poderia elaborar o terceiro passo? Como, exatamente, alguém executaria a substituição através do registro de expressão?
precisa saber é o seguinte
Que tal fazer um mapa: `nnoremap <cs>: s / $ / \ = eval (substitute (@ 0, '[^ 0-9]', '+', 'g')) / <cr>`
SergioAraujo
9
:r!awk '{sum+=$6} END {print "Total: "sum}' %

Explicação:

:r ........... read (put result in this file)
! ............ external command
awk .......... external tool
{sum+=$6} .... sixth field (awk considers spaces as field separator)
END .......... at the end
{print "Total: "sum} --> string "Total: " plus your result
% ............ current file

Eu tenho tentado uma função que funciona aqui:

" This function requires you select the numbers
fun! SumVis()
    try
        let l:a_save = @a
        norm! gv"ay
        let @a = substitute(@a,'[^0-9. ]','+','g')
        exec "norm! '>o"
        exec "norm! iTotal \<c-r>=\<c-r>a\<cr>"
     finally
        let @a = l:a_save
     endtry
endfun
vnoremap <leader>s :<C-u>call SumVis()<cr>

Usando o mapa acima incluído, tudo o que você precisa fazer após carregar a função é selecionar os números que deseja somar e usar <leader>spara resumir a área selecionada.

Explicação da função:

Ele usa a estrutura try/finally/endtrypara capturar erros.

let l:a_save = @a .......... if whe have register 'a' we save it temporarelly
norm! gv"a  ................................... gv --> reselects and captures selection to 'register a'
let @a = substitute(@a,'[^0-9. ]','+','g') .... removes all but numbers, dots and spaces from 'register a' and puts '+' among the numbers
exec "norm! '>o"  ............................. opens new line bellow selection. see :h '>
exec "norm! iTotal: \<c-r>=\<c-r>a\<cr>" ...... insert "Total: " plus 'expression register result
let @a = l:a_save ............................. restores original 'a' register content

Se você quiser experimentar esta função, faça o seguinte: Copie esta função no seu navegador e execute este comando no vim. :@+ Isso permitirá que você use :call SumVis()normalmente.

:@+ ......... loads `+` register making the function avaiable

É necessário que você faça uma seleção visual do bloco com ctrl+ v, desmarque e, finalmente, chame a função. Ou você pode usar o mapa sugerido, que por si só remove a seleção antes do cálculo.

SergioAraujo
fonte
7

Meu plugin csv permite isso. Use o :SumColcomando e não deixe de ler a documentação.

Christian Brabandt
fonte
5

Criar um plugin ou codificá-lo no vimscript parece um pouco pesado. Eu acredito em um vim livre de plugins e boa composição com ferramentas externas.

Aqui está um comando único, com base nos user2571881, que funciona mesmo que o buffer não tenha sido salvo.

:%!awk -F '|' '{print; sum+=$4}; END {print "Total: "sum}'

Se você deseja salvar este comando para uso futuro, convém nomeá-lo:

:command! -range=% -nargs=1 SumColumn <line1>,<line2>!awk -F '|' '{print; sum+=$('<args>' + 1)} END {print "Total: "sum}'

Funciona com seleção visual. Se você selecionar algumas linhas e entrar no modo de comando, o vim prefixará seu comando :'<,'>, que é o intervalo de linhas para a seleção visual. Então você pode executar:

:'<,'>SumColumn 3

e somará apenas a terceira coluna das linhas selecionadas. Por padrão, o intervalo é %, então

:SumColumn 3

somará a terceira coluna de todas as linhas.

EDIT: Se você quiser especificar outros separadores de campos e usar como padrão a coluna contada até a última, poderá cobrir o comando bashe manipular os argumentos com ele, assim:

:command! -range=% -nargs=* SumColumn <line1>,<line2>!bash -c 'awk -F ${2:-|} "{print; sum+=\$(${1:-NF - 2} + 1)} END {print \"Total: \"sum}"' sumcolumn <args>

Agora,

:SumColumn

contará a última coluna de uma tabela com "|" separadores de campo,

:SumColumn 3

contará a terceira coluna de uma tabela com "|" separadores de campo e

:SumColumn 3 +

contará a terceira coluna de uma tabela com separadores de campos "+".

JoL
fonte
Como lidar com outros possíveis separadores de campo? Apenas para tornar a solução mais genérica.
SergioAraujo 15/09
@ user2571881, editei a resposta, mostrando isso.
Jol
A adição de funções como o SumColumnvimrc ao @JoL significa que você simplesmente possui seus 'plugins' no seu vimrc. Felizmente, você é bom em manter isso com o tempo. Para mim, os plugins fornecem documentação, separação em partes significativas, tirando proveito de outras habilidades. Eu contribuo para o upstream, que melhora plugins incríveis, que ninguém tem tempo para criar todos eles por conta própria (exceto tpope). Você não usa vim-surround, vim-fugitive, vim-easy-align / vim-lion, vim-impaired, vim-commentary, ultisnips ou ft-specific, como vim-go, vim-rails, vimtex?
Hotschke 26/07/19
@ Hotschke Quando cheguei aqui, vi a pergunta e pensei: "bem, apenas passe pelo awk". Mas então, vi que a resposta aceita era "ei, faça o download deste centenas de plug-ins LOC e instale-o". A terceira resposta foi: "ei, faça o download deste milhares de plugins LOC e instale-o". É um exagero e inchaço. Mesmo que você precise somar colunas mais de uma vez na vida, é um exagero. Minha resposta tem como objetivo mostrar como você pode fazer isso em um único comando no-plugins, no-nonsense, se você precisar fazer isso apenas uma vez, e como você pode fazer um comando simples com parâmetros, se precisar fazer isso frequentemente.
26418 JoL
@Hotschke Para responder sua pergunta, eu costumava instalar todos os plugins sob o sol que pareciam remotamente legais, mas meu vim era incrivelmente lento (leia "um pouco lento", que é intolerável para um editor). Ao analisar mais os documentos do vim, percebi que realmente não precisava dos plugins. Muitos dos recursos de estoque eram bons o suficiente e, para aqueles que o vim não tinha, o shell era o caminho a seguir. Fundamentalmente (ignorando as exceções feitas), de acordo com a filosofia do Unix, o vim é um editor que interage bem com outras ferramentas do SO. Acredito que é a melhor maneira de utilizá-lo. Não há plugins desde então.
31418 JoL
2

Se as colunas estiverem alinhadas corretamente, isso poderá ser feito com um oneliner simples.

  1. primeiro selecione a coluna no modo visual em blocos, pois outras respostas demonstram -> CTRL-V+ mover o cursor
  2. puxe a seleção com y
  3. type: :echo eval(join(split(@", '\_s\+'), '+'))que divide o texto arrancado em espaços e novas linhas, junta novamente o elemento com +caractere e avalia a string.
  4. Outra maneira de proceder: substitua as novas linhas por +e avalie: :echo eval(substitute(@", "\n", '+', 'g'))- eval()é a coisa mais próxima de reducenós.

Caso contrário, você precisará usar outros truques para contar os campos. Por exemplo, split(getline('.'), "[ \t|]\\+")pode ser usado para dividir as colunas de uma linha em sua matriz. A partir daí, torna-se tão simples quanto:

  1. selecione suas linhas no modo visual
  2. :echo eval(join(map(getline("'<", "'>"), { -> split(v:val, "[ \t|]\\+")[2] }), '+'))

Para se livrar dos valores mágicos (número do campo - 1 e +), ele pode se tornar um comando

:command! -range=% -nargs=+ OnField 
    \ echo { field, what -> eval(join(map(getline(<line1>, <line2>), { -> split(v:val, "[ \t|]\\+")[field-1] }), what))}(<f-args>)

Que pode ser usado com:

:OnField  3 +
:2,5OnField  3 +
:'<,'>Onfield 3 *   " after line-wise selection
....

Nota: Aqui eu uso lambdas do Vim 7.4.1xxx

Luc Hermitte
fonte
1

vmap ++do plugin vmathpor Damian Conway

  1. Instale o plugin do github (apenas 178 sloc), por exemplo

    $ wget https://raw.githubusercontent.com/thoughtstream/Damian-Conway-s-Vim-Setup/master/plugin/vmath.vim -P ~/.vim/pack/manual/start/damians-tools/plugin
    
  2. Adicione mapeamento ao seu vimrc

    vmap <silent><expr>  ++  VMATH_YankAndAnalyse()
    

    No entanto, eu sugeriria usar outra coisa, por exemplo gA

  3. Vá para a terceira coluna 2f|e selecione a coluna no modo de bloco visual<C-V>G$
  4. Pressione ++(ou o mapeamento escolhido)
  5. Os resultados são mostrados e armazenados em registros (soma em s)
  6. Inserir soma do registro s, por exemplo, com"sp

Para uma apresentação deste plugin, veja o vídeo do YouTube Damian Conway, "Mais Instantaneamente Melhor Vim" - OSCON 2013 (a partir do minuto 29).

Hotschke
fonte
1

Ferramenta CLI externa csvstatdo csvkit

:!csvstat -d '|' -H -c 4 --sum %
69.5

Breve explicação das opções

  • -d DELIMITERCaractere delimitante do arquivo CSV de entrada. Aqui |.
  • -H Especifique que o arquivo CSV de entrada não tenha linha de cabeçalho.
  • -c COLUMNSUma lista separada por vírgula de índices ou nomes de colunas a serem examinados. O padrão é todas as colunas.
  • --sum Somas de saída apenas.

Essa ferramenta também fornece min, max, média, mediana, stdev (desvio padrão), conta valores exclusivos, lista de valores frequentes.

Inserir no arquivo com

<C-r>=system("csvstat -d '|' -H -c 4 --sum FILENAME 2> /dev/null")  

Instalação

No macOS, o csvkit está disponível via homebrew e no Debian / Ubuntu e similares, com os quais pode ser instalado $ sudo apt install csvkit.

Hotschke
fonte