3

I do not believe this is true permutations, as I do not want a combination of IDs duplicated in a different order.

I have a lists of 1 to x IDs:

List #1:  1001 1002 1003 1004
List #2:  1002 1004 1005
List #3:  1001 1003 1006
List #4:  1002 1003 1005 1006 1007 1008 1010

etc.

Keeping in mind the lists are variable in length, I need a way to get all possible combinations of the IDs in a list (but not the same combination in a different order).

For instance, List #1 would return:

1001
1002
1003
1004
1001 1002
1001 1003
1001 1004
1002 1003
1002 1004
1003 1004
1001 1002 1003
1001 1002 1004
1001 1003 1004
1002 1003 1004
1001 1002 1003 1004

List #2 would return:

1002
1004
1005
1002 1004
1002 1005
1004 1005
1002 1004 1005

I need the solution to work in a bash script. In all fairness, I could call Python, PHP, etc.

Any input is greatly appreciated.

countermode
  • 7,373
  • 5
  • 31
  • 58
MSF004
  • 295
  • 4
  • 9
  • 1
    What have you tried so far? Do you have a formula for creating these combinations? How have you tried to program that formula? – Stephen Harris Sep 14 '16 at 23:33
  • This is almost-but-not-quite a [power set](https://en.wikipedia.org/wiki/Power_set), where you are not interested in the null/empty set. – Jeff Schaller Sep 15 '16 at 13:04

3 Answers3

2

Using python:

>>> from itertools import combinations
>>> a = (1001, 1002, 1003, 1004)
>>> [list(combinations(a, i)) for i in range(1, len(a)+1)]
[[(1001,), (1002,), (1003,), (1004,)], [(1001, 1002), (1001, 1003), (1001, 1004), (1002, 1003), (1002, 1004), (1003, 1004)], [(1001, 1002, 1003), (1001, 1002, 1004), (1001, 1003, 1004), (1002, 1003, 1004)], [(1001, 1002, 1003, 1004)]]

To display this in a nicer format:

>>> print '\n'.join('\n'.join(' '.join(str(i) for i in c) for c in combinations(a, i)) for i in range(1, len(a)+1))
1001
1002
1003
1004
1001 1002
1001 1003
1001 1004
1002 1003
1002 1004
1003 1004
1001 1002 1003
1001 1002 1004
1001 1003 1004
1002 1003 1004
1001 1002 1003 1004

Running from a bash command line

$ python -c "from itertools import combinations; a=(1001, 1002, 1003, 1004); print '\n'.join('\n'.join(' '.join(str(i) for i in c) for c in combinations(a, i)) for i in range(1, len(a)+1))"
1001
1002
1003
1004
1001 1002
1001 1003
1001 1004
1002 1003
1002 1004
1003 1004
1001 1002 1003
1001 1002 1004
1001 1003 1004
1002 1003 1004
1001 1002 1003 1004

Running as a shell function

Let's define a shell function:

$ combo() { python -c "import sys, itertools; a=sys.argv[1:]; print '\n'.join('\n'.join(' '.join(str(i) for i in c) for c in itertools.combinations(a, i)) for i in range(1, len(a)+1))" "$@"; }

We can run the function as follows:

$ combo 1001 1002 1003 1004
1001
1002
1003
1004
1001 1002
1001 1003
1001 1004
1002 1003
1002 1004
1003 1004
1001 1002 1003
1001 1002 1004
1001 1003 1004
1002 1003 1004
1001 1002 1003 1004
John1024
  • 73,527
  • 11
  • 167
  • 163
  • That was perfect! thank you! I used the "Running from a bash command line" version and just stuck it in my bash script. Thank you for the various options as well! – MSF004 Sep 15 '16 at 02:04
0

With bash:

#! /bin/bash
declare -a list=(1001 1002 1003 1004)

show() {
    local -a results=()
    let idx=$2
    for (( j = 0; j < $1; j++ )); do
        if (( idx % 2 )); then results=("${results[@]}" "${list[$j]}"); fi
        let idx\>\>=1
    done
    echo "${results[@]}"
}

let n=${#list[@]}
for (( i = 1; i < 2**n; i++ )); do
    show $n $i
done

Probably not the fastest implementation ever, but it works:

1001
1002
1001 1002
1003
1001 1003
1002 1003
1001 1002 1003
1004
1001 1004
1002 1004
1001 1002 1004
1003 1004
1001 1003 1004
1002 1003 1004
1001 1002 1003 1004
Satō Katsura
  • 13,138
  • 2
  • 31
  • 48
0

Yet Another Bash solution, based on IVlad's binary iteration method, also borrowing the brace expansion idea from Cyrus with Malte Skoruppa's generalization:

function binpowerset() (
  list=($@)
  eval binary=( $(for((i=0; i < ${#list[@]}; i++)); do printf '%s' "{0..1}"; done) )
  nonempty=0
  for((power=0; power < ${#binary[*]}; power++))
  do
    binrep=${binary[power]}
    for ((charindex=0; charindex < ${#list[*]}; charindex++))
    do
      if [[ ${binrep:charindex:1} = "1" ]]
      then
         printf "%s " ${list[charindex]}
         nonempty=1
      fi
    done
    [[ $nonempty -eq 1 ]] && printf "\n"
  done
)

Call it like this:

$ binpowerset 1001 1003 1006
1006
1003
1003 1006
1001
1001 1006
1001 1003
1001 1003 1006

It's not space-efficient at all, as it builds up a binary-representation array that has 2N elements, where N is the number of elements in the set. It's also not time-efficient, as it builds that binary array each time you call the function. It is all wrapped in a sub-shell, so it won't pollute your variable namespace. It specifically excludes the "null" or empty set, as per this Questions's sample output.

Jeff Schaller
  • 66,199
  • 35
  • 114
  • 250
  • If you look closely, this approach is not really different from mine. The main difference is I'm using bits in numbers 1..2^N-1 instead of your `binary` array. :) – Satō Katsura Sep 18 '16 at 18:40