2008-08-07 70 views
43

Tôi đang làm việc trên một dự án đòi hỏi phải thao tác các ma trận khổng lồ, đặc biệt là tổng kết kim tự tháp để tính toán copula.Cách tốt nhất để tạo một mảng thưa thớt trong C++ là gì?

Tóm lại, tôi cần theo dõi số lượng giá trị tương đối nhỏ (thường là giá trị 1 và trong trường hợp hiếm hơn 1) trong một biển số không trong ma trận (mảng đa chiều).

Mảng thưa thớt cho phép người dùng lưu trữ một số lượng nhỏ giá trị và giả sử tất cả các bản ghi chưa xác định là giá trị đặt trước. Vì nó không thể lưu trữ tất cả các giá trị trong bộ nhớ, tôi chỉ cần lưu trữ một vài phần tử khác 0. Điều này có thể là vài triệu mục.

Tốc độ là ưu tiên lớn và tôi cũng muốn tự động chọn số lượng biến trong lớp khi chạy.

Tôi hiện đang làm việc trên một hệ thống sử dụng cây tìm kiếm nhị phân (b-tree) để lưu trữ các mục nhập. Có ai biết về một hệ thống tốt hơn?

Trả lời

25

Đối với C++, bản đồ hoạt động tốt. Một vài triệu đồ vật sẽ không thành vấn đề. 10 triệu mục mất khoảng 4,4 giây và khoảng 57 meg trên máy tính của tôi.

ứng dụng thử nghiệm của tôi là như sau:

#include <stdio.h> 
#include <stdlib.h> 
#include <map> 

class triple { 
public: 
    int x; 
    int y; 
    int z; 
    bool operator<(const triple &other) const { 
     if (x < other.x) return true; 
     if (other.x < x) return false; 
     if (y < other.y) return true; 
     if (other.y < y) return false; 
     return z < other.z; 
    } 
}; 

int main(int, char**) 
{ 
    std::map<triple,int> data; 
    triple point; 
    int i; 

    for (i = 0; i < 10000000; ++i) { 
     point.x = rand(); 
     point.y = rand(); 
     point.z = rand(); 
     //printf("%d %d %d %d\n", i, point.x, point.y, point.z); 
     data[point] = i; 
    } 
    return 0; 
} 

Bây giờ để tự động chọn số lượng các biến, giải pháp đơn giản nhất là để đại diện cho chỉ số như là một chuỗi, và sau đó sử dụng chuỗi như một chìa khóa cho bản đồ . Ví dụ, một mục nằm ở [23] [55] có thể được biểu diễn thông qua chuỗi "23,55". Chúng tôi cũng có thể mở rộng giải pháp này cho các thứ nguyên cao hơn; chẳng hạn như đối với ba chiều, một chỉ số tùy ý sẽ trông giống như "34,45,56". Việc triển khai đơn giản kỹ thuật này như sau:

std::map data<string,int> data; 
char ix[100]; 

sprintf(ix, "%d,%d", x, y); // 2 vars 
data[ix] = i; 

sprintf(ix, "%d,%d,%d", x, y, z); // 3 vars 
data[ix] = i; 
+1

điều gì về hiệu suất của việc nhận yếu tố phạm vi từ điều này hoặc kiểm tra xem phạm vi có hoàn toàn trong mảng không? –

+2

Việc thực hiện toán tử Whanhee

+1

Vì nó đã không được cố định trong một thời gian dài, tôi lấy sự tự do để thay thế nó bằng cách thực hiện đúng. – celtschk

3

Bảng băm có thể chèn nhanh và tra cứu. Bạn có thể viết một hàm băm đơn giản vì bạn biết bạn sẽ chỉ xử lý các cặp nguyên như các khóa.

1

Cách tốt nhất để thực hiện ma trận thưa thớt là không thực hiện chúng - ít nhất là không phải của riêng bạn. Tôi sẽ đề nghị để BLAS (mà tôi nghĩ là một phần của LAPACK) mà có thể xử lý ma trận thực sự rất lớn.

+0

LAPACK là thư viện dành cho các ma trận dày đặc. BLAS tiêu chuẩn cũng dành cho các ma trận dày đặc. Có một gói Sparse BLAS (thông qua NIST) nhưng điều này khác với BLAS chuẩn. – codehippo

4

Chi tiết nhỏ trong so sánh chỉ mục. Bạn cần phải làm một hoặc null so sánh, nếu không:

a= (1, 2, 1); b= (2, 1, 2); 
(a<b) == (b<a) is true, but b!=a 

Edit: Vì vậy, việc so sánh có lẽ nên được:

return lhs.x<rhs.x 
    ? true 
    : lhs.x==rhs.x 
     ? lhs.y<rhs.y 
      ? true 
      : lhs.y==rhs.y 
       ? lhs.z<rhs.z 
       : false 
     : false 
16

Cũng giống như một lời khuyên: các phương pháp sử dụng chuỗi như chỉ số thực sự là rất chậm. Một giải pháp hiệu quả hơn nhưng nếu không tương đương sẽ là sử dụng các vectơ/mảng. Hoàn toàn không cần phải viết các chỉ mục trong một chuỗi.

typedef vector<size_t> index_t; 

struct index_cmp_t : binary_function<index_t, index_t, bool> { 
    bool operator()(index_t const& a, index_t const& b) const { 
     for (index_t::size_type i = 0; i < a.size(); ++i) 
      if (a[i] != b[i]) 
       return a[i] < b[i]; 
     return false; 
    } 
}; 

map<index_t, int, index_cmp_t> data; 
index_t i(dims); 
i[0] = 1; 
i[1] = 2; 
// … etc. 
data[i] = 42; 

Tuy nhiên, việc sử dụng map không thực sự hiệu quả vì việc triển khai cây tìm kiếm nhị phân cân bằng.Cấu trúc dữ liệu hoạt động tốt hơn nhiều trong trường hợp này sẽ là một bảng băm (ngẫu nhiên).

+0

aka. unordered_map – Andrew

+0

@Andrew Không hoàn toàn. Câu trả lời này đặt trước C++ 11, và do đó 'std :: unordered_map', trong một thời gian dài. Ngày nay, tôi khuyên bạn nên sử dụng tùy chọn sau nhưng không thay thế cho câu trả lời của tôi vì 'std :: vector' dường như không cung cấp triển khai phù hợp cho' std :: hash'. Điều đó nói rằng, trừ khi chỉ số thực sự có kích thước biến (không chắc), một 'std :: unordered_map >' nên thực tế làm việc ra khỏi hộp (mặc dù giao diện chắc chắn có thể được cải thiện). –

0

Vì chỉ các giá trị với [a] [b] [c] ... [w] [x] [y] [z] là hậu quả, chúng tôi chỉ lưu trữ các chỉ số, chứ không phải giá trị 1 chỉ là về mọi nơi - luôn luôn giống nhau + không có cách nào để băm nó. Lưu ý rằng lời nguyền của chiều kích là hiện tại, đề nghị đi với một số công cụ thành lập NIST hoặc Boost, ít nhất là đọc các nguồn cho rằng để vượt qua sai lầm không cần thiết. Nếu công việc cần nắm bắt các phân bố phụ thuộc thời gian và khuynh hướng tham số của các tập dữ liệu không xác định, thì một Bản đồ hoặc B-Cây có gốc không có giá trị có lẽ là không thực tế. Chúng tôi có thể lưu trữ chỉ các indice mình, băm nếu đặt hàng (sensibility cho trình bày) có thể cấp dưới để giảm thời gian miền tại thời gian chạy, cho tất cả 1 giá trị. Vì các giá trị khác không phải là một số ít, một ứng cử viên rõ ràng cho chúng là bất kỳ cấu trúc dữ liệu nào bạn có thể tìm thấy dễ dàng và dễ hiểu. Nếu tập dữ liệu thực sự là kích thước khổng lồ, tôi gợi ý một số loại cửa sổ trượt tự quản lý tệp/đĩa/liên tục-io, di chuyển các phần dữ liệu vào phạm vi cần thiết. (viết mã mà bạn có thể hiểu) Nếu bạn đang cam kết cung cấp giải pháp thực tế cho một nhóm làm việc, việc không làm như vậy sẽ khiến bạn mất lòng thương xót của các hệ điều hành cấp người tiêu dùng có mục tiêu duy nhất là lấy bữa trưa của bạn.

0

Dưới đây là triển khai tương đối đơn giản nên cung cấp tra cứu nhanh hợp lý (sử dụng bảng băm) cũng như lặp nhanh hơn các phần tử khác 0 trong hàng/cột.

// Copyright 2014 Leo Osvald 
// 
// Licensed under the Apache License, Version 2.0 (the "License"); 
// you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at 
// 
//  http://www.apache.org/licenses/LICENSE-2.0 
// 
// Unless required by applicable law or agreed to in writing, software 
// distributed under the License is distributed on an "AS IS" BASIS, 
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
// See the License for the specific language governing permissions and 
// limitations under the License. 

#ifndef UTIL_IMMUTABLE_SPARSE_MATRIX_HPP_ 
#define UTIL_IMMUTABLE_SPARSE_MATRIX_HPP_ 

#include <algorithm> 
#include <limits> 
#include <map> 
#include <type_traits> 
#include <unordered_map> 
#include <utility> 
#include <vector> 

// A simple time-efficient implementation of an immutable sparse matrix 
// Provides efficient iteration of non-zero elements by rows/cols, 
// e.g. to iterate over a range [row_from, row_to) x [col_from, col_to): 
// for (int row = row_from; row < row_to; ++row) { 
//  for (auto col_range = sm.nonzero_col_range(row, col_from, col_to); 
//   col_range.first != col_range.second; ++col_range.first) { 
//  int col = *col_range.first; 
//  // use sm(row, col) 
//  ... 
//  } 
template<typename T = double, class Coord = int> 
class SparseMatrix { 
    struct PointHasher; 
    typedef std::map< Coord, std::vector<Coord> > NonZeroList; 
    typedef std::pair<Coord, Coord> Point; 

public: 
    typedef T ValueType; 
    typedef Coord CoordType; 
    typedef typename NonZeroList::mapped_type::const_iterator CoordIter; 
    typedef std::pair<CoordIter, CoordIter> CoordIterRange; 

    SparseMatrix() = default; 

    // Reads a matrix stored in MatrixMarket-like format, i.e.: 
    // <num_rows> <num_cols> <num_entries> 
    // <row_1> <col_1> <val_1> 
    // ... 
    // Note: the header (lines starting with '%' are ignored). 
    template<class InputStream, size_t max_line_length = 1024> 
    void Init(InputStream& is) { 
    rows_.clear(), cols_.clear(); 
    values_.clear(); 

    // skip the header (lines beginning with '%', if any) 
    decltype(is.tellg()) offset = 0; 
    for (char buf[max_line_length + 1]; 
     is.getline(buf, sizeof(buf)) && buf[0] == '%';) 
     offset = is.tellg(); 
    is.seekg(offset); 

    size_t n; 
    is >> row_count_ >> col_count_ >> n; 
    values_.reserve(n); 
    while (n--) { 
     Coord row, col; 
     typename std::remove_cv<T>::type val; 
     is >> row >> col >> val; 
     values_[Point(--row, --col)] = val; 
     rows_[col].push_back(row); 
     cols_[row].push_back(col); 
    } 
    SortAndShrink(rows_); 
    SortAndShrink(cols_); 
    } 

    const T& operator()(const Coord& row, const Coord& col) const { 
    static const T kZero = T(); 
    auto it = values_.find(Point(row, col)); 
    if (it != values_.end()) 
     return it->second; 
    return kZero; 
    } 

    CoordIterRange 
    nonzero_col_range(Coord row, Coord col_from, Coord col_to) const { 
    CoordIterRange r; 
    GetRange(cols_, row, col_from, col_to, &r); 
    return r; 
    } 

    CoordIterRange 
    nonzero_row_range(Coord col, Coord row_from, Coord row_to) const { 
    CoordIterRange r; 
    GetRange(rows_, col, row_from, row_to, &r); 
    return r; 
    } 

    Coord row_count() const { return row_count_; } 
    Coord col_count() const { return col_count_; } 
    size_t nonzero_count() const { return values_.size(); } 
    size_t element_count() const { return size_t(row_count_) * col_count_; } 

private: 
    typedef std::unordered_map<Point, 
          typename std::remove_cv<T>::type, 
          PointHasher> ValueMap; 

    struct PointHasher { 
    size_t operator()(const Point& p) const { 
     return p.first << (std::numeric_limits<Coord>::digits >> 1)^p.second; 
    } 
    }; 

    static void SortAndShrink(NonZeroList& list) { 
    for (auto& it : list) { 
     auto& indices = it.second; 
     indices.shrink_to_fit(); 
     std::sort(indices.begin(), indices.end()); 
    } 

    // insert a sentinel vector to handle the case of all zeroes 
    if (list.empty()) 
     list.emplace(Coord(), std::vector<Coord>(Coord())); 
    } 

    static void GetRange(const NonZeroList& list, Coord i, Coord from, Coord to, 
         CoordIterRange* r) { 
    auto lr = list.equal_range(i); 
    if (lr.first == lr.second) { 
     r->first = r->second = list.begin()->second.end(); 
     return; 
    } 

    auto begin = lr.first->second.begin(), end = lr.first->second.end(); 
    r->first = lower_bound(begin, end, from); 
    r->second = lower_bound(r->first, end, to); 
    } 

    ValueMap values_; 
    NonZeroList rows_, cols_; 
    Coord row_count_, col_count_; 
}; 

#endif /* UTIL_IMMUTABLE_SPARSE_MATRIX_HPP_ */ 

Để đơn giản, nó là immutable, nhưng bạn có thể làm cho nó có thể thay đổi; hãy chắc chắn thay đổi std::vector thành std::set nếu bạn muốn "chèn" hiệu quả hợp lý (thay đổi số không thành số không khác).

3

Eigen là thư viện đại số tuyến tính C++ có implementation của ma trận thưa thớt. Nó thậm chí còn hỗ trợ hoạt động ma trận và giải quyết (LU factorization vv) được tối ưu hóa cho ma trận thưa thớt.

0

tôi sẽ đề nghị làm một cái gì đó như:

typedef std::tuple<int, int, int> coord_t; 
typedef boost::hash<coord_t> coord_hash_t; 
typedef std::unordered_map<coord_hash_t, int, c_hash_t> sparse_array_t; 

sparse_array_t the_data; 
the_data[ { x, y, z } ] = 1; /* list-initialization is cool */ 

for(const auto& element : the_data) { 
    int xx, yy, zz, val; 
    std::tie(std::tie(xx, yy, zz), val) = element; 
    /* ... */ 
} 

Để giúp giữ cho dữ liệu của bạn thưa thớt, bạn có thể muốn viết một lớp con của unorderd_map, mà lặp tự động bỏ qua (và xóa) bất kỳ mục có giá trị 0.

2

Danh sách đầy đủ các giải pháp có thể được tìm thấy trong wikipedia. Để thuận tiện, tôi đã trích dẫn các phần có liên quan như sau.

https://en.wikipedia.org/wiki/Sparse_matrix#Dictionary_of_keys_.28DOK.29

Dictionary of phím (DOK)

DOK bao gồm một cuốn từ điển mà bản đồ (hàng, cột) -pairs với giá trị trong những yếu tố. Các phần tử bị thiếu trong từ điển được lấy bằng không. Định dạng này tốt cho việc tăng dần số lượng xây dựng một ma trận thưa thớt theo thứ tự ngẫu nhiên, nhưng không tốt cho việc lặp lại so với các giá trị khác không theo thứ tự từ điển. Một thông thường, sẽ tạo một ma trận theo định dạng này và sau đó chuyển đổi sang định dạng khác có hiệu quả hơn để xử lý .[1]

Danh sách liệt kê (LIL)

cửa hàng LIL một danh sách cho mỗi hàng, với mỗi mục có chứa các chỉ số cột và giá trị. Thông thường, các mục nhập này được sắp xếp theo chỉ số cột để tra cứu nhanh hơn. Đây là một định dạng khác phù hợp cho việc xây dựng ma trận gia tăng . [2]

Phối hợp danh sách

(COO)

COO cửa hàng một danh sách (hàng, cột, giá trị) tuples. Lý tưởng nhất, các mục được sắp xếp (theo chỉ mục hàng, sau đó chỉ mục cột) để cải thiện truy cập ngẫu nhiên lần. Đây là một định dạng khác tốt cho việc xây dựng ma trận gia tăng . [3]

nén hàng thưa thớt (CSR, định dạng Yale CRS hoặc)

Các nén hàng thưa thớt (CSR) hoặc lưu trữ hàng nén (CRS) định dạng đại diện cho một ma trận M bởi ba (one- chiều), tương ứng là chứa các giá trị khác không, khoảng cách của hàng và cột chỉ mục. Nó tương tự như COO, nhưng nén các chỉ mục hàng, do đó, tên. Định dạng này cho phép truy cập hàng nhanh và ma trận-vector phép nhân (Mx).

Các vấn đề liên quan