Seite 1 von 1

indexable skip list

Verfasst: Sonntag 10. April 2022, 05:33
von Brando
Hallo, in folgendem Code versuche ich mich an der get_node Funktion und der len Funktion. Folgender Code:

Code: Alles auswählen

# A program to implement a skip list.

# TODO: implement the `getNode` function, which is a prerequisite for __getitem__ and __setitem__
# TODO: implement the `length`

import random
import math as np

class Node:
  """
  An item in the skip list holding the data itself and pointers to 
  the successors of the item at all levels.
  """
 
  def __init__(self, data, max_lvl):
    self.data = data
    self.successors = [None] * (max_lvl + 1)
    # By convention, the width is 0 if no successor is present.
    # Otherwise it specifies the deviation of the indices between 
    # this item and the succeeding item in a level.
    self.widths = [0] * (max_lvl + 1)

  def __str__(self):
    return f'Node(data={self.data}, \
successors={list(map(lambda n: n.data if n else None, self.successors))}, \
widths={self.widths})'

class SkipList:
  """
  A class modelling a skip list and providing common list operations.
  """
  
  def __init__(self, max_lvl, p):
    self.max_lvl = max_lvl
    self.head = None
    self.p = p

  def insert(self, data, index = None):
    """
    Insert a new element into the list.
    
    If index isn't specified, or the index exceeds the highest index 
    in the list, the item is inserted at the tail of the list.
    """
    if self.head == None:
      self.head = Node(data, self.max_lvl)
      return self.head
    else:
      n = Node(data, self.max_lvl)
      if index is not None and index < 1:
        # item will be inserted before current head
        n.successors = [self.head] * (self.max_lvl + 1)
        n.widths = [1] * (self.max_lvl + 1)
        self.head = n
      else:
        current = self.head
        # find the item in each level that will preceed the newly inserted item
        to_update = [[None, 0]] * (self.max_lvl + 1)
        i = 0
        for level in range(self.max_lvl, -1, -1):
          while current.successors[level] and \
                (index == None or current.widths[level] + i < index):
            i = i + current.widths[level]
            current = current.successors[level]
          to_update[level] = (current, i)

        # choose the max level in that a pointer to the new item should be added
        max_lvl = 0
        while random.random() < self.p and max_lvl < self.max_lvl:
          max_lvl = max_lvl + 1

        # insert the new item and update its predecessors 
        for level in range(self.max_lvl + 1):
          pred, pred_index = to_update[level]
          if level <= max_lvl:
            n.successors[level] = pred.successors[level]
            new_pred_width = i - pred_index + 1
            if pred.successors[level]:
              n.widths[level] = pred.widths[level] - new_pred_width + 1
            pred.widths[level] = new_pred_width
            pred.successors[level] = n
          else:
            pred.widths[level] = pred.widths[level] + 1
      return n

  def get_node(self, i):
    node = self.p
    self.maxlevels = int(1 + np.log(self.max_lvl, 2))
    i += 1
    for level in reversed(range(self.maxlevels)):
        while node.widths[level] <= i:
            i -= node.widths[level]
            node = node.successors[level]
    return node
    """
    Returns the node of this skip list at the given index or None if no 
    item exist for this index.
    """
    # TODO your code here
    

  def __getitem__(self, index):
    """Overwrites reading with the array operator `list[i]`."""
    n = self.get_node(index)
    if n:
      return n.data
    else:
      raise IndexError

  def __setitem__(self, index, value):
    """Overwrites writing with the array operator `list[i]`."""
    n = self.get_node(index)
    if n:
      n.data = value
    else:
      raise IndexError

  def __len__(self):
    """Return the amount of items in this list."""
    # TODO your code here
    o = self.p
    length = 0
    if o == None: 
        length = 0 
    else: 
        length = 1
    return self.length

  def __str__(self):
    result = ""
    if self.head:
      for i in range(self.max_lvl, -1, -1):
        str = f"lvl {i}: {self.head.data}"
        current = self.head
        while current.successors[i]:
          str += f"  -{current.widths[i]}>  {current.successors[i].data}"
          current = current.successors[i]
        if result != "":
          result += "\n"
        result += str
    return result

if __name__ == 'builtins' or __name__ == '__main__':
  l = SkipList(4, .5)
  l.insert(15)
  l.insert(4)
  l.insert(2)
  l.insert(5)
  l.insert(10)
  l.insert(11)
  l.insert(56)
  l.insert(200, 0)
  l.insert(400, 7)
  print("Skip List structure:")
  print(l)
Funktioniert aber noch nicht, so wie ich es will. Widths wird gibt es nicht.

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 09:01
von Brando
So weit muss ich also durch die Liste iterieren und die Items zählen für die len Funktion! Wie mache ich das?

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 09:19
von Sirius3
Eingerückt wird mit 4 Leerzeichen pro Ebene, nicht 2.
`math` `np` zu nennen, führt zur totalen Verwirrung. Nicht machen! Du benutzt daraus ja auch gar nichts.
\ benutzt man nicht, um Zeilen fortzusetzen, das ist schwierig zu lesen und zu verstehen, es gibt immer bessere Lösungen, hier bei Dir 3 Strings.
Man benutzt keine kryptischen Abkürzungen. Vokale sind nicht teuerer als Konsonanten: max_lvl -> max_level!
Zusammengehörende Daten gehören zusammen, also successors und widths in einer Liste.

Code: Alles auswählen

class Node:
    """
    An item in the skip list holding the data itself and pointers to 
    the successors of the item at all levels.
    """

    def __init__(self, data, max_level):
        self.data = data
        # By convention, the width is 0 if no successor is present.
        # Otherwise it specifies the deviation of the indices between 
        # this item and the succeeding item in a level.
        self.successors = [(None, 0)] * (max_level + 1)

    def __str__(self):
        return (f'Node(data={self.data}, '
                f'successors={[n.data if n else None for n, w in self.successors]}, '
                f'widths={[w for n, w in self.successors]})')
In `get_node` wird eine neues Attribut eingeführt (maxlevels). Alle Attrribute müssen aber schon in __init__ angelegt werden. Hier macht das neue Attribut aber auch gar keinen Sinn (ich glaube die Variable an sich ist überflüssig).
Genauso wird in __len__ auf ein Attribut `length` zugegriffen, das gar nicht exisitiert.
`str` ist der Name eines Types, das sollte nicht durch eine lokale Variable verdeckt werden.
Die `insert`-Methode ist mir zu lang und zu wirr, um erkennen zu können, ob da alles richtig ist.
Weißt Du, wie das Prinzip der Skip-List funktioniert?

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 09:19
von ThomasL
Ich weiss ja nicht ob ihr/du schon O() Notation kennt, aber um die Länge einer Liste zu bestimmen über alle Elemente zu iterieren, ist der worst case, da O(n).
Erstelle einfach eine Variable self._length die in __init__ mit 0 startet und beim einfügen wird sie erhöht, beim löschen erniedrigt
und in __len__ gibst du einfach den Wert zurück. Best case O(1).

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 09:39
von Brando
Also für die Länge habe ich self.self_length gebildet. Aber es fehlt noch die Richtigkeit folgender Methode, die ich so instantiiert habe:

Code: Alles auswählen

def get_node(self, i):
    r = self.head
    if r != None:
        for level in range(self.max_lvl, -1, -1):
            if r.data != i: 
                r = r.successors[level]
        return r 
    return None 

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 10:11
von Sirius3
@ThomasL: ich glaube, es soll die Länge ohne eine weitere Variable ermittelt werden, sonst lernt man ja nichts, und da ist der mittlere Case O(log(n)).

@Brando: nochmal die Frage, ob Du die funktionsweise einer Skip-Liste verstanden hast? Ohne ein genaues Bild davon zu haben, wird es schwierig eine korrekte Implementierung hin zu bekommen.
`i` (schlechter Name) ist der `index`, und hat nichts mit `data` zu tun. `r` ist auch ein schlechter Name für ein `node`.

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 10:24
von Brando
Is the following correct?

Code: Alles auswählen

def get_node(self, i):
    r = self.head
    if r != None:
        for level in range(self.max_lvl, -1, -1):
            if r.widths[level] != i: 
                r = r.successors[level]
        return r 
    return None 

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 10:38
von __deets__
Hast du es probiert? Hat es das geliefert, was es liefern soll?

Mit None vergleicht man nicht mit !=, sondern mit dem is-Operator.

Code: Alles auswählen

if r is not None:

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 10:49
von Brando
Oder dieser Code:

Code: Alles auswählen

def get_node(self, k):
    x = self.head
    pos = 0 
    for i in range(self.max_lvl, -1, -1):
        while pos + x.widths[i] < k:
            pos = pos + x.widths[i]
            x = x.successors[i]
    if x == None:
        return None
    else:
        return x 

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 10:56
von __deets__
Ich bin mir unsicher, was du hier gerade probierst. Raetst du Code, und hoffst, dass wir dann sagen "ja, aber eigentlich so"? Denn ob das funktioniert, das siehst du doch selbst, wenn es das tut, was es tun soll, oder nicht?

Dieses if/else ist quatsch. Denn wenn x None ist, dann kannst du es auch einfach zurueckgeben.

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 11:48
von __blackjack__
@Brando: Die `__str__()`-Methode produziert eine falsche Zeichenkette weil man `Node` ja gar nicht mit den Argumenten aufrufen kann, die da suggeriert werden. Und das würde so auch eher in die `__repr__()`-Methode gehören.

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 11:48
von Brando
Leider bekomme ich vom System in dem ich das programmieren soll nur zurück ob die asserts funktionieren. Ich kann also keine Probeausgaben machen, oder sonst etwas zusätzlich ausgeben! So weiß ich nicht wie ich dran bin!

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 11:50
von Brando
Noch ein Lösungvorschlag:

Code: Alles auswählen

def get_node(self, k):
    
    
    node = self.head
    k += 1
    for level in reversed(range(self.max_lvl)):
        while node.widths[level] <= k:
            k -= node.widths[level]
            node = node.successors[level]
    return node

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 11:54
von Sirius3
Dann benutze doch ein richtiges Python zum programmieren. Wenn Du nur so ein eingeschränktes System hast, ist ein sinnvolles Entwickeln nicht möglich.

Wie würdest Du testen, das Deine get_node-Methode funktioniert?

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 11:55
von __deets__
Du kannst den Code *lokal* ausfuehren, und dann siehst du auch all deinen Ausgaben, um zu verstehen, was da passiert.

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 16:11
von Brando
Also die Lösung beim Einfügen die Elemente zu zählen stößt auf folgendes Problem:
Skip List structure:
lvl 4: 200 -1> 15
lvl 3: 200 -1> 15 -2> 2 -1> 5
lvl 2: 200 -1> 15 -2> 2 -1> 5
lvl 1: 200 -1> 15 -1> 4 -1> 2 -1> 5
lvl 0: 200 -1> 15 -1> 4 -1> 2 -1> 5 -1> 10 -1> 11 -1> 400 -1> 56

Sind das jetzt 8 Elemente, oder sind es über alle Ebenen zusammen genommen 19?
Dann habe ich jetzt den Code in PyCharm ausprobiert, und bekomme auch obiges Resultat, aber es werden einige Methoden im Code gar nicht aktiviert. Es scheint so zu sein, dass Codeelemente erst im assert geprüft werden. Dort wird es auch gegen eine Liste mit 15000 Elementen geprüft!

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 16:35
von Brando
Habe jetzt mit PyCharm das Programm übernommen. Erstes Problem: beim Einfügen zu zählen trifft auf Schwierigkeiten.

Skip List structure:
lvl 4: 200 -1> 15
lvl 3: 200 -1> 15 -2> 2 -1> 5
lvl 2: 200 -1> 15 -2> 2 -1> 5
lvl 1: 200 -1> 15 -1> 4 -1> 2 -1> 5
lvl 0: 200 -1> 15 -1> 4 -1> 2 -1> 5 -1> 10 -1> 11 -1> 400 -1> 56

Es wurden nur 8 Elemente eingefügt, aber über die Ebenen verteilt sind es 19!

Das zweite Problem: Bestimmte Codeabschnitte werden in PyCharm nicht verwendet, sie werden erst in den asserts abgeprüft! Ich kann also meinen Code nicht richtig prüfen! Dort wird auch gegenüber 15000 Eingabedaten geprüft!

Re: indexable skip list

Verfasst: Sonntag 10. April 2022, 19:36
von Sirius3
@Brando: ich schrieb ja schon, dass das Zählen beim Einfügen nicht sinnvoll ist, sondern dass es wirklich eine __len__-Methode braucht, die dann die Anzahl der Elemente in der Liste zählt.
Und wenn ich Deine Frage hier so lese, dann hast Du noch nicht verstanden was eine Skip-Liste ist.
Und ohne dieses Verständnis kannst Du schlecht weitere Methoden implementieren.
Wenn Du die eigentlichen Tests nicht kennst, dann mußt Du wohl oder übel selbst Tests schreiben.

Re: indexable skip list

Verfasst: Dienstag 12. April 2022, 16:21
von FreakyViolet
Hallo zusammen,

mal zur Aufklärung...der Code stammt aus einem Online-Kurs zu Sustainable Software Engineering, den ich auch gerade belege, und muss als Hausaufgabe gelöst werden. In diesem Abschnitt geht es um die Energieeffizienz verschiedener Algorithmen/Datenstrukturen und Beispielen - allerdings ist der Kurs viel breiter gefasst und behandelt zu 80% theoretische Ansätze, wie man in der Softwarearchitektur, Hardware-/Providerauswahl, Projektmanagement und Unternehmensführung effizientere Lösungen zum Zweck der Ressourcenschonung entwickeln kann. Deshalb fühlt es sich etwas überfordernd an, dass nun zum Ende hin für das Bestehen Programmieraufgaben gefordert werden. Ich besuche den Kurs aus meiner Demand Management Rolle und meine Python Basiskenntnisse sind da wohl zu dünn für... und anderen Teilnehmer finden das Thema wohl auch spannend ohne Developer zu sein (wie ich oder vllt. Brando)...
Man kann tatsächlich auch nichts zum Test ausführen, weil direkt auto-bewertet wird. Aber wie schon erwähnt, könnte man auch einfach lokal bei sich kopieren und probieren.

Deswegen wäre ich super dankbar, falls ihr vielleicht nochmal erklären könntet, was falsch läuft bzw. auf was man achten müsste. Also keine fertige Lösung, so vermessen will ich gar nicht sein und mag ja auch was lernen und nicht nur abschreiben :D Aber im kursinternen Teilnehmerforum darf man die Lösungen nicht miteinander besprechen/austauschen/sich helfen und alleine bekommen wir es wohl nicht alle hin :/ Und ich habe auch verstanden, warum eine Skip List effizienter ist, als jedes einzelne Item abzuklappern. Aber ich kann es einfach nicht umsetzen/erweitern.

Hier mal der originale Aufgabentext:
An exercise to give you an insight into a more advanced data structure and the implementation of its operations

The data structure we will look at is an Indexable Skip List. A Skip List is an implementation of the list data structure that has computational complexity of O(log(n)) for inserting, removing and looking up (by value) items.

It achieves that by using a simple singly linked list alongside multiple ‘express lanes’. These additional levels of the list skip the inserting of new items with a specified probability (p). Instead the deviation of the index to the next item is kept (width). The higher the level the less items are present.

When inserting a new item firstly the highest level (max_lvl) is iterated until the index where to insert is bypassed or the end of the list on that level is reached. Then, the search is continued in the next lower level starting from the last visited item. Doing so, not all items need to be visited to find a certain one, and the complexity of O(log(n)) is achieved.

The exercise provides you with a basic implementation of an Indexable Skip List. Your task is to implement further operations by looking at the existing implementation and the comments on the functions to implement.

Evaluation requirements:

Your program still produces correct output when inserting arbitrary data using insert.
Your program implements the get_node and __len__ functions. Pay attention to edge cases like when the list is empty, or the first/last item should be returned.
Your program achieves complexity of O(log(n)) for the get_node and __len__ functions. This will be checked by evaluating the execution time when 15000 items are inserted and then queried with get_node. This should not take longer than 10 seconds.
Click Run to simply run your program and Score to check whether you meet the evaluation criteria.