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: MEDIUM

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) = ([], [])
	while digits:
		digit = digits.pop()
		if digits and digit == digits[-1]:
			pairs.append(digit)
			digits.pop()
		else:
			singles.append(digit)
	if pairs and pairs[0] == '0':
		pairs = []
	if not singles:
		singles = ['']
	return int(''.join(pairs) + singles[0] + ''.join(pairs[::-1]))

def numeric_palindrome(*args):
	args = Counter(args)
	candidates = set()
	for n in [0, 1]:
		if args[n] > 2:
			args[n] = 2
	args = list(args.elements())
	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))


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('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)