functor_纯Java中的Functor和Monad示例

functor

本文最初是我们使用RxJava进行React式编程的附录。 但是,尽管与React式编程非常相关,但对monad的介绍却不太适合。 因此,我决定将其取出并作为博客文章单独发布。 我知道,“ 我对单子的自己的,一半正确和一半的完整解释 ”是编程博客上的新“ Hello,world ”。 然而,本文从Java数据结构和库的特定角度研究了函子和monad。 因此,我认为值得分享。

RxJava的设计和构建基于非常基本的概念,例如函子monoidmonad 。 尽管Rx最初是为命令式C#语言建模的,并且我们正在学习RxJava,并在类似的命令式语言上工作,但该库还是源于函数式编程。 在意识到RxJava API的紧凑性之后,您应该不会感到惊讶。 几乎只有少数几个核心类,通常是不可变的,并且所有内容都主要由纯函数组成。

随着函数式编程(或函数式样式)的最新兴起(最普遍地用Scala或Clojure等现代语言表示),monads成为了广泛讨论的话题。 他们周围有很多民间传说:

monad是endofunctors类别中的monoid,这是什么问题?
詹姆斯·伊里

该monad的诅咒是,一旦您获得了顿悟,一旦您理解了“哦,就是这样”,您就失去了向任何人解释它的能力。
道格拉斯·克罗克福德

绝大多数程序员,尤其是那些没有函数式编程背景的程序员,都倾向于认为monad是某种神秘的计算机科学概念,因此从理论上讲,它对他们的编程事业无济于事。 这种消极的观点可以归因于数十篇文章或博客文章太抽象或太狭窄。 但是事实证明,甚至标准的Java库都存在monad,特别是自Java Development Kit(JDK)8起(稍后会有更多介绍)。 绝对妙不可言的是,一旦您第一次了解monad,突然之间就会有几个完全不相同的目的无关的类和抽象变得熟悉。

Monad概括了各种看似独立的概念,因此学习Monad的另一种化身只需很少的时间。 例如,您不必学习CompletableFuture在Java 8中的工作方式,一旦意识到它是monad,就可以精确地知道它是如何工作的,并且可以从其语义中得到什么。 然后您会听说RxJava听起来有很多不同,但是由于Observable是monad,因此没有太多可添加的。 您已经不知不觉中已经遇到过许多其他的单子示例。 因此,即使您实际上没有使用RxJava,本节也将是有用的复习。

函子

在解释什么是monad之前,让我们研究一个称为functor的简单结构。 函子是封装某些值的类型化数据结构。 从语法的角度来看,函子是具有以下API的容器:

import java.util.function.Function;interface Functor<T> {<R> Functor<R> map(Function<T, R> f);}

但是仅仅语法是不足以了解什么是函子。 functor提供的唯一操作是带函数f map() 。 此函数接收框内的任何内容,对其进行转换并将结果按原样包装到另一个函子中。 请仔细阅读。 Functor<T>始终是不可变的容器,因此map不会使执行该操作的原始对象发生突变。 取而代之的是,它返回包装在全新函子中的结果(或结果–请耐心等待),该函子可能是类型R 此外,在应用标识函数(即map(x -> x)时,函子不应执行任何操作。 这种模式应始终返回相同的函子或相等的实例。

通常将Functor<T>与保存T实例进行比较,其中与该值交互的唯一方法是对其进行转换。 但是,没有从函子解开或逃逸的惯用方式。 值始终在函子的上下文内。 函子为什么有用? 它们使用一个统一的,适用于所有对象的统一API来概括多个通用习语,如集合,promise,Optionals等。 让我介绍几个函子,以使您更流畅地使用此API:

interface Functor<T,F extends Functor<?,?>> {<R> F map(Function<T,R> f);
}class Identity<T> implements Functor<T,Identity<?>> {private final T value;Identity(T value) { this.value = value; }public <R> Identity<R> map(Function<T,R> f) {final R result = f.apply(value);return new Identity<>(result);}}

需要额外的F类型参数来进行Identity编译。 在前面的示例中,您看到的是最简单的函子,仅包含一个值。 您只能使用map方法内部的值对其进行转换,但无法提取它。 这被认为超出了纯函子的范围。 与函子进行交互的唯一方法是应用类型安全的转换序列:

Identity<String> idString = new Identity<>("abc");
Identity<Integer> idInt = idString.map(String::length);

或流利地,就像您编写函数一样:

Identity<byte[]> idBytes = new Identity<>(customer).map(Customer::getAddress).map(Address::street).map((String s) -> s.substring(0, 3)).map(String::toLowerCase).map(String::getBytes);

从这个角度来看,在函子上的映射与调用链式函数没有太大不同:

byte[] bytes = customer.getAddress().street().substring(0, 3).toLowerCase().getBytes();

您为什么还要烦恼这种冗长的包装,不仅不提供任何附加值,而且也无法将内容提取回去? 好吧,事实证明您可以使用此原始函子抽象对其他几个概念建模。 例如,从Java 8开始的java.util.Optional<T>是带有map()方法的函子。 让我们从头开始实现它:

class FOptional<T> implements Functor<T,FOptional<?>> {private final T valueOrNull;private FOptional(T valueOrNull) {this.valueOrNull = valueOrNull;}public <R> FOptional<R> map(Function<T,R> f) {if (valueOrNull == null)return empty();elsereturn of(f.apply(valueOrNull));}public static <T> FOptional<T> of(T a) {return new FOptional<T>(a);}public static <T> FOptional<T> empty() {return new FOptional<T>(null);}}

现在变得有趣了。 FOptional<T>函子可以保存一个值,但也可以为空。 这是一种对null进行编码的类型安全的方法。 构造FOptional方法有两种:通过提供值或创建empty()实例。 在这两种情况下,就像IdentityFOptional是不可变的,我们只能从内部与值交互。 FOptional不同之FOptional在于,如果转换函数f为空,则它可能不会应用于任何值。 这意味着函子可能不必完全封装类型T一个值。 它也可以包装任意数量的值,就像List …functor:

import com.google.common.collect.ImmutableList;class FList<T> implements Functor<T, FList<?>> {private final ImmutableList<T> list;FList(Iterable<T> value) {this.list = ImmutableList.copyOf(value);}@Overridepublic <R> FList<?> map(Function<T, R> f) {ArrayList<R> result = new ArrayList<R>(list.size());for (T t : list) {result.add(f.apply(t));}return new FList<>(result);}
}

API保持不变:在转换T -> R使用函子,但是行为却大不相同。 现在,我们对FList每个项目应用转换,以声明方式转换整个列表。 因此,如果您有一个customers列表,并且想要他们的街道列表,则非常简单:

import static java.util.Arrays.asList;FList<Customer> customers = new FList<>(asList(cust1, cust2));FList<String> streets = customers.map(Customer::getAddress).map(Address::street);

这不再像说customers.getAddress().street()那样简单,您不能在一组客户上调用getAddress() ,必须在每个单独的客户上调用getAddress() ,然后将其放回一个集合中。 顺便说一句,Groovy发现这种模式是如此普遍,以至于实际上有一个语法糖: customer*.getAddress()*.street() 。 该运算符称为散点图,实际上是变相的map 。 也许您想知道为什么我要在map list手动遍历list而不是使用Java 8中的Streamlist.stream().map(f).collect(toList()) ? 这会响吗? 如果我告诉您Java java.util.stream.Stream<T>也是一个函子怎么办? 顺便说一句,一个单子?

现在,您应该看到函子的第一个好处–它们抽象了内部表示形式,并为各种数据结构提供了一致且易于使用的API。 作为最后一个示例,让我介绍类似于Future promise函数。 Promise “承诺”某一天将提供一个值。 它尚未出现,可能是因为产生了一些后台计算,或者我们正在等待外部事件。 但是它将在将来出现。 完成Promise<T>的机制并不有趣,但是函子的性质是:

Promise<Customer> customer = //...
Promise<byte[]> bytes = customer.map(Customer::getAddress).map(Address::street).map((String s) -> s.substring(0, 3)).map(String::toLowerCase).map(String::getBytes);

看起来很熟悉? 这就是重点! Promise函子的实现超出了本文的范围,甚至不重要。 不用说,我们非常接近从Java 8实现CompletableFuture ,并且几乎从RxJava中发现了Observable 。 但是回到函子。 Promise<Customer>尚未持有Customer的值。 它有望在将来具有这种价值。 但是我们仍然可以像使用FOptionalFList一样映射此类函子–语法和语义完全相同。 行为遵循函子表示的内容。 调用customer.map(Customer::getAddress)产生Promise<Address> ,这意味着map是非阻塞的。 customer.map() 不会等待基础的customer承诺完成。 相反,它返回另一个不同类型的承诺。 当上游承诺完成时,下游承诺将应用传递给map()的函数并将结果传递给下游。 突然,我们的函子使我们能够以非阻塞方式流水线进行异步计算。 但是您不必理解或学习-因为Promise是函子,所以它必须遵循语法和法则。

函子还有许多其他很好的例子,例如以组合方式表示值或错误。 但是现在是时候看看单子了。

从函子到单子

我假设您了解函子是如何工作的,为什么它们是有用的抽象。 但是函子并不像人们期望的那样普遍。 如果您的转换函数(作为参数传递给map()那个)返回函子实例而不是简单值,会发生什么? 好吧,函子也只是一个值,所以没有坏事发生。 将返回的所有内容放回函子中,以便所有行为都保持一致。 但是,假设您有以下方便的方法来解析String

FOptional<Integer> tryParse(String s) {try {final int i = Integer.parseInt(s);return FOptional.of(i);} catch (NumberFormatException e) {return FOptional.empty();}
}

例外是会破坏类型系统和功能纯度的副作用。 在纯函数语言中,没有例外的地方,毕竟我们从来没有听说过在数学课上抛出例外,对吗? 错误和非法条件使用值和包装器明确表示。 例如, tryParse()接受一个String但并不简单地返回int或在运行时静默引发异常。 通过类型系统,我们明确告知tryParse()可能失败,字符串格式错误不会有任何异常或错误。 此半故障由可选结果表示。 有趣的是,Java已经检查了必须声明和处理的异常,因此从某种意义上讲,Java在这方面比较纯净,它没有隐藏副作用。 但是,无论好坏,通常在Java中不建议使用检查异常,因此让我们回到tryParse() 。 用已经包装在FOptional String组成tryParse似乎很有用:

FOptional<String> str = FOptional.of("42");
FOptional<FOptional<Integer>> num = str.map(this::tryParse);

这不足为奇。 如果tryParse()返回一个int您将得到FOptional<Integer> num ,但是由于map()函数返回FOptional<Integer>本身,因此它被包装两次,成为笨拙的FOptional<FOptional<Integer>> 。 请仔细查看类型,您必须了解为什么在这里获得此双重包装。 除了看上去很恐怖之外,在函子中放一个函子会破坏构图和流畅的链接:

FOptional<Integer> num1 = //...
FOptional<FOptional<Integer>> num2 = //...FOptional<Date> date1 = num1.map(t -> new Date(t));//doesn't compile!
FOptional<Date> date2 = num2.map(t -> new Date(t));

在这里,我们尝试通过将int转换为+ Date +来映射FOptional的内容。 具有Functor<Integer> int -> Date的功能,我们可以轻松地从Functor<Integer>Functor<Date> ,我们知道它是如何工作的。 但是在num2情况下情况变得复杂。 num2.map()接收的输入不再是int而是FOoption<Integer> ,显然java.util.Date没有这样的构造函数。 我们通过双重包裹来破坏了函子。 但是,具有返回函子而不是简单值的函数非常普遍(例如tryParse() ),因此我们不能简单地忽略这种要求。 一种方法是引入一种特殊的无参数join()方法,以“展平”嵌套函子:

FOptional<Integer> num3 = num2.join()

它可以工作,但是因为这种模式太普遍了,所以引入了一种名为flatMap()特殊方法。 flatMap()map非常相似,但希望作为参数接收的函数返回函子-或monad是精确的:

interface Monad<T,M extends Monad<?,?>> extends Functor<T,M> {M flatMap(Function<T,M> f);
}

我们仅得出结论, flatMap只是一种语法糖,可以实现更好的组合。 但是flatMap方法(通常称为Haskell的bind>>= )具有所有不同之处,因为它允许以纯函数式的形式构成复杂的转换。 如果FOptional是monad的实例,则解析突然可以按预期进行:

FOptional<Integer> num = FOptional.of(42);
FOptional<Integer> answer = num.flatMap(this::tryParse);

Monads不需要实现map ,可以轻松地在flatMap()之上实现它。 实际上, flatMap是启用全新转换领域的基本运算符。 显然,就像函子一样,语法顺从性还不足以将某个类称为monad, flatMap()运算符必须遵循monad定律,但它们非常直观,就像flatMap()与标识的关联性一样。 后者要求m(x).flatMap(f)与持有值x任何monad和函数f f(x)相同。 我们不会深入研究monad理论,而让我们关注实际含义。 当内部结构并非无关紧要时,Monad便会发光,例如Promise monad,它将在将来具有一定的价值。 您可以从类型系统中猜测Promise在以下程序中的表现吗? 首先,所有可能花费一些时间才能完成的方法都会返回Promise

import java.time.DayOfWeek;Promise<Customer> loadCustomer(int id) {//...
}Promise<Basket> readBasket(Customer customer) {//...
}Promise<BigDecimal> calculateDiscount(Basket basket, DayOfWeek dow) {//...
}

现在,我们可以将这些函数组合起来,就好像它们都是使用单子运算符进行了阻塞一样:

Promise<BigDecimal> discount = loadCustomer(42).flatMap(this::readBasket).flatMap(b -> calculateDiscount(b, DayOfWeek.FRIDAY));

这变得很有趣。 flatMap()必须保留monadic类型,因为所有中间对象都是Promise 。 不仅仅是保持类型有序-先前的程序突然完全异步! loadCustomer()返回Promise因此不会阻塞。 readBasket()接受Promise拥有(将要拥有的一切)并应用返回另一个Promise的函数,依此类推。 基本上,我们建立了一个异步计算管道,其中后台完成一个步骤会自动触发下一步。

探索

有两个单子并将它们包含的值组合在一起是很常见的。 但是函子和monad均不允许直接访问其内部,这是不纯的。 相反,我们必须谨慎地应用转换,而不能逃脱monad。 假设您有两个单子,并且您想将它们合并

import java.time.LocalDate;
import java.time.Month;Monad<Month> month = //...
Monad<Integer> dayOfMonth = //...Monad<LocalDate> date = month.flatMap((Month m) ->dayOfMonth.map((int d) -> LocalDate.of(2016, m, d)));

请花点时间研究前面的伪代码。 我没有使用任何真正的monad实现(例如PromiseList来强调核心概念。 我们有两个独立的monad,一个是Month类型,另一个是Integer类型。 为了从中构建LocalDate ,我们必须构建一个嵌套的转换,该转换可以访问两个monad的内部。 仔细研究这些类型,尤其要确保您了解为什么我们在一个地方使用flatMap在另一个地方使用map() 。 想想如果您还有第三个Monad<Year> ,那么您将如何构造此代码。 这种应用两个参数(在本例中为md )的函数的模式非常普遍,以至于Haskell中有一个特殊的辅助函数,称为liftM2 ,它完全在mapflatMap上实现了这种转换。 在Java伪语法中,它看起来像这样:

Monad<R> liftM2(Monad<T1> t1, Monad<T2> t2, BiFunction<T1, T2, R> fun) {return t1.flatMap((T1 tv1) ->t2.map((T2 tv2) -> fun.apply(tv1, tv2)));
}

您不必为每个monad实现此方法, flatMap()足够了,而且它对所有monad都一致地起作用。 当您考虑如何将其与各种monad一起使用时, liftM2非常有用。 例如listM2(list1, list2, function)将对list1list2 (笛卡尔积)中的每对可能的项应用function 。 另一方面,对于可选选项,仅当两个可选选项均为非空时,它将应用功能。 更好的是,对于Promise monad,当两个Promise都完成时,将异步执行一个函数。 这意味着我们只是发明了一个简单的同步机制(分叉联接算法中的join() ,该同步机制包含两个异步步骤。

我们可以轻松地在flatMap()之上构建的另一个有用的运算符是filter(Predicate<T>) ,该运算符接收monad内部的所有内容,如果不符合某些谓词,则将其完全丢弃。 在某种程度上,它类似于map但不是1-to-1映射,而是1-to-0-or-1。 同样, filter()对于每个monad具有相同的语义,但是取决于我们实际使用的monad,其功能相当惊人。 显然,它允许从列表中过滤掉某些元素:

FList<Customer> vips = customers.filter(c -> c.totalOrders > 1_000);

但是它也可以正常工作,例如对于可选项目。 在这种情况下,如果可选内容不符合某些条件,我们可以将非空可选内容转换为空值。 空的可选部分保持不变。

从单子列表到单子列表

另一个来自flatMap()有用运算符是sequence() 。 您只需查看类型签名即可轻松猜测其作用:

Monad<Iterable<T>> sequence(Iterable<Monad<T>> moands)

通常,我们有一堆相同类型的monad,而我们想要一个具有该类型列表的monad。 对您来说,这听起来似乎很抽象,但却非常有用。 想象一下,您想通过ID同时从数据库中加载一些客户,因此您多次对不同的ID使用loadCustomer(id)方法,每次调用都返回Promise<Customer> 。 现在,您有了Promise的列表,但您真正想要的是客户列表,例如要在Web浏览器中显示的客户列表。 sequence() (在RxJava中sequence()称为concat()merge() ,具体取决于用例)是为此目的而构建的:

FList<Promise<Customer>> custPromises = FList.of(1, 2, 3).map(database::loadCustomer);Promise<FList<Customer>> customers = custPromises.sequence();customers.map((FList<Customer> c) -> ...);

通过为每个ID调用database.loadCustomer(id) ,我们可以在其上map一个表示客户ID的FList<Integer> (您看到FList是一个函子吗?) 这导致Promise列表非常不便。 sequence()节省了一天的时间,但是再次这不仅是语法糖。 前面的代码是完全非阻塞的。 对于不同种类的monads, sequence()仍然有意义,但是在不同的计算上下文中。 例如,可以将FList<FOptional<T>>更改为FOptional<FList<T>> 。 顺便说一句,您可以在flatMap()之上实现sequence() (就像map()一样flatMap()

一般而言,这只是关于flatMap()和monad有用性的冰山一角。 尽管源于晦涩的类别理论,但即使在Java之类的面向对象的编程语言中,monad也被证明是极其有用的抽象。 能够组成返回单子函数的函数非常有用,以至于数十个无关的类遵循单子行为。

而且,一旦将数据封装在monad中,通常很难显式地将其取出。 这种操作不是monad行为的一部分,并且经常导致非惯用语代码。 例如, Promise<T>上的Promise.get()可以从技术上返回T ,但是只能通过阻塞来返回,而所有基于flatMap()运算符都是非阻塞的。 另一个示例是FOptional.get()可能会失败,因为FOptional可能为空。 即使FList.get(idx)从列表偷窥特定元素听起来很别扭,因为你可以替换for与循环map()经常。

我希望您现在了解为什么单子如此流行。 即使在像Java这样的面向对象的语言中,它们也是非常有用的抽象。

翻译自: https://www.javacodegeeks.com/2016/06/functor-monad-examples-plain-java.html

functor

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/335935.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【开源项目】Android开发内置App自动升级

1.准备工作&#xff0c;首先需要在AndroidManifest.xml中的application中加入以下内容 <providerandroid:name"android.support.v4.content.FileProvider"android:authorities"需要更新app的包名.fileprovider"android:grantUriPermissions"true&q…

智能雷达物位计说明书_?浅谈人工检尺法和雷达液位计在油罐液位测量中的应用...

近年来&#xff0c;油罐液位的测量工作已成为工业测量中十分重要的部分。过去&#xff0c;在油罐测量方面&#xff0c;人工检尺法是进行液位测量的主要方法之一&#xff0c;但随着科技的发展和进步&#xff0c;雷达液位计等智能化液位测量设备使油罐的监控和测量方式趋向于多元…

【安卓开发 】Android初级开发(九)Android中封装View提供接口供点击事件回调的方法及使用

自古一楼先上图 package com.example.mydialog;import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.View; import android.view.Window; i…

spring 自定义日志_Spring和Hibernate的自定义审核日志

spring 自定义日志如果您需要对所有数据库操作进行自动审核 &#xff0c;并且正在使用Hibernate…&#xff0c;则应使用Envers或spring data jpa auditing 。 但是&#xff0c;如果由于某些原因您不能使用Envers&#xff0c;则可以使用Hibernate事件侦听器和spring事务同步来实…

打印pdf就一页_Excel表格打印技巧汇总,看完才发现,你连基础打印技巧都不知道...

前天被老板削了一顿&#xff0c;说我打印的表格连标题行都没有&#xff01;昨天被老板削了两顿&#xff0c;说我打印的表格太浪费纸&#xff01;今天被老板削了五顿&#xff0c;说我表格打印出来的数据居然是错误&#xff01;……在挨削了很多次之后&#xff0c;学会了一些技巧…

机器学习java_如何开始使用Java机器学习

机器学习java什么是开始使用Java机器学习的最佳工具&#xff1f; 他们已经存在了一段时间&#xff0c;但如今看来&#xff0c;每个人都在谈论人工智能和机器学习。 对于科学家和研究人员而言&#xff0c;它已经不再是秘密&#xff0c;几乎可以在任何新兴技术中实现。 在下面的…

【科学工具】矩阵篇

# codingutf-8 import numpy as np# 矩阵加法 Matrix addition ,同型矩阵可以加减操作&#xff0c;即行列数相等 A np.array([[3, 2, 1], [1, 2, 3]]) B np.array([[3, 2, 1], [1, 2, 3]]) print(A B)# 矩阵数乘 Matrix multiplication C np.array([3, 2, 1]) print (C * 2…

nginx delete form表单 收不到参数_HTTP 文件上传的一个后端完善方案(NginX)

(给PHP开发者加星标&#xff0c;提升PHP技能)转自&#xff1a;林伯格https://breeze2.github.io/blog/scheme-nginx-php-js-upload-process前言很多网站都会有上传文件的功能&#xff0c;比如上传用户头像&#xff0c;上传个人简历等等&#xff0c;除非是网盘类的网站&#xff…

fegin 参数丢失_许多参数和丢失的信息

fegin 参数丢失代码越少越好&#xff1f; 对象越少越好&#xff1f; 是真的吗 像往常一样&#xff0c;这取决于。 在某些情况下&#xff0c;通过添加更多内容&#xff0c;我们会添加不必要的复杂性。 当我们仅出于“将来可能需要这种额外的灵活性”而创建接口或其他抽象时&…

【安卓开发 】Android初级开发(十)Android中app自动更新版本号比较

//版本号比较:前者小返回true&#xff0c;前者大返回false public static boolean versionCompareTo(String version1, String version2) {Log.d("输出第一个参数",version1);Log.d("输出第二个参数",version2);version1 version1 null ? "" …

高通写号工具_高通推出桌面平台新ARM处理器并认为我们的电脑性能没必要那么高...

高通公司在日前举办的骁龙技术峰会上宣布推出骁龙7c / 8c处理器 , 这些处理器全部都是面向笔记本电脑推出的。这也是高通和微软合作推出 Windows 10 ARM 设备的组成部分 , 高通希望能够在桌面平台挑战英特尔统治地位。如果你有印象的话或许还记得高通此前推出的骁龙 8cx芯片组&…

java创建一个不可变对象_使用不可变对象创建值对象

java创建一个不可变对象在回答我最近的文章中AutoValue&#xff1a;生成的不可变的值类 &#xff0c; 布兰登认为&#xff0c;这可能是有趣的&#xff0c;看看如何AutoValue比较项目Lombok和Immutables和凯文借调这一点。 我同意这是一个好主意&#xff0c;但是我首先将这篇文章…

混合高斯模型_高斯混合模型(GMM)

下图所示&#xff0c;显然用右边的图描述当前分布更加合理&#xff0c;即应用了两个高斯分布。图中每一个样本点同时属于任何一个高斯模型。高斯混合模型 从几何角度来理解&#xff0c;GMM是由多个高斯分布叠加而成&#xff0c;可以看做是多个高斯分布的加权平均。其中&#x…

【安卓开发 】Android初级开发(网络操作)

URI部分 URI详情 uri的具体案例使用参考&#xff0c;app与网页之间的页面跳转 H5唤醒app并跳转到指定页面 H5打开APP技术总结 H5页面唤醒app的方法 Android配置Scheme使用浏览器唤起APP的方式&#xff0c;以及不生效问题解决 网页唤起app,并传值到app中使用的全过程 and…

dynamodb java_使用Java第2部分查询DynamoDB项

dynamodb java在上一篇文章中&#xff0c;我们有机会发布了一些基本的DynamoDB查询操作。 但是&#xff0c;除了基本操作之外&#xff0c;DynamoDB api还为我们提供了一些额外的功能。 投影是具有类似选择功能的功能。 您选择应从DynamoDB项中提取哪些属性。 请记住&#xf…

【H.264/AVC视频编解码技术】第五章【哈夫曼编码】

本文章所需要的内容需要自行准备一个名为input.txt的文本文件作为案例演示。内容选择英语小短文即可 第一步,建立哈夫曼数 #include <iostream> #include <fstream> #include <queue> #include <vector> #include <string>using namespace st…

gitpython git diff_Python全栈开发-git常用命令

欢迎关注我的号Python全栈开发-git常用命令​mp.weixin.qq.com### Python全栈开发-git常用命令本节内容- github介绍- 安装- 仓库创建& 提交代码- 代码回滚- 工作区和暂存区- 撤销修改- 删除操作- 远程仓库- 分支管理- 多人协作- github使用- 忽略特殊文件.gitignore### 2.…

来的多可选_您的框架有多可扩展性?

来的多可选在参加会议时&#xff0c;我们总是会遇到高素质的决策者&#xff0c;他们经常问同样的问题&#xff1a; 您的框架有多可扩展性&#xff1f;如果我需要的比您开箱即用的功能还多呢&#xff1f; 。 这个问题非常合理&#xff0c;因为他们只是不想被卡在开发曲线的中间&…

【H.264/AVC视频编解码技术】第六章【指数哥伦布编码】

H264中语法元素描述符 指数哥伦布 (Exponential-Golomb) 熵编码 指数哥伦布编码同哈夫曼编码一样,都是变长编码。 二者的显著区别: 信源相关性:哈夫曼编码依赖于信源的概率分布;指数哥伦布与信源无关。 额外信息:哈夫曼编码的数据必须额外携带与信源匹配的码表;指…

python素描效果_python实现图片素描效果

代码如下&#xff1a;from PIL import Image #图像处理模块import numpy as npa np.asarray(Image.open("这里是原图片的路径").convert(L)).astype(float)#将图像以灰度图的方式打开并将数据转为float存入np中depth 10. # (0-100)grad np.gradient(a) #取图像灰度…