2017-02-20 14 views
11

У меня есть список строк. Я хочу назначить уникальный номер каждой строке (точное число не важно) и создать список с той же длиной, используя эти цифры, по порядку. Ниже моя лучшая попытка на него, но я не счастлив по двум причинам:Назначьте число для каждого уникального значения в списке

  1. Он предполагает, что одни и те же значения находятся рядом друг с другом

  2. я должен был начать список с 0, в противном случае результат будет неправильным

Мой код:

names = ['ll', 'll', 'll', 'hl', 'hl', 'hl', 'LL', 'LL', 'LL', 'HL', 'HL', 'HL'] 
numbers = [0] 
num = 0 
for item in range(len(names)): 
    if item == len(names) - 1: 
     break 
    elif names[item] == names[item+1]: 
     numbers.append(num) 
    else: 
     num = num + 1 
     numbers.append(num) 
print(numbers) 

Я хочу сделать код более общим, поэтому он будет работать с неизвестным списком. Есть идеи?

+0

как насчет сортировки списка перед применением алгоритма –

ответ

11

без использования внешних библиотек (проверьте EDIT для Pandas раствора), вы можете сделать это следующим образом :

d = {ni: indi for indi, ni in enumerate(set(names))} 
numbers = [d[ni] for ni in names] 

Краткое объяснение:

В первой строке вы присваиваете номер каждому уникальному элементу в своем списке (хранящемся в словаре d; вы можете легко создать его, используя понимание словаря; set возвращает уникальные элементы names).

Затем во второй строке вы выполните список и сохраните фактические цифры в списке numbers.

Одним из примеров, чтобы проиллюстрировать, что она прекрасно работает для неупорядоченных списков:

# 'll' appears all over the place 
names = ['ll', 'll', 'hl', 'hl', 'hl', 'LL', 'LL', 'll', 'LL', 'HL', 'HL', 'HL', 'll'] 

Это выход для numbers:

[1, 1, 3, 3, 3, 2, 2, 1, 2, 0, 0, 0, 1] 

Как вы можете видеть, количество 1, связанный с ll появляется в правильных местах.

EDIT

Если у вас есть Pandas доступны, вы можете также использовать pandas.factorize:

import pandas as pd 

pd.factorize(names) 

вернется

(array([(array([0, 0, 1, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0]), 
array(['ll', 'hl', 'LL', 'HL'], dtype=object)) 

Поэтому

numbers = pd.factorize(names)[0] 
0

Поскольку вы указываете строки на целые числа, это предполагает использование dict. Таким образом, вы можете сделать следующее:

d = dict() 

counter = 0 

for name in names: 
    if name in d: 
     continue 
    d[name] = counter 
    counter += 1 

numbers = [d[name] for name in names] 
+1

Downvoter, позаботьтесь, чтобы объяснить? –

-1

Вы можете попробовать это также: -

names = ['ll', 'll', 'll', 'hl', 'hl', 'hl', 'LL', 'LL', 'LL', 'HL', 'HL', 'HL'] 

indexList = list(set(names)) 

print map(lambda name:indexList.index(name),names) 
+2

В чем смысл обертывания 'indexList.index' в лямбда? –

+0

@StefanPochmann, да, вы также можете написать эту карту (indexList.index, names), если вам не нужно писать lambda –

2

мне удалось очень немного изменить сценарий, и он выглядит нормально:

names = ['ll', 'hl', 'll', 'hl', 'LL', 'll', 'LL', 'HL', 'hl', 'HL', 'LL', 'HL', 'zzz'] 
names.sort() 
print(names) 
numbers = [] 
num = 0 
for item in range(len(names)): 
    if item == len(names) - 1: 
     break 
    elif names[item] == names[item+1]: 
     numbers.append(num) 
    else: 
     numbers.append(num) 
     num = num + 1 
numbers.append(num) 
print(numbers) 

Вы можете видеть, что это очень simmilar, только дело в том, что вместо добавления номер для следующего элемента я добавить номер для текущего элемента. Это все. О, и сортировка. Сначала он сортирует капитал, а затем строчный регистр в этом примере, вы можете играть с sort(key= lambda:x ...), если хотите это изменить. (Возможно, вот так: names.sort(key = lambda x: (x.upper() if x.lower() == x else x.lower())) )

3

Чтобы сделать его более универсальным, вы можете обернуть его в функцию, поэтому эти жестко заданные значения не нанесут никакого вреда, потому что они являются локальными.

При использовании эффективных подстановок-контейнеров (я буду использовать простой словарь), вы можете сохранить первый индекс каждой строки без потери производительности на много:

def your_function(list_of_strings): 

    encountered_strings = {} 
    result = [] 

    idx = 0 
    for astring in list_of_strings: 
     if astring in encountered_strings: # check if you already seen this string 
      result.append(encountered_strings[astring]) 
     else: 
      encountered_strings[astring] = idx 
      result.append(idx) 
      idx += 1 
    return result 

И это будет назначать индексы в порядке (даже если это не важно):

>>> your_function(['ll', 'll', 'll', 'hl', 'hl', 'hl', 'LL', 'LL', 'LL', 'HL', 'HL', 'HL']) 
[0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3] 

Это нужно только одну итерацию над списком строк, что позволяет даже обрабатывать генераторы и аналогичные.

6

Если условие состоит в том, что числа уникальны и точное число не имеет значения, то вы можете построить сопоставление каждого элемента в списке с уникальным номером на лету, назначив значения из объекта count:

from itertools import count 

names = ['ll', 'll', 'hl', 'hl', 'LL', 'LL', 'LL', 'HL', 'll'] 

d = {} 
c = count() 
numbers = [d.setdefault(i, next(c)) for i in names] 
print(numbers) 
# [0, 0, 2, 2, 4, 4, 4, 7, 0] 

Вы можете избавиться от дополнительных имен, используя map в списке и объекта подсчета, и настройки функции карты как {}.setdefault (см @ комментарий StefanPochmann в):

from itertools import count 

names = ['ll', 'll', 'hl', 'hl', 'LL', 'LL', 'LL', 'HL', 'll'] 
numbers = map({}.setdefault, names, count()) # call list() on map for Py3 
print(numbers) 
# [0, 0, 2, 2, 4, 4, 4, 7, 0] 

В качестве дополнительного, вы можете также использовать np.unique, в случае, если у вас уже есть NumPy установлена:

import numpy as np 

_, numbers = np.unique(names, return_inverse=True) 
print(numbers) 
# [3 3 2 2 1 1 1 0 3] 
+4

Нет необходимости в дополнительных переменных, если вы делаете «list (map ({}. Setdefault, names, кол-())) '. –

+0

@StefanPochmann Довольно аккуратный! –

+0

В первом решении вы можете использовать 'len (d)' вместо 'next (c)', a la: 'numbers = [d.setdefault (i, len (d)) для i в именах] – RootTwo

3

Если у вас есть k разных значений, это отображает их в целые числа 0 в k-1 в порядке первое появление:

>>> names = ['b', 'c', 'd', 'c', 'b', 'a', 'b'] 
>>> tmp = {} 
>>> [tmp.setdefault(name, len(tmp)) for name in names] 
[0, 1, 2, 1, 0, 3, 0] 
0

Вот похожее factorizing решение с collections.defaultdict и itertools.count:

import itertools as it 
import collections as ct 


names = ['ll', 'll', 'hl', 'hl', 'LL', 'LL', 'LL', 'HL', 'll'] 

dd = ct.defaultdict(it.count().__next__) 
[dd[i] for i in names] 
# [0, 0, 1, 1, 2, 2, 2, 3, 0] 

Каждое новое явление вызывает следующий целое число в itertools.count и добавляет новую запись в dd.