2017-02-20 19 views
11

Mam listę ciągów. Chcę przypisać unikalny numer do każdego ciągu (dokładna liczba nie jest ważna) i utworzyć listę o tej samej długości, używając tych numerów w kolejności. Poniżej jest moja najlepsza próba na to, ale nie jestem zadowolony z dwóch powodów:Przypisanie numeru każdej unikatowej wartości na liście

  1. Zakłada ona, że ​​te same wartości są obok siebie

  2. musiałem zacząć listę z 0, w przeciwnym razie wyjście byłoby błędne

Mój kod:

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) 

Chcę, aby kod był bardziej ogólny, więc będzie działał z nieznaną listą. Jakieś pomysły?

+0

jak sortować listę przed zastosowaniem algorytmu –

Odpowiedz

11

bez korzystania z zewnętrznej biblioteki (sprawdź EDIT rozwiązania Pandas) można to zrobić w następujący sposób :

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

Krótkie wyjaśnienie:

W pierwszym wierszu przypisujesz numer do każdego unikalnego elementu na liście (przechowywany w słowniku d; możesz go łatwo stworzyć za pomocą słownika ze zrozumieniem; set zwraca unikalne elementy names).

Następnie, w drugim wierszu, wykonujesz listę i zapisujesz rzeczywiste liczby na liście numbers.

Jednym z przykładów dla zilustrowania, że ​​również działa dobrze na nieposortowane list:

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

To wyjście dla numbers:

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

Jak widać, liczba 1 związane ll pojawia się w odpowiednich miejscach.

EDIT

Jeśli masz Pandas dostępne, można również użyć pandas.factorize:

import pandas as pd 

pd.factorize(names) 

powróci

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

Dlatego

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

Ponieważ odwzorowujesz łańcuchy znaków na liczby całkowite, co sugeruje użycie dyktanda. Więc można wykonać następujące czynności:

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

Dół, staraj się wyjaśnić? –

-1

Można spróbować to również: -

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

Jaki jest sens zawijania 'indexList.index' w lambda? –

+0

@StefanPochmann, tak, możesz zapisać to również map (indexList.index, names), jeśli nie musisz pisać lambda –

2

udało mi się zmodyfikować skrypt bardzo lekko i wygląda ok:

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) 

Widać to bardzo simmilar, jedyną rzeczą jest to, że zamiast dodawania numer dla następnego elementu i dodać numer do bieżącego elementu. To wszystko. Och, i sortowanie. Sortuje najpierw kapitał, a następnie małe litery w tym przykładzie, możesz grać z sort(key= lambda:x ...), jeśli chcesz to zmienić. (Być może w ten sposób: names.sort(key = lambda x: (x.upper() if x.lower() == x else x.lower())) )

3

Aby uczynić go bardziej ogólnym, można zawinąć go w funkcję, więc te zakodowane wartości nie wyrządzają żadnej szkody, ponieważ są lokalne.

Jeśli używasz wydajnych odnośników-kontenery (użyję zwykłego słownika) można zachować pierwszy indeks każdej struny nie tracąc zbyt dużo wydajność:

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 

A to przypisać indeksy w kolejności (nawet jeśli nie jest to istotne):

>>> 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] 

to potrzebuje tylko jednej iteracji na liście ciągów, które umożliwia nawet generatory procesowe i podobne.

6

Jeśli warunkiem jest, że liczby są niepowtarzalne, a dokładna liczba nie jest ważna, można zbudować mapowanie odnoszące się do każdej pozycji na liście do unikalnego numeru w locie, przypisując wartości z obiektu zliczania:

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] 

można pozbyć się dodatkowych nazw za pomocą map na liście i obiektu liczenia i ustawiania funkcji map jako {}.setdefault (patrz @ komentarzu StefanPochmann za):

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] 

jako dodatkowy, można również użyć np.unique, w przypadku jeśli masz już numpy zainstalowane:

import numpy as np 

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

Nie potrzebujesz dodatkowych zmiennych, jeśli wykonujesz 'list (map ({}. Setdefault, names, count())) '. –

+0

@StefanPochmann Całkiem schludny! –

+0

W pierwszym rozwiązaniu możesz użyć 'len (d)' zamiast 'next (c)', a la: 'numbers = [d.setdefault (i, len (d)) dla i w nazwach] – RootTwo

3

Jeśli masz k różne wartości, to mapuje je do liczb całkowitych 0 do k-1 w kolejności pierwszy występ:

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

Oto podobna factorizing rozwiązanie z collections.defaultdict i 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] 

Każde nowe zjawisko nazywa najbliższej liczby całkowitej w itertools.count i dodaje nowy wpis do dd.