在列表中查找组合的更优雅的方法是什么?

时间:2018-07-04 05:51:01

标签: python python-3.x combinatorics

我想出了一种算法来查找pairs的三元组(我称它们为trairs),条件是所有元素中必须全部存在3个元素(硬币),而只有3个元素3 pairs

但是,可能有可能以更优雅的方式解决相同的问题。例如,我正在索引所有循环,这使它看起来特别复杂。另外,那里的break令我不舒服!

输入数据为pairs,它是list中的str
例如。 pairs = ['BCH/BTC','BCH/ETH','DASH/USD','BTC/USDT','ETH/BTC']

所需的输出是list个字符串中的一个list
例如。 trair = [['BCH/BTC','BCH/ETH','ETH/BTC]]

def find_trairs(exchange):
    ''' Find possible triplets of pairs (trairs) that can be traded for unbalance.

    Example of trairs:
    'ETC/BTC' 'ETC/ETH' 'ETH/BTC'
    'BCH/BTC' 'BCH/EUR' 'BTC/EUR'

    '''
    exchange_pairs = exchange.symbols #loads all pairs from the exchange.
    pairs = list(filter(lambda x: not '.d' in x, exchange_pairs)) #filters off 
    #the darkpool pairs.

    pair = ['', '', '']
    coin = ['', '', '']
    trair = []

    #Semi-optimized loop to look for triplece of pairs.
    #Example:['BCH/BTC', 'BCH/EUR', 'BTC/EUR']
    for i in range(len(pairs)-3):
        #not all coins are 3 digits long, we must find the slash that separetes
        #each coin in order to have a robust algorithm.
        slash_position = pairs[i].find('/') 
        coin[0] = pairs[i][0:slash_position]
        coin[1] = pairs[i][slash_position+1:]
        for j in range(i+1, len(pairs)-2):
            if (coin[0] in pairs[j]):
                slash_position = pairs[j].find('/') 
                coin[2] = pairs[j][slash_position+1:]
                for k in range(j+1, len(pairs)-1):
                    if coin[1] in pairs[k] and coin[2] in pairs[k]:
                        trair.append([pairs[i], pairs[j], pairs[k]])
                        break

    return trair

有任何提示或评论吗?

6 个答案:

答案 0 :(得分:3)

使用itertools排列,并过滤结果并消除重复项:

import itertools

currency_pairs = ['BCH/BTC', 'BCH/ETH', 'DASH/USD', 'BTC/USDT', 'ETH/BTC']
set_triplets = set()
for triplet in itertools.permutations(currency_pairs, 3):
    c1, c2 = triplet[0].split('/')
    if (c1 in triplet[1] or c1 in triplet[2]) and (c2 in triplet[1] or c2 in triplet[2]):
        set_triplets.add(tuple(sorted(triplet)))
for triplet in set_triplets:
    print(triplet)

输出:

('BCH/ETH', 'BTC/USDT', 'ETH/BTC') 
('BCH/BTC', 'BCH/ETH', 'BTC/USDT')
('BCH/BTC', 'BCH/ETH', 'ETH/BTC')

请注意,三元组中的货币对的顺序在字典上是递增的,不要期望第一个货币对始终是两个货币对之间的链接。

答案 1 :(得分:3)

您可以使用

coins = set('/'.join(pairs).split('/'))

在交易所获得一组所有可能的硬币。然后,您可以使用itertools.combinations

来获得len 3的所有可能子集
triples = combinations(coins, 3)

通过取三元组中len 2的组合,可以得到“ trairs”。

trairs = [{frozenset(pair) for pair in combinations(triple, 2)}
          for triple in triples]

结果是3个项目集的列表,其中每个项目都是代表硬币对的冻结集。


交易所可能不直接支持所有可能的货币对。如果是这样,您可以添加一个额外的过滤步骤以删除无效的“ trairs”。

pairset = set(frozenset(pair.split('/')) for pair in pairs)
trairs = [trair for trair in trairs if trair <= pairset]

<=检查trair是否是配对对的子集。


frozenset的集合列表与问题的结构更匹配,因此可以满足您的需求,但这并不完全是指定的输出形式。

您可以使用

将冻结的集转换回字符串,将三元组转换为列表。
[['/'.join(pair) for pair in trair] for trair in trairs]]

组合实际上是无序的,但是尚不明确这个问题是否重要,因为购买例如BTC/ETH与销售ETH/BTC等相同,目前尚不清楚相同三元组的其他订购有什么用途。因此,您可以将三元组保留为一组,并使用像这样的字母对。

[{'/'.join(sorted(pair)) for pair in trair} for trair in trairs]]

答案 2 :(得分:2)

这里是主要使用标准库功能的方法:

id      organization    invoice salesOrderNumber
12830   43              12975   705
12831   43              12976   705

会给:

  

[([['BCH','BTC'],['BCH','ETH'],['ETH','BTC'])]

答案 3 :(得分:2)

有效方法

这将很快在输入列表中搜索有效的三元组。希望它也相当清楚和简单。但这确实规范了对的顺序(即,将每个对按字母顺序排列)。让我知道是否有问题。

pairs = ['BCH/BTC','BCH/ETH','DASH/USD','BTC/USDT','ETH/BTC']
# make a dict of all alphabetically higher partners for each symbol
pair_dict = {}
for pair_str in pairs:
    p0, p1 = sorted(pair_str.split('/'))
    # create sets if needed, then record this pair
    pair_dict.setdefault(p0, set())
    pair_dict.setdefault(p1, set())
    pair_dict[p0].add(p1)

# process the dict, finding all valid triplets of pairs
triplets = list()
for p0 in pair_dict:
    p0_pairs = pair_dict[p0]
    for p1 in p0_pairs:
        p1_pairs = pair_dict[p1]
        for p2 in p1_pairs:
            if p2 in p0_pairs:
                # there's a chain from p0 to p1 to p2 to p0;
                # add them to the list of triplets
                triplets.append((p0, p1, p2))
final = [[p0+'/'+p1, p1+'/'+p2, p2+'/'+p0] for p0, p1, p2 in triplets]
print(final)
# [['BCH/BTC', 'BTC/ETH', 'ETH/BCH']]

我使用集而不是pair_dict中的列表,因为它们可以更快地搜索并且可以消除任何重复项。另外,for p0 in pair_dictfor p0 in pair_dict.keys()相同,而for p0, p1, p2 in triplets的意思是“从triplets中取出每个元素并将其拆分为p0p1p1变量。”

更简单的方法

如果您正在寻找更简单的方法(尽管效率较低),则可以尝试以下代码。

请注意,这依赖于一些有趣的事情。 1.如果您对每对中的硬币进行排序,并且对每个三元组中的硬币对进行排序,那么可以确保每个有效的三元组看起来像['a / b','a / c','b / c'],其中a,b和c是不同的硬币,按字母顺序排列。 2.如果您向itertools.combinations()提供排序列表,则三元组将产生will also be sorted

因此,下面的代码在每个对中进行排序,然后对对列表进行排序,然后使用itertools.combinations()来获取已排序的三元组。然后,它检查这些三胞胎中的任何一个是否与所需的模式匹配。

import itertools
# added another pair to make it more interesting
pairs = ['BCH/BTC','BCH/ETH','DASH/USD','BTC/USDT','ETH/BTC','USDT/ETH']
pairs_normalized = sorted(sorted(pair.split('/')) for pair in pairs)
triplets = [
    (p1, p2, p3) 
    for p1, p2, p3 in itertools.combinations(pairs_normalized, 3)
    # look for ['a/b', 'a/c', 'b/c'] pattern
    if p1[0] == p2[0] and p1[1] == p3[0] and p2[1] == p3[1]
]
output = [['/'.join(p) for p in triplet] for triplet in triplets]
print(output)
# [['BCH/BTC', 'BCH/ETH', 'BTC/ETH'], ['BTC/ETH', 'BTC/USDT', 'ETH/USDT']]

答案 4 :(得分:2)

列出理解力,我敢肯定,这可以改善,您可以尝试这样的事情:

>>> pairs = ['BCH/BTC','BCH/ETH','DASH/USD','BTC/USDT','ETH/BTC']
# ['BCH/BTC','BCH/ETH','DASH/USD','BTC/USDT','ETH/BTC']

获取硬币:

>>> coins = [j for i in pairs for j in i.split('/')]
# ['BCH', 'BTC', 'BCH', 'ETH', 'DASH', 'USD', 'BTC', 'USDT', 'ETH', 'BTC']

获取出现多次的硬币并保存在集合中,以避免重复

>>> coins = {coin for coin in coins if coins.count(coin)>1}
# {'BCH', 'BTC', 'ETH'}

找到只有这些硬币出现的地方:

>>>  trairs = [i for i in pairs for j in coins if i.split('/')[0] and i.split('/')[1] in j]
# ['BCH/BTC', 'BCH/ETH', 'ETH/BTC']

使用过滤器:

>>> trairs = filter(lambda x: (x.split('/')[0] and x.split('/')[1]) in coins, pairs)
# ['BCH/BTC', 'BCH/ETH', 'ETH/BTC']

itertools的新更新:

>>> coins = {j for j in set('/'.join(pairs).split('/')) if '/'.join(pairs).split('/').count(j)>1}
# {'BCH', 'BTC', 'ETH'}

然后

>>> trairs = list(itertools.combinations(coins, 2))
# [('ETH', 'BCH'), ('ETH', 'BTC'), ('BCH', 'BTC')]

答案 5 :(得分:0)

谢谢大家的帮助。

当前实现为:

import ccxt
from collections import Counter
from itertools import combinations, chain

def parse_pair(raw_pair):
    return raw_pair.split('/')

def is_trair(trair_candidate):
    # assuming that all currencies appear twice means we have a trair
    currency_counts = Counter(chain.from_iterable(trair_candidate))
    return set(currency_counts.values()) == {2}

def format_trairs(u_trair):
    '''fortmat the trairs to the correct format. List of List of str.

    input:
        u_trair: trairs in the format List of Tuples of list of strings.
        eg.:[(['ABT','BTC'],['ABT',ETH'],['ETH','BTC])]
    output:
        trair: trais in the format List of list of str.
        ['ABT/BTC', 'ABT/ETH', 'ETH/BTC']   
    '''
    trair= [] #stores the trairs in the correct format.
    for one_trair in u_trair:
        t_trair = [] #temporary trair. 
        for one_pair in one_trair:
            t_trair.append(one_pair[0]+'/'+one_pair[1])
        trair.append(t_trair)
    return trair

exchange = ccxt.kucoin()
market = exchange.load_markets()
exchange_pairs = exchange.symbols

raw_pairs = list(filter(lambda x: not '.d' in x, exchange_pairs))

pairs = map(parse_pair, raw_pairs)
trair_candidates = combinations(pairs, r=3)
#filter the actual trairs from all trair_candidates
u_trair = list(filter(is_trair,trair_candidates)) #unformated trairs.
trair = format_trairs(u_trair) #formats the trairs to list of list os strings.