নম্পি: 2 ডি অ্যারে হিসাবে 1 ডি অ্যারের উপাদানগুলির সূচক পান


10

আমার কাছে এইরকম একটি অদ্ভুত অ্যারে রয়েছে: [1 2 2 0 0 1 3 5]

2d অ্যারে হিসাবে উপাদানগুলির সূচি পাওয়া সম্ভব? উদাহরণস্বরূপ উপরের ইনপুটটির উত্তরটি হবে[[3 4], [0 5], [1 2], [6], [], [7]]

বর্তমানে আমাকে বিভিন্ন মানগুলি লুপ করতে হবে numpy.where(input == i)এবং প্রতিটি মানের জন্য কল করতে হবে, যার যথেষ্ট পরিমাণে ইনপুট সহ ভয়ঙ্কর পারফরম্যান্স রয়েছে।


np.argsort([1, 2, 2, 0, 0, 1, 3, 5])দেয় array([3, 4, 0, 5, 1, 2, 6, 7], dtype=int64)। তাহলে আপনি কেবল পরবর্তী উপাদানগুলির তুলনা করতে পারেন।
vb_rises

উত্তর:


11

এখানে একটি ও (সর্বোচ্চ (এক্স) + লেন (এক্স)) পদ্ধতিটি ব্যবহার করে scipy.sparse:

import numpy as np
from scipy import sparse

x = np.array("1 2 2 0 0 1 3 5".split(),int)
x
# array([1, 2, 2, 0, 0, 1, 3, 5])


M,N = x.max()+1,x.size
sparse.csc_matrix((x,x,np.arange(N+1)),(M,N)).tolil().rows.tolist()
# [[3, 4], [0, 5], [1, 2], [6], [], [7]]

এটি পজিশনে (x [0], 0), (x [1], 1), এন্ট্রি সহ একটি স্পার্স ম্যাট্রিক্স তৈরি করে কাজ করে ... CSC(সংকীর্ণ স্পার্স কলাম) ফর্ম্যাটটি ব্যবহার করে এটি বরং সহজ। ম্যাট্রিক্সটি তখন LIL(লিঙ্কযুক্ত তালিকা) ফর্ম্যাটে রূপান্তরিত হয় । এই ফর্ম্যাটটি প্রতিটি সারির জন্য কলামের সূচকগুলিকে এর rowsগুণাবলী হিসাবে একটি তালিকা হিসাবে সংরক্ষণ করে , তাই আমাদের যা করা দরকার তা হ'ল তা এটিকে তালিকায় রূপান্তর করা।

মনে রাখবেন যে ছোট অ্যারে argsortভিত্তিক সমাধানগুলি সম্ভবত দ্রুততর তবে কিছু খুব বেশি আকারে নয় এটি অতিক্রম করবে।

সম্পাদনা করুন:

argsortভিত্তিক numpyএকমাত্র সমাধান:

np.split(x.argsort(kind="stable"),np.bincount(x)[:-1].cumsum())
# [array([3, 4]), array([0, 5]), array([1, 2]), array([6]), array([], dtype=int64), array([7])]

যদি গ্রুপগুলির মধ্যে সূচকগুলির ক্রম বিবেচনা না করে তবে আপনি চেষ্টাও করতে পারেন argpartition(এটি এই ছোট উদাহরণে কোনও পার্থক্য না ঘটায় তবে এটি সাধারণভাবে নিশ্চিত নয়):

bb = np.bincount(x)[:-1].cumsum()
np.split(x.argpartition(bb),bb)
# [array([3, 4]), array([0, 5]), array([1, 2]), array([6]), array([], dtype=int64), array([7])]

সম্পাদনা করুন:

@ দিবাকর ব্যবহারের বিরুদ্ধে সুপারিশ করেছেন np.split। পরিবর্তে, একটি লুপ সম্ভবত দ্রুত:

A = x.argsort(kind="stable")
B = np.bincount(x+1).cumsum()
[A[B[i-1]:B[i]] for i in range(1,len(B))]

অথবা আপনি ব্র্যান্ড নিউ (পাইথন 3.8 +) ওয়ালরাস অপারেটরটি ব্যবহার করতে পারেন:

A = x.argsort(kind="stable")
B = np.bincount(x)
L = 0
[A[L:(L:=L+b)] for b in B.tolist()]

সম্পাদনা করুন (সম্পাদিত):

(খাঁটি নোংরা নয়): নাম্বার বিকল্প হিসাবে (@ প্রেরকের পোস্ট দেখুন) আমরা পাইথ্রানও ব্যবহার করতে পারি।

সংকলন pythran -O3 <filename.py>

import numpy as np

#pythran export sort_to_bins(int[:],int)

def sort_to_bins(idx, mx):
    if mx==-1: 
        mx = idx.max() + 1
    cnts = np.zeros(mx + 2, int)
    for i in range(idx.size):
        cnts[idx[i] + 2] += 1
    for i in range(3, cnts.size):
        cnts[i] += cnts[i-1]
    res = np.empty_like(idx)
    for i in range(idx.size):
        res[cnts[idx[i]+1]] = i
        cnts[idx[i]+1] += 1
    return [res[cnts[i]:cnts[i+1]] for i in range(mx)]

এখানে numbaহুইস্কারের পারফরম্যান্স অনুসারে জয়:

repeat(lambda:enum_bins_numba_buffer(x),number=10)
# [0.6235917090671137, 0.6071486569708213, 0.6096088469494134]
repeat(lambda:sort_to_bins(x,-1),number=10)
# [0.6235359431011602, 0.6264424560358748, 0.6217901279451326]

পুরানো জিনিস:

import numpy as np

#pythran export bincollect(int[:])

def bincollect(a):
    o = [[] for _ in range(a.max()+1)]
    for i,j in enumerate(a):
        o[j].append(i)
    return o

সময় বনাম নাম্বা (পুরানো)

timeit(lambda:bincollect(x),number=10)
# 3.5732191529823467
timeit(lambda:enumerate_bins(x),number=10)
# 6.7462647299980745

এটি @ র্যান্ডির উত্তরের চেয়ে কিছুটা দ্রুত গতিতে শেষ হয়েছে
ফ্রেডেরিকো স্কার্ডং

একটি লুপ-ভিত্তিক এর চেয়ে ভাল হওয়া উচিত np.split
দিবাকর

@ দিবাকর ভাল বক্তব্য, ধন্যবাদ!
পল পান্জার

8

আপনার ডেটার আকারের উপর নির্ভর করে একটি সম্ভাব্য বিকল্প হ'ল কেবল বাদ numpyএবং ব্যবহার collections.defaultdict:

In [248]: from collections import defaultdict

In [249]: d = defaultdict(list)

In [250]: l = np.random.randint(0, 100, 100000)

In [251]: %%timeit
     ...: for k, v in enumerate(l):
     ...:     d[v].append(k)
     ...:
10 loops, best of 3: 22.8 ms per loop

তারপরে আপনি একটি অভিধান দিয়ে শেষ করুন {value1: [index1, index2, ...], value2: [index3, index4, ...]}। টাইম স্কেলিং অ্যারের আকারের সাথে লিনিয়ারের খুব কাছাকাছি, সুতরাং 10,000,000 আমার মেশিনে ~ 2.7 লাগে যা যথেষ্ট পরিমাণে যুক্তিসঙ্গত বলে মনে হয়।


7

যদিও অনুরোধটি একটি numpyসমাধানের জন্য, তবে আমি সিদ্ধান্ত নিয়েছি যে এটি একটি আকর্ষণীয় numbaভিত্তিক সমাধান রয়েছে কিনা তা দেখার জন্য । এবং সত্যিই আছে! এখানে একটি পদ্ধতির যা পার্টিশনযুক্ত তালিকাকে একটি একক পূর্বনির্ধারিত বাফারে সংরক্ষিত র‌্যাগড অ্যারে হিসাবে প্রতিনিধিত্ব করে। এটি পল পাঞ্জারargsort প্রস্তাবিত পদ্ধতির থেকে কিছুটা অনুপ্রেরণার দরকার । (পুরানো সংস্করণের জন্য যা এটি করেনি, তবে এটি সহজ ছিল, নীচে দেখুন))

@numba.jit(numba.void(numba.int64[:], 
                      numba.int64[:], 
                      numba.int64[:]), 
           nopython=True)
def enum_bins_numba_buffer_inner(ints, bins, starts):
    for x in range(len(ints)):
        i = ints[x]
        bins[starts[i]] = x
        starts[i] += 1

@numba.jit(nopython=False)  # Not 100% sure this does anything...
def enum_bins_numba_buffer(ints):
    ends = np.bincount(ints).cumsum()
    starts = np.empty(ends.shape, dtype=np.int64)
    starts[1:] = ends[:-1]
    starts[0] = 0

    bins = np.empty(ints.shape, dtype=np.int64)
    enum_bins_numba_buffer_inner(ints, bins, starts)

    starts[1:] = ends[:-1]
    starts[0] = 0
    return [bins[s:e] for s, e in zip(starts, ends)]

এটি 75 মিলিয়ন দশকে দশ মিলিয়ন আইটেমের তালিকাটি প্রক্রিয়াকরণ করে, যা খাঁটি পাইথনে লিখিত তালিকা-ভিত্তিক সংস্করণ থেকে প্রায় 50x স্পিডআপ।

একটি ধীর তবে কিছুটা বেশি পঠনযোগ্য সংস্করণের জন্য, গতিশীল আকারের "টাইপযুক্ত তালিকাগুলি" এর জন্য সম্প্রতি যুক্ত পরীক্ষামূলক সহায়তার উপর ভিত্তি করে আমার আগে যা ছিল তা এখানে রয়েছে যা আমাদের প্রতিটি বিনকে আরও দ্রুত আউট-অর্ডারে পূরণ করতে দেয়।

এই numbaধরণের ইনফারেন্স ইঞ্জিনটি বেশ খানিকটা লড়াই করে এবং আমি নিশ্চিত যে এই অংশটি পরিচালনা করার আরও ভাল উপায় আছে। এটি উপরের তুলনায় প্রায় 10x ধীর হয়ে গেছে।

@numba.jit(nopython=True)
def enum_bins_numba(ints):
    bins = numba.typed.List()
    for i in range(ints.max() + 1):
        inner = numba.typed.List()
        inner.append(0)  # An awkward way of forcing type inference.
        inner.pop()
        bins.append(inner)

    for x, i in enumerate(ints):
        bins[i].append(x)

    return bins

আমি নিম্নলিখিতগুলির বিরুদ্ধে এইগুলি পরীক্ষা করেছি:

def enum_bins_dict(ints):
    enum_bins = defaultdict(list)
    for k, v in enumerate(ints):
        enum_bins[v].append(k)
    return enum_bins

def enum_bins_list(ints):
    enum_bins = [[] for i in range(ints.max() + 1)]
    for x, i in enumerate(ints):
        enum_bins[i].append(x)
    return enum_bins

def enum_bins_sparse(ints):
    M, N = ints.max() + 1, ints.size
    return sparse.csc_matrix((ints, ints, np.arange(N + 1)),
                             (M, N)).tolil().rows.tolist()

আমি এগুলির অনুরূপ প্রাক্পম্পাইল্ড সিথন সংস্করণটির বিরুদ্ধেও পরীক্ষা করেছি enum_bins_numba_buffer(নীচে বিস্তারিত বর্ণিত)।

দশ মিলিয়ন এলোমেলো অন্তরের তালিকায় ( ints = np.random.randint(0, 100, 10000000)) আমি নিম্নলিখিত ফলাফলগুলি পেয়েছি:

enum_bins_dict(ints)
3.71 s ± 80.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

enum_bins_list(ints)
3.28 s ± 52.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

enum_bins_sparse(ints)
1.02 s ± 34.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

enum_bins_numba(ints)
693 ms ± 5.81 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

enum_bins_cython(ints)
82.3 ms ± 1.77 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

enum_bins_numba_buffer(ints)
77.4 ms ± 2.06 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

চিত্তাকর্ষকভাবে, কাজ করার এই numbaপদ্ধতিটি cythonএকই কাজটির একটি সংস্করণকে ছাড়িয়ে যায় এমনকি সীমানা-চেকিং বন্ধ করে দেয়। pythranএটি ব্যবহার করে এই পদ্ধতির পরীক্ষা করার জন্য এখনও আমার যথেষ্ট পরিচিতি নেই তবে আমি তুলনা দেখতে আগ্রহী। সম্ভবত এই স্পিডআপের উপর ভিত্তি করে মনে হচ্ছে pythranসংস্করণটিও এই পদ্ধতির সাথে বেশ খানিকটা দ্রুত হতে পারে।

cythonকিছু বিল্ড নির্দেশাবলীর সাথে এখানে রেফারেন্সের জন্য সংস্করণ। একবার cythonইনস্টল হয়ে গেলে আপনার setup.pyমতো একটি সাধারণ ফাইলের প্রয়োজন হবে :

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
import numpy

ext_modules = [
    Extension(
        'enum_bins_cython',
        ['enum_bins_cython.pyx'],
    )
]

setup(
    ext_modules=cythonize(ext_modules),
    include_dirs=[numpy.get_include()]
)

এবং সাইথন মডিউল enum_bins_cython.pyx,:

# cython: language_level=3

import cython
import numpy
cimport numpy

@cython.boundscheck(False)
@cython.cdivision(True)
@cython.wraparound(False)
cdef void enum_bins_inner(long[:] ints, long[:] bins, long[:] starts) nogil:
    cdef long i, x
    for x in range(len(ints)):
        i = ints[x]
        bins[starts[i]] = x
        starts[i] = starts[i] + 1

def enum_bins_cython(ints):
    assert (ints >= 0).all()
    # There might be a way to avoid storing two offset arrays and
    # save memory, but `enum_bins_inner` modifies the input, and
    # having separate lists of starts and ends is convenient for
    # the final partition stage.
    ends = numpy.bincount(ints).cumsum()
    starts = numpy.empty(ends.shape, dtype=numpy.int64)
    starts[1:] = ends[:-1]
    starts[0] = 0

    bins = numpy.empty(ints.shape, dtype=numpy.int64)
    enum_bins_inner(ints, bins, starts)

    starts[1:] = ends[:-1]
    starts[0] = 0
    return [bins[s:e] for s, e in zip(starts, ends)]

আপনার ওয়ার্কিং ডিরেক্টরিতে এই দুটি ফাইল সহ, এই কমান্ডটি চালান:

python setup.py build_ext --inplace

তারপরে আপনি ব্যবহার করে ফাংশনটি আমদানি করতে পারেন from enum_bins_cython import enum_bins_cython


আমি অবাক হই যদি আপনি পাইথ্রান সম্পর্কে সচেতন হন যা খুব বিস্তৃত ভাষায় নাম্বার সাথে সমান। আমি আমার পোস্টে পাইথ্রন সমাধান যুক্ত করেছি। এই উপলক্ষে পাইথ্রানের উপরের হাত রয়েছে বলে মনে হবে, একটি দ্রুত এবং অনেক বেশি পাইথোনিক সমাধান সরবরাহ করবে।
পল পানজার

@ পলপঞ্জার আকর্ষণীয়! আমি এটা শুনিনি। আমি সংগ্রহ করেছি যে নাম্বা ডেভগুলি তালিকা কোডটি স্থিতিশীল হয়ে যাওয়ার পরে প্রত্যাশিত সিনট্যাকটিক চিনি যুক্ত করবে। এখানেও কোনও সুবিধা / গতি বাণিজ্য বন্ধ রয়েছে বলে মনে হয় - জিট ডেকোরেটর পৃথক প্রম্পম্পাইল মডিউলগুলির প্রয়োজনের পদ্ধতির তুলনায় একটি সাধারণ পাইথন কোড বেসের সাথে একীভূত করা খুব সহজ। তবে স্কিপি পদ্ধতির উপর একটি 3x স্পিডআপ সত্যিই চিত্তাকর্ষক, এমনকি আশ্চর্যজনক!
প্রেরক

কেবল মনে আছে যে আমি মূলত এর আগে এটি করেছি: stackoverflow.com/q/55226662/7207392 । আপনি কি প্রশ্নোত্তরে আপনার নাম্বা এবং সিথন সংস্করণ যুক্ত করতে আপত্তি করবেন? কেবলমাত্র পার্থক্যটি: আমরা 0,1,2 সূচকগুলি বিন্যস্ত করি না ... পরিবর্তে অন্য অ্যারে করি। এবং আমরা ফলস্বরূপ অ্যারে কাটা আসলে বিরক্ত করি না।
পল পানজার

@ পলপাঞ্জার আহ খুব দুর্দান্ত। আমি আজ বা কাল কোন এক সময় এটি যুক্ত করার চেষ্টা করব। আপনি কি পৃথক উত্তর বা আপনার উত্তরের একটি সম্পাদনার পরামর্শ দিচ্ছেন? যেভাবেই হোক খুশি!
প্রেরক

গ্রেট! আমি মনে করি একটি পৃথক পোস্ট ভাল হবে তবে দৃ strong় পছন্দ নয়।
পল পানজার

6

এটি করার জন্য এখানে একটি সত্যই অদ্ভুত উপায় যা ভয়ানক, তবে আমি ভাগ করে না নেওয়া খুব মজাদার বলে দেখেছি - এবং সমস্ত numpy!

out = np.array([''] * (x.max() + 1), dtype = object)
np.add.at(out, x, ["{} ".format(i) for i in range(x.size)])
[[int(i) for i in o.split()] for o in out]

Out[]:
[[3, 4], [0, 5], [1, 2], [6], [], [7]]

সম্পাদনা: এই পথটি আমি খুঁজে পেতে পারে এটি সেরা পদ্ধতি। এটি এখনও পলপ্যানজারের argsortসমাধানের চেয়ে 10 গতি কম :

out = np.empty((x.max() + 1), dtype = object)
out[:] = [[]] * (x.max() + 1)
coords = np.empty(x.size, dtype = object)
coords[:] = [[i] for i in range(x.size)]
np.add.at(out, x, coords)
list(out)

2

আপনি সংখ্যার অভিধান তৈরি করে এটি করতে পারেন, কীগুলি সংখ্যা হবে এবং মানগুলি সেই সূচী হওয়া উচিত যা এটি দেখেছে, এটি করার একটি দ্রুততম উপায় এটি আপনি কোডটি বেলো দেখতে পারেন:

>>> import numpy as np
>>> a = np.array([1 ,2 ,2 ,0 ,0 ,1 ,3, 5])
>>> b = {}
# Creating an empty list for the numbers that exist in array a
>>> for i in range(np.min(a),np.max(a)+1):
    b[str(i)] = []

# Adding indices to the corresponding key
>>> for i in range(len(a)):
    b[str(a[i])].append(i)

# Resulting Dictionary
>>> b
{'0': [3, 4], '1': [0, 5], '2': [1, 2], '3': [6], '4': [], '5': [7]}

# Printing the result in the way you wanted.
>>> for i in sorted (b.keys()) :
     print(b[i], end = " ")

[3, 4] [0, 5] [1, 2] [6] [] [7] 

1

সুডোকোড:

  1. সর্বাধিক মান থেকে তার ন্যূনতম অ্যারের সর্বনিম্ন মান এবং তারপরে আরও একটি থেকে বিয়োগ করে "2 ডি অ্যারেতে 1 ডি অ্যারের সংখ্যা" পান get আপনার ক্ষেত্রে এটি 5-0 + 1 = 6 হবে

  2. এটির মধ্যে 1d অ্যারে সংখ্যার সাহায্যে একটি 2 ডি অ্যারের সূচনা করুন। আপনার ক্ষেত্রে, এটিতে 1 ডি অ্যারের সাথে একটি 2 ডি অ্যারে শুরু করুন। প্রতিটি 1 ডি অ্যারে আপনার অদ্ভুত অ্যারেতে একটি অনন্য উপাদানের সাথে মিল রাখে, উদাহরণস্বরূপ, প্রথম 1 ডি অ্যারে '0' এর সাথে মিলবে, দ্বিতীয় 1 ডি অ্যারে '1' এর সাথে মিলবে ...

  3. আপনার অদ্ভুত অ্যারের মধ্য দিয়ে লুপ করুন, উপাদানটির সূচকটি ডান অনুবর্তী 1 ডি অ্যারেতে রেখে দিন। আপনার ক্ষেত্রে, আপনার নম্পি অ্যারেতে প্রথম উপাদানটির সূচকটি দ্বিতীয় 1 ডি অ্যারেতে রাখা হবে, আপনার নপি অ্যারেতে দ্বিতীয় উপাদানটির সূচকটি তৃতীয় 1 ডি অ্যারেতে রাখা হবে, ....

আপনার সিঙ্গু অ্যারের দৈর্ঘ্যের উপর নির্ভর করে এই সিউডোকোডটি চলতে রৈখিক সময় নেবে।


1

এটি আপনাকে যা দিতে চায় ঠিক তা দেয় এবং আমার মেশিনে 10,000,000 এর জন্য প্রায় 2.5 সেকেন্ড সময় নিতে পারে:

import numpy as np
import timeit

# x = np.array("1 2 2 0 0 1 3 5".split(),int)
x = np.random.randint(0, 100, 100000)

def create_index_list(x):
    d = {}
    max_value = -1
    for i,v in enumerate(x):
        if v > max_value:
            max_value = v
        try:
            d[v].append(i)
        except:
            d[v] = [i]
    result_list = []
    for i in range(max_value+1):
        if i in d:
            result_list.append(d[i])
        else:
            result_list.append([])
    return result_list

# print(create_index_list(x))
print(timeit.timeit(stmt='create_index_list(x)', number=1, globals=globals()))

0

সুতরাং উপাদানের একটি তালিকা দেওয়া, আপনি (উপাদান, সূচক) জোড়া করতে চান। রৈখিক সময়ে, এটি করা যেতে পারে:

hashtable = dict()
for idx, val in enumerate(mylist):
    if val not in hashtable.keys():
         hashtable[val] = list()
    hashtable[val].append(idx)
newlist = sorted(hashtable.values())

এটিতে ও (এন) সময় নেওয়া উচিত। আমি এখনের মতো দ্রুত সমাধানের কথা ভাবতে পারি না, তবে আমি যদি আপডেট করি তবে এখানে আপডেট করব।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.