Python 3で「1000000000000000inrange(1000000000000001)」が非常に高速なのはなぜですか?

2240
Rick supports Monica 2015-05-07 05:32.

このrange()関数は、実際にはPython 3のオブジェクト型であり、ジェネレーターと同様に、そのコンテンツをその場で生成することを理解しています。

この場合、1兆が範囲内にあるかどうかを判断するには、1兆の値を生成する必要があるため、次の行には非常に長い時間がかかると予想していました。

1000000000000000 in range(1000000000000001)

さらに、ゼロをいくつ追加しても、計算にはほぼ同じ時間(基本的には瞬間的)がかかるようです。

私もこのようなことを試しましたが、計算はまだほとんど瞬時です:

1000000000000000000000 in range(0,1000000000000000000001,10) # count by tens

自分の範囲関数を実装しようとすると、結果はあまり良くありません!!

def my_crappy_range(N):
    i = 0
    while i < N:
        yield i
        i += 1
    return

range()それをとても速くする内部でオブジェクトは何をしていますか?


Martijn Pietersの回答はその完全性のために選択されましたが、Python 3で本格的なシーケンスになることの意味と、Python実装全体での関数最適化の潜在的な不整合に関する情報/警告については、abarnertの最初の回答も参照してください。。abarnertの他の回答は、もう少し詳しく説明し、Python 3での最適化の背後にある歴史(およびPython 2での最適化の欠如)に関心のある人のためのリンクを提供します。pokewimによる回答は、興味のある人に関連するCソースコードと説明を提供します。range__contains__xrange

11 answers

2307
Martijn Pieters 2015-05-07 05:33.

Python 3range()オブジェクトは、すぐに数値を生成しません。これは、オンデマンドで数値を生成するスマートシーケンスオブジェクトです。含まれているのは開始値、停止値、ステップ値だけです。オブジェクトを反復処理すると、反復ごとに次の整数が計算されます。

オブジェクトはobject.__contains__フックも実装し、あなたの数がその範囲の一部であるかどうかを計算します。計算は(ほぼ)一定時間の演算*です。範囲内のすべての可能な整数をスキャンする必要はありません。

range()オブジェクトドキュメントから:

range通常のlistまたはに対するタイプの利点は、tuple範囲オブジェクトが表す範囲のサイズに関係なく、常に同じ(少量の)メモリを使用することです(、、および値のみを格納しstart、個々のアイテムとサブ範囲を計算するため)必要に応じて)。stopstep

したがって、少なくとも、range()オブジェクトは次のことを行います。

class my_range(object):
    def __init__(self, start, stop=None, step=1):
        if stop is None:
            start, stop = 0, start
        self.start, self.stop, self.step = start, stop, step
        if step < 0:
            lo, hi, step = stop, start, -step
        else:
            lo, hi = start, stop
        self.length = 0 if lo > hi else ((hi - lo - 1) // step) + 1

    def __iter__(self):
        current = self.start
        if self.step < 0:
            while current > self.stop:
                yield current
                current += self.step
        else:
            while current < self.stop:
                yield current
                current += self.step

    def __len__(self):
        return self.length

    def __getitem__(self, i):
        if i < 0:
            i += self.length
        if 0 <= i < self.length:
            return self.start + i * self.step
        raise IndexError('Index out of range: {}'.format(i))

    def __contains__(self, num):
        if self.step < 0:
            if not (self.stop < num <= self.start):
                return False
        else:
            if not (self.start <= num < self.stop):
                return False
        return (num - self.start) % self.step == 0

これには、実際にrange()サポートされているいくつかの機能(.index()or.count()メソッド、ハッシュ、等価性テスト、スライスなど)がまだありませんが、アイデアが得られるはずです。

また__contains__、整数テストのみに焦点を当てるように実装を簡略化しました。実range()オブジェクトに非整数値(のサブクラスを含むint)を指定すると、含まれているすべての値のリストに対して包含テストを使用する場合と同様に、一致するかどうかを確認するために低速スキャンが開始されます。これは、整数を使用した等価性テストをサポートするだけで、整数演算もサポートすることが期待されていない他の数値タイプを引き続きサポートするために行われました。包含テストを実装した元のPythonの問題を参照してください。


* Pythonの整数には制限がないため、ほぼ一定の時間です。したがって、数学演算もNが大きくなるにつれて時間とともに大きくなり、これがO(log N)演算になります。すべて最適化されたCコードで実行され、Pythonは整数値を30ビットのチャンクに格納するため、ここに含まれる整数のサイズが原因でパフォーマンスに影響が出る前に、メモリが不足します。

892
abarnert 2015-05-07 06:01.

ここでの根本的な誤解は、それrangeがジェネレーターであると考えることです。そうではありません。実際、それはいかなる種類のイテレータでもありません。

これは非常に簡単にわかります。

>>> a = range(5)
>>> print(list(a))
[0, 1, 2, 3, 4]
>>> print(list(a))
[0, 1, 2, 3, 4]

ジェネレーターの場合、一度繰り返すと使い果たされます。

>>> b = my_crappy_range(5)
>>> print(list(b))
[0, 1, 2, 3, 4]
>>> print(list(b))
[]

range実際に、あることはちょうどリストのように、シーケンスです。これをテストすることもできます:

>>> import collections.abc
>>> isinstance(a, collections.abc.Sequence)
True

これは、シーケンスであるためのすべてのルールに従う必要があることを意味します。

>>> a[3]         # indexable
3
>>> len(a)       # sized
5
>>> 3 in a       # membership
True
>>> reversed(a)  # reversible
<range_iterator at 0x101cd2360>
>>> a.index(3)   # implements 'index'
3
>>> a.count(3)   # implements 'count'
1

arangeとaの違いlistは、arange遅延シーケンスまたは動的シーケンスであるということです。すべての値を記憶しているわけではなく、、、、およびを記憶しているだけstartstopstepオンデマンドで値を作成します__getitem__

(ちなみに、と同じタイプprint(iter(a))range使用していることに気付くでしょう。それはどのように機能しますか?Aは、のC実装を提供するという事実を除いて、特別なことは何も使用しないので、あまりにも。)listiteratorlistlistiteratorlist__getitem__range


さて、Sequence.__contains__一定の時間でなければならないということは何もlistありません。実際、のようなシーケンスの明らかな例では、そうではありません。しかし、それができないと言うこと何もありません。そして、実際にすべての値を生成してテストするよりも、range.__contains__数学的にチェックするだけの方が実装が簡単です((val - start) % stepただし、負のステップを処理するために少し複雑です)。それでは、なぜそれをより良い方法で行うべきではないのでしょうか。

しかし、これが起こることを保証する言語には何もないようです。Ashwini Chaudhariが指摘しているように、整数に変換して数学的なテストを行う代わりに、非整数値を指定すると、すべての値を繰り返して1つずつ比較することになります。そして、CPython3.2 +とPyPy3.xのバージョンにこの最適化が含まれているという理由だけで、それは明らかに良い考えであり、簡単に実行できます。IronPythonまたはNewKickAssPython3.xがそれを除外できなかった理由はありません。(実際、CPython 3.0-3.1に含まれていませんでした。)


rangeように実際にジェネレーターである場合my_crappy_range__contains__この方法でテストすることは意味がありません。少なくとも、意味のある方法は明らかではありません。最初の3つの値をすでに繰り返している場合1でもin、ジェネレーターはありますか?テスト1により、最大1(または最初の値>= 1)までのすべての値を反復して消費する必要がありますか?

403
wim 2015-05-07 05:41.

ソース、ルークを使用してください!

CPythonでは、range(...).__contains__(メソッドラッパー)は最終的に、値が範囲内にある可能性があるかどうかをチェックする単純な計算に委任します。ここでの速度の理由は、範囲オブジェクトの直接の反復ではなく、境界に関する数学的推論を使用しているためです。使用されるロジックを説明するには:

  1. 数が間にあることを確認startしてstop、と
  2. ストライド値が数値を「ステップオーバー」しないことを確認してください。

たとえば994、次のrange(4, 1000, 2)理由によります。

  1. 4 <= 994 < 1000、および
  2. (994 - 4) % 2 == 0

完全なCコードを以下に示します。これは、メモリ管理と参照カウントの詳細のために少し冗長ですが、基本的な考え方は次のとおりです。

static int
range_contains_long(rangeobject *r, PyObject *ob)
{
    int cmp1, cmp2, cmp3;
    PyObject *tmp1 = NULL;
    PyObject *tmp2 = NULL;
    PyObject *zero = NULL;
    int result = -1;

    zero = PyLong_FromLong(0);
    if (zero == NULL) /* MemoryError in int(0) */
        goto end;

    /* Check if the value can possibly be in the range. */

    cmp1 = PyObject_RichCompareBool(r->step, zero, Py_GT);
    if (cmp1 == -1)
        goto end;
    if (cmp1 == 1) { /* positive steps: start <= ob < stop */
        cmp2 = PyObject_RichCompareBool(r->start, ob, Py_LE);
        cmp3 = PyObject_RichCompareBool(ob, r->stop, Py_LT);
    }
    else { /* negative steps: stop < ob <= start */
        cmp2 = PyObject_RichCompareBool(ob, r->start, Py_LE);
        cmp3 = PyObject_RichCompareBool(r->stop, ob, Py_LT);
    }

    if (cmp2 == -1 || cmp3 == -1) /* TypeError */
        goto end;
    if (cmp2 == 0 || cmp3 == 0) { /* ob outside of range */
        result = 0;
        goto end;
    }

    /* Check that the stride does not invalidate ob's membership. */
    tmp1 = PyNumber_Subtract(ob, r->start);
    if (tmp1 == NULL)
        goto end;
    tmp2 = PyNumber_Remainder(tmp1, r->step);
    if (tmp2 == NULL)
        goto end;
    /* result = ((int(ob) - start) % step) == 0 */
    result = PyObject_RichCompareBool(tmp2, zero, Py_EQ);
  end:
    Py_XDECREF(tmp1);
    Py_XDECREF(tmp2);
    Py_XDECREF(zero);
    return result;
}

static int
range_contains(rangeobject *r, PyObject *ob)
{
    if (PyLong_CheckExact(ob) || PyBool_Check(ob))
        return range_contains_long(r, ob);

    return (int)_PySequence_IterSearch((PyObject*)r, ob,
                                       PY_ITERSEARCH_CONTAINS);
}

アイデアの「肉」は次の行に記載されています

/* result = ((int(ob) - start) % step) == 0 */ 

最後range_containsに、コードスニペットの下部にある関数を見てください。正確な型チェックが失敗した場合、説明されている巧妙なアルゴリズムを使用せず、代わりに_PySequence_IterSearch!を使用して範囲のダム反復検索にフォールバックします。この動作はインタプリタで確認できます(ここではv3.5.0を使用しています)。

>>> x, r = 1000000000000000, range(1000000000000001)
>>> class MyInt(int):
...     pass
... 
>>> x_ = MyInt(x)
>>> x in r  # calculates immediately :) 
True
>>> x_ in r  # iterates for ages.. :( 
^\Quit (core dumped)
154
poke 2015-05-07 05:41.

Martijnの答えに追加すると、これはソースの関連部分です(Cでは、範囲オブジェクトはネイティブコードで記述されているため)。

static int
range_contains(rangeobject *r, PyObject *ob)
{
    if (PyLong_CheckExact(ob) || PyBool_Check(ob))
        return range_contains_long(r, ob);

    return (int)_PySequence_IterSearch((PyObject*)r, ob,
                                       PY_ITERSEARCH_CONTAINS);
}

したがって、PyLongオブジェクト(intPython 3にある)の場合、range_contains_long関数を使用して結果を決定します。そして、その関数は基本的obに、が指定された範囲内にあるかどうかをチェックします(ただし、Cでは少し複雑に見えます)。

intオブジェクトでない場合は、値が見つかるまで(または見つからないまで)反復にフォールバックします。

ロジック全体は、次のように疑似Pythonに変換できます。

def range_contains (rangeObj, obj):
    if isinstance(obj, int):
        return range_contains_long(rangeObj, obj)

    # default logic by iterating
    return any(obj == x for x in rangeObj)

def range_contains_long (r, num):
    if r.step > 0:
        # positive step: r.start <= num < r.stop
        cmp2 = r.start <= num
        cmp3 = num < r.stop
    else:
        # negative step: r.start >= num > r.stop
        cmp2 = num <= r.start
        cmp3 = r.stop < num

    # outside of the range boundaries
    if not cmp2 or not cmp3:
        return False

    # num must be on a valid step inside the boundaries
    return (num - r.start) % r.step == 0
113
abarnert 2015-05-07 11:42.

あなたは迷っている場合は、なぜこのような最適化が追加されたrange.__contains__、そしてなぜそれがされなかったに追加xrange.__contains__2.7に:

まず、Ashwini Chaudharyが発見したように、問題1766304は、最適化するために明示的に開かれました[x]range.__contains__。このパッチは受け入れられ、3.2チェックインされましたが、2.7にバックポートされませんでした。「xrangeは非常に長い間このように動作していたため、パッチをこれほど遅くコミットするために何が必要かわからない」ためです。(2.7はその時点でほぼ出ていました。)

一方:

もともとxrangeは、かなりシーケンスではないオブジェクトでした。3.1ドキュメントは言います:

範囲オブジェクトの動作はほとんどありませんlen。インデックス作成、反復、および関数のみをサポートします。

これは完全に真実ではありませんでした。xrangeオブジェクトは、実際にインデックスとして自動的に来るいくつか他のものをサポートしlen*を含む__contains__(線形検索を経由して)。しかし、当時、それらを完全なシーケンスにする価値があるとは誰も考えていませんでした。

次に、Abstract Base Classes PEPの実装の一環として、同じ「ごくわずかな動作」しか処理しなかったとしても、どの組み込み型をどのABCの実装としてマークするか、およびxrange/rangeを実装すると主張するかを理解することが重要でしたcollections.Sequence問題9213まで、誰もその問題に気づきませんでした。その問題のためのパッチが追加されていないだけindexcount3.2のにrange、それはまた、働いていた再最適化された__contains__(と同じ数学を共有するindexと、直接で使用されていますcount)。** この変更は3.2にも適用され、「新しいメソッドを追加するバグ修正である」ため、2.xにバックポートされませんでした。(この時点で、2.7はすでにrcステータスを過ぎていました。)

したがって、この最適化を2.7に戻すチャンスは2回ありましたが、どちらも拒否されました。


*実際、インデックス作成だけで無料で反復を取得できますが、2.3では xrangeオブジェクトにカスタムイテレータがあります。

**最初のバージョンは実際にそれを再実装し、詳細が間違っていましたMyIntSubclass(2) in range(5) == False。たとえば、それはあなたに与えるでしょう。しかし、パッチのダニエルStutzbachの更新バージョンが遅い、ジェネリックへのフォールバックを含め、前のコードのほとんどを復元_PySequence_IterSearch前の3.2というrange.__contains__最適化が適用されないとき、暗黙的に使用していました。

50
Stefan Pochmann 2015-05-07 06:04.

他の回答はすでにそれをよく説明していますが、範囲オブジェクトの性質を説明する別の実験を提供したいと思います。

>>> r = range(5)
>>> for i in r:
        print(i, 2 in r, list(r))

0 True [0, 1, 2, 3, 4]
1 True [0, 1, 2, 3, 4]
2 True [0, 1, 2, 3, 4]
3 True [0, 1, 2, 3, 4]
4 True [0, 1, 2, 3, 4]

ご覧のとおり、範囲オブジェクトは、その範囲を記憶しているオブジェクトであり、1回限りのジェネレーターではなく、何度も使用できます(反復中であっても)。

30
Sławomir Lenart 2018-03-17 00:47.

それはすべて、評価への怠惰なアプローチと、のいくつかの追加の最適化に関するものrangeです。範囲内の値は、実際に使用するまで、またはさらに最適化するために計算する必要はありません。

ちなみに、あなたの整数はそれほど大きくありません、考えてみてください sys.maxsize

sys.maxsize in range(sys.maxsize) かなり速いです

最適化により、指定された整数を範囲の最小値と最大値と比較するのは簡単です。

だが:

Decimal(sys.maxsize) in range(sys.maxsize) かなり遅いです。

(この場合、には最適化がないrangeため、Pythonが予期しない10進数を受け取った場合、Pythonはすべての数値を比較します)

実装の詳細に注意する必要がありますが、将来変更される可能性があるため、信頼しないでください。

20
RBF06 2019-01-16 06:56.

TL; DR

によって返されるオブジェクトは、range()実際にはrangeオブジェクトです。このオブジェクトはイテレータインターフェイスを実装しているため、ジェネレータ、リスト、またはタプルのように、値を順番に繰り返すことができます。

ただし、オブジェクトが演算子の右側に表示されたときに実際に呼び出されるインターフェイス実装されてい__contains__ますin。この__contains__()メソッドは、boolの左側にあるアイテムinがオブジェクト内にあるかどうかのを返します。rangeオブジェクトはその境界とストライドを知っているので、これはO(1)で非常に簡単に実装できます。

2
Naruto 2019-11-26 07:50.
  1. 最適化により、与えられた整数を最小範囲と最大範囲だけで比較するのは非常に簡単です。
  2. Python3でrange()関数が非常に高速である理由は、ここでは、範囲オブジェクトを直接反復するのではなく、境界に数学的推論を使用するためです。
  3. したがって、ここでロジックを説明するために:
    • 番号が開始と停止の間にあるかどうかを確認します。
    • ステップ精度の値が数値を超えていないかどうかを確認してください。
  4. 例をとると、997はrange(4、1000、3)にあります。理由は次のとおりです。

    4 <= 997 < 1000, and (997 - 4) % 3 == 0.

1
benjimin 2020-03-11 16:45.

最適化を呼び出さないようにジェネレーターの理解を使用するx-1 in (i for i in range(x))大きなx値を試してくださいrange.__contains__

0
Matej Novosad 2020-10-10 06:29.

TLDR; rangeは等差数列なので、オブジェクトがそこにあるかどうかを非常に簡単に計算できます。リストのようにすばやくリストされていれば、インデックスを取得することもできます。

Related questions

MORE COOL STUFF

「水曜日」シーズン1の中心には大きなミステリーがあります

「水曜日」シーズン1の中心には大きなミステリーがあります

Netflixの「水曜日」は、典型的な10代のドラマ以上のものであり、実際、シーズン1にはその中心に大きなミステリーがあります.

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ケイト・ミドルトンは、州の夕食会と州の訪問中にカミラ・パーカー・ボウルズからスポットライトを奪いたくなかった、と専門家は言う.

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンが、オリビア・ワイルドが彼女とハリー・スタイルズとの間の「難しい」が「非常に友好的」な分割を恒久的にすることを望んでいる理由を見つけてください.

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする 

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする&nbsp;

エリザベス女王の死後、ケイト・ミドルトンが舞台裏で「非常に困難な時期」を過ごしていたと伝えられている理由を調べてください.

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セント ヘレナ島のジェイコブズ ラダーは 699 段の真っ直ぐ上る階段で、頂上に到達すると証明書が発行されるほどの難易度です。

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

Total War:Warhammer:Kotakuレビュー

Total War:Warhammer:Kotakuレビュー

私はこのゲームを嫌う準備ができていました。先週の前に、Total War:Warhammerについての私の考えがありました:それでもここに私は、私の手にある完成品であり、私は変わった男です。

涙の道:軍事化された帝国主義勢力がスタンディングロックキャンプを占領

涙の道:軍事化された帝国主義勢力がスタンディングロックキャンプを占領

スタンディングロックスー族のメンバーと水の保護者は、ノースダコタ州のスタンディングロックにあるオセティサコウィンキャンプを去ります。(Twitter経由のCNNスクリーンショット)火と煙がスカイラインを覆い、スタンディングロックスー族のメンバーと水の保護者が、聖なるものを守りながら建てた家、オセティサコウィン(セブンカウンシルファイアーズ)キャンプから行進し、太鼓を打ち、歌い、祈りました。ダコタアクセスパイプラインとしても知られる「ブラックスネーク」からの土地。

シアーズとKマートはイヴァンカ・トランプの商品を自分たちで取り除いています

シアーズとKマートはイヴァンカ・トランプの商品を自分たちで取り除いています

写真:APシアーズとKマートは、イヴァンカ・トランプのトランプホームアイテムのコレクションも、誰も購入したくないために削除しました。シアーズとKマートの両方の親会社であるシアーズホールディングスは、土曜日のABCニュースへの声明で、彼らが気にかけていると辛抱強く説明しましたトランプラインを売り続けるにはお金を稼ぐことについてあまりにも多く。

ポテトチップスでたった10分でスペインのトルティーヤを作る

ポテトチップスでたった10分でスペインのトルティーヤを作る

伝統的なスペインのトルティーヤは通常、オリーブオイルで柔らかくなるまで調理されたポテトから始まります(30分以上かかる場合があります)が、ケトルで調理されたポテトチップスの助けを借りてわずか10分でテーブルに置くことができます。上のビデオはすべてがバラバラにならないように裏返す方法を含め、レシピ全体を説明しますが、必要なのは4〜5個の卵と3カップのケトルチップスだけです。

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、子供向けのパズルの本の序文を書き、ジョージ王子、シャーロット王女、ルイ王子と一緒にテキストを読むと述べた.

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

Yak's Produce は、数十個のつぶれたメロンを野生動物のリハビリ専門家であるレスリー グリーンと彼女のルイジアナ州の救助施設で暮らす 42 匹の動物に寄付しました。

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

8 枚目のスタジオ アルバムのリリースに向けて準備を進めているデミ ロヴァートは、「スーパー グレート ガイ」と付き合っている、と情報筋は PEOPLE に確認しています。

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

イーサン プラスの誕生日のお祝いは、TLC のウェルカム トゥ プラスビルのシーズン 4 のフィナーレで、戦争中の母親のキム プラスと妻のオリビア プラスを結びつけました。

仕事の生産性を高める 8 つのシンプルなホーム オフィスのセットアップのアイデア

仕事の生産性を高める 8 つのシンプルなホーム オフィスのセットアップのアイデア

ホームオフィスのセットアップ術を極めよう!AppExert の開発者は、家族全員が一緒にいる場合でも、在宅勤務の技術を習得しています。祖父や曽祖父が共同家族で暮らしていた頃の記憶がよみがえりました。

2022 年、私たちのデジタル ライフはどこで終わり、「リアル ライフ」はどこから始まるのでしょうか?

20 年前のタイムトラベラーでさえ、日常生活におけるデジタルおよびインターネットベースのサービスの重要性に驚くことでしょう。MySpace、eBay、Napster などのプラットフォームは、高速化に焦点を合わせた世界がどのようなものになるかを示してくれました。

ニューロマーケティングの秘密科学

ニューロマーケティングの秘密科学

マーケティング担当者が人間の欲望を操作するために使用する、最先端の (気味が悪いと言う人もいます) メソッドを探ります。カートをいっぱいにして 3 桁の領収書を持って店を出る前に、ほんの数点の商品を買いに行ったことはありませんか? あなたは一人じゃない。

地理情報システムの日: GIS 開発者として学ぶべき最高の技術スタック

地理情報システムの日: GIS 開発者として学ぶべき最高の技術スタック

私たちが住んでいる世界を確実に理解するには、データが必要です。ただし、空間参照がない場合、このデータは地理的コンテキストがないと役に立たなくなる可能性があります。

Language