알고리즘 목록

알고리즘 목록

두 목록을 빼는 빠른 방법은 무엇입니까?1. 이러한 목록은 작을 수 있으며 셸을 사용하여 작업하는 간단한 방법일 수 있습니다. 또는 목록이 길 수도 있고 외부 도구가 더 빠른 방법일 수도 있습니다.

두 개의 목록이 있다고 가정해 보겠습니다.

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

listrlist1에서 list2의 모든 요소를 ​​제거하여 다음과 동일한 결과 list()를 얻는 방법 :

listr=( 4 6 10 )

목록은 파일 내부에 위치할 수도 있는데, 목록이 큰 경우(너무 많은 메모리를 사용할 수 있음) 그래야 합니다.

단순화를 위해 모든 알고리즘을 커뮤니티 답변에 넣었습니다.

그곳에서 실시된 다양한 테스트에 대해 읽어보세요.

초기 문제는 중복되지 않은 list2의 전체 목록(list1)에서 누락된 요소를 찾는 것입니다.

그러나 목록이 다음과 같은 경우:

list1=( a a b b b c     d d   )
list2=(     b b   c c c d d e )

및 정의여러 에피소드뺄셈은 다음과 같습니다이 페이지에서, 예상 결과는 다음과 같습니다.

listr= ( a a b )

알고리즘 1과 3만 제대로 작동합니다.
알고리즘 2나 4 중 어느 것도 이를 수행할 수 없습니다.
이 정의와 일치하도록 알고리즘 5(comm)를 실행할 수 있습니다 comm -23.
알고리즘 6(zsh)이 실패합니다. 나는 그것을 작동시키는 방법을 모른다.
알고리즘 7(통신). 위에서 언급했듯이 사용이 -23작동합니다.

아직 모든 알고리즘을 분석하지는 않았습니다.대칭 차이 설정정의는 다음을 생성해야 합니다.

listr=( a a b c c e )

하지만 comm -3 list1.txt list2.txt | tr -d ' \t'작동합니다.


1예, 쉘에서 텍스트 파일(행 목록)을 처리하는 것은 좋지 않은 생각이며 느리게 설계되었다는 것을 알고 있습니다.
하지만 거기에는피할 수 없을 것 같은 상황.
나(우리)는 제안에 열려 있습니다.

답변1

comm다음을 사용하여 두 목록에 공통된 항목을 제거 할 수 있습니다 .

listr=($(comm -3 <(printf "%s\n" "${list1[@]}" | sort) <(printf "%s\n" "${list2[@]}" | sort) | sort -n))

이는 두 목록을 예상 순서대로 정렬하고 comm비교한 후 두 목록의 고유한 항목만 출력한 다음 다시 번호순으로 정렬합니다.

두 목록이 모두 사전순으로 정렬된 경우(~에 따르면LC_COLLATE), 다시 정렬하는 것을 피할 수 있습니다.

listr=($(comm --nocheck-order -3 <(printf "%s\n" "${list1[@]}") <(printf "%s\n" "${list2[@]}")))

비교해야 할 값이 파일에 저장되어 있는 경우에도 잘 작동합니다.

답변2

#!/bin/zsh
list1=( 1 2 3 4 5 6 7 8 9 10 11 12 )
list2=( 1 2 3   5   7 8 9    11 12 )
listr=("${(@)list1:|list2}")
typeset -p listr

답변3

추상적인:

  • 긴 목록의 경우 목록이 이미 정렬되어 있으면 comm(alg7)이 가장 빠릅니다.

  • zsh 솔루션은 (지금까지) 가장 빠릅니다.만약에파일을 읽지 않습니다. 즉, 목록이 "메모리에" 제공됩니다. 그러나 파일에서 값을 읽어야 하는 다른 모든 솔루션과 비교하는 것은 공정하지 않습니다. 원래 코드(테스트에서 파일을 읽는 시간을 피함)를 파일을 읽는 시간도 포함하는 코드로 변경했습니다.


이것은 커뮤니티 답변이며 각 답변에 대한 시간만 보고됩니다.

제발하다모든 것을 비교하려면 솔루션/옵션을 편집하고 추가하세요.


알고리즘 목록

  • alg1: 순진한 루프 솔루션.
  • alg2: 외부 sort합계 사용uniq -u
  • alg3: bash에서 문자열을 처리합니다.
  • alg4: 정렬된 목록에 -v를 추가하세요(감사합니다@쿠살라난다)
  • alg5: 통신(감사합니다@Stephenkit)
  • alg6: zsh (감사합니다@루아)
  • alg7: 통신하지만 이미 정렬된 파일에 있습니다(감사합니다@Stephenkit)

zsh 매뉴얼의 참고 사항:

${name:|arrayname}
arrayname이 배열 변수의 이름인 경우(내용이 아님) arrayname에 포함된 모든 요소는 이름 대체에서 제거됩니다.

시험

이 문제에 접근하는 방법은 여러 가지가 있으므로 답을 (공정하게) 테스트하기 위한 일반적인 프레임워크가 필요합니다.

일부 지침(불공평하다고 생각되면 변경하세요):

  • 합리적인 정확도를 얻으려면 충분한 반복을 측정하십시오.
  • 사용 중인 인클로저 내부를 측정합니다(인클로저를 싣거나 내리지 마십시오).
  • /dev/null로 인쇄하거나 리디렉션하지 않음으로써 출력 오버헤드를 방지합니다.

테스트 코드:

#!/bin/bash
alg1(){
         arr=( "${list1[@]}" )
         for i in "${list2[@]}"; do
             for j in "${!arr[@]}"; do
         if [[ "$i" == "${arr[j]}" ]]; then
             unset arr["$j"]
             break
         fi
             done
     done
     printf '%s ' "${arr[@]}"; echo
}

alg2(){
         arr=($({ printf '%s\n' "${list1[@]}" "${list2[@]}"; } | sort | uniq -u))
         printf '%s ' "${arr[@]}"; echo
}

alg3(){
         a=" $(printf '%s ' ${list1[@]})" # Watch the space at the start!!.
         for i in "${list2[@]}"; do
         a=${a/ "$i" / };
     done
     printf '%s\n' "$a"
}

alg4(){  join -v 1 list1.txt list2.txt ; }

alg5(){  #listr=$(
                    comm -3 <(printf "%s\n" "${list1[@]}" | sort) \
                            <(printf "%s\n" "${list2[@]}" | sort) |
                sort -n
     #)
      }

alg6(){ zsh -c '  alg6(){
                           list1=( $(cat list1.txt) )
                           list2=( $(cat list2.txt) )
                           listr=("${(@)list1:|list2}")
                           typeset -p listr
                        }
                  TIMEFMT="%E %U %S"
                  time ( for ((k=0;k<'"$1"';k++)); do alg6; done; )
                '
      }
#: <<-\_comment_
alg7(){ comm -23 list1.txt list2.txt; }

try(){ for ((k=0;k<$1;k++)); do "$2"; done; }

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

#list1=( a a b b b c     d d   )
#list2=(     b b   c c c d d e )

size=1000000
list1=( "0" $(seq 1 "$size") )
list2=( "${list1[@]}" ); unset "list2[123]" "list2[234]" "list2[345]"

printf '%s\n' "${list1[@]}" | sort >list1.txt
printf '%s\n' "${list2[@]}" | sort >list2.txt

repeats=${1:-10}; shift
out=${1:-no}    ; shift
(($#==0)) && set -- alg{1..7}

TIMEFORMAT='%R %U %S'
for   i
do    printf '%s ' "$i"
      if [[ $out == no ]]; then
      [[ $i != alg6 ]] &&
          time try "$repeats" "$i" >/dev/null ||
          time alg6 "$repeats" >/dev/null
      else
      [[ $i != alg6 ]] &&
          time try "$repeats" "$i"            ||
          time alg6 "$repeats"
      fi
done

결과:

짧은 목록(코드에 표시됨):

$ ./script
alg1 2.056 0.806 1.237
alg2 3.478 3.126 1.756
alg3 0.999 0.728 0.304
alg4 1.186 0.780 0.434
alg5 5.234 1.926 1.722
alg6 2.71s 1.64s 1.26s
     2.758 1.637 1.265
alg7 1.156 0.799 0.422

alg6의 시간은 zsh에 의해 보고되고 나중에는 bash에 의해 보고됩니다.
또한 파일 읽기가 함수 외부로 이동하면 zsh의 실행 시간이 더 작아집니다(0.050).

더 긴 목록

500개의 값(10회 반복)만으로 목록을 테스트하면 alg1이 매우 비효율적이라는 것을 알 수 있습니다. 추가 테스트에서 제거하십시오.

alg1 4.149 3.471 0.657
alg2 0.145 0.055 0.063
alg3 0.219 0.180 0.009
alg4 0.039 0.015 0.003
alg5 0.149 0.018 0.027
alg6 0.06s 0.02s 0.02s
     0.087 0.030 0.018
alg7 0.033 0.008 0.008

5k 값(10회 반복)을 테스트하면 alg3도 비효율적임을 알 수 있습니다.

alg2 0.590 0.526 0.187
alg3 12.957 12.888 0.044
alg4 0.098 0.047 0.008
alg5 0.654 0.028 0.036
alg6 0.16s 0.12s 0.04s
     0.211 0.118 0.044
alg7 0.038 0.022 0.014

50,000개 값 테스트(10회 반복):

alg2 6.487 5.838 1.611
alg4 0.488 0.469 0.019
alg5 5.073 0.250 0.056
alg6 1.42s 1.20s 0.21s
     1.467 1.206 0.216
alg7 0.271 0.247 0.014

500k(10회)

alg4 5.471 5.269 0.156
alg6 15.14s 13.33s 1.91s
     15.215 13.335 1.926
alg7 2.833 2.655 0.138

1M용(10회 반복)

alg4 11.127 10.804 0.251
alg7 5.772 5.525 0.230

관련 정보