แผนที่สองทาง / ย้อนกลับ [ซ้ำ]


103

ฉันกำลังทำสวิตช์บอร์ดนี้ใน python ซึ่งฉันต้องติดตามว่าใครกำลังคุยกับใครดังนั้นถ้า Alice -> Bob แสดงว่า Bob -> Alice

ใช่ฉันสามารถเติมแผนที่แฮชได้สองแผนที่ แต่ฉันสงสัยว่ามีใครมีความคิดที่จะทำกับแผนที่นี้หรือไม่

หรือแนะนำโครงสร้างข้อมูลอื่น

ไม่มีการสนทนาหลายรายการ สมมติว่านี่เป็นศูนย์บริการลูกค้าดังนั้นเมื่ออลิซโทรเข้าไปที่แผงสวิตช์เธอจะคุยกับบ็อบเท่านั้น คำตอบของเขายังไปที่เธอเท่านั้น


15
โปรดทราบว่าคุณกำลังอธิบายแผนที่เชิงอคติ
Nick Dandoulakis

นี่คือการใช้งานพจนานุกรม bijective อย่างง่ายแม้ว่าฉันจะไม่รู้ว่าจะตรงตามข้อกำหนดด้านประสิทธิภาพของคุณหรือไม่ (ลิงก์จากบทความบล็อกนี้เกี่ยวกับการเพิ่ม Bimap สำหรับ Pythonซึ่งมีการอภิปรายที่ดีในหัวข้อนี้)
ระบบหยุดชั่วคราว

2
ถ้าอลิซกำลังคุยกับบ็อบฉันเข้าใจว่าเธอไม่สามารถคุยกับชาร์ลส์ได้ บ็อบไม่สามารถคุยกับใครได้อีก? นอกจากนี้คุณสามารถสนทนาได้กี่คนและกี่คนในแต่ละช่วงเวลา
ระบบ PAUSE

ไม่ ... ไม่อยู่บนสวิตช์บอร์ดของฉัน ข้อความใด ๆ ที่อลิซส่งถึงฉันจะต้องส่งถึงบ็อบ เพียงแค่ฉันจะกำหนดเส้นทางการสนทนาหลายพันรายการพร้อมกัน แต่แต่ละคนจะคุยกับคนอื่นทีละคนเท่านั้น
Sudhir Jonathan

1
ไม่ ... ฉันแค่ต้องการกำหนดเส้นทางข้อความของลูกค้าไปยังโอเปอเรเตอร์และในทางกลับกัน ... ไม่ได้จัดเก็บการสนทนา แต่อย่างใด
Sudhir Jonathan

คำตอบ:


93

คุณสามารถสร้างประเภทพจนานุกรมของคุณเองโดยการคลาสย่อยdictและเพิ่มตรรกะที่คุณต้องการ นี่คือตัวอย่างพื้นฐาน:

class TwoWayDict(dict):
    def __setitem__(self, key, value):
        # Remove any previous connections with these values
        if key in self:
            del self[key]
        if value in self:
            del self[value]
        dict.__setitem__(self, key, value)
        dict.__setitem__(self, value, key)

    def __delitem__(self, key):
        dict.__delitem__(self, self[key])
        dict.__delitem__(self, key)

    def __len__(self):
        """Returns the number of connections"""
        return dict.__len__(self) // 2

และใช้งานได้ดังนี้:

>>> d = TwoWayDict()
>>> d['foo'] = 'bar'
>>> d['foo']
'bar'
>>> d['bar']
'foo'
>>> len(d)
1
>>> del d['foo']
>>> d['bar']
Traceback (most recent call last):
  File "<stdin>", line 7, in <module>
KeyError: 'bar'

ฉันแน่ใจว่าฉันไม่ได้ครอบคลุมทุกกรณี แต่คุณควรเริ่มต้น


2
@SudhirJonathan: คุณสามารถไปได้ไกลกว่านี้ด้วยแนวคิดนี้ตัวอย่างเช่นเพิ่ม.addวิธีการเพื่อให้คุณสามารถทำสิ่งต่างๆเช่นd.add('Bob', 'Alice')แทนที่จะใช้ไวยากรณ์ที่ฉันแสดง ฉันจะรวมการจัดการข้อผิดพลาดบางอย่างด้วย แต่คุณจะได้รับแนวคิดพื้นฐาน :)
Sasha Chedygov

1
ฉันเดาว่าสิ่งนี้อยู่ภายใต้การเพิ่มเติมเหล่านั้น แต่การลบคู่คีย์เก่าเมื่อตั้งค่าใหม่จะเป็นประโยชน์ ( d['foo'] = 'baz'จะต้องลบbarคีย์เพิ่มเติม)
beardc

@TobiasKienzler: คุณถูกต้อง แต่นี่ถูกระบุว่าเป็นข้อสันนิษฐานในคำถาม
Sasha Chedygov

5
นอกจากนี้ยังควรค่าแก่การกล่าวถึง: คลาสย่อยdictทำให้เกิดพฤติกรรมที่ทำให้เข้าใจผิดที่นี่เพราะถ้าคุณสร้างวัตถุด้วยเนื้อหาเริ่มต้นโครงสร้างจะเสีย __init__ต้องมีการลบล้างเพื่อให้การก่อสร้างd = TwoWayDict({'foo' : 'bar'})ทำงานได้อย่างถูกต้อง
Henry Keiter

19
แค่อยากบอกว่ามีห้องสมุดสำหรับสิ่งนี้: pip install bidict. URL: pypi.python.org/pypi/bidict
user1036719

46

ในกรณีพิเศษของคุณคุณสามารถจัดเก็บทั้งสองอย่างไว้ในพจนานุกรมเดียว:

relation = {}
relation['Alice'] = 'Bob'
relation['Bob'] = 'Alice'

เนื่องจากสิ่งที่คุณกำลังอธิบายคือความสัมพันธ์แบบสมมาตร A -> B => B -> A


3
อืม ... ใช่ฉันชอบอันนี้ที่สุด พยายามหลีกเลี่ยงการสร้างสองรายการ แต่นี่เป็นความคิดที่ดีที่สุดแล้ว
Sudhir Jonathan

1
ยังคงคิดว่าแผนที่สองทางควรจะเป็นไปได้: - /
Sudhir Jonathan

หากจะต้องมีประสิทธิภาพจากนั้นภายใต้ฝาครอบคุณต้องมีคีย์ทั้งสองเพื่อจัดทำดัชนีในโครงสร้างข้อมูลดัชนีบางอย่างไม่ว่าจะเป็นแฮชรายการที่เรียงลำดับไบนารีทรีอาร์เรย์ส่วนต่อท้ายที่เต็มไปด้วย sistrings หรือบางสิ่ง แปลกใหม่มากขึ้น วิธีง่ายๆในการทำ Python คือใช้แฮช
Kragen Javier Sitaker

@SudhirJonathan ถ้าคุณต้องการอย่างแท้จริงสองแผนที่ทางมีลักษณะที่bidictเป็นเช่นที่กล่าวถึงในคำถามนี้ - ทราบปัญหาประสิทธิภาพการทำงานที่กล่าวถึงโดยอายะในความคิดเห็นในคำถามล่อของฉันแม้ว่า
Tobias Kienzler

25

ฉันรู้ว่ามันเป็นคำถามที่เก่า แต่ผมอยากจะพูดถึงอีกวิธีที่ดีในการแก้ไขปัญหานี้คือแพคเกจหลามbidict ตรงไปตรงมามากที่จะใช้:

from bidict import bidict
map = bidict(Bob = "Alice")
print(map["Bob"])
print(map.inv["Alice"])

24

ฉันจะเติมแฮชที่สองด้วย

reverse_map = dict((reversed(item) for item in forward_map.items()))

7
มีวงเล็บพิเศษอยู่ที่นั่น:reverse_map = dict(reversed(item) for item in forward_map.items())
Andriy Drozdyuk

1
วิธีง่ายๆที่ดีหากคุณจะไม่อัปเดตคำสั่งต่อไป ฉันใช้my_dict.update(dict(reversed(item) for item in my_dict.items()))
Gilly

เมื่อใช้รหัสนี้ใน Python 3 ฉันได้รับคำเตือน: Unexpected type(s): (Generator[Iterator[Union[str, Any]], Any, None]) Possible types: (Mapping) (Iterable[Tuple[Any, Any]]). มีความคิดอย่างไรที่จะกำจัดคำเตือน?
Kerwin Sneijders

ใน python3 ไม่มีคำเตือนนี้:my_dict.update({item[1]: item[0] for item in my_dict.items()})
mmindenhall

9

แผนที่แฮชสองแผนที่น่าจะเป็นโซลูชันที่ทำงานได้เร็วที่สุดโดยสมมติว่าคุณสามารถสำรองหน่วยความจำได้ ฉันจะรวมสิ่งเหล่านี้ไว้ในคลาสเดียว - ภาระของโปรแกรมเมอร์คือการทำให้แน่ใจว่าแฮชแมปทั้งสองซิงค์กันอย่างถูกต้อง


2
+1 นั่นคือสิ่งที่เสนอราคาโดยทั่วไปบวกน้ำตาลสำหรับการเข้าถึงการทำแผนที่ผกผันโดยใช้mydict[:value]เพื่อรับkey(ในราคาของประสิทธิภาพ)
Tobias Kienzler

6

คุณมีสองประเด็นที่แยกจากกัน

  1. คุณมีวัตถุ "การสนทนา" หมายถึงบุคคลสองคน เนื่องจากบุคคลสามารถสนทนาได้หลายรายการคุณจึงมีความสัมพันธ์แบบกลุ่มต่อกลุ่ม

  2. คุณมีแผนที่จากบุคคลไปยังรายการการสนทนา การเปลี่ยนใจเลื่อมใสจะมีบุคคลคู่หนึ่ง

ทำอะไรแบบนี้

from collections import defaultdict
switchboard= defaultdict( list )

x = Conversation( "Alice", "Bob" )
y = Conversation( "Alice", "Charlie" )

for c in ( x, y ):
    switchboard[c.p1].append( c )
    switchboard[c.p2].append( c )

5

ไม่ไม่มีวิธีใดที่จะทำได้โดยไม่ต้องสร้างพจนานุกรมสองเล่ม จะเป็นไปได้อย่างไรที่จะนำสิ่งนี้ไปใช้กับพจนานุกรมเพียงเล่มเดียวในขณะที่ยังคงให้ประสิทธิภาพเทียบเท่า

คุณควรสร้างประเภทที่กำหนดเองซึ่งรวมพจนานุกรมสองพจนานุกรมและแสดงฟังก์ชันการทำงานที่คุณต้องการ


3

วิธีที่ละเอียดน้อยกว่าโดยยังคงใช้การย้อนกลับ:

dict(map(reversed, my_dict.items()))


2

อีกวิธีหนึ่งที่เป็นไปได้คือการใช้คลาสย่อยdictที่มีพจนานุกรมต้นฉบับและติดตามเวอร์ชันที่กลับรายการ การเก็บคำสั่งแยกจากกันจะมีประโยชน์หากคีย์และค่าทับซ้อน

class TwoWayDict(dict):
    def __init__(self, my_dict):
        dict.__init__(self, my_dict)
        self.rev_dict = {v : k for k,v in my_dict.iteritems()}

    def __setitem__(self, key, value):
        dict.__setitem__(self, key, value)
        self.rev_dict.__setitem__(value, key)

    def pop(self, key):
        self.rev_dict.pop(self[key])
        dict.pop(self, key)

    # The above is just an idea other methods
    # should also be overridden. 

ตัวอย่าง:

>>> d = {'a' : 1, 'b' : 2} # suppose we need to use d and its reversed version
>>> twd = TwoWayDict(d)    # create a two-way dict
>>> twd
{'a': 1, 'b': 2}
>>> twd.rev_dict
{1: 'a', 2: 'b'}
>>> twd['a']
1
>>> twd.rev_dict[2]
'b'
>>> twd['c'] = 3    # we add to twd and reversed version also changes
>>> twd
{'a': 1, 'c': 3, 'b': 2}
>>> twd.rev_dict
{1: 'a', 2: 'b', 3: 'c'}
>>> twd.pop('a')   # we pop elements from twd and reversed  version changes
>>> twd
{'c': 3, 'b': 2}
>>> twd.rev_dict
{2: 'b', 3: 'c'}

2

มีไลบรารีขยายคอลเลกชันใน pypi: https://pypi.python.org/pypi/collections-extended/0.6.0

การใช้คลาส bijection นั้นง่ายพอ ๆ กับ:

RESPONSE_TYPES = bijection({
    0x03 : 'module_info',
    0x09 : 'network_status_response',
    0x10 : 'trust_center_device_update'
})
>>> RESPONSE_TYPES[0x03]
'module_info'
>>> RESPONSE_TYPES.inverse['network_status_response']
0x09

2

ฉันชอบข้อเสนอแนะของ bidict ในหนึ่งในความคิดเห็น

pip install bidict

การใช้งาน:

# This normalization method should save hugely as aDaD ~ yXyX have the same form of smallest grammar.
# To get back to your grammar's alphabet use trans

def normalize_string(s, nv=None):
    if nv is None:
        nv = ord('a')
    trans = bidict()
    r = ''
    for c in s:
        if c not in trans.inverse:
            a = chr(nv)
            nv += 1
            trans[a] = c
        else:
            a = trans.inverse[c]
        r += a
    return r, trans


def translate_string(s, trans):
    res = ''
    for c in s:
        res += trans[c]
    return res


if __name__ == "__main__":
    s = "bnhnbiodfjos"

    n, tr = normalize_string(s)
    print(n)
    print(tr)
    print(translate_string(n, tr))    

เนื่องจากไม่มีเอกสารเกี่ยวกับเรื่องนี้มากนัก แต่ฉันมีคุณสมบัติทั้งหมดที่ต้องการจากการทำงานอย่างถูกต้อง

พิมพ์:

abcbadefghei
bidict({'a': 'b', 'b': 'n', 'c': 'h', 'd': 'i', 'e': 'o', 'f': 'd', 'g': 'f', 'h': 'j', 'i': 's'})
bnhnbiodfjos

1

โมดูลส่วนขยาย kjbuckets C มีโครงสร้างข้อมูล "กราฟ" ซึ่งฉันเชื่อว่าให้สิ่งที่คุณต้องการ


ขออภัยฉันไม่ได้พูดถึงมัน แต่อยู่ในแอพเอ็นจิ้น ... ดังนั้นจึงไม่มีส่วนขยาย C
Sudhir Jonathan

1

นี่เป็นอีกหนึ่งการใช้งานพจนานุกรมสองทางโดยการขยายdictคลาสpythons ในกรณีที่คุณไม่ชอบสิ่งอื่น ๆ เหล่านี้:

class DoubleD(dict):
    """ Access and delete dictionary elements by key or value. """ 

    def __getitem__(self, key):
        if key not in self:
            inv_dict = {v:k for k,v in self.items()}
            return inv_dict[key]
        return dict.__getitem__(self, key)

    def __delitem__(self, key):
        if key not in self:
            inv_dict = {v:k for k,v in self.items()}
            dict.__delitem__(self, inv_dict[key])
        else:
            dict.__delitem__(self, key)

ใช้เป็นพจนานุกรม python ปกติยกเว้นในการก่อสร้าง:

dd = DoubleD()
dd['foo'] = 'bar'

1

วิธีที่ฉันชอบทำสิ่งนี้คือ:

{my_dict[key]: key for key in my_dict.keys()}

1
ยินดีต้อนรับสู่ StackOverflow! โปรดแก้ไขคำตอบของคุณและเพิ่มคำอธิบายเพิ่มเติมสำหรับรหัสของคุณควรเพิ่มคำอธิบายด้วยว่าเหตุใดจึงแตกต่างจากคำตอบอื่น ๆ อีก 14 ข้อ คำถามนี้มีอายุมากกว่าสิบปีและมีคำตอบที่ได้รับการยอมรับแล้วและมีคำตอบที่อธิบายได้ดีและได้รับการโหวตสูงเช่นกัน หากไม่มีรายละเอียดเพิ่มเติมในโพสต์ของคุณแสดงว่ามีคุณภาพต่ำกว่าและอาจถูกลดคะแนนหรือลบออก การเพิ่มข้อมูลเพิ่มเติมดังกล่าวจะช่วยพิสูจน์การมีอยู่ของคำตอบของคุณ
Das_Geek
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.