Create a function that finds the largest palindromic number made from the product of **at least 2** of the given arguments.

### Notes

* Only non-negative numbers will be given in the argument
* You don't need to use all the digits of the products
* Single digit numbers are considered palindromes
* Optimization is needed: dealing with ones and zeros in a smart way will help a lot




## Examples

```
[937, 113] --> 81518
```
As `937 * 113 = 105881` and the largest palindromic number that can be arranged from the digits of result is: `81518`


Another one:

```
[57, 62, 23] --> 82128

             product     palindrome
57 * 62      = 3534   -->  353
57 * 23      = 1311   -->  131
62 * 23      = 1426   -->  6
57 * 62 * 23 = 81282  -->  82128
```

One more:
```
[15, 125, 8] --> 8

             product     palindrome
15 * 125     = 1875   -->  8
15 * 8       = 120    -->  2
125 * 8      = 1000   -->  1
15 * 125 * 8 = 15000  -->  5
```

Difficulty: interview

Starter code:

def numeric_palindrome(*args):
	

Solutions:
from collections import Counter
from itertools import combinations
from operator import mul
from functools import reduce

def largest_palindrom_from(n):
    digits = sorted(str(n))
    singles, pairs = [], []
    
    # group digits to pairs and singles
    while digits:
        digit = digits.pop()
        if digits and digit == digits[-1]:
            pairs.append(digit)
            digits.pop()
        else:
            singles.append(digit)
    
    # avoid trailing zeros
    if pairs and pairs[0] == '0':
        pairs = []
    
    # return largest possible palindrom
    if not singles:
        singles = ['']
    return int( ''.join(pairs) + singles[0] + ''.join(pairs[::-1]) )


def numeric_palindrome(*args):
    args = Counter(args)
    candidates = set()
    
    # remove extra 0s and 1s
    for n in [0, 1]:
        if args[n] > 2:
            args[n] = 2
    
    args = list(args.elements())
    
    # check all possible products
    for n in range(2, len(args)+1):
        for combo in combinations(args, n):
            product = reduce(mul, combo)
            candidates.add( largest_palindrom_from(product) )
    
    return max(candidates)

from collections import Counter
from itertools import combinations
from functools import reduce
from operator import mul

def best_pal(array):
    outside, inside = [], ''
    for k,v in Counter(array).items():
        if v > 1: outside.append(k*(v//2))
        if v % 2: inside = max(inside, k)
    outside = ''.join(sorted(outside))
    if outside and outside[-1] == '0': outside = ''
    return int(''.join(outside[::-1] + inside + outside))

def numeric_palindrome(*args):
    args = list(filter(None, args))
    if len(args) < 2: return 0
    if 1 in args: args = list(filter(lambda x: x>1, args)) + [1]
    if len(args) == 1: return best_pal(str(args[0]))
    return max(best_pal(str(reduce(mul, test))) for i in range(2, len(args)+1) for test in combinations(args, i))

from itertools import combinations as comb

def numeric_palindrome(*args):
    args=[item for item in args if item>0]
    if len(args)<2: return 0
    if 1 in args: args=[item for item in args if item>1]+[1]
    if len(args)==1: return max_pal(list(str(args[0])))
    maxval=-1
    for i in range(2,len(args)+1):
        base=comb(args, i)
        for combo in base:
            prod=1
            for item in combo:
                prod*=item
            maxval=max(maxval, max_pal(list(str(prod))))
    return maxval

def max_pal(array):
    even=sorted([n*(array.count(n)/2) for n in set(array) if array.count(n)>1])
    odd=[n for n in set(array) if array.count(n)%2==1]
    odd=[""] if len(odd)==0 else odd
    if len(even)==1 and even[0][0]=='0': even=[""]
    return int("".join(even[::-1]+[max(odd)]+even))

#Largest Numeric Palindrome
printit = False

def prefilter(available):
    ones = None
    for _ in range(available.count(0)):
        available.remove(0)
    if len(available) == 1:
        available.append(0)
    for _ in range(available.count(1)):
        available.remove(1)
        ones = True
    if len(available) >= 1 and ones:
        available.extend([1])
    elif len(available) == 1 and ones:
        available.extend([1])
    elif len(available) == 0 and ones:
        available.extend([1,1])
    else:
        available.extend([0,0])

def comb(available, used):
    if len(available) == 0:
        pass
    else:
        head = available.pop()
        used.append(head)
        if len(used) >= 2:
            yield(list(used))
        for x in comb(available[:],used[:]):
            yield x
        used.pop()
        for x in comb(available[:],used[:]):
            yield x

def numeric_palindrome(*args):
    lst = [*args]
    prefilter(lst)
    lst = [x for x in comb(lst,[])]
    if printit:
        print('sorted:',sorted([sorted(x) for x in lst]))
    if lst == []:
        return 0
    if printit:
        print(lst) #
    products = []
    for nums in lst:
        result = 1
        for num in nums:
            result *= num
        products.append(str(result))
    products = [str(x) for x in products]
    if printit:
        print(products) #
    lsts = [sorted([int(char) for char in product],reverse=True) for product in products]
    if printit:
        print(lsts) #
    palindromes = []
    pal_ind = 0
    for nums in lsts:
        sel_nums = []
        if printit:
            print(sel_nums) #
        odd_added = False
        for num in sorted(list(set(nums)),reverse=True):
            num_count = nums.count(num)
            if num_count % 2 == 0:
                for i in range(num_count):
                    sel_nums.append(num)
            elif num_count % 2 == 1 and not odd_added:
                for i in range(num_count):
                    sel_nums.append(num)
                    odd_added = True
            else:
                for i in range(num_count-1):
                    sel_nums.append(num)
                    odd_added = True
        if printit:
            print('sel_nums:',sel_nums) #
        i = 0
        while len(sel_nums) > 0:
            if sel_nums.count(sel_nums[0]) == 1:
                sel_nums.append(sel_nums.pop(0))
            if len(sel_nums) > 1 and i == 0:
                palindromes.insert(pal_ind,[])
                palindromes[pal_ind].insert(0,str(sel_nums.pop(0)))
                palindromes[pal_ind].insert(1,str(sel_nums.pop(0)))
                if printit:
                    print('first branch, i:',str(i),palindromes[pal_ind]) #
                i += 1
                continue
            elif len(sel_nums) == 1 and i == 0:
                palindromes.insert(pal_ind,list(str(sel_nums.pop(0))))
                if printit:
                    print('second branch, i:',str(i),palindromes[pal_ind]) #
                i += 1
                continue
            elif len(sel_nums) >= 2:
                palindromes[pal_ind].insert(i,str(sel_nums.pop(0)))
                palindromes[pal_ind].insert(-i,str(sel_nums.pop(0)))
                if printit:
                    print('third branch, i:',str(i),palindromes[pal_ind]) #
                i += 1
                continue
            elif len(sel_nums) == 1:
                palindromes[pal_ind].insert(i,str(sel_nums.pop()))
                if printit:
                    print('forth branch, i:',str(i),palindromes[pal_ind]) #
                i += 1
                continue
        if printit:
            print('building:',palindromes[pal_ind]) #
            print(str(i)) #
        pal_ind += 1
    palindromes = [int(''.join(x)) for x in palindromes]
    while str(max(palindromes))[-1] == '0' and len(str(max(palindromes))) > 1:
        max_num = max(palindromes)
        new_num = str(max_num).strip('0')
        palindromes.remove(max_num)
        palindromes.append(int(new_num))
    return max(palindromes)

def palindrome(num):
    l = list(str(num))
    if len(l) == 1:
            return num
    else:
        l2 = [x for x in l if l.count(x)>=2]
        l22 = list(set(l2))
        l1 = [x for x in list(set(l)) if l.count(x)%2 != 0]
        l22.sort()
        if len(l2)==0 and len(l1) > 0 or len(l2) == l2.count('0'):
            return int(max(l1))
        else:
            l2a = ''
            for i in range(len(l22)):
                if l2.count(l22[i])%2 > 0: 
                    n = l2.count(l22[i])//2
                else:
                    n = l2.count(l22[i])/2
                l2a = l2a + l22[i] * int(n)
            l2b = l2a[::-1]
            if len(l1)>0:
                l2c = l2b + str(max(l1)) + l2a
            else:
                l2c = l2b + l2a
            return int(l2c)

def product1(list,pro,i,out):
    if i < len(list):
        proa = pro
        prob = list[i]
        
        pro = proa * prob
        if pro not in out:
            out.append(pro)
        product1(list,pro,i+1,out)
        product1(list,proa,i+1,out)
        product1(list,prob,i+1,out)
        
def numeric_palindrome(*args):
    args1 = [x for x in args if x > 1]
    args1.sort()
    out1 = []
    pro1 = []
    if args1 == [] and args.count(1) > 1:
        return 1
    elif args.count(0) == len(args) - 1 or args1 == []:
        return 0
    elif len(args1) == 1:
        return palindrome(args1[0])
    else:
        product1(args1,args1[0],1,pro1)
        if args.count(1) > 0:
            pro1 = pro1 + args1
        
        for j in pro1:
            out = palindrome(j)
            out1.append(out)
    return max(out1)


from itertools import combinations
from functools import reduce

def max_pal(n):
    if n < 10: return n
    a = [0]*10
    while n:
        n,d = divmod(n,10)
        a[d] += 1
    s = ""
    for d,n in enumerate(a):
        c = str(d)
        while n > 1:
            s = c+s+c
            n -= 2
        a[d] = n
    if s.startswith('0'): s = ""
    i = next((i for i in range(9,-1,-1) if a[i]),-1)
    if ~i: s = s[:len(s)>>1] + str(i) + s[len(s)+1>>1:]
    return int(s)

def numeric_palindrome(*a):
    z = 0 in a
    o = a.count(1)
    a = [n for n in a if n > 1]
    if o: a.append(1)
    a = [max_pal(reduce(int.__mul__,r)) for i in range(2,len(a)+1) for r in combinations(a,i)]
    if z: a.append(0)
    if o > 1: a.append(1)
    return max(a)

import itertools
from functools import reduce
from operator import mul
import re


def max_palindrome(x):
    s = str(x)
    left, odds = [], []
    for c in set(s):
        cnt = s.count(c)
        d, d1 = cnt//2, cnt % 2
        if d:
            left.append(c*d)
        if d1:
            odds.append(c)
    left.sort(reverse=True)
    s1 = ''.join(left)
    m = max(odds) if odds else ''
    temp = s1+m+s1[::-1]
    temp = re.sub(r'0*$', '', temp)
    return int(temp)


def numeric_palindrome(*args):
    arr = [x for x in args if x > 1]
    if 1 in args:
        arr.append(1)
    if len(arr) > 1:
        res = 0
        for i in range(2, len(arr)+1):
            temp = [max_palindrome(reduce(mul, x))
                    for x in itertools.combinations(arr, i)]
            res = max(max(temp), res)
        return res
    elif len(arr) == 1:
        return max_palindrome(arr[0]) if args.count(1)>1 in args else 0
    else:
        return 1 if args.count(1) > 1 else 0


from itertools import combinations
def funk(*args):
    num = 1
    for i in args[0]:
        num *= i
    num = str(num)
    d_2 = {i: num.count(i)//2 for i in num if num.count(i)//2}
    d_1 = {i: num.count(i)%2 for i in num if num.count(i)%2}
    main_part = ''.join([d_2[i] * i for i in sorted(d_2, reverse=True)]) 
    add_part = max(d_1) if d_1 else ''
    res = main_part + add_part + main_part[::-1]
    res = res.strip('0')
    return int(res) 

def numeric_palindrome(*args):
    if all([i in [0, 1] for i in args]) and args.count(1) >= 2:
        return 1
    l_new = [i for i in args if i not in [0,1]]
    l_new = l_new + [1] if 1 in args else l_new
    l = [l_new]
    if len(l_new) < 2:
        return 0
    
    for i in range(2, len(l_new)):
        l += [tuple_ for tuple_ in combinations(l_new, i)]
    l = [funk(i) for i in l]
    return max(l)

from functools import reduce
from itertools import combinations
from collections import Counter

def max_palindrome(num):
    counter = Counter(str(num))
    one = [int(x) for x, v in list(counter.items()) if v % 2 == 1]
    if 0 < len(one):
        one = str(max(one))
    else:
        one = ""
    more = []
    for x in counter:
        while 2 <= counter[x]:
            more.append(x)
            counter[x] -= 2
    more.sort()
    if 0 < len(more) and more[-1] == '0':
        more = ""
    more = "".join(more)
    res = more[::-1] + one + more
    return int(res)

def numeric_palindrome(*args):
    palindromes = []
    one = args.count(1)
    args = [arg for arg in args if 1 < arg]
    if 0 < one:
        args.append(1)
    if 1 < one:
        args.append(1)
        
    for n in range(2, len(args)+1):
        comb = list(combinations(args, n))
        for c in comb:
            product = reduce(lambda a, b: a * b, c)
            palindromes.append(max_palindrome(product))
    return max(palindromes) if 0 < len(palindromes) else 0


from collections import Counter
def numeric_palindrome(*args):
    seen = set()
    products = set()
    for number in args:
        for multiplier in seen.union(products):
            products.add(multiplier*number)
        seen.add(number)

    palindromes = set()
    for number in products:
        occurrences = Counter(str(number))
        repeated= []
        max_single = ''
        for char, count in occurrences.items():
            if count//2 > 0 and char != '0':
                repeated.extend([char]*(count//2))
            if count%2 == 1:
                if max_single < char: max_single = char
        
        half_palindrome = ''
        while repeated:
            maximum = max(repeated)
            half_palindrome += maximum
            repeated.remove(maximum)
        if half_palindrome: half_palindrome += '0'*(occurrences['0']//2)
        palindrome = half_palindrome + max_single + half_palindrome[::-1]
        palindromes.add(int(palindrome))
    return max(palindromes)