[Python] Waarde vinden in nested list

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • RonG
  • Registratie: Mei 2007
  • Laatst online: 25-05 10:52
Ik ben bezig met Python te leren en loop tegen iets aan wat ik niet goed kan oplossen en heb het gevoel dat ik veel te moeilijk denk. Ik heb een nested list:

code:
1
l = [[1, "Frits", 3], [2, "Dirk", 6], [3, "Jan", 30]]


Nu wil ik een nieuw element aan deze list toevoegen, maar alleen indien de naam nog niet voorkomt in de list. Zoals je ziet staat de naam op iedere 2e positie van ieder element. Stel dat de list geen nested list was maar gewoon een simpele list:

code:
1
l = ["Frits", "Dirk", "Jan"]


Kan ik simpelweg het volgende doen:

code:
1
2
3
new_name = "Peter"
if new_name not in l:
    l.append(new_name)


Ik heb nu het volgende wat wel werkt, maar ik denk dat dit "pythonesker" kan:

code:
1
2
3
4
5
6
7
new_name = "Peter"
exists = 0
for i, name, x in l:
    if name == new_name:
        exists += 1
if exists == 0:
    l.append([2, new_name, 7])


Kan dit korter of handiger? Ik unpack nu alles, maar ik weet al zeker dat ik alleen op de 2e positie van ieder element hoef te zoeken.

[ Voor 0% gewijzigd door RonG op 21-07-2015 15:54 . Reden: taalfoutje ]


Acties:
  • 0 Henk 'm!

  • kutagh
  • Registratie: Augustus 2009
  • Laatst online: 27-06 10:34
Korter kan zeker d.m.v. list comprehensions. Voor het n-de element van een array accessen, gebruik je de syntax array[n-1] (dus 1e element van array l = l[0]).
Python:
1
2
3
4
5
6
7
l = [[1, "Frits", 3], [2, "Dirk", 6], [3, "Jan", 30]]
new_name = "Frits"

filtered = [x for x in l if x[1] == new_name]

if len(filtered) == 0:
    l.append([2, new_name, 2])

Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 23:07
Jouw oorspronkelijke oplossing, maar dan verbeterd:
Python:
1
2
3
4
5
6
7
new_name = "Peter"
for x in l:
    if x[1] == new_name:
        exists = 1
        break
if exists == 0:
    l.append([2, new_name, 7])

1. Als je hebt gevonden wat je zocht. Stop dan het zoeken. Dat scheelt toch weer tijd. Vandaar de break.
2. Ook heeft de exists += 1 in jouw geval geen meerwaarde over exists = 1.
3. Aangezien je een standaard multi-dimensional array gebruikt, kan je ook direct a.d.v. de indexes je waardes opzoeken. Zie ook daarin mijn aanpassing:
Python:
1
2
3
print l[0][0] # 1
print l[1][2] # 6
print l[2][1] # Jan


Wil je het echt kort, dan is het antwoord hierboven al gegeven.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • RonG
  • Registratie: Mei 2007
  • Laatst online: 25-05 10:52
Thanks voor de reacties!
kutagh schreef op dinsdag 21 juli 2015 @ 14:48:
Korter kan zeker d.m.v. list comprehensions.
Ah daar had ik echt niet op gekomen, ik ga na de basics ook nog even goed in list comprehensions duiken. Er zit veel in maar ik blijf dat hele korte wel lastig vinden. ;)
CurlyMo schreef op dinsdag 21 juli 2015 @ 14:50:
Jouw oorspronkelijke oplossing, maar dan verbeterd:
Thanks voor de tips, ziet er inderdaad netter uit zo, stom dat ik niet op die x[1] kom, want dat principe van indexen snap ik wel.

[ Voor 0% gewijzigd door RonG op 21-07-2015 15:55 . Reden: weer taalfoutje.. ]


Acties:
  • 0 Henk 'm!

  • WernerL
  • Registratie: December 2006
  • Laatst online: 22:41
Een list comprehension lijkt me alleen zo overkill. Regel 4 van Kutagh zijn code kan ook eenvoudig met de filter functie geschreven worden

Python:
1
filtered = filter(lambda x: x[1] == new_name, l)


Levert exact hetzelfde resultaat op alleen is de code wat eenvoudiger. :-)

List comprehensions zijn bedoeld als syntatic sugar om lijsten op te bouwen zodat je niet handmatig for-loopjes hoeft te bouwen. Maar sommige zaken kunnen ook prima opgelost worden met filter, map of reduce.

[ Voor 26% gewijzigd door WernerL op 21-07-2015 15:56 ]

Roses are red, violets are blue, unexpected '{' on line 32.


Acties:
  • 0 Henk 'm!

Anoniem: 481943

Zoals WernerL aangeeft kan het ook zonder list comprehensions, één manier is het gebruik van any(iterable).

Python:
1
2
if not any( map(lambda x: x[1] == new_name, l) ):
    l.append([2, new_name, 7])


of met filter in plaats van map:

[code=python]
if not any( filter(lambda x: x[1] == new_name, l) ):
l.append([2, new_name, 7])
[/code]


met behulp van een generator expression:

Python:
1
2
if not any( (x[1] == new_name for x in l) ):
    l.append([2, new_name, 7])

[ Voor 50% gewijzigd door Anoniem: 481943 op 22-07-2015 21:53 ]


Acties:
  • 0 Henk 'm!

  • deadinspace
  • Registratie: Juni 2001
  • Laatst online: 21-06 13:44

deadinspace

The what goes where now?

RonG schreef op dinsdag 21 juli 2015 @ 14:30:
Nu wil ik een nieuw element aan deze list toevoegen, maar alleen indien de naam nog niet voorkomt in de list. Zoals je ziet staat de naam op iedere 2e positie van ieder element.
Je kunt de lijsten "binnenstebuiten draaien" met zip:
>>> l = [[1, "Frits", 3], [2, "Dirk", 6], [3, "Jan", 30]]
>>> zip(*l)
[(1, 2, 3), ('Frits', 'Dirk', 'Jan'), (3, 6, 30)]

>>> zip(*l)[1]
('Frits', 'Dirk', 'Jan')

Je snippet voor simpele lijsten zou dan zoiets worden:
Python:
1
2
3
new_name = "Peter"
if new_name not in zip(*l)[1]:
    l.append([2, new_name, 7])


Maar als de namen uniek moeten zijn dan is er misschien een beter idee: een dictionary gebruiken met de naam als key. Dan is een naam altijd gegarandeerd uniek. Testen of een naam aanwezig is is snel, onafhankelijk van de lengte van de lijst. Een naam updaten is makkelijk (en ook snel).

Je krijgt dan zoiets:

>>> l = {'Dirk': [2, 6], 'Jan': [3, 30], 'Frits': [1, 3]}
>>> l['Jan']
[3, 30]

>>> l['Jan'] = [3, 40]
>>> l
{'Dirk': [2, 6], 'Jan': [3, 40], 'Frits': [1, 3]}

>>> 'Peter' in l
False

>>> l['Peter'] = [5, 6]
>>> 'Peter' in l
True

Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Of gewoon de functie gebruiken die ervoor bedoeld is:

Python:
1
2
3
exists = any(itertools.ifilter(lambda x: x[1] == new_name, l))
if not exists:
  ...


De reden voor het gebruik van itertools is dat die een iterator teruggeeft. Wanneer je de built-in filter() functie gebruikt krijg je een list terug. Dit laat any() toe om te stoppen met itereren van het moment er 1 match gevonden is. Bij filter() zou de rest van de list voor niets gemaakt zijn.

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • RayNbow
  • Registratie: Maart 2003
  • Laatst online: 21:40

RayNbow

Kirika <3

Anoniem: 481943 schreef op dinsdag 21 juli 2015 @ 17:19:
of met filter in plaats van map:

Python:
1
2
if not any( filter(lambda x: x[1] == new_name, l) ):
    l.append([2, new_name, 7])
H!GHGuY schreef op dinsdag 21 juli 2015 @ 19:50:
Of gewoon de functie gebruiken die ervoor bedoeld is:

Python:
1
2
3
exists = any(itertools.ifilter(lambda x: x[1] == new_name, l))
if not exists:
  ...
Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import itertools
l = [[1, "Frits", 3], [2, "Dirk", 6], [3, "Jan", 30]]

class Evil(list):
    def __init__(self, *args, **kwargs):
        list.__init__(self, *args, **kwargs)
    
    def __nonzero__(self):
        return False


l.append( Evil([1, "Aap", 20]) )

new_name = "Aap"

exists_1 = any(itertools.ifilter(lambda x: x[1] == new_name, l))
exists_2 = any(x[1] == new_name for x in l)

print exists_1 # False
print exists_2 # True

Ipsa Scientia Potestas Est
NNID: ShinNoNoir


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

RayNbow schreef op woensdag 22 juli 2015 @ 07:13:
[...]

[...]

Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import itertools
l = [[1, "Frits", 3], [2, "Dirk", 6], [3, "Jan", 30]]

class Evil(list):
    def __init__(self, *args, **kwargs):
        list.__init__(self, *args, **kwargs)
    
    def __nonzero__(self):
        return False


l.append( Evil([1, "Aap", 20]) )

new_name = "Aap"

exists_1 = any(itertools.ifilter(lambda x: x[1] == new_name, l))
exists_2 = any(x[1] == new_name for x in l)

print exists_1 # False
print exists_2 # True
Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
l = range(10)

class Evil:
  def __init__(self, value):
    self.value = false;
  def __eq__(self, other):
    if isinstance(other, Types.IntType):
      return False
    return other == self.value

l.append(Evil(11))

print any(x == 11 for x in l)


Of beter gezegd:
Waarom zou je dat doen (lees: liegen)?

Zo kun je elke code kapot maken. Je "liegt" namelijk over de __nonzero__ check (i.e. bool conversie), dan kun je ook niet verwachten dat de rest van je code zomaar werkt.

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • RayNbow
  • Registratie: Maart 2003
  • Laatst online: 21:40

RayNbow

Kirika <3

H!GHGuY schreef op woensdag 22 juli 2015 @ 11:20:
[...]


Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
l = range(10)

class Evil:
  def __init__(self, value):
    self.value = false;
  def __eq__(self, other):
    if isinstance(other, Types.IntType):
      return False
    return other == self.value

l.append(Evil(11))

print any(x == 11 for x in l)


Of beter gezegd:
Waarom zou je dat doen (lees: liegen)?
Het was een gefabriceerd voorbeeld dat dichter bij de oorspronkelijke code staat.

Maar stel dat we een simpeler voorbeeld nemen:
Python:
1
2
3
4
5
6
import itertools
xs = [0, 1, 2, 3]
to_find = 0
exists = any(itertools.ifilter(lambda x: x == to_find, xs))

print exists

Wat komt hier uitrollen? Ik heb zelf wel eens te maken gehad dat een bepaalde waarde als False werd gezien en dat het resulteerde in onjuist gedrag.

In dit geval moet je niet filteren en any gebruiken; any is namelijk bedoeld voor iterables met booleans.

Wat wel werkt is het volgende:
Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
import itertools

def notempty(iterable):
    for x in iterable:
        return True
    else:
        return False

xs = [0, 1, 2, 3]
to_find = 0

exists = notempty(itertools.ifilter(lambda x: x == to_find, xs))
print exists

Ipsa Scientia Potestas Est
NNID: ShinNoNoir


Acties:
  • 0 Henk 'm!

  • Wolfboy
  • Registratie: Januari 2001
  • Niet online

Wolfboy

ubi dubium ibi libertas

Eindelijk een zinnig doel voor de for/else constructie ;)
Python:
1
2
3
4
5
for _, name, _ in l:
    if name == new_name:
        break
else:
    l.append([2, new_name, 7])
H!GHGuY schreef op woensdag 22 juli 2015 @ 11:20:
Of beter gezegd:
Waarom zou je dat doen (lees: liegen)?

Zo kun je elke code kapot maken. Je "liegt" namelijk over de __nonzero__ check (i.e. bool conversie), dan kun je ook niet verwachten dat de rest van je code zomaar werkt.
Het voorbeeld maakt het probleem duidelijker maar de nonzero operator kan in meer gevallen "verkeerde" resultaten geven. Eigenlijk zijn ze niet verkeerd maar je moet hem wel goed gebruiken :)

Je signature lijkt me zeer relevant hier :P

[ Voor 61% gewijzigd door Wolfboy op 22-07-2015 12:24 ]

Blog [Stackoverflow] [LinkedIn]


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

RayNbow schreef op woensdag 22 juli 2015 @ 12:05:
[...]

Het was een gefabriceerd voorbeeld dat dichter bij de oorspronkelijke code staat.

Maar stel dat we een simpeler voorbeeld nemen:
Python:
1
2
3
4
5
6
import itertools
xs = [0, 1, 2, 3]
to_find = 0
exists = any(itertools.ifilter(lambda x: x == to_find, xs))

print exists

Wat komt hier uitrollen? Ik heb zelf wel eens te maken gehad dat een bepaalde waarde als False werd gezien en dat het resulteerde in onjuist gedrag.

In dit geval moet je niet filteren en any gebruiken; any is namelijk bedoeld voor iterables met booleans.
Mijn excuses, foutje
Python:
1
2
3
exists = any(itertools.imap(lambda x: x[1] == new_name, l))
if not exists:
  ...


Fout goed gespot, maar slecht gecommuniceerd?
Wolfboy schreef op woensdag 22 juli 2015 @ 12:19:
Eindelijk een zinnig doel voor de for/else constructie ;)
Python:
1
2
3
4
5
for _, name, _ in l:
    if name == new_name:
        break
else:
    l.append([2, new_name, 7])


[...]
Deze is inderdaad ook prima. Persoonlijk vind ik em minder leesbaar (i.e. de intentie druipt er niet vanaf) maar misschien is dat omdat de for/else geen typische constructie is (ik ken maar 1 taal die hem heeft).
Het voorbeeld maakt het probleem duidelijker maar de nonzero operator kan in meer gevallen "verkeerde" resultaten geven. Eigenlijk zijn ze niet verkeerd maar je moet hem wel goed gebruiken :)
Sowieso moet je design-by-contract volgen. De __nonzero__ operator overloaden op manieren die dat contract schenden is gewoon not done. Ik had gewoon een bug geschreven en wou geen misbruik maken van een of andere assumption.

[ Voor 35% gewijzigd door H!GHGuY op 22-07-2015 14:05 ]

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • RonG
  • Registratie: Mei 2007
  • Laatst online: 25-05 10:52
Ok, dit gaat mij allemaal beetje te ver als beginneling :) Ik moet zeggen dat ik die for else nog het meest clean oogt (en begrijpelijk voor mij). Heb er nog even voor gezocht op internet en kwam dit tegen:
the else suite is executed after the for, but only if the for terminates normally (not by a break).
Ik ga sowieso even studeren op de functies verder genoemd, maar voor nu ben ik geholpen. Thanks!

Acties:
  • 0 Henk 'm!

  • Wolfboy
  • Registratie: Januari 2001
  • Niet online

Wolfboy

ubi dubium ibi libertas

H!GHGuY schreef op woensdag 22 juli 2015 @ 14:00:
Deze is inderdaad ook prima. Persoonlijk vind ik em minder leesbaar (i.e. de intentie druipt er niet vanaf) maar misschien is dat omdat de for/else geen typische constructie is (ik ken maar 1 taal die hem heeft).
Mee eens, ik schrijf al heel wat jaren voornamelijk Python en dit is de eerste keer dat ik er een echt legitiem doel voor heb :P

Denk overigens dat het met een kleine comment prima duidelijk kan zijn maar het is een vrij onbekende structuur inderdaad.

Blog [Stackoverflow] [LinkedIn]

Pagina: 1