Python中實現 (a and b or c) in xx 這種邏輯最簡潔的方式是怎樣的?

判斷 a 和 b 都在 xx裡面 ,或者 c 在 xx 裡面
當然實際可能不止 3 個元素參與判斷。
怎樣用簡潔通用的代碼實現這個邏輯?

直接 按照題目上那樣寫雖然不報錯,但是語義卻完全不同
In [177]: print (1 and 2) in [1,1,3]
False
In [178]: print 1 and 2
2
In [179]: print (1 and 2) in [2,3]
True


類型是你的好幫手。你這個 (a and b or c) in cc 一眼看上去類型不太對,先讓我們把它寫的更加 explicit 一些:

in_(or_(and_(contains(a), contains(b)), contains(c), cc)

可以看出,如果上述式子的類型是 Bool,而且 forsome x. a, b, c :: x; cc :: Set x; contains :: x -&> Set x -&> Bool,那麼一個合理的類型推斷是 and_, or_ :: (x -&> Bool) -&> (x -&> Bool) -&> x -&> Bool; in_ :: forall x. x -&> x。

正如 F91 所說,類型清楚之後,剩下的就是填空了。我們先用 Haskell 寫一遍:

contains x = foldr combine False
where
combine x" r = x" == x || r

-- Control.Concatenative
-- 也可以寫成 bi f g h = h &<$&> f &<*&> g
bi f g h x = f x `h` g x

and_ f g = bi f g ()
or_ f g = bi f g (||)

main = print x
where
x = (contains 1 `and_` contains 2 `or_` contains 3) [1, 3, 5]

寫成 Python 就是

import operator

def contains(x):
def call(xs):
return x in xs
return call

def bi(f, g, h):
def call(x):
return h(f(x), g(x))
return call

def and_(f, g):
return bi(f, g, operator.and_)

def or_(f, g):
return bi(f, g, operator.or_)

print(or_(and_(contains(1), contains(2)), contains(3))([1, 3, 5]))

核心做法是這樣。至於用 typeclass 或者 RTTI + operator overloading 來把它寫的漂亮一些,那就留作作業了...


Haskell可以,先上效果圖:

λ&> (1 2 || 3) `belongs` [1]
False
λ&> (1 2 || 3) `belongs` [2]
False
λ&> (1 2 || 3) `belongs` [1, 2]
True
λ&> (1 2 || 3) `belongs` [3]
True
λ&> (1 2 || 3) `belongs` [4]
False
λ&> (1 2 || not 3) `belongs` [3]
False

反過來也可以:

λ&> :set -XOverloadedLists
λ&> ([1, 2] [2, 3] || [3, 4]) `contains` 1
False
λ&> ([1, 2] [2, 3] || [3, 4]) `contains` 2
True
λ&> ([1, 2] [2, 3] || [3, 4]) `contains` 3
True
λ&> ([1, 2] [2, 3] || [3, 4]) `contains` 4
True

完整代碼:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}

module BoolLike where

import GHC.Exts
import Prelude hiding ((), (||), not, and, or)
import qualified Prelude as P

newtype BoolLike a = BoolLike ((a -&> Bool) -&> Bool)

() :: BoolLike a -&> BoolLike a -&> BoolLike a
BoolLike f BoolLike g = BoolLike $ k -&> f k P. g k
infixr 3

(||) :: BoolLike a -&> BoolLike a -&> BoolLike a
BoolLike f || BoolLike g = BoolLike $ k -&> f k P.|| g k
infixr 2 ||

not :: BoolLike a -&> BoolLike a
not (BoolLike f) = BoolLike $ k -&> P.not (f k)

toBoolLike :: a -&> BoolLike a
toBoolLike x = BoolLike $ k -&> k x

belongs :: Eq a =&> BoolLike a -&> [a] -&> Bool
belongs (BoolLike f) xs = f (x -&> x `elem` xs)

contains :: Eq a =&> BoolLike [a] -&> a -&> Bool
contains (BoolLike f) x = f (xs -&> x `elem` xs)

instance Num a =&> Num (BoolLike a) where
fromInteger = toBoolLike . fromInteger
(+) = undefined
(-) = undefined
(*) = undefined
abs = undefined
signum = undefined

instance IsList (BoolLike [a]) where
type Item (BoolLike [a]) = a
fromList = toBoolLike
toList = undefined

Update:
第一個版本僅適用於數值和列表,以下給出通用版本:

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ViewPatterns #-}

module BoolLike where

import Prelude hiding ((), (||), not)
import qualified Prelude as P

newtype BoolLike a = BoolLike ((a -&> Bool) -&> Bool)

toBoolLike :: a -&> BoolLike a
toBoolLike x = BoolLike $ k -&> k x

(toBool -&> BoolLike f) (toBool -&> BoolLike g) = BoolLike $ k -&> f k P. g k
(toBool -&> BoolLike f) || (toBool -&> BoolLike g) = BoolLike $ k -&> f k P.|| g k
not (toBool -&> BoolLike f) = BoolLike $ k -&> P.not (f k)

infixr 3
infixr 2 ||

belongs (toBool -&> BoolLike f) xs = f (x -&> x `elem` xs)
contains (toBool -&> BoolLike f) x = f (xs -&> x `elem` xs)

type family BoolItem a where
BoolItem (BoolLike a) = BoolLike a
BoolItem a = BoolLike a

class IsBool a where
toBool :: a -&> BoolItem a

instance IsBool (BoolLike a) where
toBool = id

instance {-# OVERLAPPABLE #-} (BoolItem a ~ BoolLike a) =&> IsBool a where
toBool = toBoolLike

λ&> data Color = R | G | B deriving Eq
λ&> (R G || B) `belongs` [R]
False
λ&> (R G || B) `belongs` [G]
False
λ&> (R G || B) `belongs` [B]
True
λ&> (R G || B) `belongs` [R, G]
True

λ&> ([R, G] [G, B] || [B]) `contains` R
False
λ&> ([R, G] [G, B] || [B]) `contains` G
True
λ&> ([R, G] [G, B] || [B]) `contains` B
True

最後,如果對寫法要求不這麼嚴格的話,就可以省下這些trick了。

λ&> (($ 1) &<&> ($ 2) &<||&> ($ 3)) (`elem` [1])
False
λ&> (($ 1) &<&> ($ 2) &<||&> ($ 3)) (`elem` [2])
False
λ&> (($ 1) &<&> ($ 2) &<||&> ($ 3)) (`elem` [3])
True
λ&> (($ 1) &<&> ($ 2) &<||&> ($ 3)) (`elem` [1,2])
True
λ&> :t ($)
($) :: (a -&> b) -&> a -&> b
λ&> :t ($ 1)
($1) :: Num a =&> (a -&> b) -&> b
-- b 為 Bool 的時候,就是 BoolLike 了
λ&> :t (&<&>)
(&<&>) :: Applicative f =&> f Bool -&> f Bool -&> f Bool
-- 這個函數只是 liftA2 ()
λ&> :t ()
() :: Bool -&> Bool -&> Bool

-- 對其他數據結構同樣有效
data Color = R | G | B deriving Eq

λ&> (($ R) &<&> ($ G) &<||&> ($ B)) (`elem` [B])
True
λ&> (($ R) &<&> ($ G) &<||&> ($ B)) (`elem` [R])
False
λ&> (($ R) &<&> ($ G) &<||&> ($ B)) (`elem` [G])
False
λ&> (($ R) &<&> ($ G) &<||&> ($ B)) (`elem` [B])
True
λ&> (($ R) &<&> ($ G) &<||&> ($ B)) (`elem` [R, G])
True


如果都是 hashable,用集合;
否則自己實現一個函數來做判斷。

「(a and b or c) in xx 這種邏輯」是不通的邏輯,不管是在 Python 里還是在英語里。正確的表述,你在問題描述里已經寫了。用英語表述為:

either a and b in xx, or c in xx.


(lambda ok: ok(a) and ok(b) or ok(c)) (lambda x: x in xs)

效果:

&>&>&> xs = [1, 2, 3, 4, 5]
&>&>&> a = 1
&>&>&> b = 10
&>&>&> c = 5
&>&>&> (lambda ok: ok(a) and ok(b) or ok(c)) (lambda x: x in xs)
True
&>&>&> c = 6
&>&>&> (lambda ok: ok(a) and ok(b) or ok(c)) (lambda x: x in xs)
False
&>&>&> b = 2
&>&>&> (lambda ok: ok(a) and ok(b) or ok(c)) (lambda x: x in xs)
True

PS: 這個答案其實就是對自然語言的一個直譯, 自然語言是" a 和 b 或者 c 在 xs 裡面" , 其實仔細理解一下需求, 這句話想表達的其實是: " a滿足某個條件 且 b滿足該條件 或者 c滿足該條件, 該條件是 在xs裡面", 把這個理解翻譯成代碼就得到上面的答案了.

PPS: 我覺得題主很愛思考, 能問這樣的問題, 說明題主很關注語言的表達能力, 題主的代碼必定會寫得越來越漂亮, 那些樓上冷嘲熱諷的答主, 請無視他們就好...

----------------- 2016-11-26 更新 --------------------------

這個題目下面的回答排名好詭異... 算了, 畢竟題目比較簡單沒門檻, 所以可能大家點贊點得比較隨意?

不過, 樓上幾位, 能把Haskell寫得那麼丑, 我怎麼越看越像是在黑我大哈斯卡???

事實上, 我上面給出的那段代碼已經夠短了... 你再折騰又能如何... 畢竟上不了天啊..

其實這個問題很直接, 就是把 a, b, c 三個數字, 映射到了 a", b", c" 三個布爾值, 然後對這三個布爾值做了一系列邏輯運算... 直接寫也就是這樣樣子, 其實寫成這樣也已經滿足 DRY 原則了. 旁邊有些回答可能是更短, 但其實並沒有避免對於 "是xs中的元素" 這段邏輯的重複, 而這裡通過簡單地做一個lambda抽象就做到了.

xs :: [Int]
xs = [1, 2, 3, 4, 5]

a, b, c :: Int
a = 1
b = 10
c = 5

r = ok -&>
let
a" = ok a
b" = ok b
c" = ok c
in a" b" || c"

rst :: Bool
rst = r (`elem` xs)

當然, 這裡是可以用到Monad來搞定的, 當然其實結果相差不大 (下面用Monad重新實現 r)

r :: (Int -&> Bool) -&> Bool
r = do
a" &<- ($ a) b" &<- ($ b) c" &<- ($ c) return (a" b" || c")

能像上面這麼寫, 是因為 (a-&>) 是 Monad 的一個 instance .

當然其實也沒什麼太大意思, 這段代碼, 跟上面那一行python, 其實是一個意思, 並且由於 它並沒有更簡潔畢竟題主問的是python 所以原本實在沒啥放出來的道理...

我把這段放出來, 只是想讓大家看到, Haskell其實是提供了現成的抽象工具來做類似的事情的, 樓上一些同學通過重新定義一些邏輯運算符來做類似的事情其實完全不必要, 為這麼一個小需求寫幾十行沒有復用價值的玩具代碼出來也會讓不明真相的朋友覺得Haskell "很過度設計, 很sb, 很low, 很缺乏抽象能力"... 畢竟重載算符這種事情很多語言(包括題主問的python)都能做, 還做得比你簡單多了..

-------------------------2016-11-30---------------------------

不知道為什麼這個答案還是被沉底....

那就再補一個好玩的吧.. 看到大家對重載算符的版本好像很喜歡.. 我也來湊個熱鬧咯

import Control.Applicative
(^) = liftA2 ()
(||^) = liftA2 (||)

r = ($ a) ^ ($ b) ||^ ($ c)

rst = r (`elem` xs)

你看, 其實三行就夠了.....畢竟抽象工具都是現成的... Pythoner們是不是有點心動呢?

-----------------------------------------------------------------------

剛剛仔細看了一下陳文龍同學的答案, 發現還是有點意思的,
其實他寫了很多代碼去重載 和 ||, 都是為了繞開類型上的限制.
因為本來 的類型是 Bool -&> Bool -&> Bool
然後為了讓 可以用在任意類型上, 就要擴展到 a -&> a -&> Bool
然後為了讓最終的 a 如何變成 Bool 的邏輯可以被指定, 就不能直接返回 Bool,
而是要返回一個 (a -&> Bool) -&> Bool , 也就是在得到一個 a -&> Bool , 比如 (`elem` xs) 這樣的東西之後, 再返回Bool
然後, 又因為 得到的東西, 可以繼續被用作 的參數, 比如在 (1 2) 3 這個表達式里 的兩個參數, 左邊是個 (a -&> Bool) -&> Bool , 而右邊是個 a, 所以要弄一個抽象的坑讓這兩類東西都能放進去...
當然這麼玩就太複雜了, 而且類型上其實就相當不直觀了, 雖然最終效果是最接近題主想要的表達的. 不過還是學習了.


少的話就是a in xx and b in xx or c in xx,多的話用set
===============================================
補充一下,這個問題的話,我們還是首先要將生活語言轉化為數學語言,生活語言說A、B或者C在XX裡面,在數學上可以有兩種描述方式:基於邏輯的,和基於集合的
基於邏輯的是:
(A in X wedge B in X)vee (C in X)
基於集合的是
{A,B} subseteq X vee {C} subseteq X
Python中的表述幾乎是跟這個一一對應的,我們用邏輯來表述就是

(a in xx and b in xx) or (c in xx)

用集合表示就是

xx_set = set(xx)
xx_set.issuperset((a,b)) or xx_set.issuperset((c,))

剩下的僅僅是如果很多的話,怎麼重新表達這個邏輯的問題而已,如果多的話,自然可以考慮用generator、all、any,那麼就是:

and_or_test = [[a,b], [c]]
result = any(all(o in xx for o in and_test) for and_test in and_or_test)

或者

or_set = [[a,b], [c]]
xx_set = set(xx)
result = any(xx_set.issuperset(and_set) for and_set in or_set)

任何一個布爾邏輯最終都可以寫成先and再or的形式,所以這樣一個表足夠表述所有可能的邏輯了。考慮到效率問題,即使是第一種情況也應當盡量將xx表述為set()類型,來獲得O(1)的in的執行效率


題主表達得不好,但是提出的問題是對的。這個問題本質上是有沒有辦法簡化:

f(a) and f(b) or f(c)

讓我們不用一個一個的寫這個f。

所以,問題就是:

是否可以構造一個DSL,通過這個DSL可以構造一個logic框架----在Python裡面就是一個函數f: (a" -&> Bool) -&> Bool, 它接受一個 條件 , 返回一個Bool值。


具體在某個場景下是否有必要這麼干是一碼事,但是這顯然是一個有趣的問題。

具體的構造 @方澤圖 的回答裡面給得很漂亮啦。


你這樣寫代碼是錯的,不要混亂地靠想像力使用邏輯操作符,樓下的代碼則不夠簡潔

如果是我,會用集合,一行解決


@蕭井陌

({a, b} &<= xxx) and not ({c} &<= xxx)


這種判斷,Pythonic中規中矩的做法還是用集合。當然,如果是僅僅a、b、c,還體現不出用集合的好處,但是條件組合越多,越能體現用集合的優勢。

以下詳細舉例:

# 數據
a = "foo"
b = "bar"
c = "joy"
d = "quu"
lst = ["foo", "rii", "bar", "doo", "ruu"]

# 轉換成集合
set1 = set([a, b])
set2 = set([c, d])
t_set = set(lst)

i_set1 = set1 t_set
i_set1 == set1 # (a and b) in lst
i_set2 = set2 t_set
i_set2 != set() and i_set2 &<= set2 # (c or d) in lst # 小結 # ((a and b) in lst) or (c in lst) if i_set1 == set1 or (c in lst): pass # ((a and b) in lst) or ((c or d) in lst) if i_set1 == set1 or (i_set2 != set() and i_set2 &<= set2): pass # 測試 lists = [["foo", "rii", "bar", "doo", "ruu"], ["foo", "rii", "joy", "doo", "ruu"], ["foo", "rii", "bar", "doo", "joy"], ["buu", "rii", "see", "joy", "ruu"], ["buu", "foo", "see", "tii", "ruu"], ["buu", "waa", "see", "tii", "bar"], ["buu", "goo", "see", "tii", "ruu"], ["buu", "waa", "quu", "tii", "bar"]] # ((a and b) in lst) or (c in lst) for l in lists: t_set = set(l) i_set1 = set1 t_set (lambda a, b, c, lst: print(i_set1 == set1 or (c in lst)))(a, b, c, l) else: print() # ((a and b) in lst) or ((c or d) in lst) for l in lists: t_set = set(l) i_set1 = set1 t_set i_set2 = set2 t_set (lambda a, b, c, d, lst: print(i_set1 == set1 or (i_set2 != set() and i_set2 &<= set2)))(a, b, c, d, l)

運行結果如下圖所示:


使用 Perl 6 中的class Junction 是很簡潔的。

my ($a,$b,$c) = &;
my @list = &;
say so $a $b | $c ∈ @list; # True


lst = [a, b, c]
寫成函數,用for 循環迭代,用in 和 and or組合起來判斷。
不難,自己寫一下。


湊熱鬧,來個Scala版的。首先看下運行效果:

scala&> import In._
import In._

scala&> "a" in "abcd"
res0: Boolean = true

scala&> "b" in "abcd"
res1: Boolean = true

scala&> "e" in "abcd"
res2: Boolean = false

scala&> "f" in "abcd"
res3: Boolean = false

scala&> "a" and "b" in "abcd"
res4: Boolean = true

scala&> "e" or "f" in "abcd"
res5: Boolean = false

scala&> ("a" and "b" and "c") or ("e" and "f") and "h" in "abcd"
res6: Boolean = false

scala&> ("a" and "b" and "c") or ("e" and "f") or "h" in "abcd"
res7: Boolean = true

當然,我們還可以把上面的表達式"a" and "b" and "c"之類的保存起來,重複使用,並且可以查看該表達式的字元串表示:

scala&> val expr = ("a" and "b" and "c") or ("e" and "f" and "g")
expr: In.In[Char] = Or(And(And(In(a),In(b)),In(c)),And(And(In(e),In(f)),In(g)))

scala&> expr in "abc"
res8: Boolean = true

scala&> expr in "efg"
res9: Boolean = true

scala&> expr in "abef"
res10: Boolean = false

實現代碼很簡單,如下:

object In {

trait In[A] {
def and(that: In[A]): In[A] = And(this, that)
def or(that: In[A]): In[A] = Or(this, that)
def and(b: A): In[A] = And(this, b)
def or(b: A): In[A] = Or(this, b)
def in(seq: Seq[A]): Boolean
}

implicit class Single[A](val a: A) extends In[A] {
def in(seq: Seq[A]): Boolean = seq.contains(a)
override def toString = s"In($a)"
}
case class And[A](c1: In[A], c2: In[A]) extends In[A] {
def in(seq: Seq[A]): Boolean = (c1 in seq) (c2 in seq)
}
case class Or[A](c1: In[A], c2: In[A]) extends In[A] {
def in(seq: Seq[A]): Boolean = (c1 in seq) || (c2 in seq)
}
}


我現在的做法是 實現
all_in(values,target) #判斷所有values 是不是都在 target 裡面
in_all(value,targets)#判斷 value 在所有的targets 裡面
one_in(values,target)# 至少有一個 value 在target 裡面
in_one(value,targets)# value 至少存在一個target裡面 就是 原生的 a in xx 語法

這幾個函數,比如 題目的邏輯 就可以寫成 all_in([a,b],xx) or (c in xx)
(a or b or c and (d or e))in xx 寫成 one_in([a,b,c],xx) and one_in([d,e],xx)

這樣做有什麼缺點?


python中的in只能表達元素在集合中,而你的題目必須用到in,所以只能分解成a在xx中並且b在xx中或者c在xx中。這是語法決定的。


推薦閱讀:

關於學習數據結構與演算法的一些疑惑?
在這種情況下,我應該如何努力才能如願成為一名計算機大神?
Python 適合初學編程的人學嗎?
優秀的程序員都應該常接觸電腦,但為什麼有些牛 X 程序員卻沒有近視?
如果出現《我是程序員》這樣的節目,你覺得有什麼看點?

TAG:Python | 編程 | 編程學習 | Python入門 |