Каков более элегантный способ поиска комбинаций в списке? - PullRequest
0 голосов
/ 04 июля 2018

Я придумал этот алгоритм, чтобы найти триплеты 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 голосов
/ 07 июля 2018

Спасибо всем за помощь.

Текущая реализация:

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.
0 голосов
/ 04 июля 2018

Список понимания, и я уверен, что это можно улучшить, вы можете попробовать что-то вроде этого:

>>> 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'}

1021 * тогда *

>>> trairs = list(itertools.combinations(coins, 2))
# [('ETH', 'BCH'), ('ETH', 'BTC'), ('BCH', 'BTC')]
0 голосов
/ 04 июля 2018

Эффективный подход

Это очень быстро выполнит поиск допустимых триплетов в вашем списке ввода. Надеюсь, это также довольно ясно и просто. Но это нормализует порядок пар (т.е. размещает каждую пару в алфавитном порядке). Дайте мне знать, если это проблема.

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_dict совпадает с for p0 in pair_dict.keys(), а for p0, p1, p2 in triplets означает «взять каждый элемент из triplets и разбить его на переменные p0, p1 и p1».

Упрощенный подход

Если вы ищете что-то более простое (хотя и менее эффективное), вы можете попробовать код ниже.

Обратите внимание, что это зависит от нескольких интересных вещей. 1. Если вы сортируете монеты в каждой паре, а также сортируете пары в каждом триплете, то вам гарантированно, что каждый действительный триплет будет выглядеть как ['a / b', 'a / c', 'b / c'], где a, b и c - разные монеты в алфавитном порядке. 2. Если вы передадите отсортированный список в itertools.combinations(), то триплеты, которые он выдает , также будут отсортированы .

Таким образом, приведенный ниже код сортирует внутри каждой пары, затем сортирует список пар, а затем использует 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']]
0 голосов
/ 04 июля 2018

Вот метод, который использует в основном функциональность из стандартной библиотеки:

from collections import Counter
from itertools import combinations, chain

raw_pairs = ['BCH/BTC', 'BCH/ETH', 'DASH/USD', 'BTC/USDT', 'ETH/BTC']

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}

pairs = map(parse_pair, raw_pairs)

trair_candidates = combinations(pairs, r=3)

# filter the actual trairs from all trair_candidates
trairs = filter(is_trair, trair_candidates)

print(list(trairs))

Даст:

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

0 голосов
/ 04 июля 2018

Вы можете использовать

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

, чтобы получить набор всевозможных монет на бирже. Затем вы можете получить все возможные подмножества len 3, используя itertools.combinations с

triples = combinations(coins, 3)

Вы можете получить свои «следы», взяв комбинации len 2 из ваших троек.

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 подмножеством набора пар.


Список наборов frozensets лучше соответствует структуре задачи, поэтому может быть достаточным для ваших нужд, но это не совсем указанная форма вывода.

Вы можете преобразовать frozensets обратно в строки и тройки в списки, используя

[['/'.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]]
0 голосов
/ 04 июля 2018

Использование перестановок 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')

Обратите внимание, что порядок валютных пар в триплете лексикографически возрастает, не ожидайте, что первая пара всегда будет связующим звеном между двумя другими.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...