2011-10-24 7 views
11

¿Cómo se comparan dos matrices en bash para encontrar todos los valores de intersección?Intersección de matriz en bash

Digamos:
array1 contiene los valores 1 y 2
matriz2 contiene valores 2 y 3

debería volver 2 como resultado.

Mi propia respuesta, que no puedo publicar sin embargo, debido a la pequeña reputación:

for item1 in $array1; do 
    for item2 in $array2; do 
     if [[ $item1 = $item2 ]]; then 
      result=$result" "$item1 
     fi 
    done 
done 

Busco soluciones alternativas también.

+0

Pongo No creo que vayas a encontrar una mejor manera de hacer esto. Bash realmente no está diseñado para la manipulación de matrices, y no puedo pensar en una herramienta de línea de comandos que pueda usarse para encontrar la intersección de dos matrices. –

+0

Aquí es donde brilla Perl. – RHT

Respuesta

12

Los elementos de la lista 1 se utilizan como expresión regular alzó en lista2 (expresada como una cadena: $ {lista2 [*]}):

list1=(1 2 3 4 6 7 8 9 10 11 12) 
list2=(1 2 3 5 6 8 9 11) 

l2=" ${list2[*]} "     # add framing blanks 
for item in ${list1[@]}; do 
    if [[ $l2 =~ " $item " ]] ; then # use $item as regexp 
    result+=($item) 
    fi 
done 
echo ${result[@]} 

El resultado es

1 2 3 6 8 9 11 
+0

Aunque parece que muchas respuestas proporcionadas para esta pregunta funcionarían para la intersección de matriz o lista. Estoy escogiendo esta respuesta, ya que no requiere perl y parece proporcionar un atajo para no usar un segundo ciclo a través de regexp. También responde a la pregunta original de intersección de matriz, aunque estaba buscando intersecciones de lista, debería volver a escribir las listas como matrices. Gracias a todos. – dabest1

2

Si fueron dos archivos (en lugar de matrices) que buscaba líneas que se intersectaran, podría usar el comando comm.

$ comm -12 file1 file2 
+2

Esto solo funciona si los archivos están ordenados. – ndn

1

Su respuesta no funcionará, por dos razones:

  • $array1 simplemente se expande al primer elemento de array1. (Al menos, en mi versión instalada de Bash es así como funciona. Eso no parece ser un comportamiento documentado, por lo que puede ser una peculiaridad dependiente de la versión)
  • Después de que se agrega el primer elemento al result, result entonces contendrá un espacio, por lo que la próxima ejecución de result=$result" "$item1 se portará mal. (En lugar de anexar al result, ejecutará el comando que consiste en los dos primeros elementos, con la variable de entorno result configurada en la cadena vacía.) Corrección: Resulta que estaba equivocado acerca de esto: división de palabras doesn no se lleva a cabo dentro de las asignaciones. (Véanse los comentarios a continuación.)

Lo que quiere decir esto:

result=() 
for item1 in "${array1[@]}"; do 
    for item2 in "${array2[@]}"; do 
     if [[ $item1 = $item2 ]]; then 
      result+=("$item1") 
     fi 
    done 
done 
+0

Tal vez tengo una matriz y una lista confundida. ¿Hay alguna diferencia entre las matrices y las listas en bash? – dabest1

+1

@ dabest1: "Lista" no es un término técnico en Bash. Si no quiso decir "matriz", creo que debe haber querido decir algo vago, como "una cadena que contiene espacios en blanco, donde el espacio en blanco debe interpretarse como separación de los componentes de la cadena". Obviamente no hay un término de una palabra para eso. :-) Si publica algo del código circundante que muestra cómo se inicializan estas "matrices" y cómo las está utilizando, eso probablemente aclarará mucho. – ruakh

+0

Además, * independientemente de lo que signifique, su línea 'result = $ result" "$ item1' no va a hacer lo que piensa, a menos que haya establecido la variable' IFS' en algo extraño, lo cual realmente duda que tengas (Y si * has * configurado la variable 'IFS' a algo raro, ¡entonces tienes problemas diferentes!) – ruakh

7

Tomando @ respuesta de Raihan y hacer que funcione con los no-archivos (aunque se crean FDS) Sé que es un poco de un truco pero parecía buena alternativa

El efecto secundario es que la matriz de salida se ordenará lexicográficamente, espero que esté bien (tampoco sepa qué tipo de datos tiene, así que acabo de probar con números, puede haber trabajo adicional necesario si tiene cadenas con char especial s etc)

result=($(comm -12 <(for X in "${array1[@]}"; do echo "${X}"; done|sort) <(for X in "${array2[@]}"; do echo "${X}"; done|sort))) 

Pruebas:

$ array1=(1 17 33 99 109) 
$ array2=(1 2 17 31 98 109) 

result=($(comm -12 <(for X in "${array1[@]}"; do echo "${X}"; done|sort) <(for X in "${array2[@]}"; do echo "${X}"; done|sort))) 

$ echo ${result[@]} 
1 109 17 

P. S. Estoy seguro de que había una manera de sacar la matriz a un valor por línea sin el bucle for, simplemente lo olvido (¿IFS?)

+0

Bastante buena solución - Estoy desconcertado en cuanto a lo que sucede con dos archivos de entrada estándar en el subconjunto - Parece que de alguna manera está usando/proc/self/fd, pero no soy capaz de hacer que funcione con cualquier otra cosa (por ejemplo, cat/echo) – Soren

+0

@ Soren: Consulte http://www.gnu.org/s/bash/manual/bash.html#Process-Substitution. A pesar de la apariencia similar a la redirección de entrada std, esas expresiones realmente se reemplazan por nombres de archivo. No sé por qué no puedes hacer que funcione con 'cat'. En mi sistema, 'cat <(echo foo) <(barra de eco)' imprime 'foo bar' (en dos líneas). ¿Eso no sucede en el tuyo? – ruakh

+3

'printf - '% s \ n'" $ {matriz [@]} '' dará salida a cada elemento en una línea separada. –

0

Ahora que entiendo lo que quiere decir con "matriz", piense, antes que nada, que debería considerar usar matrices Bash reales. Son mucho más flexibles, ya que (por ejemplo) los elementos de matriz pueden contener espacios en blanco, y puede evitar el riesgo de que * y ? activen la expansión del nombre de archivo.

Pero si usted prefiere utilizar su actual enfoque de cadenas delimitadas por espacio, entonces estoy de acuerdo con la sugerencia del RHT utilizar Perl:

result=$(perl -e 'my %array2 = map +($_ => 1), split /\s+/, $ARGV[1]; 
        print join " ", grep $array2{$_}, split /\s+/, $ARGV[0] 
       ' "$array1" "$array2") 

(Los saltos de línea son sólo para facilitar la lectura, se puede deshacerse de ellos si lo desea.)

en el comando Bash anteriormente, el programa Perl incrustado crea un hash llamado %array2 que contiene los elementos de la segunda matriz, y luego se imprime ningún elemento de la primera matriz que existe en %array2.

Esto se comportará de forma ligeramente diferente a su código en la forma en que maneja los valores duplicados en la segunda matriz; en su código, si array1 contiene x dos veces y array2 contiene x tres veces, y luego result contendrá x seis veces, mientras que en mi código, result contendrá x sólo dos veces. No sé si eso importa, ya que no conozco sus requisitos exactos.