ProgramingTip

메모리에 테이블 형식 데이터를 유지하기위한 데이터 구조?

bestdevel 2020. 10. 29. 08:25
반응형

메모리에 테이블 형식 데이터를 유지하기위한 데이터 구조?


내 시나리오는 다음과 달라집니다. 프로그램에서 사용하게 사용하는 데이터 테이블 (소수 필드, 백 행 적용)이 있습니다. 또한이 데이터가 많은 경우에 CSV로 저장하고 시작할 때 사용됩니다. 모든 옵션 (SQLite)이 겸손한 요구 사항에 비해 과도하게 사용되기 때문에 데이터베이스를 사용하지 않기 때문에 내로 결정했습니다.

내 데이터가 다음과 같다고 가정합니다 (파일에서 제목없이 쉼표로 구분되어 있으며 이는 단지 그림 일뿐입니다).

 Row  | Name     | Year   | Priority
------------------------------------
 1    | Cat      | 1998   | 1
 2    | Fish     | 1998   | 2
 3    | Dog      | 1999   | 1 
 4    | Aardvark | 2000   | 1
 5    | Wallaby  | 2000   | 1
 6    | Zebra    | 2001   | 3

메모 :

  1. 행은 파일에 기록 된 "실제"값이거나 행 번호를 최대 자동 생성 된 값일 수 있습니다. 어느 쪽이든 메모리에 존재합니다.
  2. 이름은 고유합니다.

데이터로 수행하는 작업 :

  1. ID (반복) 또는 이름 (직접 액세스)을 기반으로 행을 조회합니다.
  2. 여러 필드를 기준으로 다른 순서로 테이블을 표시합니다. 예를 들어 우선 순위, 연도, 연도, 우선 순위 등을 기준으로 정렬해야합니다.
  3. 예를 들어 1997 년과 2002 년 사이의 연도를 가진 행 수 또는 1998 년의 행 수와 우선 순위> 2 등의 매개 변수 집합을 기반으로 인스턴스를 계산해야합니다.

SQL에 대한 "울음"을 알고 있습니다.

데이터 구조에 가장 많은 것이 선택됩니다. 다음은 몇 가지 선택 사항입니다.

행 목록 목록 :

a = []
a.append( [1, "Cat", 1998, 1] )
a.append( [2, "Fish", 1998, 2] )
a.append( [3, "Dog", 1999, 1] )
...

열 목록 목록 (add_row 대한 API가 분명히있을 것입니다) :

a = []
a.append( [1, 2, 3, 4, 5, 6] )
a.append( ["Cat", "Fish", "Dog", "Aardvark", "Wallaby", "Zebra"] )
a.append( [1998, 1998, 1999, 2000, 2000, 2001] )
a.append( [1, 2, 1, 1, 1, 3] )

열 목록 사전 (문자열 키를 대체하기 위해 상수를 만들 수 있음) :

a = {}
a['ID'] = [1, 2, 3, 4, 5, 6]
a['Name'] = ["Cat", "Fish", "Dog", "Aardvark", "Wallaby", "Zebra"] 
a['Year'] = [1998, 1998, 1999, 2000, 2000, 2001] 
a['Priority'] = [1, 2, 1, 1, 1, 3] 

키가 (Row, Field)의 튜플 인 사전 :

Create constants to avoid string searching
NAME=1
YEAR=2
PRIORITY=3

a={}
a[(1, NAME)] = "Cat"
a[(1, YEAR)] = 1998
a[(1, PRIORITY)] = 1
a[(2, NAME)] = "Fish"
a[(2, YEAR)] = 1998
a[(2, PRIORITY)] = 2
...

그리고 다른 방법이 확신합니다. 그러나 각 방법은 내 요구 사항 (복잡한 주문 및 계산)과 관련하여 단점이 있습니다.

권장되는 접근 방식은 무엇입니까?

편집하다 :

성능은 나에게 중요한 문제가 아닙니다. 테이블이 너무 작기 때문에 거의 모든 작업이 밀리 초 범위에있을 것이 믿습니다. 이는 내 애플리케이션에 대한 문제가 아닙니다.


조회, 재고 및 임의 재고가 필요한 메모리에 "테이블"이 있으면 SQL이 필요합니다. SQLite를 사용하는 것이지만 SQLite가 메모리 내 전용 데이터베이스를 사용할 수있는 것을 알고 계십니까?

connection = sqlite3.connect(':memory:')

그런 다음 SQLite의 모든 기능을 사용하여 메모리에 테이블을 생성 / 삭제 / 쿼리 / 업데이트 할 수있는 파일 작업을 마쳤을 때 남은 파일이 없습니다. 그리고 Python 2.5 sqlite3부터는 표준 라이브러리에 실제로 "과잉"이 아닙니다.

다음은 데이터베이스를 만들고 채우는 방법에 대한 샘플입니다.

import csv
import sqlite3

db = sqlite3.connect(':memory:')

def init_db(cur):
    cur.execute('''CREATE TABLE foo (
        Row INTEGER,
        Name TEXT,
        Year INTEGER,
        Priority INTEGER)''')

def populate_db(cur, csv_fp):
    rdr = csv.reader(csv_fp)
    cur.executemany('''
        INSERT INTO foo (Row, Name, Year, Priority)
        VALUES (?,?,?,?)''', rdr)

cur = db.cursor()
init_db(cur)
populate_db(cur, open('my_csv_input_file.csv'))
db.commit()

SQL을 사용하지 않는 사전 목록을 사용하지 않습니다.

lod = [ ] # "list of dicts"

def populate_lod(lod, csv_fp):
    rdr = csv.DictReader(csv_fp, ['Row', 'Name', 'Year', 'Priority'])
    lod.extend(rdr)

def query_lod(lod, filter=None, sort_keys=None):
    if filter is not None:
        lod = (r for r in lod if filter(r))
    if sort_keys is not None:
        lod = sorted(lod, key=lambda r:[r[k] for k in sort_keys])
    else:
        lod = list(lod)
    return lod

def lookup_lod(lod, **kw):
    for row in lod:
        for k,v in kw.iteritems():
            if row[k] != str(v): break
        else:
            return row
    return None

테스트 결과 :

>>> lod = []
>>> populate_lod(lod, csv_fp)
>>> 
>>> pprint(lookup_lod(lod, Row=1))
{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'}
>>> pprint(lookup_lod(lod, Name='Aardvark'))
{'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'}
>>> pprint(query_lod(lod, sort_keys=('Priority', 'Year')))
[{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'},
 {'Name': 'Dog', 'Priority': '1', 'Row': '3', 'Year': '1999'},
 {'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'},
 {'Name': 'Wallaby', 'Priority': '1', 'Row': '5', 'Year': '2000'},
 {'Name': 'Fish', 'Priority': '2', 'Row': '2', 'Year': '1998'},
 {'Name': 'Zebra', 'Priority': '3', 'Row': '6', 'Year': '2001'}]
>>> pprint(query_lod(lod, sort_keys=('Year', 'Priority')))
[{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'},
 {'Name': 'Fish', 'Priority': '2', 'Row': '2', 'Year': '1998'},
 {'Name': 'Dog', 'Priority': '1', 'Row': '3', 'Year': '1999'},
 {'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'},
 {'Name': 'Wallaby', 'Priority': '1', 'Row': '5', 'Year': '2000'},
 {'Name': 'Zebra', 'Priority': '3', 'Row': '6', 'Year': '2001'}]
>>> print len(query_lod(lod, lambda r:1997 <= int(r['Year']) <= 2002))
6
>>> print len(query_lod(lod, lambda r:int(r['Year'])==1998 and int(r['Priority']) > 2))
0

개인적으로 저는 SQLite 버전을 더 좋아합니다. 유형을 더 잘 보존하고 (Python의 추가 변환 코드없이) 요구 사항을 수용하기 위해 쉽게 확장되기 때문입니다. 하지만 다시 SQL에 익숙해 져서 YMMV입니다.


내가 아는 아주 오래된 질문이지만 ...

pandas DataFrame은 여기서 선택 옵션 인 것입니다.

http://pandas.pydata.org/pandas-docs/version/0.13.1/generated/pandas.DataFrame.html

광고에서

레이블이 지정된 축 (행 및 열)이있는 2 차원 크기 변경 가능, 잠재적으로 이질적인 표 형식 데이터 구조. 산술 연산은 행 및 열 레이블 모두에 정렬됩니다. Series 객체에 대한 dict-like 컨테이너로 생각할 수 있습니다. 기본 Pandas 데이터 구조

http://pandas.pydata.org/


개인적으로 행 목록 목록을 사용합니다. 각 행의 데이터는 항상 동일한 순서이므로 각 목록에서 해당 요소에 액세스하기 만하면 열별로 쉽게 정렬 할 수 있습니다. 또한 각 목록의 특정 열을 기반으로 쉽게 계산하고 검색 할 수도 있습니다. 기본적으로 2 차원 배열에 가깝습니다.

여기서 유일한 단점은 데이터가 어떤 순서로되어 있는지 알아야하고, 그 순서를 변경하면 일치하도록 검색 / 정렬 루틴을 변경해야한다는 것입니다.

당신이 할 수있는 또 다른 일은 사전 목록을 갖는 것입니다.

rows = []
rows.append({"ID":"1", "name":"Cat", "year":"1998", "priority":"1"})

이렇게하면 매개 변수의 순서를 알 필요가 없으므로 목록의 각 "연도"필드를 살펴볼 수 있습니다.


행이 dict 또는 더 나은 행 객체 목록 인 Table 클래스가 있습니다.

테이블에서 행을 직접 추가하지 말고 몇 가지 조회 맵을 업데이트하는 방법이 있습니다. 예를 들어, 순서대로 행을 추가하지 않거나 id가 연속적이지 않은 경우 idMap도 가질 수 있습니다.

class Table(object):
    def __init__(self):
        self.rows =  []# list of row objects, we assume if order of id
        self.nameMap = {} # for faster direct lookup for row by name

    def addRow(self, row):
        self.rows.append(row)
        self.nameMap[row['name']] = row

    def getRow(self, name):
        return self.nameMap[name]


table = Table()
table.addRow({'ID':1,'name':'a'})

첫째, 복잡한 데이터 검색 시나리오가 있다는 점을 감안할 때 SQLite조차도 과잉이라고 확신합니까?

결국 Greenspun의 Tenth Rule을 의역하는 SQLite의 절반을 비공식적으로 지정하고 버그로 가득 찬 임시 구현을하게 됩니다.

즉, 단일 데이터 구조를 선택하면 검색, 정렬 또는 계산 중 하나 이상에 영향을 미치므로 성능이 가장 중요하고 데이터가 일정하다면 다른 목적을 위해 둘 이상의 구조를 고려할 수 있습니다.

무엇보다도 더 일반적인 작업을 측정하고 비용이 적게 드는 구조를 결정합니다.


저는 개인적으로 꽤 최근에 BD_XML이라는 lib를 작성했습니다.

가장 기본적인 존재 이유는 XML 파일과 SQL 데이터베이스간에 데이터를주고받는 방법으로 사용되기 때문입니다.

스페인어로 작성되었지만 (프로그래밍 언어에서 중요한 경우) 매우 간단합니다.

from BD_XML import Tabla

Tabla (Table)라는 개체를 정의하며, pep-246 호환 데이터베이스 인터페이스의 사전 생성 된 연결 개체를 식별하기위한 이름으로 만들 수 있습니다.

Table = Tabla('Animals') 

그런 다음 agregar_columna(add_column) 메서드를 사용하여 열을 추가해야합니다 .와 함께 다양한 키워드 인수를 사용할 수 있습니다.

  • campo (필드) : 필드의 이름

  • tipo (유형) : 저장되는 데이터의 유형은 'varchar'및 'double'과 같은 것이거나 데이터베이스 후자로 내보내는 데 관심이없는 경우 Python 객체의 이름 일 수 있습니다.

  • defecto (기본값) : 행을 추가 할 때 아무것도 없으면 열에 대한 기본값을 설정합니다.

  • 다른 3 개가 있지만 실제로는 작동하지 않고 데이터베이스 용으로 만 존재합니다.

처럼:

Table.agregar_columna(campo='Name', tipo='str')
Table.agregar_columna(campo='Year', tipo='date')
#declaring it date, time, datetime or timestamp is important for being able to store it as a time object and not only as a number, But you can always put it as a int if you don't care for dates
Table.agregar_columna(campo='Priority', tipo='int')

그런 다음 + = 연산자를 사용하여 행을 추가합니다 (또는 추가 행이있는 복사본을 만들려면 +).

Table += ('Cat', date(1998,1,1), 1)
Table += {'Year':date(1998,1,1), 'Priority':2, Name:'Fish'}
#…
#The condition for adding is that is a container accessible with either the column name or the position of the column in the table

그런 다음 XML을 생성하고 exportar_XML(export_XML) 및 escribir_XML(write_XML) 을 사용하여 파일에 쓸 수 있습니다 .

file = os.path.abspath(os.path.join(os.path.dirname(__file__), 'Animals.xml'))
Table.exportar_xml()
Table.escribir_xml(file)

그런 다음 importar_XML파일 이름과 문자열 리터럴이 아닌 파일을 사용하고 있음을 나타내는 (import_XML)을 사용하여 다시 가져옵니다 .

Table.importar_xml(file, tipo='archivo')
#archivo means file

많은

이것은 SQL 방식으로 Tabla 객체를 사용할 수있는 방법입니다.

#UPDATE <Table> SET Name = CONCAT(Name,' ',Priority), Priority = NULL WHERE id = 2
for row in Table:
    if row['id'] == 2:
        row['Name'] += ' ' + row['Priority']
        row['Priority'] = None
print(Table)

#DELETE FROM <Table> WHERE MOD(id,2) = 0 LIMIT 1
n = 0
nmax = 1
for row in Table:
    if row['id'] % 2 == 0:
        del Table[row]
        n += 1
        if n >= nmax: break
print(Table)

이 예는 'id'라는 열을 가정하지만 예를 들어 너비 row.pos로 바꿀 수 있습니다.

if row.pos == 2:

파일은 다음에서 다운로드 할 수 있습니다.

https://bitbucket.org/WolfangT/librerias

참고 URL : https://stackoverflow.com/questions/1038160/data-structure-for-maintaining-tabular-data-in-memory

반응형